Edit question points in Bulk in LearnDash LMS

Let’s say you have more than 2000 questions in your questions bank in LearnDash.

You created these progressively over the last year, so the manual work was justifiable.

Now, for a new cohort of your course, you want to change the points of a set of questions, say, 10% of your questions bank or about 200+.

I don’t know about you, but I don’t feel like manually editing 200+ questions one by one to change the points assigned to them.

So, how can we do this in a bulk operation?

My product Bulk Actions for LearnDash helps you edit question points in bulk.

And in a natural way that blends in with the WordPress and LearnDash admin options.

Simply go to LearnDash LMS > Questions.


Select the questions you want to edit in bulk.

In the drop down at the top left corner of the questions list, select Edit then click Apply.


Once the bulk edit panel expands, set the value you want in the Question Points field.


Then click update.

After the page reloads, you’ll see all selected questions now have the points you just assigned.

How cool is that?

Bulk Actions for LearnDash is a work in progress so stay tunned.

Let me know what you think in the comments.

Título de Prueba

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas risus tellus, tempus vitae sapien et, congue aliquet tellus. Fusce blandit scelerisque dolor ac dictum. Maecenas malesuada magna ipsum, in aliquam nunc porta sit amet. Suspendisse eleifend facilisis risus, non euismod quam suscipit hendrerit. Morbi varius odio id sapien euismod commodo. Aenean lacinia pellentesque lacus, et dignissim velit dapibus vitae. Duis at nibh odio. Quisque elementum neque nisi, eget molestie ante vehicula quis. Duis egestas nibh non felis dictum, ut tincidunt nunc rhoncus. Nulla pharetra vulputate nisi, et scelerisque turpis sagittis et. In rhoncus convallis arcu, nec semper ligula tristique non. Nunc ac sapien commodo, auctor quam a, sodales est. Suspendisse cursus tincidunt nisi, quis viverra ipsum eleifend quis. Integer varius justo eu lacus sodales condimentum. Maecenas vitae lobortis enim.

[google_map_for_business addresses=”96-16 Queens Boulevard, Queens, NY”]

In felis dolor, placerat in mi ac, imperdiet congue metus. In fringilla lacus eu enim consectetur, id hendrerit lacus volutpat. In mattis eu orci non ullamcorper. In elementum felis augue, quis fermentum nisi porta vitae. Nulla accumsan congue facilisis. Vivamus in mattis arcu. Ut a ex justo. Proin posuere nisl in nisi suscipit eleifend et ut lectus. Nullam interdum varius tortor, at rutrum libero tempor ac. Sed quis pharetra diam. Morbi a odio sagittis, aliquam risus at, faucibus nibh.

Este es contenido que se va a mostrar unicamente en la publicación.

Phasellus et nisl non augue malesuada tristique non non ligula. Phasellus volutpat justo sed elit ornare, vel molestie tellus laoreet. Maecenas a commodo velit. Mauris et neque id tellus consequat laoreet. Sed tristique tortor nibh, sit amet pulvinar ipsum pulvinar bibendum. Ut eu nunc enim. Maecenas in imperdiet sem. Proin quis pretium enim. Maecenas in dui nec diam aliquet luctus et vel est.

Proin tristique facilisis augue, nec condimentum velit fringilla eget. Suspendisse massa dui, lobortis at enim ut, condimentum efficitur nulla. Vivamus nec felis nec augue cursus molestie eu vitae libero. Phasellus et dui nec neque convallis volutpat quis sed diam. Nam lacinia dui nec lectus efficitur mattis. Maecenas a arcu velit. Suspendisse hendrerit nulla a turpis vestibulum porta. Suspendisse potenti. Vestibulum euismod nulla nec arcu facilisis porta. Quisque sodales est sed porta imperdiet. Quisque vitae mauris lacus. Nunc non neque lorem. Etiam et varius orci. Integer at pulvinar leo.

Morbi vel dictum mi. Proin et porta neque. Proin ac lectus a neque posuere consectetur. Donec tempor mi quis erat vehicula auctor. Ut placerat eros ligula, porttitor mattis velit lobortis a. Praesent consequat nunc erat, ut semper justo efficitur nec. Maecenas sed tellus et nisi congue lobortis. Mauris fringilla orci ac gravida iaculis. Phasellus posuere, ligula vel volutpat feugiat, mauris purus venenatis nisl, vel convallis sapien ligula at nisl. Integer molestie nec orci et sollicitudin. Integer erat urna, pretium sit amet placerat id, rutrum nec erat. In sed arcu vel urna sollicitudin tristique. In hac habitasse platea dictumst. Duis commodo, orci sed bibendum rutrum, urna velit mollis urna, sed commodo quam ex et enim. Nulla ac pharetra erat. Aliquam eget eros magna.

Bulk “assign categories to quiz” questions in LearnDash LMS

Assigning categories to quizzes themselves is pretty much straightforward since the quiz custom post type in LearnDash supports Quiz Categories as a taxonomy of the custom post type.

The problem arises with Quiz Questions because Question Categories are handled differently. They are not taxonomy of the custom post type, so you cannot select them with a direct approach as you would expect.

Simply put, there is no default way to add Question Categories via the Bulk Actions edit option.

The solution?

A plugin I created a couple of weeks ago called Bulk Edit Questions Category for LearnDash.

In short, after installing the plugin, go LearnDash LMS > Questions and select any questions from the list of questions.

In the Bulk Actions drop down select Edit and then click Apply.

When the bulk actions panel expands, in the Category field select any the desired category you want to set for the selected questions then click the blue update button.


After the page reloads you’ll see the new category is correctly assigned to the questions previously selected.

Bulk Edit Questions Category for LearnDash is available for free on my GitHub account: https://github.com/j2machado/bulk-edit-question-category-for-learndash.

This functionality will be part of a major project called Bulk Actions for LearnDash. It is a work in progress right now, so stay tuned.

Let me know what you think in the comments.

Bypass Drip Content for LearnDash

A couple of weeks ago I came up with the idea of designing a way to Bypass Drip Content in LearnDash in an simple way on a user per user basis and that also allows the user to mark the bypassed drip content as complete.

The Problem

Drip Content in LearnDash allows you to stop users from accessing a lesson immediately after they enroll in a course and sets them to wait for a certain period of time you configure in the lesson’s settings.

But there might be a case where you want to exclude some users from the drip content rule and grant them immediate access. Let’s you have a course that sells for $97 and every lesson is available every 7 days from one another.

Then say you want to offer a premium version of it at $297 and between the premium benefits give the users full access to all lessons at once.

To bypass drip content for LearnDash right now you’d need to either do a complex setup with a CRM and an automation tool like Zapier, Pabbly, WP Fusion, or change the user’s enrollment date manually for each user.

The Solution

The plugin I came up with Bypass Drip Content for LearnDash helps you bypass drip content on a lesson by lesson basis, picking users one by one or including users from a group at once (supports multiple groups).

You can download it for free from the repository in my GitHub account: https://github.com/j2machado/bypass-drip-content-for-learndash.

But what is drip content in LearnDash anyway?

Essentially, LearnDash LMS allows you to drip content by setting a release time in the Lesson Release Schedule option. You can access this option in the Lesson Settings. There are two ways to reach the Lesson Settings:

  1. Go to Lessons in the WordPress admin and select a lesson from the list.
    After selecting the lesson and the lesson edit screen loads, click the Settings tab at the top left area.
  2. Go to Courses in the WordPress admin and select any course.
    After selecting the course go to the Builder tab at the top menu.
    In the Lesson list below in the course builder, expand the Lesson you want to edit and click the Edit link.

No matter what process you choose, after selecting the Lesson, go to the Settings tab like so:

The Lesson Release Schedule option is the third option inside Lesson Access Settings:

You have three modes:

  1. Immediately – this enables access to the Lesson immediately (no drip).
  2. Enrollment-based. The lesson will be available X number of days after the user enrolled in a course.
  3. Specific date – The lesson will be available on a specific date regardless of the date the user enrolled in the course. If the current date is greater than the date specified in the “Specific Date” option, then the user will be granted immediate access.

After setting the Lesson Release Schedule for your lessons, the course content is now on a drip row.

To bypass drip content, LearnDash has a filter ld_lesson_access_from__visible_after.

This filter accepts three arguments, $lesson_access_from, $lesson_id, $user_id.

  • $lesson_access_from – The timestamp when the Lesson becomes available.
  • $lesson_id – The lesson post ID in WordPress.
  • $user_id – The user ID in WordPress.

You can set the filter to return 0 or the output of the PHP time() function and that will suffice to bypass the drip content like so:

add_filter( 'ld_lesson_access_from__visible_after' , 'bypass_drip_content_for_learndash');

function bypass_drip_content_for_learndash($lesson_access_from, $lesson_id, $user_id){
    return time(); //Can return 0 too.
}

But what about bypassing drip content for specific users, and ignore the remaining users?

That’s where Bypass Drip Content for LearnDash comes in handy.

Sure you can do your own logic making an array of user IDs then mapping that array with the current logged in user, etc. That’s basically what the Bypass Drip Content for LearnDash does already for you.

After installing and activating the plugin on your LearnDash website, go to the Settings tab in the lesson you want to enable bypass drip content and you’ll see the following at the very bottom of the Lesson Settings screen:

Click the radio switch to Enable Bypass Drip Content.

Then you’ll see two options get expanded:

  • Select Users to Bypass – Allows you to select specific users allowed to bypass the current lesson.
  • Select Groups to Bypass – Allows you to select groups. All members of any select groups are allowed to bypass the current lesson.

You can download the plugin completely for free from my GitHub account: https://github.com/j2machado/bypass-drip-content-for-learndash.

CANVA APP DEVELOPMENT TIP: Scrollable View without the Scrollable Container

When needing to make a Scrollable View in a Canva app, you can wrap the view in a Scrollable Container component from the @canva/app-ui-kit set of components.

But, let’s say that for some reason you don’t want to use the Scrollable Container provided by Canva’s App SDK, you can use an alternative with React Native Web and the ScrollView component provided by it.

To make it work, you may first need to install React Native Web if you haven’t:

npm i react-native-web

A minimal example would look like this:

import { Box, TextInput, Button, Text, Rows, TypographyCard } from "@canva/app-ui-kit";
import { ScrollView } from "react-native-web";
import "@canva/app-ui-kit/styles.css";

return(
<ScrollView style={{ maxHeight: '400px', overflowY: 'auto' }}>
    <Rows spacing="1">
        <Box
            padding="2"
            marginTop="2"
            border="solid"
            borderWidth="1"
            background="secondary"
            >
            <Rows spacing="1">
                <Text></Text>  
            </Rows>
        </Box>
    </Rows>
</ScrollView>
);

Disable the Elementor Theme Builder programmatically

If for any reason you see yourself in the need to deactivate the Elementor Theme Builder, you can use the following snippet:

add_action( 'wp_loaded', 'remove_elementor_register_conditions', 1 ); // Hook with priority 1

function remove_elementor_register_conditions() {

    // Get Elementor Pro's Module instance.
    $theme_builder_module = \ElementorPro\Modules\ThemeBuilder\Module::instance();

    // Access Conditions Manager.
    $conditions_manager = $theme_builder_module->get_conditions_manager();

    // Check if not null.
    if ( $conditions_manager ) {
        remove_action( 'wp_loaded', [ $conditions_manager, 'register_conditions' ] ); // Unhook the register_conditions() method from the Theme Builder module.
}

Reliably check for the checkout page earlier than is_checkout()

The is_checkout() method is the standard to build conditionally logic around on whether the user is on the checkout page or not.

But what if it is too early for is_checkout() to be available?

Paying attention to the WordPress load lifecycle, is_checkout is available “too late” in the cycle.

It may work when called in a function hooked to template_reditect or wp_head.

If you need it before the template has loaded, is_checkout renders completely useless.

So, you need to get creative to check whether the current page is the checkout page or not.

A solution I came up with recently for a project is like so:

add_action('wp_loaded', function(){
  
    //Get the page ID from the WooCommerce option.
    $checkout_page_id = get_option( 'woocommerce_checkout_page_id' );

    // Transform the obtained string value into an integer.
    $checkout_page_id = (int)$checkout_page_id;

    // Get the Request Slug.
	  $request_uri = $_SERVER['REQUEST_URI'];
	  // Get the Post ID from the obtained slug.
	  $post_id = url_to_postid($request_uri);

    // Compare the two IDs.
	  if( $post_id == $checkout_page_id ) {
		    exit('this is the checkout page'); // If the IDs match identically, then we are on the checkout page.
	  }
});

The example above uses a combination of retrieving the checkout page ID stored in the wp_options table as a WooCommerce option.

Then obtains the Page ID of the current query.

When the IDs match, we are on the checkout page.

Quick Note to Self on All In One WP Migration by Servmask and error 403

When trying to import a dump file from AIO WP Migration by Servmask, in some servers, the import stales at the import progress bar not updating forever.

This is usually related to an error 403.

Most of the times, it is a problem at the server side.

If you are using Cloudflare with the domain, you may benefit from whitelisting the server’s IP address and also your IP address in use, either your unfiltered IP address or VPN IP address.

This can be done under Security > WAF > Tools.

Screenshot credits to Dustin Hyle from Iridum Hosting.

But going back to the server side of things, you can try temporarily turning off ModSecurity while the import happens.

Remember to turn ModSecurity back on after the import is complete.

Last but not least, in very very rare cases it could be a permissions issue in the file. This can happen on the system you are using, after the file is downloaded it could be stored with read-only permissions. Ensure to set the file permissions to something workable like 755.

How to add text or an icon to the Shipping Method label in WooCommerce

For your available shipping methods, you may want to add an image, text, or any other type of indicator at the farther right corner of the Shipping Method name.

You can do so with the woocommerce_cart_shipping_method_full_label filter.

This filters the shipping method full label and accepts two parameters, $label, the label of the shipping method, and $method, the actual method object.

To filter a specific method label, you can reference the method by its id.

You can access the method_id property of the method object.

Then set different cases in a switch statement covering the different methods you want to filter.

At the end, always return $label.

/*
* Filter the shipping method labels and add an extra text string to the right side of the method's label.
*
* @param $label. The shipping method label.
* @param $method. The method object.
* @return $label. The shipping method label, whether modified or left intact.
*/
add_filter( 'woocommerce_cart_shipping_method_full_label', 'filter_woocommerce_cart_shipping_method_full_label', 10, 2 ); 

function filter_woocommerce_cart_shipping_method_full_label( $label, $method ) { 
   // Use the condition here with $method to apply an image or text string to a specific method.      
	
	switch($method->method_id){
		case 'free_shipping':
			$label = $label . '<span>FREE</span>';
			break;
		case 'flat_rate':
			$label = $label . '<span>Icon</span>';
			break;
	}
	
	
   return $label; // Always return $label.
}

This is tested and works perfectly with CheckoutWC 9.0.25 (the latest version at the time of writing).

Prevent the CheckoutWC’s Side Cart from opening when editing a page with Elementor.

There is a problem with Elementor and CheckoutWC, and that is, when you try to edit any page in the Elementor editor, and you save a change, the CheckoutWC’s Side Cart will auto-open for some strange reason.

This problem is somewhat complex to solve as essentially, the Elementor editor lives in its own environment. This means that if you try to run a WordPress hook from the WordPress core or a third-party plugin, it may or may not work.

I found a workaround to get rid of the Side Cart auto opening when editing a page with Elementor.

It is not the most elegant solution, but hey, it works!

The logic behind it is simple: Detect if the Elementor editor exists on the current page, with JavaScript and CSS selectors. If it does, hide the Side Cart’s floating icon, drawer element, and the background overlay.

See the code below:

function custom_inline_script_for_elementor() {
    // Check if we are in the admin area and return early if we are not
    if (is_admin()) {
        return;
    }

    ?>
    <script>
    (function() {
        var checkAndHide = function() {
            if (document.body.classList.contains('elementor-editor-active')) {
                var elementsToHideIds = ['cfw-side-cart-floating-button', 'cfw-side-cart-overlay', 'cfw-side-cart'];
                elementsToHideIds.forEach(function(id) {
                    var elementToHide = document.getElementById(id);
                    if (elementToHide) {
                        elementToHide.style.display = 'none';
                    }
                });
            }
        };

        // Check immediately
        checkAndHide();

        // Then check repeatedly for a few seconds
        var intervalId = setInterval(checkAndHide, 500);

        // Stop checking after 5 seconds
        setTimeout(function() {
            clearInterval(intervalId);
        }, 5000);
    })();
    </script>
    <?php
}
add_action('wp_footer', 'custom_inline_script_for_elementor');