Jump to content

WHMCS JoshQ

WHMCS Technical Analyst II
  • Posts

    37
  • Joined

  • Last visited

  • Days Won

    7

Everything posted by WHMCS JoshQ

  1. Hey @Azhar Patel, The best way to achieve this would be using either Promotion Codes or Client Groups, as per: It would be possible to put together a hook to auto-apply these, or you can create product URLs that have the discount already applied: https://docs.whmcs.com/8-13/products/promotions/#promotion-links
  2. I'm afraid that this is not something that is currently possible. If this is a feature that you'd like to be implemented, please consider browsing our Feature Requests site, and opening a new request if one doesn't already exist for this: https://requests.whmcs.com./
  3. This is certainly strange, @Toto! Please feel free to open a ticket with our Support team, and we'd be glad to give you a hand with this.
  4. Hi @Rehost24, There are lots of possible causes. Indeed, compromise is one of those, and that is particularly likely if WHMCS is hosted in a shared environment. I'd recommend reviewing our Enhancing Security guide: https://docs.whmcs.com/8-13/installation-guide/initial-configuration/enhancing-security/ Beyond that, it would be worth consulting with a System Administrator or Security Specialist who can take a look at your installation and identify any possible weak points. Just to further the advice given above, it is possible to force all clients to reset their passwords by removing the stored password hashes from the databases: https://help.whmcs.com/m/managing/l/1535126-forcing-a-password-reset UPDATE `tblusers` SET `password` = '', `email_verification_token_expiry` = NULL, `email_verified_at` = NULL, `reset_token_expiry` = NULL WHERE 1; Of course, you should then send an e-mail to all clients instructing them to reset their password using the standard Forgot Password flow. Hope this helps.
  5. Hi @ppollaci, Time tracking is built in to our official Project Management addon, so that might meet your needs: https://www.whmcs.com/project-management/ If not, then there will be lots of options on our Marketplace: https://marketplace.whmcs.com/ Should none of those meet your needs, then you could post in the Developer Corner forum, and someone may be happy to help you get a new addon made. Hope this helps.
  6. Hey @Naresh Kumar, Please feel free to open a ticket with us, and we'd be glad to give you a hand with this.
  7. @RadWebHosting's recommendation here is brilliant, and would be my suggestion too! Of course, you can also see a list of all overdue invoices at Billing > Invoices > Overdue in the Admin Area. If you'd be interested in a new event being added to your Notifications settings for when an invoice becomes overdue, I'd recommend opening a feature request here: https://requests.whmcs.com./ I can absolutely see how this would be useful, so getting that feature request open will allow others to comment on and vote for your request. Hope this helps :-)
  8. Hi @0WebHost, You can achieve this using a single product, but would need to create a hook that allocates a server based on a client's selected option. I've written a post about how you can achieve that here: Hope this is helpful! :-)
  9. Hi all, Just to chime in here, I wanted to add that WHMCS 7.8 was only validated to work with version of MySQL up to v8, as you can see in the System Environment Guide that has been archived on the Wayback Machine: https://web.archive.org/web/20231126221038/https://docs.whmcs.com/System_Environment_Guide#Version_Compatibility However, we would fully expect MariaDB and MySQL versions higher than this to work so long as no significant changes were made to the syntax of commands. It's great to know that all is working for you on MariaDB 10.6, @rockhost! We would, of course, recommend that you use the latest version of MariaDB possible so you can take advantage of their security/bug fixes.
  10. Hi all, The script has been updated to account for situations where a client is not already logged in. It will now prompt the user to register for an account. Once they have an account and have verified their e-mail, they will be able to place an order. Please be sure to review the updated post thoroughly if you are planning to use this in production, and particularly ensure that you have a Captcha and Web Application Firewall setup before proceeding. You might all be interested in voting on this feature request if you'd like forced e-mail verification to be implemented natively: https://requests.whmcs.com/idea/force-email-verification-before-account-provisioning As noted in the post, please keep in mind that this is not an officially supported script, and does not come with any guarantee from us. We'll do our best to help you out via WHMCS.Community and have done our best to test it in multiple situations, but any usage of this is fully at your discretion.
  11. Hi @Jade D, I have responded to your ticket, but just wanted to follow up here so there's an answer if anybody else comes across this. The underlying issue here is that your web server was advertising a domain other than that registered for your license. This causes the license cache in your install to be invalid, and so your install needs to check in with our licensing infrastructure to validate your license. Since the details we held about your license were different to those being advertised by your web server, your install kept making a request to our licensing API. We rate limit all requests made to our licensing infrastructure to ensure stability thereof for all our customers. If your server sends too many requests, it will be temporarily blacklisted as part of this rate limit. To resolve this, make sure that your web server is only advertising the server_name that we have listed in your Valid Domains. Wait for 30 minutes or so and the rate limit will be removed, then your install will get a response back from the licensing servers that your license is valid and access will be restored. If anybody is unable to get to the bottom of this, please reach out to our support team and we can help!
  12. I'm pleased that you've found this hook helpful! There are no plans to include this in WHMCS natively at this time. Our hook system exists to enable you to add features like this easily, and keeping this as a hook means that you are able to customise the logic behind it.
  13. @Damo's suggestion is correct. The presence of Sitejet within WHMCS is dependent on its being enabled on any connected cPanel / Plesk servers. If you do not want to offer Sitejet to your customers, disable the feature on your cPanel / Plesk servers. If WHMCS does not detect that Sitejet is supported, then it will not display anything related to Sitejet. Hope this helps 🙂
  14. It is not currently possible to control whether 2FA is required based on the IP address of the user. I can certainly see how this would be a useful feature, so please do consider opening up a feature request for it so that our team can consider it: https://requests.whmcs.com/ Make sure to leave your votes on the request, too! The more votes a request gets, the more likely it is that we will consider implementing it.
  15. If you're looking for API credentials for your WHMCS installation, then you can follow the instructions at the link below to get those created: https://docs.whmcs.com/system/authentication/api-credentials/ Hope this helps.
  16. Our cPanel provisioning module leverages cPanel's create_user_session API endpoint to get an SSO URL for the user: https://api.docs.cpanel.net/openapi/whm/operation/create_user_session/ So that URL is being provided to WHMCS by your cPanel server. I'd advise that you / your sysadmin take a look at your settings in WHM to see why this is happening. Hope this helps 😊
  17. If whitelisting IPs isn't practicable, then the correct solution would be using an Access Key: https://developers.whmcs.com/api/access-control/ I would not recommend using the approach above.
  18. Introduction There may be use-cases where a business needs clients to verify their email address prior to permitting them to place orders. This could be part of meeting Know-Your-Customer regulations, helping combat automated bot orders, spam or fraudulent orders. By utilising action hooks, we can require that they have verified ownership of their e-mail address prior to being able to place an order. This measure can form part of your multi-layered approach to combatting the risks of conducting business online. Enable Email Verification & Client Registration The first thing that we need to do is enable email verification in WHMCS - without this, adding this hook won't really achieve anything. To do this, navigate to Configuration > System Settings > General Settings > Security (tab) in the admin area. Then, enable the Email Verification option and click Save Changes. More information can be found in our documentation here. Furthermore, we will also need to permit clients to register for an account without first placing an order. That's because we need to make it so that clients can only place an order if they have a valid account with a verified e-mail address, and we can't do that through the regular buy flow. To dot his, navigate to Configuration > System Settings > General Settings > Other (tab) in the admin area. Then, enable the Allow Client Registration option and click Save Changes. It is really important that you also make sure that you have either hCaptcha or reCAPTCHA v3 enabled in System Settings > General Settings > Security (tab) before doing this, and have a Web Application Firewall (WAF) setup in front of your installation, as per our documentation on preventing Spam Orders, to ensure that bots are unable to register for an account. Without taking this precautions, you may see an influx of spam account registrations, though it is important to note that they will still be unable to place an order without verifying their e-mail. We'd also recommend enabling a Fraud Module! Once you've implemented all of the above, we can start writing our script. Creating the hook file Navigate to /includes/hooks in your WHMCS installation, and create a new file called restrictorders.php (or something similar). Open the file, and start by adding the opening <?php tag, a header comment and the standard if statement to prevent the file from being accessed directly. <?php /* * Prevent orders from clients with an unverified e-mail * * @author WHMCS Josh Q <support@whmcs.com> * @copyright Copyright (c) WHMCS Ltd. All Rights Reserved. * @link https://www.whmcs.com/ * */ if (!defined("WHMCS")) die("This file cannot be accessed directly"); Next, we'll define a new variable, ALLOW_UNVERIFIED_EMAILS , which allows us to quickly toggle whether we'd like to enable this restriction. # Allow clients with unverified e-mails to place orders? define("ALLOW_UNVERIFIED_EMAILS", false); Lastly, we'll define the logic for the hook itself. In this case, we're using the ShoppingCartValidateCheckout hook, which allows us to block orders based on our own defined logic. We'll first check whether we're allowing accounts with unverified e-mail addresses to create orders. Where we're not (i.e. when the variable has a value of false ), we'll check whether the client a) exists and b) whether they have a verified e-mail. The only instance in which the order can be placed is where the client exists (i.e. is logged in) and has verified their e-mail address. add_hook("ShoppingCartValidateCheckout", 1, function($vars){ if (ALLOW_UNVERIFIED_EMAILS==false){ $client = Menu::context("client"); if (is_null($client)) { return array("You must <a href='/register.php'>register an account</a> and verify your e-mail before you can place an order."); } if ($client->isEmailAddressVerified()==false) { return array("You must verify your e-mail address before you can checkout."); } } }); End Result If the client is not logged in, they will be prompted to register for an account: If the client is logged in but has not verified their e-mail address, they'll be prompted to verify their e-mail address: The full hook script is as follows: <?php /* * Prevent orders from clients with an unverified e-mail * * @author WHMCS Josh Q <support@whmcs.com> * @copyright Copyright (c) WHMCS Ltd. All Rights Reserved. * @link https://www.whmcs.com/ * */ if (!defined("WHMCS")) die("This file cannot be accessed directly"); # Allow clients with unverified e-mails to place orders? define("ALLOW_UNVERIFIED_EMAILS", false); add_hook("ShoppingCartValidateCheckout", 1, function($vars){ if (ALLOW_UNVERIFIED_EMAILS==false){ $client = Menu::context("client"); if (is_null($client)) { return array("You must <a href='/register.php'>register an account</a> and verify your e-mail before you can place an order."); } if ($client->isEmailAddressVerified()==false) { return array("You must verify your e-mail address before you can checkout."); } } }); Disclaimer Please note that this hook has not been extensively tested and is provided as-is without any obligation for support/further troubleshooting. Use this at your discretion.
  19. For businesses that heavily utilise WHMCS's API, you may be sending API requests from many different IP addresses or IP ranges, all of which need to be whitelisted at System Settings > General Settings > Security (tab) > API IP Access Restriction in the Admin Area. Each individual IP address must be added to the allow list (it is not possible to list IP ranges in CIDR notation at this time). The process of adding a range of IP addresses can be sped up by using a PHP script to add them in bulk. Before we begin, WHMCS support does not recommended directly interacting with the database where it can be avoided. We would therefore only recommend following this guide if you have an existing understanding of both PHP and database administration, and have taken a full database backup. WHMCS will not be held responsible for any data loss that might occur if you do not heed this advice. How are API Allowed IPs stored? The list of IPs allowed to interact with the API are stored as a serialized array in tblconfiguration.value where tblconfiguration.setting = "APIAllowedIPs". A serialized array, in this particular context, is a way for us to store an array as a string in the database. Handling existing data in WHMCS's database To make things easy, let's create a function that takes in the current serialized array, which you can retrieve from the database, a new IP and a note, and then returns the new serialized array. Here it is, with everything explained in inline comments: function addNewIP($currentValue, $ip, $note) { // Take the $currentValue, and unserialize it so we can interact with it directly. $data = unserialize($currentValue); // Define our new entry, based on the $ip and $note provided in the function call. $newEntry = array( 'ip' => $ip, 'note' => $note ); // Add the new entry to the existing array. $data[] = $newEntry; // Re-serialize the array. $newValue = serialize($data); // Return to the serialized version of the new array. return $newValue; } Iterating through IP ranges We now need to iterate through all IP address in a given range and add them to our array. Let's create another function to do this: function addNewIPRange($currentValue, $startIP, $endIP, $note) { // Convert the start and end IP addresses to long integers so we can iterate through them $startIP = ip2long($startIP); $endIP = ip2long($endIP); // Initialize $newValue $newValue = $currentValue; // Iterate through the IP range for ($ip = $startIP; $ip <= $endIP; $ip++) { // Convert the current IP back to string format $newIP = long2ip($ip); // Call the addNewIP function for each IP in the range $newValue = addNewIP($newValue, $newIP, $note); } // Return the modified value return $newValue; } Note that we'll still need to provide the current value from the database, and this will be updated each time an IP in the range is added. Testing it out First of all, we need to retrieve the current serialized array from the database. This can be done programmatically using Capsule, but in this case we'll just retrieve it manually: SELECT value FROM tblconfiguration WHERE setting = 'APIAllowedIPs' Then we can call addNewIPRange to add an IP range, or just addNewIP if we only want to add a single address. Of course, when you're using this you wouldn't need to add all of the extra echo lines or comments - these are just included to help demonstrate how this is working. // Store current APIAllowedIPs value in variable $currentValue = 'a:5:{i:0;a:2:{s:2:"ip";s:0:"";s:4:"note";s:0:"";}i:1;a:2:{s:2:"ip";s:12:"10.3.137.243";s:4:"note";s:0:"";}i:2;a:2:{s:2:"ip";s:7:"8.8.8.8";s:4:"note";s:7:"Testing";}i:3;a:2:{s:2:"ip";s:7:"1.1.1.1";s:4:"note";s:7:"Testing";}i:4;a:2:{s:2:"ip";s:11:"12.13.14.15";s:4:"note";s:7:"Testing";}}'; // Let's see our $currentValue in a more readable format so that we can compare it to our new version echo "<h1>Starting value</h1>"; echo "<pre>"; print_r(unserialize($currentValue)); "</pre>"; echo "<br/><hr/>"; // To add a new IP range, call addNewIPRange, and pass in the $currentValue, IP range and a note: $testRange = addNewIPRange($currentValue, '10.20.30.40', '10.20.30.60', 'Test IP Range'); echo "<h1>With our added IP range</h1>"; echo "<h2>Serialised version:</h2>"; echo $testRange; echo "<h2>Unserialised version:</h2>"; echo '<pre>'; print_r(unserialize($testRange)); '</pre>'; echo "<br/><hr/>"; // To add a single IP, call addNewIP, and pass in our new value, address and a note: $testSingle = addNewIP($testRange, '192.168.1.1', 'Single IP'); echo "<h1>And with one more address</h1>"; echo "<h2>Serialised version:</h2>"; echo $testSingle; echo "<h2>Unserialised version:</h2>"; echo '<pre>'; print_r(unserialize($testSingle)); '</pre>'; echo "<br/><hr/>"; You can then add the new data to the database manually, or using Capsule. Doing the latter is beyond the scope of this article. The final script Let's finish by tidying up the scirpt a bit, and showing an example. Let's say we wanted to whitelist the following: 192.168.45.23 - management VPN 192.168.45.24 - development VPN Range from 10.20.0.126 to 10.20.10.159 - backend servers Here's what your script would look like: <?php function addNewIP($currentValue, $ip, $note) { $data = unserialize($currentValue); $newEntry = array( 'ip' => $ip, 'note' => $note ); $data[] = $newEntry; $newValue = serialize($data); return $newValue; } function addNewIPRange($currentValue, $startIP, $endIP, $note) { $startIP = ip2long($startIP); $endIP = ip2long($endIP); $newValue = $currentValue; for ($ip = $startIP; $ip <= $endIP; $ip++) { $newIP = long2ip($ip); $newValue = addNewIP($newValue, $newIP, $note); } return $newValue; } $currentValue = 'a:5:{i:0;a:2:{s:2:"ip";s:0:"";s:4:"note";s:0:"";}i:1;a:2:{s:2:"ip";s:12:"10.3.137.243";s:4:"note";s:0:"";}i:2;a:2:{s:2:"ip";s:7:"8.8.8.8";s:4:"note";s:7:"Testing";}i:3;a:2:{s:2:"ip";s:7:"1.1.1.1";s:4:"note";s:7:"Testing";}i:4;a:2:{s:2:"ip";s:11:"12.13.14.15";s:4:"note";s:7:"Testing";}}'; $manVpnIP = addNewIP($currentValue, "192.168.45.23", "Management VPN"); $devVpnIP = addNewIP($manVpnIP, "192.168.45.24", "Development VPN"); $backendServers = addNewIPRange($devVpnIP, "10.20.0.126", "10.20.0.159", "Backend Server"); echo $backendServers; The output of this script would be the new serialized array, and you can set this directly in the database! Again - please make sure to take a backup before manually interacting with WHMCS's database. UPDATE tblconfiguration SET value = '<add your serialized array here>' WHERE setting = 'APIAllowedIPs'; Important: You must use single quotes and not double quotes, else the command's format will not be valid (since double quotes are used in the serialized array itself). Once you have run this, login to the WHMCS admin area and navigate to System Settings > General Settings > Security (tab). You should now see a list of all your IPs listed in the API IP Access Restriction setting. Further considerations After adding so many IP addresses, you may find that you are no longer able to add any more addresses via the Admin Area. This will be because your PHP max_input_vars is being exhausted. It is not a bug with WHMCS, nor is it something that we can mitigate - this is how PHP is configured by default. https://www.php.net/manual/en/info.configuration.php#ini.max-input-vars To rectify this, please increase the max_input_vars value in your PHP configuration. Should you need assistance with doing this, please contact your hosting provider or system administrator. It is also possible that you might reach the maximum size for the value column in tblconfiguration. In the event of this occurring, you can change the type of the column to MEDIUMTEXT. Please keep in mind that this is not officially supported, and you should definitely take a database backup before proceeding. 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!
  20. 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: Iterate through all of your mappings ('filters') Check all fields for each filter 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 If the field value is not blank, carry on to checks Check if any keywords are a substring of the field value If they are, add that infraction to an array of errors to be returned If not, do nothing If there are any errors, return them. 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: 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: 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!
  21. Hi @mohammed alatreby, I've looked into this for you, and it is hypothetically possible by modifying your client area template such that the Order Now button makes an AJAX request to cart.php. However, the difficulty is that the user needs to complete a few stages of configuration prior to adding the item (domain or product) to the cart, which means that this would likely not achieve your desired outcome. If you want this to be a truly one click = add to cart experience, then you would probably need to create your own custom storefront using our API, since this is simply not how WHMCS is intended to behave. Should you want to try to implement this yourself, I'd recommend opening a thread in the Developer Corner, as I'm sure that some of our users on there would be glad to help. If you'd rather hire someone to create this customisation for you, then I'd post in Service Offers & Requests. You might also like to open a feature request if this is a feature that you'd like to be implemented natively. Please note that WHMCS Support are not able to assist in creating such a customisation because it is out of scope. Hope this helps 🙂
  22. For companies operating hosting infrastructure in multiple countries, it is often desirable to allow the customer to choose where they would like their account to be hosted. There are several ways in which this could be done. One possible solution would be to create separate products for each location, and assign the appropriate Server Group to each one. However, this would mean that you would have to maintain several instances of the same product; if you wanted to update the product in the future, you would have to do so for each instance. Another solution, and the one that I will be covering in this post, is to create one product that allows users to select their location via a Custom Field. This means that all accounts with the same 'plan' are assigned to the same product, which is more streamlined for administrative purposes, and more logical for the end user. However, WHMCS is not able to automatically select the correct location for a new account in this scenario because it doesn't understand the concept of a 'location'. By default, WHMCS can only pick a server from the Server Group assigned under the product's Module Settings - this wouldn't work for this scenario. Therefore we are going to need to write an action hook that can automatically select a server based on the location specified by the client. This guide assumes that you have already added all of your Servers to WHMCS, and that they are all working as expected. Step 1: Create the a new product (and group if applicable) for your hosting plan The first thing that you need to do is create a new product for this hosting plan via System Settings > Products/Services, as per our documentation. Make sure to setup pricing and your module settings according to your requirements. Step 2: Add a new custom field With your product now setup, you should create a new Custom Field for the product called Location. You can do this in the Custom Fields tab in your product settings. Something like the following would work nicely: Step 3: Create the action hook that will assign the correct server In this example, we will only have three servers - one per selectable location. These are: Server ID 1: VPS node in London (UK) Server ID 2: VPS node in Paris (FR) Server ID 3: VPS node in New York (US) Keep in mind that you will need to substitute these IDs with the correct ones for your hosting environment. To achieve the desired outcome, we will be making use of the PreModuleCreate hook, which is called prior to the Create command being run. To get started, create a new file at /includes/hooks called set_serverid_by_location_hook.php With this file created, paste in the following content: <?php add_hook('PreModuleCreate', 1, function($vars) { // Check if the product ID matches a supported product $productId = $vars['pid']; $supportedProducts = array(1, 2, 3); // List of supported product IDs // If it doesn't, do nothing if (!in_array($productId, $supportedProducts)) { return; } // Define serverid and location pairings statically $serverIdMappings = array( 'London (UK)' => '1', 'Paris (FR)' => '2', 'New York (US)' => '3', // Add more location-serverid pairs as needed ); // Get the Location custom field value $location = isset($vars['customfields']['Location']) ? $vars['customfields']['Location'] : ''; // Check if the location is supported if (!isset($serverIdMappings[$location])) { logActivity("Server ID not found for location: $location"); return; } // Override parameters for account creation return array( 'serverid' => $serverIdMappings[$location], // Add any other parameters you want to override here ); }); ?> This script will set the server ID based on the Location defined by the customer. If you have multiple servers per location and want to assign one randomly, then the above will need to be modified slightly. Something like this should do the trick (the modified sections have comments - the other parts do not): <?php add_hook('PreModuleCreate', 1, function($vars) { $productId = $vars['pid']; $supportedProducts = array(1, 2, 3); if (!in_array($productId, $supportedProducts)) { return; } // Define serverid and location pairings // This time, there can be multiple server IDs per location. $serverIdMappings = array( 'London (UK)' => array('serverid1', 'serverid2', 'serverid3'), // Multiple server IDs for London 'Paris (FR)' => array('serverid4', 'serverid5'), // Multiple server IDs for Paris 'New York (US)' => array('serverid6', 'serverid7'), // Multiple server IDs for New York // Add more location-serverid pairs as needed ); $location = isset($vars['customfields']['Location']) ? $vars['customfields']['Location'] : ''; if (!isset($serverIdMappings[$location])) { logActivity("Server IDs not found for location: $location"); return; } // Get a random server ID from the list of server IDs for the location $randomServerId = $serverIdMappings[$location][array_rand($serverIdMappings[$location])]; return array( 'serverid' => $randomServerId, ); }); ?> The result of this will be that the server is provisioned in the appropriate location at the time of module creation. Keep in mind that changing the value of the Location field will not update the location of the account. To do this, you will need to migrate the account to the new server. The above code worked at the time of writing. Please keep in mind that this is a customisation to WHMCS, and as such our Support team won't be able to troubleshoot any issues that you encounter with it - this extends beyond the remit of our support package. Any questions or feedback on the above are always welcome.
  23. Hi @arthur34, Since you are looking to generate a report identical to the VAT MOSS report, except on a monthly basis rather than quarterly, your best option would be to duplicate the VAT MOSS report and customise it to your requirements: https://docs.whmcs.com/Reports#Sales_Tax_Liability https://docs.whmcs.com/Customizing_Reports We don't encode the source code for reports, because we recognise that our clients may want to customise them to their requirements. Essentially, you can copy the /modules/reports/vat_moss.php file (call it something like vat_moss_monthly.php), and then edit the contents to your requirements. Though our Support team won't be able to help with this, as we can't offer assistance in creating customisations for WHMCS, one of our wonderful WHMCS.Community members may be happy to help you put together something like this in the Developer Corner.
  24. In case anybody else encounters this issue, it is caused by Gmail not liking <style> tags, and preferring that styling be done with inline CSS. This is not something that we can really 'fix', as we would prefer to retain customisation options for our clients, however you might be able to get around this by implementing an automatic inliner within an action hook: https://github.com/tijsverkoyen/CssToInlineStyles This case was closed, as this is not something that we can really resolve.
×
×
  • 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