Author: whmcsisg8

11 Apr

Domain verification using Let’s Encrypt DNS Challenge Type (dns-01)

From the link https://github.com/analogic/lescript, we will get a nice script to create SSL Certificates. The script has code to verify domain after uploading a file to a folder .well-known/acme-challenge in the document root. If the domain is not yet hosted , we have to verify the domain using DNS TXT record.

This post explains about domain verification using DNS method.

First of all download the script from the link https://github.com/analogic/lescript.
Then we can use most of it’s code for domain verification using DNS too.

We have to use response we get for

$response = $this->signedRequest(
                    "/acme/new-authz", array("resource" => "new-authz",
                "identifier" => array("type" => "dns", "value" => $domain)
                    )
);

This array will have challenge details for domain verification using http, dns etc.

To get dns challenge details, use the below code

$dns_challenge = array_reduce($response['challenges'], function ($v, $w) use (&$self) {
                return $v ? $v : ($w['type'] == "dns-01" ? $w : false);
            });

From the dns challenge we can collect token and key authorization.

$dns_token = $dns_challenge['token'];

$dns_payload = $dns_token . '.' 
                    . Base64UrlSafeEncoder::encode(hash('sha256', json_encode($header), true));

You can see that token and key authorization are creating just like we create for http-01. Now the main difference is, how we calculate the DNS TXT Record.

For domain verification using http-01, we create a file named
http://${domain}/.well-known/acme-challenge/${challenge[‘token’]}
and we put the payload as content of the file.

But for dns-01 domain verification, we are not adding TXT value as payload, but we calculate a string by below code.

// name of the domain
$name = '_acme-challenge'. $domain;

//points to
$dns_txt_record = Base64UrlSafeEncoder::encode(hash('sha256',$dns_payload, true));

Then we have to add $name and $dns_txt_record to the dns zone TXT record.
We can check weather the value is added correctly or not by typing below command in terminal.

dig txt _acme-challenge.[domain]

Then we can invoke the domain verification using dns-01 type.

$result = $this->signedRequest_dns(
                $challenge_uri, array(
            "resource" => "challenge",
            "type" => "dns-01",
            "keyAuthorization" => $dns_payload,
            "token" => $dns_token
                )
        );

If anybody need more explanation or help, please do post comments.
Will answer asap.

3 Apr

WHMCS Addon Module Development Using Smarty

Main aim of this post is not to discuss all steps of WHMCS addon module development. We are discussing two points that are not well explained in many blogs.

1)How to create and delete tables using new WHMCS database component. As full_query is deprecated, we have to use Illuminate Database. This post explains how to do table creation and deletion using WHMCS Illuminate Database.

2)When creating WHMCS addon without using Smarty, We are making html code complicated with mix of single and double quotes. So in this post we are discussing how to write HTML content using Smarty.

Assume that we have to develop an addon with name ‘example’. Steps to create the addon module is explained below.

1)create a folder named `example`.
In the folder create two files.

a)example.php
b)hooks.php
Also create folders named `lang` and `templates`

2)Now let’s open the file example.php and add the below code.

  <?php

if (!defined("WHMCS"))
    die("This file cannot be accessed directly");

use Illuminate\Database\Capsule\Manager as Capsule;

   function example_config() {
    $configarray = array(
        "name" => "Sample Addon",
        "description" => "Sample Addon is just an addon to explain you",
        "version" => "1.0",
        "author" => "WHMCSTools",
        "language" => "english",
    );

    return $configarray;
}


//Table is creating here
function example_activate() {
   if (!Capsule::schema()->hasTable('sample_table')) {
        Capsule::schema()->create('sample_table', function($table) {
            $table->increments('table_id');
            $table->integer('age');
            $table->string('name', 100);
            $table->date('created_at');
            $table->float('amount', 8, 2);
            $table->text('description');
        });
    }

    return array('status' => 'success', 'description' => 'Module activated');
}
function example_deactivate() {

    Capsule::schema()->dropIfExists('sample_table');
    return array('status' => 'success', 'description' => 'Module deactivated');

}

function example_output($vars) {
}

function example_clientarea($vars) {
}

3)Now we can write what we have to display in admin side of the module
in the function example_output() {.

For example we can define the function as

function example_output($vars) {
   echo '<p>The date & time are currently ' . date("Y-m-d H:i:s") . '</p>';
}

But if we have much more HTML content to display, we have to create a big string and then echo it. Better way to display the HTML content is using a Smarty file.

For that let’s create a file named time.tpl in the folder templates.
Let’s discuss the template usage with the same simple content. But we can extend that for complex contents too.

Content of the tpl file is

 <p>The date & time are currently {$date}</p>

Now let’s discuss how to use the tpl from the example_output function.

 function example_output($vars) {
   $smarty = new Smarty();
   $smarty->assign('date', date("Y-m-d H:i:s") );
   $smarty->caching = false;
   $smarty->compile_dir = $GLOBALS['templates_compiledir'];
   $smarty->display(dirname(__FILE__) . '/templates/time.tpl');
 }
20 Mar

WHMCS Hooks Priority

WHMCS Hooks are a nice way to write custom code.

We can add hook functions to a php file (File name can be anything, let’s assume our file name as test.php) and it should be uploaded to the folder /includes/hooks.

All WHMCS hooks are listed in the page https://developers.whmcs.com/hooks/hook-index/.

Now let’s explain WHMCS hooks priority. We can add custom code to be executed on every WHMCS client area page to the below hook.

<?php
add_hook('ClientAreaPage', 1, function($vars) { 
    // 1 is the priority value
    echo "From priority value 1<br>";
});

In the above hook code, we added code to be executed on the client area page as

 echo "From priority value 1<br>";

We can add same hook functions many times.
Let’s add another ClientAreaPage hook as shown below.

add_hook('ClientAreaPage', 2, function($vars) { 
     //2 is the priority value
     echo "From priority value 2<br>";
});

So the first ClientAreaPage hook has priority 1, second hook as priority 2.
So we will get the below out put.


From priority value 1
From priority value 2

Hook with priority value 1 is executed first,then hook with priority value 2 is executed.

9 Feb

How to get WHMCS product price details by query?

If you would like to know the price details of a product in WHMCS by query,
you can use the below query.

First you have to get the currency id. It’s the Id of the currency chosen by the user.

I wrote a function named getCurrencyId to get the currency ID.

function getCurrencyId() {
    $uid = $_SESSION['uid'];
    if (empty($uid)) {
        if (!empty($_SESSION['currency'])) {
            return $_SESSION['currency'];
        } else {
            return 1;
        }
    }
    $userData = Capsule::table('tblclients')
            ->select('currency')
            ->where('id', '=', $uid)
            ->first();
    if (!empty($userData))
        return $userData->currency;
    else
	return 1;
}

Assume that we would like to know price details of the product with id = 10.
Then we have to join tables tblproducts and tblpricing on a condition
‘tblproducts.id’, ‘=’, ‘tblpricing.relid’.

$currency_id = getCurrencyId();
$pid = 10;
$product_details = Capsule::table('tblproducts')
  ->join('tblpricing', 
      'tblproducts.id', '=', 'tblpricing.relid')
  ->where('tblpricing.type', '=', 'product')
  ->where('tblproducts.id', $pid)
  ->where('tblpricing.currency', $currency_id)
  ->select('tblproducts.*', 'tblpricing.*')
  ->first();

From the result array $product_details
we can get monthly, quarterly, semiannually, annually, biennially and triennially
as given below. If no price is entered for a particular period, you will get price as -1.

$monthly_price = $product_details->monthly;
if($monthly_price != -1 ) {
   echo "monthly price is" . $monthly_price;
}
//do -1 check all of the periods.

$quarterly_price = $product_details->quarterly;
$semiannually_price = $product_details->semiannually;
$annually_price = $data->annually;
$biennially_price = $data->biennially;
$triennially_price = $data->triennially;
27 Jan

How to know WHMCS Admin Username for local API call?

For calling WHMCS API’s internally from the WHMCS, we have a function named
localAPI. To call this function we have to pass
1)command; What api you would like to call.
2)data
3)admin user name of the WHMCS.

A sample API command UpdateClientProduct can be called as shown below.

        $command = 'UpdateClientProduct';
        $postData = array(
            'serviceid' => 11, // your service id
            'recurringamount' => 17,// recurring amount
        );
        //if WHMCS admin name is admin
        $adminUsername = "admin"; 
        $results = localAPI(
                       $command,
                       $postData, 
                       $adminUsername
        );

We hard coded the admin user name in the above code.
But hard coding the admin user name is a bad way of coding.
The code won’t work if admin username is changed or the code is uploaded
to another WHMCS where admin username is different.

So we can use below function to get the admin name.

use Illuminate\Database\Capsule\Manager as Capsule;
function getAdminUserName() {
    $adminData = Capsule::table('tbladmins')
            ->where('disabled', '=', 0)
            ->first();
    if (!empty($adminData))
        return $adminData->username;
    else
        die('No admin exist. Why So?');
}

So the API can be called as below.

  $command = 'UpdateClientProduct';
  $postData = array(
            'serviceid' => $product['service_id'],
            'recurringamount' => $deducted_amount,
  );
  $adminUsername = getAdminUserName();
  $results = localAPI(
                 $command, 
                 $postData, 
                $adminUsername
  );
16 Jan

Calling worldsteram.nl APIs from WHMCS

Calling worldsteram.nl APIs from WHMCS.

worldsteram.nl Provides API ID, and that must be used with API Calls.
So first of all create a file named worldstream.php and it must be
uploaded to the folder /modules/servers/worldstream.

<?php
function worldstream_ConfigOptions() {
    return array(
        'API ID' => array(
            'Type' => 'text',
            'Size' => '30',
            'Default' => '_API-ID_',
            'Description' => 'REplace _API-ID_ with your API ID',
        )
    );
}

/**
 The content that we will display on the page
[server.com]/clientarea.php?action=productdetails&id=[SERVICE_ID]
**/
function worldstream_ClientArea($vars) {
    //store your api id in config option and fetch it.
    $api_id = $vars['configoption2'];
    //create a custom field to store your server id 
    //and fetch it
    if(!empty($vars['customfields']['Server ID'])){
       $server_id = $vars['customfields']['Server ID'];
    } 
    
    
      if (empty($server_id)) {
        $return_array['status'] = 0;
        return array(
            'templatefile' => 'no_serverid',
            'vars' => array(
                'response' => $return_array
            )
        );
      }

      /*
          Call the function getMyServer to get
          server details.
      */
      $result_array = getMyServer($api_id, $server_id);
      return array(
                'templatefile' => 'clientarea',
                'vars' => array(
                    'response' => $result_array
                ),
      );
}

/**
 Let's call the API get_dedicated_list 
**/
function getMyServer($api_id, $server_id) {
    $url = 'https://www.customerpanel.nl/api.php';
    $data = array(
        "api_id" => $api_id,
        "method" => "get_dedicated_list"
    );

    $options = array(
        'CURLOPT_TIMEOUT' => '300',
    );
    $response = curlCall($url, $data, $options);
    $return = array();
    $result_array = json_decode($response);
    if ($result_array->StatusCode == 1) {
        unset($result_array->StatusCode);
        unset($result_array->StatusMessage);
        foreach ($result_array as $k => $value) {
            if ($value->Server_Id == $server_id) {
                $return['Status'] = 1;
                $return['Default_Password'] =
                    $value->Default_Password;
                $return['Main_IP'] = $value->Main_IP;
                $return['IPv6'] = isset($value->IPv6)
                    ? $value->IPv6 : "None";
                $return['Max_Datatraffic'] = 
                    $value->Max_Datatraffic;
                $return['Status'] = $value->Status;
                return $return;
            } else {
                continue;
            }
        }
    }
    $return['Status'] = 0;
    return $return;
}

Create a folder named templates in the path /modules/servers/worldstream.
Within that folder create a file named clientarea.tpl.
Then store the below contents to that file.

<div class="row">
    
    <div class="col-sm-12">
        <strong class="text-center">
               Server Details:
        </strong>
    </div>
</div>

<div class="row">
    
    <div class="col-sm-5 text-right">
        IPv4 Addresses:
    </div>
    <div class="col-sm-7 text-left">
       {$response['Main_IP']}
    </div>

</div>
<div class="row">
    
    <div class="col-sm-5 text-right">
       IPv6 Addresses:
    </div>
    <div class="col-sm-7 text-left">
      {$response['IPv6']}
    </div>
</div>

<div class="row">
    
    <div class="col-sm-5 text-right">
           Max. Datatraffic:
    </div>
    <div class="col-sm-7 text-left">
       {$response['Max_Datatraffic']}GB
    </div>
</div>

<div class="row">
    
    <div class="col-sm-5 text-right">
          Default Password:
    </div>
    <div class="col-sm-7 text-left">
          {$response['Default_Password']}
    </div>
</div>

<div class="row">
    
    <div class="col-sm-5 text-right">
          Status:
    </div>
    <div class="col-sm-7 text-left">
       {$response['Status']}
    </div>
</div>
10 Jan

How to do FTP auto login from Linux?

How to create a FTP auto login set up in Linux?

1)Go to your home folder.

cd /home/my-name

2)Crate a file named .netrc in your home folder.

And add a line as below.
machine [ftp.myserver.com] login [my-username] password [my-password]

Then save the file

3)Set permissions so that only owner can read the file.

chmod 0600 ~/.netrc

4)From shell just type
ftp [ftp.myserver.com] [port]

You are logged in!
It won’t ask any user name or password now.

29 Dec

Fix for Codeigniter WHERE IN query issue due to comma

In the below code we have an array of ids named $list_ids. We would like to mark the status of rows with ‘my_id’ = 1, my_id = 2 and my_id = 3 as Completed.

            $data['status'] = "Completed";
            $list_ids = array(1,2,3);
            $this->db->where_in('my_id', $list_ids);
            $this->db->update('my_table', $data);
            echo $this->db->last_query();

But after executing the above query, you might be surprised. Only row with ‘my_id’ = 1 gets updated. You might notice no change to the other two rows? Why?

Look at the generated query below.

UPDATE `my_table` SET `status` = ‘Completed’ WHERE `my_id` IN(‘1,2,3’)

There is a comma before 1 and after 3. The query should be
UPDATE `my_table` SET `status` = ‘Completed’ WHERE `my_id` IN(1,2,3)
to work it as expected.

For that we need to pass an extra parameter to the where_in function. The working code is given below.

            $data['status'] = "Completed";
            $list_ids = array(1,2,3);
            $this->db->where_in('my_id', 
                   $list_ids, FALSE);
            $this->db->update('my_table', $data);
            echo $this->db->last_query();

You can see that, we changed the where_in code from
$this->db->where_in(‘my_id’, $list_ids) to
$this->db->where_in(‘my_id’, $list_ids, FALSE);

Hope it’s helpful.

26 Dec

Prevent bootstrap affix overlapping with footer

Fixing a div to the right side of your web page using
bootstrap’s affix method is easy.

Steps required are
1)Add data-spy=”affix” to the div you would like to fix in the right side. Also Add a class to the div, in the example i added the class as my-affix-div.
The whole affix div code is given below.

<div class="col-sm-5 pull-right my-affix-div" 
        data-spy="affix"  >
        <div class = "panel 
                panel-primary 
                panel-transparent affix_div" >
            <div class ="panel-heading">
                <h3 class = "panel-title">Summary</h3>
            </div>

            <div class = "panel-body">
                <div class="row">
                    <div class="col-sm-4">
                        <b>Name</b>
                    </div>

                    <div class="col-sm-3">
                        <b>Place</b>
                    </div>

                    <div class="col-sm-3">
                        <b>Phone</b>
                    </div>                  
                </div>
  
            </div>

            <div class = "panel-footer">
                <form role="form" action="#"
                     method="get">
                    <button type="submit" 
                          class="btn btn-default"
                    >
                        Clear
                    </button>
                </form>
            </div>

        </div>
    </div> 

2)Then add the below Javascript code.

$('.my-affix-div').affix(
   {offset:{top: 75, bottom: 240}}
);

In the code we specified when the div should be fixed.
If the scroll top is more than 75 pixel, then the div with the class name my-affix-div will be fixed. If we scroll to the bottom and if the distance from bottom to the div is less than 240,
the div’s position becomes absolute.

At any moment the div with data-spy=”affix” , will be in one of the three states.
1)affix-top: If your scroll top is less than 75px.
2)affix: (If your scroll top is more than 75 px
and scroll bottom is more than 240 ).
3)affix-bottom: if your scroll bottom is less than 240.
We can specify styles of those states. Below CSS code is added to specify that the position should be absolute in both
affix-bottom and affix-top states. in affix state, the div will be fixed to the right side and distance to the top of the div will be
200px.

.affix-bottom{
    position: absolute;
    right: 0;  
}
.affix-top{
    position: absolute;
    right: 0;  
}
.affix {
        top: 200px;
        right: 0;  

 }
19 Dec

How to add popover to awesome font?

If you like to display a popover as shown in the above image,
You should include following files in your html head.

    <head>
        <script src="js/jquery.min.js"></script>
        <script src="js/bootstrap.min.js"></script>
        <link rel="stylesheet" href="css/font-awesome.min.css">
        <link rel="stylesheet" href="css/bootstrap.min.css">

    </head>
                 

Below is the html body content. The popover should come when placing mouse over the awesome font with class name “fa fa-question”. The awesome font is written as an inner html of a tag. Two important attributes we added to the tag are 1)data-toggle=”popover” and 2)data-content=”[popover message]”.

<body>
<div class="container">
    <h2>Sample Popover Code</h2>
    <div class="panel-group">
        <div class="panel panel-default">
            <div class="panel-heading">Popover with awesome font</div>
            <div class="panel-body">
                <div class="row form-group">

                    <div class="col-sm-6">
                        <input class="form-control" type="text"
                               placeholder="Name"  name="name" />
                    </div>

                </div>

                <div class="row form-group text-center">

                    <div class="col-sm-6">
                        <div class="input-group">
                            <input placeholder="Address"
                                   class="form-control pooja-star"
                                   type="text" name="address"/>
                            <span class="input-group-addon">
                                <a 
                                    data-toggle="popover"
                                    data-content="Enter Address as
                                    Street,City,State">
                                    <i class="fa fa-question"></i>
                                </a>
                            </span>
                        </div>
                    </div>

                </div>
                <div class="row form-group text-center">
                    <div class="col-sm-6">
                <button type="submit" class="btn btn-primary">
                Save changes
            </button>
                </div></div>
            </div>
        </div>

    </div>
</div>
</body>

Below javascript code is required, where we set when the popover should be displayed, and also we are defining the selector.

As we set selector: ‘[data-toggle=”popover”]’ and
trigger: ‘hover’, when we place mouse over an element with attribute data-toggle=”popover” the popover function will be called.

<script type="text/javascript">
    var popOverSettings = {
        trigger: 'hover',
        container: 'body',
        html: true,
        selector: '[data-toggle="popover"]', 
 
    }
 
    $('body').popover(popOverSettings);
</script>

You can customize the look of awesome font and the background box
by adding your style to the below classes.

.input-group-addon {
    background-color: #your-color-here;
    border:0px
}

.fa-question {
    //style here
}