Set the Course Progression conditionally for specific users.

Linear Progression in LearnDash forces users to consume your course content lesson by lesson. They cannot skip through the lessons.

Let’s say you have a course with Linear progression. All enrollees are forced to access the content in this way.

But what if you want to allow a small and specific set of users to hop through the lessons freely?

You can conditionally set the Course Progression to Free Form for some specific users.

Create a Group to hold the ‘Free Form’ allowed users.

Grab the Group ID.

Add any relevant users to the Group.

Grab the Course ID.

Add the following snippet to your theme’s functions.php:

add_filter('learndash_course_progression_enabled', function($setting, $course_id){

return learndash_is_user_in_group(get_current_user_id(), GROUP_ID) && $course_id == COURSE_ID ? false : $setting;

}, 10, 2);

Ensure to replace GROUP_ID with the actual ID of the Group and COURSE_ID with the real ID of the Course you want to apply this condition.

In the logic above, the learndash_course_progression_enabled filter expects a boolean value.

To allow free form, we need to force the returned value to false when the condition matches.

NOTE: It is NOT required to add the Course to the Group.

The Group is used to identify the users who should be allowed free-form progression instead of linear progression.

Check the documentation links below:

Protect specific fields in form entries on Gravity Forms.

Let’s say you have a form that collects sensitive data like email address, user government ID, etc.

By default, when an entry is submitted all those details are exposed in the individual entry on the WordPress admin.

Let’s also say that you delegate some tasks on your website and have collaborators that can access the form entries in the WordPress admin.

You may probably want to restrict some entry details from them, to protect your user’s privacy.

Something you can do is filter the field values displayed for each entry.

You can intercept the field value and change it to whatever you prefer.

Gravity Forms provide two useful filters for this matter:

gform_entries_field_value. https://docs.gravityforms.com/gform_entries_field_value/.

gform_entry_field_value. https://docs.gravityforms.com/gform_entry_field_value/.

The difference between each is that the first one, gform_entries_field_value, intercepts the field values in the Entries list.

The second one, gform_entry_field_value, filters the field values in the Individual entry page.

Here is a sample snippet to protect the field value on the Entries list:

add_filter( 'gform_entries_field_value', 'protect_specific_field_from_entries_list', 10, 4 );
function protect_specific_field_from_entries_list( $value, $form_id, $field_id, $entry ) {
    // Replace field_id with the ID of the field you want to hide
    if ( $field_id == 17 ) {
        return 'PROTECTED'; 
    }
    
    return $value;
}

You can reference the field or fields by the field ID.

Then, on every match, return a custom value.

Here is a sample snippet to protect the field value on the individual entry post:

add_filter( 'gform_entry_field_value', 'protect_field_value_in_entry_detail', 10, 4 );
function protect_field_value_in_entry_detail( $value, $field, $entry, $form ) {
    // Replace $field->id with the ID of the field you want to protect.
    
    if ( $field->id == 17 ) {
        
        $field->label = 'PROTECTED';
        return 'PROTECTED';
    }
    
    return $value;
}

The results should be as follows:

image
image

If you inspect the elements in the browser’s dev console, the rendered values correspond to your new custom values.

image

Check the documentation links below:

Disable the Side Cart conditionally in CheckoutWC on specific pages.

The Side Cart is convenient in any store to help users access the products they are about to purchase.

But at the same time, it may also be convenient not to have it on all pages on your website.

Some pages require more attention from the user.

So a simple way you have to prevent CheckoutWC’s Side Cart from appearing in specific pages is using the cfw_disable_side_cart filter.

In short, the cfw_disable_side_cart filter intercepts the on/off option that dictates if the Side Cart is enabled or disabled.

Following the filter’s name, you need to set it to true, and the Side Cart won’t pop up.

To dynamically change the value while detecting the page, you can use a preg_match function to check if certain slug is part of the URL.

Then wrap the preg_match evaluation in a boolval() function so it returns explicitly a boolean.

For example, if you want to disable the Side Cart on your About Us page, and the slug is yourwebsite.com/about-us/, you can do so as follows:

add_filter(
	'cfw_disable_side_cart',
	function() {
		return boolval( preg_match( "/^\/about-us/", $_SERVER['REQUEST_URI'] ) );
	}
);

Remove the upload percentage from the File Upload field in Gravity Forms.

In Gravity Forms, you can add the File Upload field to allow users to upload documents when submitting your form.

It can be used for virtually any file type, from text documents to image files, etc.

Gravity Forms, like any form software, validates unfilled required fields. If a user tries to submit a form without completing the required fields, the form page will reload with an error.

If the user uploaded a file during that incorrect submission, the file will persist in the upload field through the page refresh with the error.

But, a percentage number will suffix the filename in the upload field.

Something like this:

If there were no problems with the upload during the form error evaluation, the percentage would be equal to 100%.

That means the file was completely uploaded with the upload field and is ready.

If you complete the form, the file will be submitted along with any other data entered by the user.

What if you want to remove that percentage from the upload field?

So, even if the form page is reloaded when errors are found, the percentage is not displayed.

This is possible with a filter called gform_file_upload_markup.

Documentation here: https://docs.gravityforms.com/gform_file_upload_markup/.

First, we must understand that the upload percentage is part of the File Upload field markup.

The upload percentage is an HTML element under a span tag with its own CSS class, gfield_fileupload_percent.

You could add a quick CSS snippet and hide the upload percentage with display: none, like so:

.gfield_fileupload_percent{
     display:none;
}

And that works well. But it is only a cosmetic effect.

The percentage is still there in the DOM.

An alternative method is removing the upload percentage from the File Upload field markup before rendering it in the front end.

That’s where the gform_file_upload_markup filter comes into play.

The filter in question helps you intercept the File Upload field markup in Gravity Forms.

The challenge is that a long array of markup is associated with that filter.

If not handled carefully, you could mess up the File Upload field markup.

To solve this issue, you can leverage PHP’s string manipulation capabilities.

In this instance, you can check in the filter for the existence of the upload percentage field in the File Upload markup.

If it does not exist, abort further execution and return the File Upload field markup as is.

But if it is detected, then continue the execution: fiend the start and end of the span element with the CSS class gfield_fileupload_percent assigned to it.

Then, remove the selected element from the File Upload field markup.

Below is an example snippet that would achieve just that:

add_filter( 'gform_file_upload_markup', 'remove_gfield_fileupload_percent', 10, 4 );

function remove_gfield_fileupload_percent( $file_upload_markup, $file_info, $form_id, $field_id ) {
    // Check for the presence of 'gfield_fileupload_percent' in the markup.
    $percent_pos = strpos( $file_upload_markup, 'gfield_fileupload_percent' );
    if ( $percent_pos === false ) {
        // If 'gfield_fileupload_percent' is not found, return the current markup as is.
        return $file_upload_markup;
    }

    // Find the start of the span containing 'gfield_fileupload_percent'.
    $start_pos = strrpos( substr( $file_upload_markup, 0, $percent_pos ), '<span' );

    // Find the end of the span.
    $end_pos = strpos( $file_upload_markup, '</span>', $percent_pos ) + 7;

    // Remove the span containing 'gfield_fileupload_percent'.
    $file_upload_markup = substr_replace( $file_upload_markup, '', $start_pos, $end_pos - $start_pos );

    return $file_upload_markup;
}

This code should go in your theme’s functions.php or your preferred code snippets plugin.

The code can be used as is without the need for modification.

The exception would be that you want to add any custom conditions, etc.

Until next time!

Auto-enroll users in a Group or Course based on their email domain in LearnDash LMS.

LearnDash, by default, has different enrollment types: Open, Free, Buy Now, Recurring, and Closed.

When you set a course access mode to Open, any user account registered on your website will be automatically enrolled in that course.

Non-logged-in visitors will simply see all the course content. Just LearnDash won’t track any progress as there is no user account to assign the course completion yet.

With the remaining access modes, your users must complete some action in order to enroll in the courses.

The advantage is that not everyone will be part of the course, allowing you to keep a cleaner record of the enrolled users.

The disadvantage is that it may create friction.

Going on a different route, let’s say your online academy is receiving deal offers from related brands that have the potential to have a significant and positive impact on your business.

And let’s say that part of the deal is to allow access to a set of courses ‘for free’ to employees of that brand.

But this set of courses is Buy Now or Closed, and there is no clear way to make a frictionless enrollment process for the specific group of users.

You could create a free group that allows automatic access to any user who enrolls in it for a specific set of courses.

The problem with that is that even as hard as you try, with the base LearnDash setup, any user on the website may run into the group and would have free access to the courses.

That wouldn’t be the ideal outcome.

You could also prevent access to the group with a membership plugin like Paid Memberships Pro, but again, you’d need to indicate what users are part of that membership.

Or you could even manually enroll the users in the group to control who has access to the group and who does not.

An alternative, automated, and frictionless method is to enroll the users in a course or group as they sign up to your website but filter by their email address domain if they are eligible for auto-enrollment.

Saying your deal is with Delta Airlines, whose domain is www.delta.com, check any email address from @delta.com for the account creation. If the email domain matches, then auto-enroll the newly created user in a specific group with ID 5.

The code to achieve that would go something like so:

function add_user_to_group_based_on_email( $user_id ) {
    $user_info = get_userdata( $user_id );
    $email = $user_info->user_email;

    // Get domain from the email
    $domain = array_pop(explode('@', $email));

    // Define your specific domain and group ID
    $specific_domain = 'example.com'; // Change this to your specific domain
    $group_id = 123; // Change this to your LearnDash group ID

    // Check if domain matches
    if( $domain === $specific_domain ) {
        // Use the ld_update_group_access function to add the user to the group
        ld_update_group_access( $user_id, $group_id );
    }
}
add_action( 'user_register', 'add_user_to_group_based_on_email' );

The first step is to hook into the user_register action.

That allows you to intercept all user registrations.

Get the user by ID and get the user’s email.

Use a combination of explode and array_pop first to make the email address an array separated by the @ symbol. Then only stay with the last element of the array, which is the actual domain name.

Indicate the domain you want to filter in a variable and the ID of the group you want to auto-enroll the user into.

Then, evaluate the domains. If there is a match, leverage the ld_update_group_access() function provided by LearnDash to update the access for the specific user.

That’s about it. Now over to you.

Let me know in the comments below if you have any questions or if you want to add something.

Have a good one!

Count the remaining days left for course access expiration in LearnDash

Depending on your business model, you may allow lifetime access to the course content after your students complete the course.

Maybe so they can revisit the lessons and boost their learning experience.

Or simply as a good will bonus, and allow them to see course updates in the long run.

On the other hand, you can also set an access expiration instead.

LearnDash provides a shortocde [ld_course_expire_status].

In short, it allows you to show your students their current access status. If the access has not expired yet, they’ll see the date until they have access to.

So far, so good.

But what if you want to show them a custom message like “You have 90 days left before your access expires”.

And each time they log into their course the number of days left is reduced accordingly until the expiration.

You’d need to create your own custom solution to show a custom expiration message like that.

So, the logic would work something like this:

  • Retrieve the access expiration timestamp.
  • Get the current time.
  • Calculate the difference between each timestamp in seconds.
  • Transform the resulting time difference to days.
  • Evalute if the returned difference is negative, then indicate the course has expired.
  • Build a default return value including the difference in days.

The code would be something as follows:


function ld_days_until_course_expiration( $user_id, $course_id ) {

    // Retrieve the timestamp for when the course access will expire.
    $expires_on_timestamp = ld_course_access_expires_on( $course_id, $user_id );
    
    // Return a message when the course does not have an expiration timestamp.
    if ( empty( $expires_on_timestamp ) ) {
        return __( 'Course access does not expire.', 'learndash' );
    }

    // Get the current time.
    $current_time = current_time( 'timestamp' );

    // Calculate the difference in seconds.
    $time_difference = $expires_on_timestamp - $current_time;

    // Convert the time difference to days.
    $days_left = floor( $time_difference / ( 24 * 60 * 60 ) );

    // Indicate the course has expired if the difference is negative.
    if ( $days_left <= 0 ) {
        return __( 'Course access has expired.', 'learndash' );
    }

    // Default message
    $message = sprintf( _n( 'There is %d day left before course access expires.', 'There are %d days left before course access expires.', $days_left, 'learndash' ), $days_left );

    /**
     * Filters the course expiration message.
     *
     * @param string $message Default expiration message.
     * @param int    $days_left Number of days left.
     * @param int    $user_id User ID.
     * @param int    $course_id Course ID.
     */
    return apply_filters( 'ld_course_expiration_message', $message, $days_left, $user_id, $course_id );
}

You can see in the return statement the returned value is wrapped in the apply_filters() WordPress function.

That allows you to filter the message later.

Let’s say you want to display a custom message when the user’s access is to expire in 10 days.

Maybe to create a scarcity sense and let them know they should renew their subscription.

You can do something like this:

function custom_ld_course_expiration_message( $message, $days_left, $user_id, $course_id ) {

    if ( $days_left == 10 ) {
        return __( 'Only 10 days left! Consider renewing or upgrading your course.', 'learndash' );
    }
    return $message;
}
add_filter( 'ld_course_expiration_message', 'custom_ld_course_expiration_message', 10, 4 );

The most basic usage, whatever you choice is, is simply echoing the ld_days_until_course_expiration() function like this:

add_action('wp_head', function(){
		echo ld_days_until_course_expiration( '2', '7' ); // Display the access expiration for a user with ID 2 enrolled in a course with ID 7.
});

Keep in mind the ld_days_until_course_expiration() function requires the user ID and the course ID you are checking the access expiration status for.

To make it happen dynamically, you can do the following:

  • Use the get_current_user_id() WordPress function to retrieve the current user ID.
  • Build a wrapper around get_the_ID(), to get the current post ID if it is a course.

That’s it. Now over to you.

Let me know in the comments if you have any ideas to add, or make any questions if something is not clear.

Until next time!

Display the discounted amount per product on the Order Details in WooCommerce.

After a customer completes a purchase in your WooCommerce store, they get redirected to the Order Received page.

This Order Received page contains information about the purchase, like the Order Number, the customer’s full name, email address, and payment method used, and there is also a table featuring the list of purchased items called Order Details.

Each item in the Order Details table usually has the Product name, Amount, Price, and, at the very end, a calculation of the order Subtotal, Shipping, Taxes (if any), and if you offered a discount, the discount for the total purchase.

Let’s say you are running a special deal to get sales rolling. You want to show your customers how much they are saving per item after the purchase.

Nice idea.

The problem is that WooCommerce does not have an option to display the discount per product.

So, how can you add one?

You can leverage the woocommerce_order_item_meta_end action.

In short, this action hook allows you to add custom content at the end of each order item.

The woocommerce_order_item_meta_end action allows you to use three parameters: item ID, item, and order.

First, you want to get the original item price, dividing the item subtotal by the item quantity the customer purchased.

Now, get the actual price paid per item, dividing the item total by the item quantity.

In WooCommerce, you may create Coupons that only apply to specific products. You may want to run the code only for products that qualify for the discount. Non-discounted products would not need to display a discounted price.

So, if the price the customer paid per a specific item is less than the original price of the same item, then show the discounted amount for that product.

An item will be excluded from the function when it fails this condition.

The resulting code looks like this:

add_action('woocommerce_order_item_meta_end', 'display_product_discount', 10, 3);
function display_product_discount($item_id, $item, $order) {
    // Get the original product price from the order item data
    $original_price = (float) $item->get_subtotal() / $item->get_quantity();

    // Get the actual price the customer paid for the product in the order
    $order_price_per_item = (float) $item->get_total() / $item->get_quantity();

    // Check if the product was discounted
    if ($order_price_per_item < $original_price) {
        // Calculate the discount per product
        $discount_per_product = $original_price - $order_price_per_item;

        // Display discount per product and discounted price with inline CSS
        echo '<div class="product-discount">';
        echo 'Discount: ' . wc_price($discount_per_product) . '<br>';
        echo '<span style="color: green; font-weight: bold;">Price after discount: ' . wc_price($order_price_per_item) . '</span>';
        echo '</div>';
    }
}

This is the final result

Enable LearnDash Focus Mode conditionally on a course-by-course basis.

Focus mode is the way LearnDash allows you to provide your users with a distraction-free interface in your courses.

The Focus Mode was introduced in LearnDash 3.0, and it has been available in all subsequent versions ever since.

In your WordPress admin, go to LearnDash LMS > Settings, and under the General tab, inside the Design & Content Elements meta box, scroll down to the ‘Focus Mode’ option.

There, you can enable or disable the Focus Mode.

LearnDash Focus Mode is a global feature by design. When you turn it on, you are enabling it for all courses without exceptions.

That brings the question: How can you partially enable the Focus Mode?

It would be interesting if you could say what courses should have Focus Mode and what courses shouldn’t have it, right?

In LearnDash, there is no option to enable the Focus Mode for a specific course, though.

There is no way to enable the Focus Mode and then select what courses should be excluded from it either.

A couple of weeks ago, a member on the LearnDash LMS Tips & Tricks group on Facebook made the following question:

Is there a way to enable/disable Focus Mode on a course-by-course basis?

The answer was very clear: No, unless it involves custom development.

That question stuck with me, and I decided to take it up for a challenge.

The short answer is yes; you can enable or disable the LearnDash Focus Mode on a course-by-course basis.

So, it is possible. YEAH!

Let’s dive into how you can enable or disable the LearnDash Focus mode on a course by course basis.

You can enable or disable the Focus Mode globally from the admin, as discussed before.

Behind the scenes, the Focus Mode on/off toggle button in the LearnDash settings page is connected to a value in the wp_options table in the WordPress database.

There is an option called learndash_settings_theme_ld30 that holds as value a serialized array full of options as properties.

One of those values is focus_mode_enabled.

But it actually is not there by default. At least not when you have not enabled the Focus Mode yet.

You may ask, how the heck would you find out about focus_mode_enabled value in the first place?

Scan the LearnDash code base for focus_mode_enabled, and you’ll find a file called class-ld-settings-section-theme-ld30.php inside sfwd-lms/themes/ld30/includes, which contains the focus_mode_enabled option with a default value of zero.

Now, with that information, you can run the following experiment:

Retrieve the serialized option with the WordPress get_option() function.

$ld30_settings = get_option('learndash_settings_theme_ld30', array());

The value should be automatically unserialized and transformed into an array.

Now, select the focus_mode_enabled property in the array and assign it an empty string as a value.

$ld30_settings['focus_mode_enabled'] = '';

Lastly, change the option value with your newly assigned value. Use the WordPress update_option() function to modify the value.

update_option('learndash_settings_theme_ld30', $ld30_settings);

Place all three snippets in the functions.php file of your active theme, save the changes, and load any page.

In the WordPress admin, go to LearnDash LMS > Settings, scroll down to the Focus Mode option, and you’ll see it is turned off.

Go back to your theme’s functions.php file and set the focus_mode_enabled property to ‘yes’, and save the changes.

$ld30_settings['focus_mode_enabled'] = 'yes';

Reload the tab with the LearnDash settings, scroll down to the Focus Mode option again, and you’ll see this time it is enabled.

Brilliant. You have discovered a way to programmatically enable or disable the Focus Mode.

With that knowledge, you can create a custom button in the wpadminbar that switches the Focus Mode on and off without having to go all the way down to the LearnDash settings page.

But that’s it. You still don’t have a way to conditionally enable or disable the LearnDash Focus Mode for a specific course.

However, what you now know is that the Focus Mode feature relies entirely on the value of the focus_mode_enabled option in the database.

LearnDash has some logic in place that looks for the focus_mode_enabled option value.

If it is set to yes, display Focus Mode.

If it is set to an empty string, disable Focus Mode from the view.

You’d need to somehow change the focus_mode_enable option value to yes or an empty string when a user visits a course to either enable or disable the Focus Mode for that specific course.

But then, if you use the method above of changing the option value with update_option(), you would bring a ton of conflicts to your website as with every page load, the option would be turning on and off.

The solution you need is to make LearnDash think the Focus Mode is enabled while it is not.

But how, you ask?

In WordPress, you have actions and filters, globally called hooks. There are regular hooks, and there are also dynamic hooks.

With the option_{$option} hook, you can filter the current value of an option.

For the option holding the focus_mode_enabled property, you’d reference it as follows.

option_learndash_settings_theme_ld30

And then you’d use it like this:

add_filter('option_learndash_settings_theme_ld30', 'modify_learndash_option_value');

function modify_learndash_option_value($value) {

    $value['focus_mode_enabled'] = 'yes';

    return $value; // Always return the value.
    
}

The focus_mode_enabled option is now set to yes globally. Similar to your previous experiment above, go to the LearnDash settings page again, and you’ll see the Focus Mode toggled on.

But is the Focus Mode enabled?

To LearnDash, Focus Mode is enabled. But if you retrieve the serialized option again and do a var_dump on it, you’ll see that the focus_mode_enabled option is not really part of the array:

$ld30_settings = get_option('learndash_settings_theme_ld30', array());

echo '<pre>';
var_dump($ld30_settings);
echo '</pre>';

What you really are doing is intercepting the focus_mode_enabled option value before LearnDash logic ‘sees’ it.

Then, when LearnDash sees the value, it is set to whatever you set it to.

See that the value in the database remains intact.

This is useful in case you want to enable or disable the Focus Mode globally and then virtually enable or disable it on a course-by-course basis.

The next step is to define the condition that allows you to enable the focus mode on a specific course.

If you dive a little deeper into the dynamic filters, you’ll find another one called pre_option_{$option}.

You can use any of the two option_learndash_settings_theme_ld30 or pre_option_learndash_settings_theme_ld30.

The advantage of using pre_option_{$option} is that it is called before WordPress retrieves the option from the database. This prevents a database call and allows you to set whatever value you want for the option in a virtual sense.

The experiment examples below use the pre_option_{$option} dynamic filter.

The next advantage of using the pre_option_{$option} dynamic filter is that it loads early in the WordPress load cycle.

If you see from the details discussed previously, you need to modify the value before LearnDash even know about it.

You can prove that by building a function and hooking it into the template_redirect action.

It may be enough to play around with the post object and retrieve the post ID and the post type, then check if it belongs to a course, filter the focus_mode_enabled option.

By the time all that happens, LearnDash already retrieved the focus_mode_enabled option value from the WordPress database and is loading the course layout based on that value, either with Focus Mode enabled or disabled.

In other words, you are too late in the WordPress load cycle.

You need to stick with the pre_option_learndash_settings_theme_ld30 dynamic filter.

Returning to the condition to check for the specific course, you need to be aware that the Focus Mode in a course applies only to the steps inside that course.

When you enable Focus Mode in the LearnDash settings and go to the Single Course Page, you will only see the Focus Mode when you open any step on that course, either a Lesson, Topic, Quiz, Assignment, etc.

That means there is no straightforward way to say “Hey Focus Mode, please do not appear in this course”.

You’d need to check in the lesson if it belongs to the specific course you want to enable or disable the Focus Mode for, and then run your custom logic.

Thankfully, LearnDash provides a function called learndash_get_primary_course_for_step(), which does just that.

The function requires the step ID to be passed as a parameter. It then will return the ID of the course it belongs to.

learndash_get_primary_course_for_step($current_step_id);

You’d simply store the course ID in a variable, then evaluate the output of learndash_get_primary_course_for_step() with the course ID.

Now, you’d need a way to retrieve the current step ID to run the conditional.

That is an easy feat with get_the_ID().

The problem is that the pre_option_learndash_settings_theme_ld30 filter is ‘too early’ in the load cycle.

What this means is, on that stage of the load cycle, you cannot access the post object in WordPress as it has not loaded yet.

So, how can you use the good things of “both worlds” and put together the dynamic filter but use your custom condition to enable or disable Focus Mode?

You can extract information from the requested URL, like this

$request_uri = $_SERVER['REQUEST_URI'];

Then, you can get the post ID like this:

$post_id = url_to_postid($request_uri);

Now, you are good to make your condition. Let’s say you want to enable the Focus Mode for a course with ID 728. Your code would look something like this:

$course_with_focus_mode = '728';

$request_uri = $_SERVER['REQUEST_URI'];
$post_id = url_to_postid($request_uri);

if(learndash_get_primary_course_for_step($post_id)){
  $value['focus_mode_enabled'] = 'yes';
};

IMPORTANT: You should set the ID as a string, as the learndash_get_primary_course_for_step() function returns a string.

Every time a post is queried, the code above will check if the current post belongs to the course with ID 728.

If true, then the focus_mode_enabled option will be set to yes.

Of course, you still need to wrap the code around the pre_option_learndash_settings_theme_ld30 dynamic filter, like so:

add_filter('pre_option_learndash_settings_theme_ld30', 'virtually_enable_focus_mode_request_uri');

function virtually_enable_focus_mode_request_uri($value) {

	if (!$value) {
        $value = array();
    }

    $course_with_focus_mode = '728';

    $request_uri = $_SERVER['REQUEST_URI'];
    $post_id = url_to_postid($request_uri);


    if ( learndash_get_primary_course_for_step( $post_id ) === $course_with_focus_mode )) {
        $value['focus_mode_enabled'] = 'yes';
    }

    return $value;
}

Can you include multiple courses at once? Yes, you can.

Create an array to hold a few course IDs.

Then, evaluate if the course ID retrieved is any of the courses in the array with the function is_array().

Modify the code like this:

add_filter('pre_option_learndash_settings_theme_ld30', 'virtually_enable_focus_mode_request_uri');

function virtually_enable_focus_mode_request_uri($value) {

	if (!$value) {
        $value = array();
    }

	$courses = array('7','34');

    $request_uri = $_SERVER['REQUEST_URI'];
    $post_id = url_to_postid($request_uri);


    if ( in_array(learndash_get_primary_course_for_step( $post_id ), $courses)) {
        $value['focus_mode_enabled'] = 'yes';
    }

    return $value;
}

If you are curious like me, you may have realized that this gives you the power to enable or disable the Focus Mode on a single specific step only, let’s say, a specific lesson, topic, quiz, etc.

You’d change the condition to check if the current $post_id matches a specific ID.

You can additionally check if the current post’s post type is a lesson.

add_filter('pre_option_learndash_settings_theme_ld30', 'virtually_enable_focus_mode_request_uri');

function virtually_enable_focus_mode_request_uri($value) {
    if (!$value) {
        $value = array();
    }

    $request_uri = $_SERVER['REQUEST_URI'];
    $post_id = url_to_postid($request_uri);

    if (get_post_type($post_id) === 'sfwd-lessons' && $post_id == 2337) {
        $value['focus_mode_enabled'] = 'yes';
    }

    return $value;
}

Ok, that’s it. Now, over to you.

Remember to safely test this on a staging environment before committing changes to a live site.

I tested this on LearnDash 4.9.0.1, the latest version available.

How can you test it out too?

Go to demo.learndash.com, and spin up a new LearnDash Demo.

Then, go to LearnDash LMS > Courses and retrieve some IDs from courses and lessons.

With these IDs, go to Appearance > Theme Editor, locate the functions.php file of the active theme (Kadence), and start hacking around with the snippets on this article.

Until next time!

Customize the WordPress author slug without a plugin

The author slug in WordPress is the ‘pretty link’ you can use to access the archive page of posts published by a specific user account.

To give you an example, https://obijuan.dev/author/obijuandev.

If you visit that link, you’ll get all posts created and published by my user account here on my website.

By default, WordPress gives you the author slug composed of the ‘author’ keyword followed by the account’s nicename. In most cases, this nicename can lead to a very easy guess of the actual username associated with the account, leading to potential security threats.

As a security measure, it is convenient to change this nicename to something else.

The most ‘straightforward’ method would be to change the nicename value directly in the database record for the user account.

However, in an ideal scenario, you should really not mess with the WordPress database. Even as a developer, you should keep the WordPress database intact as much as possible.

So, what other option do you have to customize the WordPress author slug?

You can leverage the power of hooks to mask the author’s nicename, hence changing the author slug with a custom name.

The main benefit of this approach is that it will protect the username from any potential attackers from knowing your account’s username.

The reasoning behind this is that attackers can use the account’s username in an attempt to crack your account’s password and gain admin access to your website.

So, how do you change the author slug in WordPress?

Let’s draw a few logical steps you can take advantage of to achieve the proposed outcome:

You need to indicate what user you will change the author slug for and the custom name you’ll replace the author slug with. You can store these in variables; please see the example below:

$specific_user = 'obijuan';
$custom_slug = 'obijuandev';

Use the author_link filter to change how WordPress links to the author’s page.

The author_link filter by default uses three parameters. For this example you only need to leverage two of those, $link and $author_id.

The first one gets the current author link value, while the other literally gets the author ID.

You also need to pass your $custom_slug and $specific_user to use the assigned values in the function’s logic.

Retrieve the user data with the get_userdata() function. This will bring the user object for a specific account.

Check if the user_login property matches the $specific_username, then build a ‘new link’ by replacing the selected account’s nicename with the $custom_slug.

add_filter('author_link', function ($link, $author_id) use ($custom_slug, $specific_username) {
    $user_info = get_userdata($author_id);
    if ($user_info->user_login == $specific_username) {
        $link = str_replace($user_info->user_nicename, $custom_slug, $link);
    }
    return $link;
}, 10, 2);

More information about the author_link filter can be found in the WordPress Code Reference: https://developer.wordpress.org/reference/hooks/author_link/.

Redirect visitors to the correct author’s page.

WordPress needs to know where to redirect the user when clicking a link with the new author slug.

You can add a rewrite rule to tell WordPress where to redirect the user.

add_rewrite_rule(
    'author/' . $custom_slug . '/?$', 
    'index.php?author_name=' . $specific_username,
    'top'
);

Lastly, ensure WordPress recognizes the new query variable.

add_filter('query_vars', function($vars) {
    $vars[] = 'author_name';
    return $vars;
});

To make the code work, wrap it around a function and hook that function into the init WordPress hook.

/*
* Snippet: Customize the WordPress author slug without a plugin.
* Author: Obi Juan.
* Author URI: https://obijuan.dev
* Explanation: https://obijuan.dev/customize-the-wordpress-author-slug-without-a-plugin/.
* Version: 1.0.0
* Publish Date: September 28th, 2023.
*/
function custom_author_slug() {
    // Set your custom slug and specific username here.
    $custom_slug = 'custom-slug';
    $specific_username = 'specific_username';

    // Customizing the author link.
    add_filter('author_link', function ($link, $author_id) use ($custom_slug, $specific_username) {
        $user_info = get_userdata($author_id);
        if ($user_info->user_login == $specific_username) {
            $link = str_replace($user_info->user_nicename, $custom_slug, $link);
        }
        return $link;
    }, 10, 2);

    // Adding a rewrite rule for the custom slug.
    add_rewrite_rule(
        'author/' . $custom_slug . '/?$', 
        'index.php?author_name=' . $specific_username,
        'top'
    );

    // Ensure WordPress recognizes the new query variable.
    add_filter('query_vars', function($vars) {
        $vars[] = 'author_name';
        return $vars;
    });
}
add_action('init', 'custom_author_slug');

Add the final version of the code to your theme’s functions.php file.

You can add it to your favorite code snippet plugin alternatively.

The third option is to create a small utility plugin to hold your code.

NOTE: The author slug will not change until you flush your Permalinks. To flush the Permalinks, in the WordPress admin go to SETTINGS > PERMALINKS, scroll down the page, and click the blue ‘Save’ button.

Over to you.

Let me know how it goes in the comments, whether you encounter any issues, if it works flawlessly on the first try (hopefully), or if you have any questions.

Git Rebase or how to reconcile your local repository with a remote repository

Sometimes when starting a new project, there is no time to go to the remote Git server to create the repository first there then clone it to your local machine.

Instead, it may be more convenient to start with git init and connect to the remote repository later.

When creating the remote repository to upload your work, it is possible you add some defaults as LICENSE and README. If your local repository does not have these changes, trying to PUSH your branch will end up in an error, with some general suggestion that you need a method to reconcile your branches.

Something you can do is fetch, then rebase origin <> master.

All you need to do is:

git fetch
git rebase origin/master

After that then you’ll see the files that were created in your remote repository are now merged with the master branch in your local repository.

Now you can finally push:

git push origin master