Saturday, January 6, 2018

XERO API Integration | XERO Public API Integration | XERO Connect Through Public API

1. Login to https://app.xero.com/Application to create an Public application
2. Now copy "Consumer Key" and "Consumer Secret", which will be needed to connect to XERO. The first step is to get a request token from using above "Consumber Key" and "Consumer Secret", then we will redirect user to XERO login screen, where user can authorize our application and redirect back to our main application with a code, we will then use the code to get access_token and refresh_token. We will store access_token and refresh_token for further use. Below scrip will help us to get Request_Token from XERO end.

<?php
session_start();
include_once "connector.php";

$callback_postfix = "xero-public-api-integration/callback.php?state=".uniqid();
$xero_connector = new XeroConnector(array(
    "oauth_callback" => "http://localhost:81/me/".$callback_postfix
));
$response = $xero_connector->execute(
    "GET", "https://api.xero.com/oauth/RequestToken"
);
if ($response["code"] == 200) {
    $parsed = $xero_connector->extract_params($response["body"]);
    $_SESSION["token"] = $parsed;
    XeroConnector::prettyPrint($parsed);
    $url = "https://api.xero.com/oauth/Authorize?oauth_token=".$parsed["oauth_token"];
    echo '<p>Authorize: <a href="'.$url.'">'.$url.'</a></p>';
}
XeroConnector::prettyPrint($response);
3. After redirected to XERO you will asked to login and then authorize application. (If you are already logged in, then ask for authorize the app)
After authorize you will redirected back you as Callback URL, with a oauth_token and oauth_verifier, you can then process those information to get access_token and refresh_token. Script and result is below respectively.

<?php
session_start();
include_once "connector.php";

XeroConnector::prettyPrint($_SESSION["token"]);
XeroConnector::prettyPrint($_REQUEST);

if (isset($_REQUEST ['oauth_verifier'])) {
    $configs = array(
        "access_token" => $_SESSION["token"]["oauth_token"],
        "access_token_secret" => $_SESSION["token"]["oauth_token_secret"]
    );
    $params = array(
        'oauth_verifier' => $_REQUEST ['oauth_verifier'],
        'oauth_token' => $_REQUEST ['oauth_token']
    );

    $xero_connector = new XeroConnector($params, $configs);
    $response = $xero_connector->execute("GET", "https://api.xero.com/oauth/AccessToken");
    if ($response["code"] == 200) {
        $parsed = $xero_connector->extract_params($response["body"]);
        $_SESSION["access_token"] = $parsed["oauth_token"];
        $_SESSION["oauth_token_secret"] = $parsed["oauth_token_secret"];
        XeroConnector::prettyPrint($parsed);
        echo '<p><a href="organization.php">View Organization Details</a></p>';
    }
    else {
        XeroConnector::prettyPrint($response);
    }
}
Response (Access token and Refresh token collected from XERO end)

Array
(
    [oauth_token] => N0UTBYDEZQFAOLPZ6JZDVJSOBQAJUS
    [oauth_token_secret] => KEOOMKNTNZYAQBNO4WYGCZ3SI33WYO
    [oauth_callback_confirmed] => true
)
Array
(
    [state] => 5a5051b091146
    [oauth_token] => N0UTBYDEZQFAOLPZ6JZDVJSOBQAJUS
    [oauth_verifier] => 7403017
    [org] => u-HyWGrAfasyv8hO$VlheW
)
Array
(
    [oauth_token] => NPTKFCAGC6KXXGU6RWOLIF6RDSUGGO
    [oauth_token_secret] => EWXBV0NWBGLBCIM0ZDSEKDUGOMCWC8
    [oauth_expires_in] => 1800
    [xero_org_muid] => u-HyWGrAfasyv8hO$VlheW
)
View Organization Details
Now you have access_token and refresh_token, below script will help to get organization details.
<?php
session_start();
include_once "connector.php";

$configs = array(
    "oauth_token" => $_SESSION["access_token"],
    "access_token_secret" => $_SESSION["oauth_token_secret"]
);
$params = array(
    "oauth_token" => $_SESSION["access_token"]
);

echo "<h3><a href='xero.php'>Start process again</a></h3>";

$xero_connector = new XeroConnector($params, $configs);
$response = $xero_connector->execute("GET", "https://api.xero.com/api.xro/2.0/Organisation");
if ($response["code"] == 200) {
    $parsed = json_decode($response["body"]);
    XeroConnector::prettyPrint($parsed);
}
XeroConnector::prettyPrint($response);
And organization details is as below:
stdClass Object
(
    [Id] => e8904090-....-46d1-....-829af831fb30
    [Status] => OK
    [ProviderName] => My Public App Rose
    [DateTimeUTC] => /Date(1515213767839)/
    [Organisations] => Array
        (
            [0] => stdClass Object
                (
                    [APIKey] => DZQNXBMG.............YFWIS0ZME
                    [Name] => Mine Test
                    [LegalName] => Mine Test
                    [PaysTax] => 1
                    [Version] => GLOBAL
                    [OrganisationType] => COMPANY
                    [BaseCurrency] => BDT
                    [CountryCode] => BD
                    [IsDemoCompany] => 
                    [OrganisationStatus] => ACTIVE
                    [FinancialYearEndDay] => 31
                    [FinancialYearEndMonth] => 12
                    [DefaultSalesTax] => Tax Exclusive
                    [DefaultPurchasesTax] => Tax Exclusive
                    [CreatedDateUTC] => /Date(1513332449000)/
                    [OrganisationEntityType] => COMPANY
                    [Timezone] => BANGLADESHSTANDARDTIME
                    [ShortCode] => !yXp1C
                    [OrganisationID] => 817ce4e3-....-4da6-....-8ad8fdfbeba2
                    [LineOfBusiness] => Marketing Software
                    [Addresses] => Array
                        (
                        )

                    [Phones] => Array
                        (
                        )

                    [ExternalLinks] => Array
                        (
                        )

                    [PaymentTerms] => stdClass Object
                        (
                        )

                )

        )

)
And finally, below is the Connector.php script:
<?php
class XeroConnector
{
    private $headers = array();
    private $parameters = array();
    private $configs = array(
        "consumer_key" => "8J3YWIAOE4XFH..........FPDB54R",
        "consumer_secret" => "7UGYHJZYITL...........B1FXCMTV",
        "core_version" => "2.0",
        "payroll_version" => "1.0",
        "file_version" => "1.0",
        "application_type" => "Public",
        "user_agent" => "Trial #3"
    );

    function __construct($parameters, $configs = array())
    {
        $this->parameters = $parameters;
        $this->configs = array_merge($this->configs, $configs);
    }

    function execute($method, $target, $post_body = "")
    {
        $this->post_body = $post_body;
        $this->method = strtoupper($method);
        $this->path = $target;
        $this->nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
        $this->buildParameters();
        $this->sign();
        return $this->curlRequest($this->sign["signed_url"]);
    }

    private function sign()
    {
        $this->parameters["oauth_signature"] = $this->generateSignature();
        $this->sign = array(
            'parameters' => $this->parameters,
            'signature' => $this->escape($this->parameters["oauth_signature"]),
            'signed_url' => $this->path . '?' . $this->normalizeParameters('true'),
            'header' => $this->getHeaderString(),
            'sbs' => $this->sbs
        );
    }

    private function getHeaderString($args = array())
    {
        $result = 'OAuth ';

        foreach ($this->parameters as $pName => $pValue) {
            if (strpos($pName, 'oauth_') !== 0)
                continue;
            if (is_array($pValue)) {
                foreach ($pValue as $val) {
                    $result .= $pName . '="' . $this->escape($val) . '", ';
                }
            }
            else {
                $result .= $pName . '="' . $this->escape($pValue) . '", ';
            }
        }
        return preg_replace('/, $/', '', $result);
    }

    private function generateSignature()
    {
        switch ($this->parameters['oauth_signature_method']) {
            case 'RSA-SHA1':
                $private_key = openssl_pkey_get_private($this->readFile($this->configs['rsa_private_key']));
                $this->sbs = $this->escape($this->method) . "&" . $this->escape($this->path) . "&" . $this->escape($this->normalizeParameters());
                openssl_sign($this->sbs, $signature, $private_key);
                openssl_free_key($private_key);
                return base64_encode($signature);
            case 'PLAINTEXT':
                return urlencode($this->configs["consumer_secret"]);
            case 'HMAC-SHA1':
                $secret = $this->configs["consumer_secret"]."&";
                if (isset($this->configs["access_token_secret"])) {
                    $secret = $secret . $this->configs["access_token_secret"];
                }
                $this->sbs = $this->escape($this->method) . '&' . $this->escape($this->path) . '&' . $this->escape($this->normalizeParameters());
                return base64_encode(hash_hmac('sha1', $this->sbs, $secret, true));
            default:
                throw new Exception('Unknown signature method');
        }
    }

    private function normalizeParameters($filter = 'false')
    {
        $elements = array();
        ksort($this->parameters);
        foreach ($this->parameters as $paramName => $paramValue) {
            if ($paramName == 'xml') {
                if ($filter == "true")
                    continue;
            }
            if (preg_match('/\w+_secret/', $paramName))
                continue;
            if (is_array($paramValue)) {
                sort($paramValue);
                foreach ($paramValue as $element)
                    array_push($elements, $this->escape($paramName) . '=' . $this->escape($element));
                continue;
            }
            array_push($elements, $this->escape($paramName) . '=' . $this->escape($paramValue));

        }
        return join('&', $elements);
    }

    private function readFile($file_path)
    {
        $fp = fopen($file_path, "r");
        $file_contents = fread($fp, 8192);
        fclose($fp);
        return $file_contents;
    }

    private function escape($string)
    {
        if ($string === 0)
            return 0;
        if (empty($string))
            return '';
        if (is_array($string))
            throw new Exception('Array passed to escape()');

        $string = rawurlencode($string);
        $string = str_replace('+', '%20', $string);
        $string = str_replace('!', '%21', $string);
        $string = str_replace('*', '%2A', $string);
        $string = str_replace('\'', '%27', $string);
        $string = str_replace('(', '%28', $string);
        $string = str_replace(')', '%29', $string);
        return $string;
    }

    private function buildParameters()
    {
        $parameters = array();
        $parameters["oauth_nonce"] = $this->getNonce(5);
        $parameters["oauth_version"] = "1.0";
        $parameters["oauth_timestamp"] = time();
        $parameters["oauth_consumer_key"] = $this->configs["consumer_key"];
        $parameters["oauth_signature_method"] = "HMAC-SHA1";
        $this->parameters = array_merge($parameters, $this->parameters);
    }

    private function getNonce($length = 5)
    {
        $result = '';
        $cLength = strlen($this->nonce_chars);
        for ($i = 0; $i < $length; $i++) {
            $rnum = rand(0, $cLength);
            $result .= substr($this->nonce_chars, $rnum, 1);
        }
        return $result;
    }

    public static function prettyPrint($o)
    {
        echo "<pre>";
        print_r($o);
        echo "</pre>";
    }

    public function extract_params($body)
    {
        $kvs = explode ( '&', $body );
        $decoded = array ();
        foreach ( $kvs as $kv ) {
            $kv = explode ( '=', $kv, 2 );
            $kv [0] = $this->safe_decode ( $kv [0] );
            $kv [1] = $this->safe_decode ( $kv [1] );
            $decoded [$kv [0]] = $kv [1];
        }
        return $decoded;
    }

    private function safe_encode($data) {
        if (is_array ( $data )) {
            return array_map ( array (
                $this,
                'safe_encode'
            ), $data );
        }
        else if (is_scalar ( $data )) {
            return str_ireplace ( array (
                '+',
                '%7E'
            ), array (
                ' ',
                '~'
            ), rawurlencode ( $data ) );
        }
        else {
            return '';
        }
    }

    private function safe_decode($data)
    {
        if (is_array ( $data )) {
            return array_map ( array (
                $this,
                'safe_decode'
            ), $data );
        }
        else if (is_scalar ( $data )) {
            return rawurldecode ( $data );
        }
        else {
            return '';
        }
    }

    private function curlRequest($url)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_VERBOSE, true);

        $this->headers['Accept'] = 'application/json';
        switch ($this->method) {
            case "GET":
                $this->content_length = 0;
                break;
            case 'POST':
                $this->headers['Content-Length'] = strlen($this->post_body);
                curl_setopt($ch, CURLOPT_POST, TRUE);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $this->post_body);
                $this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
                break;
            case 'PUT' :
                $this->headers['Content-Length'] = strlen($this->post_body);
                $fh = tmpfile();
                fwrite($fh, $this->post_body);
                rewind($fh);
                curl_setopt($ch, CURLOPT_PUT, true);
                curl_setopt($ch, CURLOPT_INFILE, $fh);
                curl_setopt($ch, CURLOPT_INFILESIZE, $this->headers ['Content-Length']);
                $this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
                break;
            default :
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->method);
        }

        if (count($this->headers) > 0) {
            $headers = array();
            foreach ($this->headers as $k => $v) {
                $headers [] = trim($k . ': ' . $v);
            }
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        }

        $response = curl_exec($ch);
        if ($response === false) {
            $response = 'Curl error: ' . curl_error($ch);
            $code = 1;
        }
        else {
            $code = curl_getinfo ($ch, CURLINFO_HTTP_CODE);
        }
        $info = curl_getinfo ($ch);

        curl_close($ch);

        return array(
            'body' => $response, 'code' => $code, 'info' => $info
        );
    }
}

Friday, January 5, 2018

Regex Domain Name Validation | Regex URL Validation


Domain Name Validation (Regex-101 Link)
^((https?):\/\/)?([a-zA-Z0-9_][-_a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9]{1,20})$



URL Validation (Regex-101 Link)
^((https?):\/\/)?([a-z]*\.)+([a-z]+)(?:\/)?([a-z]+\/?)*(?:\?([a-z]+\=?[a-z]*&?)+)?(#[a-z]*)*

JQuery | Resizable Table Example | Table Column Resizing | Resizable Table Columns Using jQuery UI | Resizable table columns with jQuery

Core Part of Code I am Using to Resize Table Columns with Percentage Value

var table = $("#table-css-border-1"), tw = table.width();
table.find("tr").each(function() {
 var tr = $(this), c = 0;
  tr.find("td, th").each(function() {
   var te = $(this), ew = ((te.width() * 100) / tw);
   te.css("width", ew + "%").addClass("resizable column-" + (++c));
    te.data("cc", ".column-" + c);
    te.data("nc", ".column-" + (c + 1));
  });
 tr.find("td:last-child, th:last-child").removeClass("resizable");
});

var r = $("#table-css-border-1 .resizable"), mw = 50;
r.resizable({
  handles: 'e',
  maxWidth: mw,
  start: function(e, ui) {
   var td = $(ui.element);
    var nd = td.next() || td.previous();
    r.resizable( "option", "maxWidth", gw(td) + gw(nd) - mw);
    table.find(td.data("cc")).css("width", "");
    table.find(td.data("nc")).css("width", "");
  },
  stop: function(e, ui) {
   var td = $(ui.element);
    var nd = td.next() || td.previous();
    table.find(td.data("cc")).css("width", (((td.width()+5) * 100)/tw)+"%");
    table.find(td.data("nc")).css("width", ((nd.width() * 100)/tw)+"%");
  }
});
function gw(e) {
 return parseFloat(e.css("width").replace(/px/g, ''));
}



JSFiddle link to play

Get Latest Record In Each MySQL Group | How to select the first/least/max row per group in SQL | Select max, min, last row for each group in SQL without a subquery | MySQL - How To Get Top N Rows per Each Group

First approach (LEFT JOIN)


SELECT s.Name,c1.Id AS Max_Score_ID,c1.Score as Max_Score
FROM Students s
LEFT JOIN Scores c1 ON (c1.Student=s.Id)
LEFT JOIN Scores c2 ON (
  c1.Student=c2.Student 
  AND (c1.Score<c2.Score OR (c1.Score=c2.Score AND c1.Id<c2.Id))
)
WHERE c2.Score IS NULL
ORDER BY c1.Score DESC;

Execution Plan: First image shows statistics, an index is used when performing the above SQL, example created in MySQL-Fiddle. And second image for large data size, its around 8 million rows, and it takes a small amount of time to execute.





Now we will go for second approach (GROUP_CONCAT WITH SUBSTRING_INDEX)



SELECT s.Name,
CAST(SUBSTRING_INDEX(
  GROUP_CONCAT(c1.Id order by c1.Score desc),',',1
) AS DECIMAL(10, 2)) AS Max_Score_ID,
CAST(SUBSTRING_INDEX(
  GROUP_CONCAT(c1.Score order by c1.Score desc),',',1
) AS DECIMAL(10, 2)) as Max_Score
FROM Students s
LEFT JOIN Scores c1 ON (c1.Student=s.Id)
GROUP BY s.Id
ORDER BY Max_Score DESC;

Execution plan: First image show statistics taken from MySQL-Fiddle, used group-concat and substring-index both to calculate (min/max) value and/or other column of the table. Second image show that its also good time effective as it also takes a small amount of time to get min/max value and table size around 8 million rows.






MySQL Fiddle Link


Thursday, December 28, 2017

PHP Round up to specific number | Round to nearest number | Round to next multiple of number



<?php
#Round to the next multiple of 5, exclude the current number
function roundNumberToUp($n, $x = 5)
{
    return round(($n + $x / 2) / $x) * $x;
}
roundNumberToUp(10);    #10
roundNumberToUp(10.2);  #15
roundNumberToUp(11.5);  #15

#Round to the nearest multiple of 5, include the current number
function roundNumberToNearest($n, $x = 5)
{
    return (round($n) % $x === 0) ? round($n) : round(($n + $x / 2) / $x) * $x;
}
roundNumberToNearest(10);   #10
roundNumberToNearest(10.3); #10
roundNumberToNearest(10.5); #15
roundNumberToNearest(11.7); #15

#Round up to an integer, then to the nearest multiple of 5
function roundNumberToNearestAsInteger($n, $x = 5)
{
    return (ceil($n) % $x === 0) ? ceil($n) : round(($n + $x / 2) / $x) * $x;
}
roundNumberToNearestAsInteger(10.3);    #15
roundNumberToNearestAsInteger(11.7);    #15



URL Redirect: Redirecting From of an iFrame


IFrame


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>IFrame Title</title>
    <script type="text/javascript" src="//code.jquery.com/jquery-1.9.1.js"></script>
    <script type="text/javascript">
        $(document).ready(function() {
            $("button").click(function() {
                window.top.location.href = 'https://pritomkumar.blogspot.com';
            });
        });
    </script>
</head>
<body>
<button type="button">Reload IFrame Parent</button>
</body>
</html>

To modify your iframe embed code, you will add the following attribute inside the opening iframe tag:
sandbox="allow-top-navigation allow-scripts allow-forms"

Container of IFrame


<style type="text/css">
    iframe {
        width: 40%;
        height: 95%;
        margin-left: calc(30%);
        border: 1px solid lightgray;
    }
</style>
<div>
    <iframe src="iframe-html.php" sandbox="allow-top-navigation allow-scripts allow-forms"></iframe>
</div>

Get Latest Record In Each MySQL Group



Table=check_group_by
id   group_by    value
-------------------------
1    1           Value 1
2    1           Value 2
3    1           Value 3
4    2           Value 4
5    2           Value 5
6    3           Value 6

Query will be as like:

SELECT id,group_by,value FROM check_group_by
WHERE id IN (
 SELECT MAX(id) FROM check_group_by GROUP BY group_by
)





SQL Fiddle Example