Jump to content


  • Content count

  • Joined

  • Last visited

  • Days Won


Kian last won the day on May 21

Kian had the most liked content!

Community Reputation

144 Excellent


About Kian

  • Rank
    Senior Member

Recent Profile Visitors

5,767 profile views
  1. Kian

    Domain sync issues and overall messiness

    Here is how you offer free domain with hosting package. As for transferred domains I think that you'll need to manually (or automatically with a custom action hook) sync the Next Due Date of the domain in question and its related hosting package this way they will be tied together. When customers transfer their domain to you, the remaining time they have with their current Registrars plays a role in the Expiry Date. Let's suppose that the transferred domain expires in 6 months from now. For generic TLDs the renewal period (1 year) is added to the current expiry date. In other words the domain will expire in 12 + 6 months from now. Technically a customer could transfer a domain that expires in 2025 😀 It happens when you change Registrar frequently. That's why in WHMCS Next Due Date and Expiry Date don't match. That said, what you see in Expiry Date is irrelevant. Even though the domain expires in 2025 your customer must pay you on a yearly basis. In other words Next Due Date is the only thing that you have to care about. You could consider Expiry Date as a "courtesy" date for you and your customers. When it comes to country-code extensions, rules could be different. A lot of TLDs don't give a damn about the remaining time. When the domain is transferred Expiry Date simply is current date + 12 months. Editing Expiry Date makes no sense if you are using Auto-Sync since it will override your changes.
  2. Kian

    service renewal confirmation message

    This hook should work. <?php use WHMCS\Database\Capsule; add_hook('InvoicePaid', 1, function($vars) { // Retreiving Products/Services from the invoice that has been just paid $HostingItems = Capsule::select(Capsule::raw('SELECT t2.id FROM tblinvoiceitems AS t1 LEFT JOIN tblhosting AS t2 ON t1.relid = t2.id WHERE t1.invoiceid = "' . $vars['invoiceid'] . '" AND t1.type = "Hosting"')); // Foreach Product/Service that has been renewed I send the "Product Renewal Confirmation" (read post below for more details) foreach ($HostingItems AS $v) { $postData['messagename'] = 'Product Renewal Confirmation'; $postData['id'] = $v->id; $results = localAPI('SendEmail', $postData); } }); The hook requires an Email Template to send the renewal confirmation via email. Go to Setup > Email Templates and press Create New Email Template with the following settings: Email Type: Product/Service Unique Name: Product Renewal Confirmation Email Title: Product Renewal Confirmation (feel free to change it) Email Body: (see below - Feel free to customize it) <p>Dear {$client_name},</p> <p>Thank you for your renewal order. Your renewal request for the product listed below has now been completed.</p> <p>Product/Service: {$service_product_name} {if $service_domain} ({$service_domain}){/if}<br />Renewal Length: {$service_billing_cycle}<br />Renewal Price: {$service_recurring_amount}<br />Next Due Date: {$service_next_due_date}</p> <p>You may login to your client area at <a href="{$whmcs_url}">{$whmcs_url}</a> to manage your domain.</p> <p>{$signature}</p> Preview:
  3. Kian


    Open templates/{YOUR_TEMPLATE}/header.tpl and look for the following meta tag: <meta name="viewport" content="width=device-width, initial-scale=1"> Replace it with: <meta name="viewport" content="{if $loggedin}width=1024{else}width=device-width, initial-scale=1{/if}"> This forces desktop view on mobile devices on all pages to loggedin users. Keep in mind that it doesn't work when resizing browser window on desktop. If you want that to work only with clientarea pages then use this code: <meta name="viewport" content="{if $loggedin AND $filename == 'clientarea'}width=1024{else}width=device-width, initial-scale=1{/if}">
  4. Kian

    remove navbar on cart page

    Given that I'm not a master of templates, I'd use this action hook: <?php add_hook('ClientAreaHeadOutput', 1, function($vars) { if ($vars['templatefile'] == 'viewcart') { $RemoveHeader = true; // "true" removes WHMCS header (logo, language picker, login/register, view cart button) $RemoveNavBar = true; // "true" removes WHMCS navbar (Home, Store, Announcements...) if ($RemoveHeader ? $output .= '<style>#header { display:none }</style>' : 0); if ($RemoveNavBar ? $output .= '<style>#main-menu { display:none }</style>' : 0); return $output; } }); add_hook('ClientAreaPageCart', 1, function($vars) { $output['DisplayMyCustomHeader'] = true; // "true" shows your custom header in PageCart return $output; }); Open templates/{YOUR_TEMPLATE}/header.tpl and look for the following statement: {if $templatefile == 'homepage'} Right befire it add the following code: {if $DisplayMyCustomHeader} <div class="home-shortcuts"> <div class="container"> <div class="row"> <div class="col-md-4 hidden-sm hidden-xs text-center"> <p class="lead">How can we help today?</p> </div> <div class="col-sm-12 col-md-8"> <ul> <li><a id="btnBuyADomain" href="domainchecker.php"><i class="fas fa-globe"></i><p>Buy A Domain <span>»</span></p></a></li> <li><a id="btnOrderHosting" href="cart.php"><i class="far fa-hdd"></i><p>Order Hosting <span>»</span></p></a></li> <li><a id="btnMakePayment" href="clientarea.php"><i class="fas fa-credit-card"></i><p>Make Payment <span>»</span></p></a></li> <li><a id="btnGetSupport" href="submitticket.php"><i class="far fa-envelope"></i><p>Get Support <span>»</span></p></a></li> </ul> </div> </div> </div> </div> {/if} And here is the result: Replace the content of {if $DisplayMyCustomHeader}{/if} with your header.
  5. Exactly. @Remitur The problem is that WHMCS language is based on sessions so it doesn't matter what's the URL. The article will always use the default language which in your case is English. This hook should work. I tested on my dev system lazily. I'm gonna update if it doesn't work. It's commented. More information are provided below. <?php use WHMCS\Database\Capsule; use WHMCS\Config\Setting; add_hook('ClientAreaPageKnowledgebase', 1, function($vars) { /** * The way I'm getting $ArticleLanguage looks complicated but is necessary to match "Hello hello-hello" title with the rewritten URLs "Hello-hello-hello" * In fact there's no way to distinguish real dashes "-" from fake ones added by Friendly URLs of WHMCS * Keep in mind that I'm forced to use "raw" static method since WHERE statement doesn't support complex functions like REPLACE */ // Getting title from querty string $PageName = pathinfo($_GET['rp'])['filename']; // Retreiving the language of the currently displayed article by ID and title $ArticleLanguage = Capsule::select(Capsule::raw('SELECT language FROM tblknowledgebase WHERE parentid = "' . $vars['kbarticle']['id'] . '" AND REPLACE(title, "-", " ") = "' . str_replace('-', ' ', $PageName) . '" LIMIT 1'))[0]->language; // Retreiving the default language of WHMCS from tblconfiguration $DefaultLanguage = Setting::getValue('Language'); /** * This section is optional but needed if you want to protect yourself against blackhat SEO techniques (read post for more info) */ // Retreiving default language title $LegitURL = Capsule::select(Capsule::raw('SELECT REPLACE(title, " ", "-") AS title FROM tblknowledgebase WHERE id = "' . $vars['kbarticle']['id'] . '" LIMIT 1'))[0]->title; // If the URL currently in use is not 100% legit I force a redirect to the article in default langiage if ($LegitURL !== $PageName AND !$ArticleLanguage) { header('Location: index.php?rp=/knowledgebase/' . $vars['kbarticle']['id'] . '/' . $LegitURL . '.html'); die(); } /** * Here we set the right language depending on the URL */ // I check if the $ArticleLanguage is different from the language currently in use. I also make sure that $ArticleLanguage is different from $_SESSION['Language'] to avoid infinite loops if ($ArticleLanguage != $DefaultLanguage AND $ArticleLanguage != $_SESSION['Language']) { // Set language $_SESSION['Language'] = $ArticleLanguage; // Redirect visitor to current page to re-load the correct language header('Location: index.php?rp=' . $_GET['rp']); die(); } }); add_hook('ClientAreaHeadOutput', 1, function($vars) { /** * This guy adds canonical URL inside <head></head> tag so that Google doesn't penalize you (read post for more info) */ if ($vars['templatefile'] == 'knowledgebasearticle') { $PageName = pathinfo($_GET['rp'])['filename']; $CanonicalURL = Capsule::select(Capsule::raw('SELECT REPLACE(title, " ", "-") AS title FROM tblknowledgebase WHERE (parentid = "' . $vars['kbarticle']['id'] . '" OR id = "' . $vars['kbarticle']['id'] . '") AND REPLACE(title, "-", " ") = "' . str_replace('-', ' ', $PageName) . '" LIMIT 1'))[0]->title; return '<link rel="canonical" href="index.php?rp=/knowledgebase/' . $vars['kbarticle']['id'] . '/' . $CanonicalURL . '.html"/>'; } }); Even though we're now showing the right language to visitors, Google and all other Search Engines still hate us because we're providing duplicate contents. Their hate is so strong that they penalize us on SERP. Here is why: whmcs.com/index.php?rp=/knowledgebase/55/How-to-boil-water.html whmcs.com/index.php?rp=/knowledgebase/55/How-to-boil-vodka.html whmcs.com/index.php?rp=/knowledgebase/55/How-to-boil-rum.html whmcs.com/index.php?rp=/knowledgebase/55/How-to-boil-wine.html 4 URLs, same page and (duplicate) content. The only way to avoid penalties is that we let them know what's the unique URL by using rel="canonical" tag. The above hook adds it automatically where it is supposed to be (<head>here</head>). And if you are questioning why someone should use fake links, take a look at blackhat SEO techniques. One of your malicious competitor could publish some of those fake-links so that weeks later Google & co. penalize you for duplicate content. The above hook protects you from this technique forcing a redirect to the default article in case the URL in use is not the legit one.
  6. Naaah you didn't and anyway I don't care 🤣 No problem at all. I read your PM multiple times. You're amazing 👍 So that means I can edit my own posts. I didn't know that! It seems that I can also hide my own replies.
  7. I wanted to mention all of you to say "Thanks" but every time I use [@]nickname I can no longer type 🤣 Guess I'm dumb. Anyway I wasn't expecting that 😍 thanks! Sadly what brian said left me speechless. I hope he replies to my PM.
  8. Exactly. As far as I know all the involved hook points (pre/after/create) keep using the initial username therefore you'll need to re-query the database to get the updated username.
  9. As far as I know these should be all the available tblinvoiceitems.type: Setup Hosting Domain, DomainRegister, DomainTransfer Upgrade Item Addon PromoHosting, PromoDomain "Empty" for manually created lines I suggest you to run multiple queries by "groups" of types like follows: SELECT [...] FROM tblinvoiceitems AS t1 LEFT JOIN tblhosting AS t2 ON t1.relid = t2.id WHERE t1.type IN ("Hosting") SELECT [...] FROM tblinvoiceitems AS t1 LEFT JOIN tbldomains AS t2 ON t1.relid = t2.id WHERE t1.type IN ("Domain", "DomainRegister", "DomainTransfer") This to avoid ending up with a single huge query with tens of JOIN based on HAVING clauses.
  10. It's doable with custom scripts but before you start coding keep in mind that it's a nightmare for tens of reasons. It took me months to achieve similar results. You need to use all the following action hooks: InvoiceCreation InvoiceCreated InvoiceCreationPreEmail InvoiceCreationAdminArea InvoicePaid InvoicePaidPreEmail Depending on other factors you may have to use also AdminAreaHeaderOutput. In fact Add Transaction from backend uses an ajax POST therefore you'll probably need to code a fallback to get your things done. Here comes the bad part. All the above hook points are redundant in inexplicable ways so your scripts must be smart enough to trigger only once otherwise you mess up your invoice numbering. Good luck. You'll need it. Don't forget that you also need to abort all invoice-related email notifications so that you can fix invoice number before customers receive PDF and emails with the default numbering of WHMCS. Obviously such emails must be re-sent via API.
  11. Kian

    Knowledgebase articles.

    I have a similar clause in the TOS of a website I own. 6 years have passed. More than 1200 people registered and accepted terms and conditions. Nobody noticed it 🤣
  12. Kian


    You can use Reports or create your own script that directly interacts with database select all records you need on a daily basis.
  13. Kian

    Knowledgebase articles.

    During the last few years I started to think that writing KB articles an more in general any content is worthless. It feels like we write to satisfy SERP instead of actual people. The new trend I'm seeing is that people are becoming more and more lazy. They turn on/off checkboxes without even reading 25 chars long descriptions. Few minutes later they blame about something that is not working as expected. Guess what? It was due to that checkbox. It seems hilarious but it's not 😟
  14. Kian


    It depends on how many users you have. Personally I'd start with the cheapest one. 2 vCores and 2 GB of RAM are perfectly fine for a small WHMCS. When you start seeing your Load Averages consistently going over 1.50 then it's time to upgrade. Nowadays you should be able to increase vCores and RAM on demand in few clicks in case your Provider supports it. Anyway since we are talking about very small prices probably it would be preferable to start with the biggest.
  15. Kian

    Knowledgebase articles.


Important Information

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