Adding custom modules to Divi

Adding custom modules to Divi

Since writing this tutorial almost two years ago, a lot has changed and adding modules to Divi is, you’ll be glad to hear, much easier than it was once upon a time. This post has been updated to reflect what it takes to add modules to Divi 3.0 and upwards. This is a little bit more advanced than our average tutorial/article. Proceed with caution and enthusiasm ? If you were referring back to this article for the ‘old way’ to do this, don’t worry, it’s down there. Just keep scrolling. I’ve been making major customisations to Divi for the better part of three years, for personal projects and Divi related products. A major part of that (particularly recently) has been creating new modules and adding them to the Divi builder so they can be used alongside the 40 or so modules that ship with Divi. Adding modules was not always an enjoyable experience. In the early days it involved a lot of trial and error, reverse engineering of Divi and a little luck to get something working. Why? Because there was no API or official instructions from Elegant Themes on how to include new modules into Divi’s interface. That’s no criticism. Divi’s success was astronomic and the growth of the third party development ecosystem caught everybody by surprise. As developers, we didn’t have official support then. We do now.

The Divi Developer API

As of Divi version 3.1, we have not only a Divi Developer API (Yay!), but also official instructions on how to use it to put together well developed, lightweight and fully integrated custom modules. This isn’t just good news for developers. Modules built within the new guidelines provide a conflict free, intuitive experience for the end user, working as expected, even in the new front end visual builder. They feel like they belong there and they share many of the settings and controls with default modules, providing a user experience in line with default modules. So before we discuss what goes into a custom module, let’s look at exactly why this new system is better.

Old Custom Modules New Custom Modules
Extends the array of modules available to the end user on the back end. ✔ ✔
Extends the array of modules available to the end user on the front end. ✖ ✔
Can be customized using the old back end builder. ✔ ✔
Can be customized using the new Visual Builder. ✖ ✔
Follows Elegant Themes development guidelines. ✖ ✔

As you can see, the new API is a godsend for anyone looking to extend Divi. With that said, let’s take a look at how we get started. Previously when working with custom modules, we were adding files to a child theme. The new way sees us working inside of a ‘functionality plugin’ that includes all of the files we need to add ‘visual builder compatible’ modules. So what goes into the plugin? Well actually, quite a lot. Here’s the file tree that comes in a basic ‘Divi Module’ plugin.

plugin-folder
├── includes
│   ├── modules
│   │ └── HelloWorld
│   │    ├── HelloWorld.jsx
│   │    ├── HelloWorld.php
│   │    └── style.css
│   ├── loader.js
│   ├── loader.php
│   └── MyExtension.php
├── languages
├── node_modules
├── scripts
│   └── frontend.js
├── styles
├── module-extension.php
├── package.json
└── README.md

Looking at this extensive list can be a bit daunting. Don’t worry, most of these files exist to aid in the development of a custom Divi module, but don’t provide any functionality for the end user. You’re not going to be required to write (or even touch) most of the files included here, because Elegant Themes have created a command line utility that does that for you. Your code is going to live in the files in bold. A module consists of a php, jsx and css file. The easiest way to think of this is that php is the back end settings, jsx is the frontend builder compatibility and css is module styling.

The Create Divi Extension

The Create Divi extension is a command line utility based on the Facebook backed Create React App. Essentially, it is an ‘installable-through-the-command-line’ local environment for creating Divi modules. It includes everything you need to create custom modules, including Webpack, ESlint and Babel, but don’t worry if you don’t know too much about what those things are, because the Create Divi Extension acts as a layer over all of those code libraries so you can harness the power of them without having to install or set them up directly. There are only a few dependencies and other tools you need to install before you can get started and they are listed below.

Node.js is a powerful runtime application that is required to build custom Divi modules using the Create Divi Extension. Depending on what local environment you are using to build local sites, you may have some version of node.js installed on your system already. You should still go straight to nodejs.org and download the full version to ensure you have the right packages.

Yarn is a package manager that Elegant Themes recommend having installed before running the Create Divi Extension. I’m not overly sure what it adds to package management above what’s included in Node.js but if Elegant Themes recommend it, that’s good enough for me.

Sounds obvious, but please make sure you have Divi installed and activated before you attempt to add things to it 🙂

This is where you’ll need Divi installed and where you’ll be using the Create Divi Extension to generate a Divi Module Plugin.

Getting Started With The Create Divi Extension

Assuming you’ve installed the above listed software properly, you’ll know want to find the file path to your plugins folder in your local install. I’m working on a Windows PC running Flywheel Local so for me I can just navigate to the plugin folder from the Flywheel Local Site Screen and then hold shift + alt + right click and then choose Copy as path from the context menu. Consult your OS and Local Environment documentation if your setup differs from mine. Next you need to open up whichever command line utility your system uses. For me it is Command Prompt where the magic happens. Be sure to run as an administrator by right clicking on the application name and choosing ‘run as administrator’. Here you want to type cd and then paste your file path after it, this will open up the plugins directory: On the new line, you want to type npx create-divi-extension new-module-demo If you have installed your dependencies correctly, a whole bunch of magic stuff is going to happen as the Divi Create Extension installs everything you need to get started building modules. This can take a few minutes. During this process you’ll be prompted to add some information about your new extension including name and description. If you’ve done everything successfully this’ll be followed by a success message. From here you’ll want to change directory (cd) again into your new module by typing cd new-module-demo. The plugin as it currently stands is still not built to work on its own so you’ll have to type yarn start at this point to compile it before you can use it. If you are using Flywheel Local, you’ll also have to update your plugin’s class to overcome a compatibility issue. You can find details of how to that and why, here. It’s important to remember that if you are working on your module then you need to keep the window with yarn start running open the whole time. Minimise it, but don’t close it. This tripped me up a bunch of times when I first started. Check back on that window regularly because if you make a mistake when editing your module, this is where it will tell you. If you’ve carried out those steps correctly then you can activate your module plugin and you’ll have an example module named HelloWorld that works correctly on the front end builder. Magic! This is a very basic example including a tinymce powered text editor, but it should give you enough to see how the build works and to also have a go at customizing it. If you want your plugin to work in a standalone environment then you’ll have to build (yarn build) and zip (yarn zip) it with yarn by using the aforementioned commands in the same directory as you did for yarn start. If you want to manually edit your plugin and get rid of the dev environment then you can use the command yarn eject, but be careful, because once you do, there’s no going back. If you would like to customize your module further, Elegant Themes has a whole series on creating custom modules in their developer documentation. I would encourage you to go through it as it really is an awesome step by step 🙂

The Divi Module Creator’s Course

If you’re absolutely loving the idea of creating highly functional modules for Divi, then I have a course on exactly that. Designed to teach you how to extend the Divi framework, the Divi Module Creators Course will equip learners with the skills and knowledge to utilize the Divi Create Extension that we’ve covered here briefly, in new and exciting ways. The Divi Module Creators Course will give learners a deep understanding of the Divi theme framework, it’s modules and how to develop custom modules that satisfy both aesthetic and functional purposes. You can learn more here.

More JSX and PHP Settings

The simple text field that comes as the example of a module input is great but if you’d like to check out some other examples, Elegant Themes have come to the rescue again: You can check out their pre-built module examples including several complex setups at the create divi extension example github repo here. You can also use these basic examples we have put together for you, by replacing the relevant bits of code in your module php and jsx files. PHP

public function get_fields() {
    return array(
        'setting_1' => array(
            'label'           => esc_html__( 'Setting 1', 'module-slug' ),
            'type'            => 'tiny_mce',
            'option_category' => 'basic_option',
            'description'     => esc_html__( 'Content entered here will appear inside the module.', 'nemo-new-module' ),
            'toggle_slug'     => 'main_content',
        ),
        'setting_2' => array(
            'label'           => esc_html__( 'Textarea Field', 'nemo-new-module' ),
            'type'            => 'textarea',
            'option_category' => 'basic_option',
            'description'     => esc_html__( 'Description for Textarea field', 'nemo-new-module' ),
            'toggle_slug'     => 'main_content',
        ),
        'setting_3' => array(
            'label'           => esc_html__( 'Text Field', 'nemo-new-module' ),
            'type'            => 'text',
            'option_category' => 'basic_option',
            'description'     => esc_html__( 'Description for text field', 'nemo-new-module' ),
            'toggle_slug'     => 'main_content',
        ),
        'setting_4' => array(
            'label'           => esc_html__( 'Yes No Field', 'nemo-new-module' ),
            'type'            => 'yes_no_button',
            'options'         => array(
                'no'  => esc_html( 'No', 'nemo-new-module' ),
                'yes' => esc_html( 'Yes', 'nemo-new-module' ),
            ),
            'option_category' => 'basic_option',
            'description'     => esc_html__( 'Description for yes no field', 'nemo-new-module' ),
            'toggle_slug'     => 'main_content',
        ),
        'setting_5' => array(
            'label'              => esc_html__( 'Image Upload', 'nemo-new-module' ),
            'type'               => 'upload',
            'option_category'    => 'basic_option',
            'choose_text'        => esc_attr__( 'Choose an image', 'nemo-new-module' ),
            'upload_button_text' => esc_attr__( 'Upload an image', 'nemo-new-module' ),
            'description'        => esc_html__( 'Upload an image ', 'nemo-new-module' ),
            'data_type'          => 'image',
            'toggle_slug'        => 'main_content',
        ),
        'setting_6' => array(
            'label'           => esc_html__( 'Range Slider', 'nemo-new-module' ),
            'type'            => 'range',
            'range_settings'  => array(
                'min'  => '50px',
                'max'  => '200px',
                'step' => '10px',
            ),
            'fixed_unit'      => 'px',
            'default'         => '100px',
            'option_category' => 'basic_option',
            'description'     => esc_html__( 'Description for Range Slider', 'nemo-new-module' ),
            'toggle_slug'     => 'main_content',
        ),
        'setting_7' => array(
            'label'           => esc_html__( 'Color Field', 'nemo-new-module' ),
            'type'            => 'color',
            'default'         => '#000000',
            'option_category' => 'basic_option',
            'description'     => esc_html__( 'Description for text field', 'nemo-new-module' ),
            'toggle_slug'     => 'main_content',
        ),

    );
}

public function render( $attrs, $content = null, $render_slug ) {
    return sprintf( '
 <div>
 <h1>%1$s</h1>
 <p>%2$s</p> 
<p>%3$s</p> 
<p class="%4$s">The class for this text changes on yes/no toggle</p> 
<img alt="change-this" src="%5$s"/>
<p>%6$s</p> 
<p>The code for this color is: %7$s</p> 
 
 </div>',
        $this->props['setting_1'], $this->props['setting_2'], $this->props['setting_3'], $this->props['setting_4'], $this->props['setting_5'], $this->props['setting_6'], $this->props['setting_7']
    );
}

 

JSX

render() {

 return (
 <div>
 <h1>{this.props.setting_1}</h1>
 <p>{this.props.setting_2}</p> 
<p>{this.props.setting_3}</p> 
<p className={this.props.setting_4}>The class for this text changes on yes/no toggle</p> 
<img alt="change-this" src={this.props.setting_5}/>
<p>{this.props.setting_6}</p> 
<p>The code for this color is: {this.props.setting_7}</p> 

 </div>
 );

}

 

If you’re not building modules yet, I hope this post helps you get started. If you are then please share them in the comments so we can see what you’re up to ! 🙂

Custom Divi Modules: The Old Way

Were you looking for the old version of this post? Everything that follows is the old way to build custom modules. Whilst helping out in my favourite Divi theme group recently a case came up for making small structural changes to a Divi page builder module. In this case, we needed to place the grid blog module header above the featured image, like so: modified blog module It occurred to me that others may be interested in editing modules in this way, so here’s how you do it.

Extending the page builder

At this point we’re assuming you have a child theme with its own functions.php set up. Add this code to your functions.php to extend the page builder module to a new file that we’re going to add to our child theme called ‘ds-custom-modules.php’…

function DS_Custom_Modules(){
 if(class_exists("ET_Builder_Module")){
 include("ds-custom-modules.php");
 }
}

function Prep_DS_Custom_Modules(){
 global $pagenow;

$is_admin = is_admin();
 $action_hook = $is_admin ? 'wp_loaded' : 'wp';
 $required_admin_pages = array( 'edit.php', 'post.php', 'post-new.php', 'admin.php', 'customize.php', 'edit-tags.php', 'admin-ajax.php', 'export.php' ); // list of admin pages where we need to load builder files
 $specific_filter_pages = array( 'edit.php', 'admin.php', 'edit-tags.php' );
 $is_edit_library_page = 'edit.php' === $pagenow && isset( $_GET['post_type'] ) && 'et_pb_layout' === $_GET['post_type'];
 $is_role_editor_page = 'admin.php' === $pagenow && isset( $_GET['page'] ) && 'et_divi_role_editor' === $_GET['page'];
 $is_import_page = 'admin.php' === $pagenow && isset( $_GET['import'] ) && 'wordpress' === $_GET['import'];
 $is_edit_layout_category_page = 'edit-tags.php' === $pagenow && isset( $_GET['taxonomy'] ) && 'layout_category' === $_GET['taxonomy'];

if ( ! $is_admin || ( $is_admin && in_array( $pagenow, $required_admin_pages ) && ( ! in_array( $pagenow, $specific_filter_pages ) || $is_edit_library_page || $is_role_editor_page || $is_edit_layout_category_page || $is_import_page ) ) ) {
 add_action($action_hook, 'DS_Custom_Modules', 9789);
 }
}
Prep_DS_Custom_Modules();

The next step is to create the file called ds-custom-modules.php and add it to our child theme root alongside functions.php and any other files we have in there.

Housing new modules

Now the next part depends on which module you’d like to edit. In the Divi theme files includes > builder > main-modules.php you’ll find all of the modules. In our case we wanted to edit the blog module so we copied everything between these two lines –

class ET_Builder_Module_Blog extends ET_Builder_Module {
EVERYTHING IN HERE GOT COPIED
new ET_Builder_Module_Blog;

Each module will start and end like this so it’s easy to know what you need to copy. There are three things that you must change in your new blog module: The class, the name and the slug. You’ll find those lines at the top of your module –

class DS_Custom_Module_Blog extends ET_Builder_Module {
 function init() {
 $this->name = esc_html__( 'Blog - Custom Grid', 'et_builder' );
 $this->slug = 'et_pb_blog_2';

You can see in our example we kept it simple and renamed it ‘Blog – Custom Grid’ and changed the slug to ‘et_pb_blog_2’, but you can change it to whatever you want to. We renamed our class to ‘DS_Custom_Module_blog’. As you can see, the new module now appears alongside our normal ones: The only other changes you should need to make is to adjust the structure or design as needed. If you plan on making CSS changes then you may want to add new classes to the various elements so you can target them separately to the standard modules, but that CSS can then be added in your style sheet as normal. For reference and testing, here is the code from our custom module

<?php

class DS_Custom_Module_Blog extends ET_Builder_Module {
    function init() {
        $this->name               = esc_html__( 'Blog - Custom Grid', 'et_builder' );
        $this->slug               = 'et_pb_blog_2';
        $this->whitelisted_fields = array(
            'fullwidth',
            'posts_number',
            'include_categories',
            'meta_date',
            'show_thumbnail',
            'show_content',
            'show_more',
            'show_author',
            'show_date',
            'show_categories',
            'show_comments',
            'show_pagination',
            'offset_number',
            'background_layout',
            'admin_label',
            'module_id',
            'module_class',
            'masonry_tile_background_color',
            'use_dropshadow',
            'use_overlay',
            'overlay_icon_color',
            'hover_overlay_color',
            'hover_icon',
        );
        $this->fields_defaults    = array(
            'fullwidth'         => array( 'off' ),
            'posts_number'      => array( 10, 'add_default_setting' ),
            'meta_date'         => array( 'M j, Y', 'add_default_setting' ),
            'show_thumbnail'    => array( 'on' ),
            'show_content'      => array( 'off' ),
            'show_more'         => array( 'off' ),
            'show_author'       => array( 'on' ),
            'show_date'         => array( 'on' ),
            'show_categories'   => array( 'on' ),
            'show_comments'     => array( 'off' ),
            'show_pagination'   => array( 'on' ),
            'offset_number'     => array( 0, 'only_default_setting' ),
            'background_layout' => array( 'light' ),
            'use_dropshadow'    => array( 'off' ),
            'use_overlay'       => array( 'off' ),
        );
        $this->main_css_element   = '%%order_class%% .et_pb_post';
        $this->advanced_options   = array(
            'fonts'  => array(
                'header' => array(
                    'label' => esc_html__( 'Header', 'et_builder' ),
                    'css'   => array(
                        'main'      => "{$this->main_css_element} h2",
                        'important' => 'all',
                    ),
                ),
                'meta'   => array(
                    'label' => esc_html__( 'Meta', 'et_builder' ),
                    'css'   => array(
                        'main' => "{$this->main_css_element} .post-meta",
                    ),
                ),
                'body'   => array(
                    'label' => esc_html__( 'Body', 'et_builder' ),
                    'css'   => array(
                        'line_height' => "{$this->main_css_element} p",
                    ),
                ),
            ),
            'border' => array(),
        );
        $this->custom_css_options = array(
            'title'          => array(
                'label'    => esc_html__( 'Title', 'et_builder' ),
                'selector' => '.et_pb_post h2',
            ),
            'post_meta'      => array(
                'label'    => esc_html__( 'Post Meta', 'et_builder' ),
                'selector' => '.et_pb_post .post-meta',
            ),
            'pagenavi'       => array(
                'label'    => esc_html__( 'Pagenavi', 'et_builder' ),
                'selector' => '.wp_pagenavi',
            ),
            'featured_image' => array(
                'label'    => esc_html__( 'Featured Image', 'et_builder' ),
                'selector' => '.et_pb_image_container',
            ),
            'read_more'      => array(
                'label'    => esc_html__( 'Read More Button', 'et_builder' ),
                'selector' => '.et_pb_post .more-link',
            ),
        );
    }

    function get_fields() {
        $fields = array(
            'posts_number'                  => array(
                'label'           => esc_html__( 'Posts Number', 'et_builder' ),
                'type'            => 'text',
                'option_category' => 'configuration',
                'description'     => esc_html__( 'Choose how much posts you would like to display per page.', 'et_builder' ),
            ),
            'include_categories'            => array(
                'label'            => esc_html__( 'Include Categories', 'et_builder' ),
                'renderer'         => 'et_builder_include_categories_option',
                'option_category'  => 'basic_option',
                'renderer_options' => array(
                    'use_terms' => false,
                ),
                'description'      => esc_html__( 'Choose which categories you would like to include in the feed.', 'et_builder' ),
            ),
            'meta_date'                     => array(
                'label'           => esc_html__( 'Meta Date Format', 'et_builder' ),
                'type'            => 'text',
                'option_category' => 'configuration',
                'description'     => esc_html__( 'If you would like to adjust the date format, input the appropriate PHP date format here.', 'et_builder' ),
            ),
            'show_thumbnail'                => array(
                'label'           => esc_html__( 'Show Featured Image', 'et_builder' ),
                'type'            => 'yes_no_button',
                'option_category' => 'configuration',
                'options'         => array(
                    'on'  => esc_html__( 'Yes', 'et_builder' ),
                    'off' => esc_html__( 'No', 'et_builder' ),
                ),
                'description'     => esc_html__( 'This will turn thumbnails on and off.', 'et_builder' ),
            ),
            'show_content'                  => array(
                'label'           => esc_html__( 'Content', 'et_builder' ),
                'type'            => 'select',
                'option_category' => 'configuration',
                'options'         => array(
                    'off' => esc_html__( 'Show Excerpt', 'et_builder' ),
                    'on'  => esc_html__( 'Show Content', 'et_builder' ),
                ),
                'affects'         => array(
                    '#et_pb_show_more',
                ),
                'description'     => esc_html__( 'Showing the full content will not truncate your posts on the index page. Showing the excerpt will only display your excerpt text.', 'et_builder' ),
            ),
            'show_more'                     => array(
                'label'           => esc_html__( 'Read More Button', 'et_builder' ),
                'type'            => 'yes_no_button',
                'option_category' => 'configuration',
                'options'         => array(
                    'off' => esc_html__( 'Off', 'et_builder' ),
                    'on'  => esc_html__( 'On', 'et_builder' ),
                ),
                'depends_show_if' => 'off',
                'description'     => esc_html__( 'Here you can define whether to show "read more" link after the excerpts or not.', 'et_builder' ),
            ),
            'show_author'                   => array(
                'label'           => esc_html__( 'Show Author', 'et_builder' ),
                'type'            => 'yes_no_button',
                'option_category' => 'configuration',
                'options'         => array(
                    'on'  => esc_html__( 'Yes', 'et_builder' ),
                    'off' => esc_html__( 'No', 'et_builder' ),
                ),
                'description'     => esc_html__( 'Turn on or off the author link.', 'et_builder' ),
            ),
            'show_date'                     => array(
                'label'           => esc_html__( 'Show Date', 'et_builder' ),
                'type'            => 'yes_no_button',
                'option_category' => 'configuration',
                'options'         => array(
                    'on'  => esc_html__( 'Yes', 'et_builder' ),
                    'off' => esc_html__( 'No', 'et_builder' ),
                ),
                'description'     => esc_html__( 'Turn the date on or off.', 'et_builder' ),
            ),
            'show_categories'               => array(
                'label'           => esc_html__( 'Show Categories', 'et_builder' ),
                'type'            => 'yes_no_button',
                'option_category' => 'configuration',
                'options'         => array(
                    'on'  => esc_html__( 'Yes', 'et_builder' ),
                    'off' => esc_html__( 'No', 'et_builder' ),
                ),
                'description'     => esc_html__( 'Turn the category links on or off.', 'et_builder' ),
            ),
            'show_comments'                 => array(
                'label'           => esc_html__( 'Show Comment Count', 'et_builder' ),
                'type'            => 'yes_no_button',
                'option_category' => 'configuration',
                'options'         => array(
                    'on'  => esc_html__( 'Yes', 'et_builder' ),
                    'off' => esc_html__( 'No', 'et_builder' ),
                ),
                'description'     => esc_html__( 'Turn comment count on and off.', 'et_builder' ),
            ),
            'show_pagination'               => array(
                'label'           => esc_html__( 'Show Pagination', 'et_builder' ),
                'type'            => 'yes_no_button',
                'option_category' => 'configuration',
                'options'         => array(
                    'on'  => esc_html__( 'Yes', 'et_builder' ),
                    'off' => esc_html__( 'No', 'et_builder' ),
                ),
                'description'     => esc_html__( 'Turn pagination on and off.', 'et_builder' ),
            ),
            'offset_number'                 => array(
                'label'           => esc_html__( 'Offset Number', 'et_builder' ),
                'type'            => 'text',
                'option_category' => 'configuration',
                'description'     => esc_html__( 'Choose how many posts you would like to offset by', 'et_builder' ),
            ),
            'use_overlay'                   => array(
                'label'           => esc_html__( 'Featured Image Overlay', 'et_builder' ),
                'type'            => 'yes_no_button',
                'option_category' => 'layout',
                'options'         => array(
                    'off' => esc_html__( 'Off', 'et_builder' ),
                    'on'  => esc_html__( 'On', 'et_builder' ),
                ),
                'affects'         => array(
                    '#et_pb_overlay_icon_color',
                    '#et_pb_hover_overlay_color',
                    '#et_pb_hover_icon',
                ),
                'description'     => esc_html__( 'If enabled, an overlay color and icon will be displayed when a visitors hovers over the featured image of a post.', 'et_builder' ),
            ),
            'overlay_icon_color'            => array(
                'label'           => esc_html__( 'Overlay Icon Color', 'et_builder' ),
                'type'            => 'color',
                'custom_color'    => true,
                'depends_show_if' => 'on',
                'description'     => esc_html__( 'Here you can define a custom color for the overlay icon', 'et_builder' ),
            ),
            'hover_overlay_color'           => array(
                'label'           => esc_html__( 'Hover Overlay Color', 'et_builder' ),
                'type'            => 'color-alpha',
                'custom_color'    => true,
                'depends_show_if' => 'on',
                'description'     => esc_html__( 'Here you can define a custom color for the overlay', 'et_builder' ),
            ),
            'hover_icon'                    => array(
                'label'               => esc_html__( 'Hover Icon Picker', 'et_builder' ),
                'type'                => 'text',
                'option_category'     => 'configuration',
                'class'               => array( 'et-pb-font-icon' ),
                'renderer'            => 'et_pb_get_font_icon_list',
                'renderer_with_field' => true,
                'depends_show_if'     => 'on',
                'description'         => esc_html__( 'Here you can define a custom icon for the overlay', 'et_builder' ),
            ),
            'background_layout'             => array(
                'label'           => esc_html__( 'Text Color', 'et_builder' ),
                'type'            => 'select',
                'option_category' => 'color_option',
                'options'         => array(
                    'light' => esc_html__( 'Dark', 'et_builder' ),
                    'dark'  => esc_html__( 'Light', 'et_builder' ),
                ),
                'depends_default' => true,
                'description'     => esc_html__( 'Here you can choose whether your text should be light or dark. If you are working with a dark background, then your text should be light. If your background is light, then your text should be set to dark.', 'et_builder' ),
            ),
            'masonry_tile_background_color' => array(
                'label'           => esc_html__( 'Grid Tile Background Color', 'et_builder' ),
                'type'            => 'color-alpha',
                'custom_color'    => true,
                'tab_slug'        => 'advanced',
                'depends_show_if' => 'off',
            ),
            'use_dropshadow'                => array(
                'label'           => esc_html__( 'Use Dropshadow', 'et_builder' ),
                'type'            => 'yes_no_button',
                'option_category' => 'layout',
                'options'         => array(
                    'off' => esc_html__( 'Off', 'et_builder' ),
                    'on'  => esc_html__( 'On', 'et_builder' ),
                ),
                'tab_slug'        => 'advanced',
                'depends_show_if' => 'off',
            ),
            'disabled_on'                   => array(
                'label'           => esc_html__( 'Disable on', 'et_builder' ),
                'type'            => 'multiple_checkboxes',
                'options'         => array(
                    'phone'   => esc_html__( 'Phone', 'et_builder' ),
                    'tablet'  => esc_html__( 'Tablet', 'et_builder' ),
                    'desktop' => esc_html__( 'Desktop', 'et_builder' ),
                ),
                'additional_att'  => 'disable_on',
                'option_category' => 'configuration',
                'description'     => esc_html__( 'This will disable the module on selected devices', 'et_builder' ),
            ),
            'admin_label'                   => array(
                'label'       => esc_html__( 'Admin Label', 'et_builder' ),
                'type'        => 'text',
                'description' => esc_html__( 'This will change the label of the module in the builder for easy identification.', 'et_builder' ),
            ),
            'module_id'                     => array(
                'label'           => esc_html__( 'CSS ID', 'et_builder' ),
                'type'            => 'text',
                'option_category' => 'configuration',
                'tab_slug'        => 'custom_css',
                'option_class'    => 'et_pb_custom_css_regular',
            ),
            'module_class'                  => array(
                'label'           => esc_html__( 'CSS Class', 'et_builder' ),
                'type'            => 'text',
                'option_category' => 'configuration',
                'tab_slug'        => 'custom_css',
                'option_class'    => 'et_pb_custom_css_regular',
            ),
        );

        return $fields;
    }

    function shortcode_callback( $atts, $content = null, $function_name ) {
        $module_id                     = $this->shortcode_atts['module_id'];
        $module_class                  = $this->shortcode_atts['module_class'];
        $fullwidth                     = $this->shortcode_atts['fullwidth'];
        $posts_number                  = $this->shortcode_atts['posts_number'];
        $include_categories            = $this->shortcode_atts['include_categories'];
        $meta_date                     = $this->shortcode_atts['meta_date'];
        $show_thumbnail                = $this->shortcode_atts['show_thumbnail'];
        $show_content                  = $this->shortcode_atts['show_content'];
        $show_author                   = $this->shortcode_atts['show_author'];
        $show_date                     = $this->shortcode_atts['show_date'];
        $show_categories               = $this->shortcode_atts['show_categories'];
        $show_comments                 = $this->shortcode_atts['show_comments'];
        $show_pagination               = $this->shortcode_atts['show_pagination'];
        $background_layout             = $this->shortcode_atts['background_layout'];
        $show_more                     = $this->shortcode_atts['show_more'];
        $offset_number                 = $this->shortcode_atts['offset_number'];
        $masonry_tile_background_color = $this->shortcode_atts['masonry_tile_background_color'];
        $use_dropshadow                = $this->shortcode_atts['use_dropshadow'];
        $overlay_icon_color            = $this->shortcode_atts['overlay_icon_color'];
        $hover_overlay_color           = $this->shortcode_atts['hover_overlay_color'];
        $hover_icon                    = $this->shortcode_atts['hover_icon'];
        $use_overlay                   = $this->shortcode_atts['use_overlay'];
        global $paged;
        $module_class        = ET_Builder_Element::add_module_order_class( $module_class, $function_name );
        $container_is_closed = false;
        // remove all filters from WP audio shortcode to make sure current theme doesn't add any elements into audio module
        remove_all_filters( 'wp_audio_shortcode_library' );
        remove_all_filters( 'wp_audio_shortcode' );
        remove_all_filters( 'wp_audio_shortcode_class' );
        if ( '' !== $masonry_tile_background_color ) {
            ET_Builder_Element::set_style( $function_name, array(
                'selector'    => '%%order_class%%.et_pb_blog_grid .et_pb_post',
                'declaration' => sprintf(
                    'background-color: %1$s;',
                    esc_html( $masonry_tile_background_color )
                ),
            ) );
        }
        if ( '' !== $overlay_icon_color ) {
            ET_Builder_Element::set_style( $function_name, array(
                'selector'    => '%%order_class%% .et_overlay:before',
                'declaration' => sprintf(
                    'color: %1$s !important;',
                    esc_html( $overlay_icon_color )
                ),
            ) );
        }
        if ( '' !== $hover_overlay_color ) {
            ET_Builder_Element::set_style( $function_name, array(
                'selector'    => '%%order_class%% .et_overlay',
                'declaration' => sprintf(
                    'background-color: %1$s;',
                    esc_html( $hover_overlay_color )
                ),
            ) );
        }
        if ( 'on' === $use_overlay ) {
            $data_icon      = '' !== $hover_icon
                ? sprintf(
                    ' data-icon="%1$s"',
                    esc_attr( et_pb_process_font_icon( $hover_icon ) )
                )
                : '';
            $overlay_output = sprintf(
                '<span class="et_overlay%1$s"%2$s></span>',
                ( '' !== $hover_icon ? ' et_pb_inline_icon' : '' ),
                $data_icon
            );
        }
        $overlay_class = 'on' === $use_overlay ? ' et_pb_has_overlay' : '';
        if ( 'on' !== $fullwidth ) {
            if ( 'on' === $use_dropshadow ) {
                $module_class .= ' et_pb_blog_grid_dropshadow';
            }
            wp_enqueue_script( 'salvattore' );
            $background_layout = 'light';
        }
        $args     = array( 'posts_per_page' => (int) $posts_number );
        $et_paged = is_front_page() ? get_query_var( 'page' ) : get_query_var( 'paged' );
        if ( is_front_page() ) {
            $paged = $et_paged;
        }
        if ( '' !== $include_categories ) {
            $args['cat'] = $include_categories;
        }
        if ( ! is_search() ) {
            $args['paged'] = $et_paged;
        }
        if ( '' !== $offset_number && ! empty( $offset_number ) ) {
            /**
             * Offset + pagination don't play well. Manual offset calculation required
             *
             * @see: https://codex.wordpress.org/Making_Custom_Queries_using_Offset_and_Pagination
             */
            if ( $paged > 1 ) {
                $args['offset'] = ( ( $et_paged - 1 ) * intval( $posts_number ) ) + intval( $offset_number );
            } else {
                $args['offset'] = intval( $offset_number );
            }
        }
        if ( is_single() && ! isset( $args['post__not_in'] ) ) {
            $args['post__not_in'] = array( get_the_ID() );
        }
        ob_start();
        query_posts( $args );
        if ( have_posts() ) {
            while ( have_posts() ) {
                the_post();
                $post_format    = et_pb_post_format();
                $thumb          = '';
                $width          = 'on' === $fullwidth ? 1080 : 400;
                $width          = (int) apply_filters( 'et_pb_blog_image_width', $width );
                $height         = 'on' === $fullwidth ? 675 : 250;
                $height         = (int) apply_filters( 'et_pb_blog_image_height', $height );
                $classtext      = 'on' === $fullwidth ? 'et_pb_post_main_image' : '';
                $titletext      = get_the_title();
                $thumbnail      = get_thumbnail( $width, $height, $classtext, $titletext, $titletext, false, 'Blogimage' );
                $thumb          = $thumbnail["thumb"];
                $no_thumb_class = '' === $thumb || 'off' === $show_thumbnail ? ' et_pb_no_thumb' : '';
                if ( in_array( $post_format, array( 'video', 'gallery' ) ) ) {
                    $no_thumb_class = '';
                } ?>
                <article
                        id="post-<?php the_ID(); ?>" <?php post_class( 'et_pb_post' . $no_thumb_class . $overlay_class ); ?>>

                    <h2 class="entry-title blog-2" style="position: relative; top: -20px; margin-bottom: 10px;"><a
                                href="<?php esc_url( the_permalink() ); ?>"><?php the_title(); ?></a></h2>
                    <?php
                    et_divi_post_format_content();
                    if ( ! in_array( $post_format, array( 'link', 'audio', 'quote' ) ) ) {
                        if ( 'video' === $post_format && false !== ( $first_video = et_get_first_video() ) ) :
                            printf(
                                '<div class="et_main_video_container">
 %1$s
 </div>',
                                $first_video
                            );
                        elseif ( 'gallery' === $post_format ) :
                            et_pb_gallery_images( 'slider' );
                        elseif ( '' !== $thumb && 'on' === $show_thumbnail ) :
                            if ( 'on' !== $fullwidth ) {
                                echo '<div class="et_pb_image_container">';
                            } ?>
                            <a href="<?php esc_url( the_permalink() ); ?>" class="entry-featured-image-url">
                                <?php print_thumbnail( $thumb, $thumbnail["use_timthumb"], $titletext, $width, $height ); ?>
                                <?php if ( 'on' === $use_overlay ) {
                                    echo $overlay_output;
                                } ?>
                            </a>
                            <?php
                            if ( 'on' !== $fullwidth ) {
                                echo '</div> <!-- .et_pb_image_container -->';
                            }
                        endif;
                    } ?>
                    <?php if ( 'off' === $fullwidth || ! in_array( $post_format, array(
                            'link',
                            'audio',
                            'quote'
                        ) ) ) { ?>
                        <?php if ( ! in_array( $post_format, array( 'link', 'audio' ) ) ) { ?>
                        <?php } ?>
                        <?php
                        if ( 'on' === $show_author || 'on' === $show_date || 'on' === $show_categories || 'on' === $show_comments ) {
                            printf( '<p class="post-meta">%1$s %2$s %3$s %4$s %5$s %6$s %7$s</p>',
                                (
                                'on' === $show_author
                                    ? sprintf( __( 'by %s', 'et_builder' ), '<span class="author vcard">' . et_pb_get_the_author_posts_link() . '</span>' )
                                    : ''
                                ),
                                (
                                ( 'on' === $show_author && 'on' === $show_date )
                                    ? ' | '
                                    : ''
                                ),
                                (
                                'on' === $show_date
                                    ? sprintf( __( '%s', 'et_builder' ), '<span class="published">' . esc_html( get_the_date( $meta_date ) ) . '</span>' )
                                    : ''
                                ),
                                (
                                ( ( 'on' === $show_author || 'on' === $show_date ) && 'on' === $show_categories )
                                    ? ' | '
                                    : ''
                                ),
                                (
                                'on' === $show_categories
                                    ? get_the_category_list( ', ' )
                                    : ''
                                ),
                                (
                                ( ( 'on' === $show_author || 'on' === $show_date || 'on' === $show_categories ) && 'on' === $show_comments )
                                    ? ' | '
                                    : ''
                                ),
                                (
                                'on' === $show_comments
                                    ? sprintf( esc_html( _nx( '1 Comment', '%s Comments', get_comments_number(), 'number of comments', 'et_builder' ) ), number_format_i18n( get_comments_number() ) )
                                    : ''
                                )
                            );
                        }
                        $post_content = get_the_content();
                        // do not display the content if it contains Blog, Post Slider, Fullwidth Post Slider, or Portfolio modules to avoid infinite loops
                        if ( ! has_shortcode( $post_content, 'et_pb_blog' ) && ! has_shortcode( $post_content, 'et_pb_portfolio' ) && ! has_shortcode( $post_content, 'et_pb_post_slider' ) && ! has_shortcode( $post_content, 'et_pb_fullwidth_post_slider' ) ) {
                            if ( 'on' === $show_content ) {
                                global $more;
                                // page builder doesn't support more tag, so display the_content() in case of post made with page builder
                                if ( et_pb_is_pagebuilder_used( get_the_ID() ) ) {
                                    $more = 1;
                                    the_content();
                                } else {
                                    $more = null;
                                    the_content( esc_html__( 'read more...', 'et_builder' ) );
                                }
                            } else {
                                if ( has_excerpt() ) {
                                    the_excerpt();
                                } else {
                                    truncate_post( 270 );
                                }
                            }
                        } else if ( has_excerpt() ) {
                            the_excerpt();
                        }
                        if ( 'on' !== $show_content ) {
                            $more = 'on' == $show_more ? sprintf( ' <a href="%1$s" class="more-link" >%2$s</a>', esc_url( get_permalink() ), esc_html__( 'read more', 'et_builder' ) ) : '';
                            echo $more;
                        }
                        ?>
                    <?php } // 'off' === $fullwidth || ! in_array( $post_format, array( 'link', 'audio', 'quote', 'gallery' ?>
                </article> <!-- .et_pb_post -->
                <?php
            } // endwhile
            if ( 'on' === $show_pagination && ! is_search() ) {
                echo '</div> <!-- .et_pb_posts -->';
                $container_is_closed = true;
                if ( function_exists( 'wp_pagenavi' ) ) {
                    wp_pagenavi();
                } else {
                    if ( et_is_builder_plugin_active() ) {
                        include( ET_BUILDER_PLUGIN_DIR . 'includes/navigation.php' );
                    } else {
                        get_template_part( 'includes/navigation', 'index' );
                    }
                }
            }
            wp_reset_query();
        } else {
            if ( et_is_builder_plugin_active() ) {
                include( ET_BUILDER_PLUGIN_DIR . 'includes/no-results.php' );
            } else {
                get_template_part( 'includes/no-results', 'index' );
            }
        }
        $posts = ob_get_contents();
        ob_end_clean();
        $class  = " et_pb_module et_pb_bg_layout_{$background_layout}";
        $output = sprintf(
            '<div%5$s class="%1$s%3$s%6$s"%7$s>
 %2$s
 %4$s',
            ( 'on' === $fullwidth ? 'et_pb_posts' : 'et_pb_blog_grid clearfix' ),
            $posts,
            esc_attr( $class ),
            ( ! $container_is_closed ? '</div> <!-- .et_pb_posts -->' : '' ),
            ( '' !== $module_id ? sprintf( ' id="%1$s"', esc_attr( $module_id ) ) : '' ),
            ( '' !== $module_class ? sprintf( ' %1$s', esc_attr( $module_class ) ) : '' ),
            ( 'on' !== $fullwidth ? ' data-columns' : '' )
        );
        if ( 'on' !== $fullwidth ) {
            $output = sprintf( '<div class="et_pb_blog_grid_wrapper">%1$s</div>', $output );
        }

        return $output;
    }
}

new DS_Custom_Module_Blog;

 

Stephen James

SJ is a web developer living in the coastal town of Southsea, England. He is a Divi and WordPress advocate and the founder of Divi Space.