Jump to content

Defining keyword blocklists for the checkout signup form


Recommended Posts

  • WHMCS Technical Analyst

For many businesses, there will be keywords that you do not want customers to be able to use. For example, you might want to prevent them from being able to create services under any of your domain names, or prevent the use of strings like "admin" or "root" in the "First Name" and "Last Name" fields.

By making use of action hooks, it is totally possible to add this functionality. In this post, I'll show you how to get this setup, and how you can add your own rules.

Overview

To make this work, we're going to be making use of Action Hooks - specifically, the ShoppingCartValidateCheckout hook.

If you've never used hooks before, I'd recommend reading our Getting Started guide, which will help you to understand the basics of what we'll be doing here.

Step 1: Set up your hook file

To begin, open your WHMCS instance's filesystem, and navigate to /path/to/whmcs/includes/hooks and create a new file keyword_filter.php.

Now open the file, and paste in the following:

<?php
 
add_hook('ShoppingCartValidateCheckout', 1, function($vars) {
 
    // Content goes here
 
});

This will tell WHMCS that we are using the ShoppingCartValidateCheckout hook, and will allow us to retrieve the variables provided to us.

As this article goes on, we'll be adding to this base layer until we have our finished, fully-functional script.

Step 2: Define your keyword filters

There are many different ways that we could define what strings are banned in which fields.

For maximum legibility and modularity, I've decided to use a map. This will allow us to associate one set of data (the "key"), in this case the field name(s) to apply the filter to, and another (the "value"), which in our case is a list of banned keywords.

Here's the code:

<?php
 
add_hook('ShoppingCartValidateCheckout', 1, function($vars) {
 
    $fieldKeywordMap = [
        [
            // The 'firstname' and 'lastname' fields cannot contain the strings
            // "admin", "root" or "superuser".
            'fields' => ["firstname", "lastname"],
            'keywords' => ["admin", "root", "superuser"]
        ],
        [
            // The 'email' field cannot contain the strings "@whmcs.com" or
            // "@google.com".
            'fields' => ["email"],
            'keywords' => ["@whmcs.com", "@google.com"]
        ],
        // Add more mappings as needed
    ];
 
});

Each set of 'fields' and 'keywords' is a mapping. You can add as many of these as you need.

We'll be returning an error based on whether or not the field contains these keywords (i.e. if they are substrings).

For example, if the user enters "admin123" for their firstname and "john.smith@google.com" for their e-mail, both of these filters will apply because the substrings "admin" and "@google.com" are present within the 'firstname' and 'email' fields respectively.

You can find a full list of available fields in the documentation for this hook.

Step 3: Implement logic to check field submissions for any banned substrings

Now that we've defined our 'filters', we need to implement the logic that's going to make them work.

Here's the full script, explained in the provided comments:

<?php
 
add_hook('ShoppingCartValidateCheckout', 1, function($vars) {
 
    $fieldKeywordMap = [
        [
            // The 'firstname' and 'lastname' fields cannot contain the strings
            // "admin", "root" or "superuser".
            'fields' => ["firstname", "lastname"],
            'keywords' => ["admin", "root", "superuser"]
        ],
        [
            // The 'email' field cannot contain the strings "@whmcs.com" or
            // "@google.com".
            'fields' => ["email"],
            'keywords' => ["@whmcs.com", "@google.com"]
        ],
        // Add more mappings as needed.
    ];
 
     // Initialise array to store error messages.
    $errors = [];
 
    // Iterate through all of your mappings.
    foreach ($fieldKeywordMap as $mapping) {
        // Get the 'fields' and 'keywords' separately for each mapping.
        $fields = $mapping['fields'];
        $keywords = $mapping['keywords'];
 
        foreach ($fields as $fieldName) {
            // Retrieve the value for the field from $vars[] provided by the hook.
            $fieldValue = $vars[$fieldName] ?? '';
 
            // Check if the field is empty - if it is, skip the checks.
            if (!$fieldValue) continue;
 
            // Check if any of the keywords are a substring of the field value.
            foreach ($keywords as $keyword) {
                if (stripos($fieldValue, $keyword) !== false) ) {
                    // If there is a match, add it to the list
                    $errors[] = "'$fieldName' cannot contain '$keyword'. Please remove this and try again.";
                }
            }
        }
    }
    // If any errors are present, return them - otherwise, return true.
    return $errors ?: true;
});

Here's a little summary of how it works:

  1. Iterate through all of your mappings ('filters')
  2. Check all fields for each filter
    1. If the field value is blank, skip all checks for that field - we need to do this since this field is only included for new users
    2. If the field value is not blank, carry on to checks
  3. Check if any keywords are a substring of the field value
    1. If they are, add that infraction to an array of errors to be returned
    2. If not, do nothing
  4. If there are any errors, return them.
  5. If all checks are passed and no keywords are present, return true.

The only part of this code that you should need to modify is the top section, where you define your filters.

If any of the filters are matched, here's what'll be displayed in WHMCS:

image-2024-2-27_19-26-12.png.2b6e95325ae302e25c756b8eab8945bb.png

Step 4: Prettify error messages (optional, but recommended)

As it stands, our hook is completely functional. If any banned words are present, an error will be returned.

However, our script looks like a customisation, and you'll probably want to make it appear like native functionality. The issues are:

  • We're currently using the $fieldName, which doesn't read like English
  • Language used doesn't fit in with the rest of WHMCS's error messages

To second issue is fixed by adjusting the error message, so that's easy! To fix the first problem, we'll use another map to correlate each $fieldName with its "English equivalent" (which I've just called $friendlyFieldName:

<?php
 
add_hook('ShoppingCartValidateCheckout', 1, function($vars) {
 
    $fieldKeywordMap = [
         [
            // The 'firstname' and 'lastname' fields cannot contain the strings
            // "admin", "root" or "superuser".
            'fields' => ["firstname", "lastname"],
            'keywords' => ["admin", "root", "superuser"]
        ],
        [
            // The 'email' field cannot contain the strings "@whmcs.com" or
            // "@google.com".
            'fields' => ["email"],
            'keywords' => ["@whmcs.com", "@google.com"]
        ],
        // Add more mappings as needed.
    ];
 
    // Map field names that you're using above to their "English equivalent".
    $friendlyFieldNames = [
        'firstname' => 'First Name',
        'lastname' => 'Last Name',
        'email' => 'Email Address',
        // Add more field name translations as needed.
    ];
 
    // Initialise array to store error messages.
    $errors = [];
 
    // Iterate through all of your mappings.
    foreach ($fieldKeywordMap as $mapping) {
        // Get the 'fields' and 'keywords' separately for each mapping.
        $fields = $mapping['fields'];
        $keywords = $mapping['keywords'];
 
        foreach ($fields as $fieldName) {
            // Retrieve the value for the field from $vars[] provided by the hook.
            $fieldValue = $vars[$fieldName] ?? '';
 
            // Check if the field is empty - if it is, skip the checks.
            if (!$fieldValue) continue;
 
            // Check if any of the keywords are a substring of the field value.
            foreach ($keywords as $keyword) {
                if (stripos($fieldValue, $keyword) !== false) {
                    // Translate the field name to a nicer name for the error message.
                    $friendlyFieldName = $friendlyFieldName[$fieldName] ?? $fieldName;
                    // Add it to the list
                    $errors[] = "$friendlyFieldName cannot contain '$keyword'";
                }
            }
        }
    }
    // If any errors are present, return them - otherwise, return true.
    return $errors ?: true;
});

Simply add a new mapping for each $fieldName that you use in the $friendlyFieldNames array, and they'll read well and feel like a native part of WHMCS:

image-2024-2-27_20-50-19.png.1ff364978c33beaa80df869c6fde7607.png

And there we have it: a hook script that can be easily extended to include as many fields and keywords as you require, and it feels totally native too!

Customising/extending this script

Feel free to customise this script to you heart's content, whether you want to change how it behaves or extend its functionality.

However, please keep in mind that our Technical Support team won't be able to help you troubleshoot any problems that you may encounter should you make any changes. Use at your own risk.

Suggestions for other "Tips & Tricks' articles

If you'd like to see an example of how we can customise WHMCS in other ways, please do give us your suggestions!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use & Guidelines and understand your posts will initially be pre-moderated