Code

Coding, Programming & Algorithms, Tips, Tweaks & Hacks
Search

PHP 7's Null Coalescing Operator

PHP 7's Null coalescing operator is more useful than I thought.

PHP
public static function read($id)
{
    $Row = MySQL::query("SELECT `Data` FROM `cb_sessions` WHERE `SessionID` = '$id'", TRUE);    
    
    # http://php.net/manual/en/function.session-start.php#120589
    //check to see if $session_data is null before returning (CRITICAL)                        
    
    if($Row['Data'] == false || is_null($Row['Data']))
    {
        $session_data = '';
    }
    else
    {
        $session_data = $Row['Data'];
    }
    
    return $session_data;    
}
PHP 7.4
can be replaced with :
PHP
public static function read($id)
{
    $Row = MySQL::query("SELECT `Data` FROM `cb_sessions` WHERE `SessionID` = '$id'", TRUE);    

    # Introduced in PHP 7 : https://stackoverflow.com/a/59687793/126833
    return $Row['Data'] ?? '';    
}
PHP 7.4
Vanakkam !

Get the element that triggered the event in an event handler function

A lot of people seem to get confused when trying to get the element that triggered the event in an event handler function. this or $(this) returns the parent element of the target.

Even before jQuery, vanilla JavaScript worked.

http://www.quirksmode.org/js/events_properties.html
JavaScript
function getTarget(e)
{
    var e = e || window.event, t;

    if (e.target) t = e.target;
    else if (e.srcElement) t = e.srcElement;

    if (t.nodeType == 3) // defeat Safari bug
    t = t.parentNode;

    return t;
}
JavaScript
Vanakkam !

Commas in HTML INPUT textfields for better readability

Sometimes, when a user enters numbers into a HTML INPUT text field, he/she enters commas too for readability esp in the case of financial inputs.

But when we send the input data via a form to the backend script for storing into a database, say MySQL database, we don't want the commas in the SQL INSERT statement.

So here's a script to show the commas while typing and remove the commas on hitting the submit button.


Vanakkam !

Encrypt and Decrypt strings in URL in PHP 7 using openssl

Here's a dead simple solution to pass through encrypted data as a value in your URL. Say you wanted to load something like https://my-product.com/loadProduct.php?id=1234 but don't want to show the real id as it may be the primary value in your database's table.

Instead, you can load it like this : https://my-product.com/loadProduct.php?id=33797030395539366e55673d and decrypt the value in your script.
PHP

<?php
define("ENCRYPTION_KEY", "123456*");
function encrypt($string) { return bin2hex(openssl_encrypt($string, 'BF-ECB', ENCRYPTION_KEY)); }
function decrypt($string) { return openssl_decrypt(hex2bin($string),'BF-ECB',ENCRYPTION_KEY); }
?>
PHP 7.2
More details on security : https://stackoverflow.com/a/50373095/126833
Vanakkam !

Always check for existence of a file in spl_autoload_register's function call

PHP
<?php
function my_autoloader_1($className)
{    
    $filename = 'test1/'.$className.'.php';
    if (file_exists($filename))
    {
        require_once $filename;
    }
    else
    {
        return false;
    }
}

# foo.php is in test2
function my_autoloader_2($className)
{    
    $filename = 'test2/'.$className.'.php';
    if (file_exists($filename))
    {
        require_once $filename;
    }
    else
    {
        return false;
    }    
}

spl_autoload_register('my_autoloader_1');
spl_autoload_register('my_autoloader_2');
 
$a = new foo();
?>
PHP 7
Vanakkam !

Availability of a file / link publically on Google Cloud Storage

If you want to check if a file exists publically on Google Cloud Storage,

PHP
<?php
$flyer = "http://commondatastorage.googleapis.com/bucket/path/to/directory/image.jpg";
$headers = @get_headers($flyer);
if ($headers[0] != "HTTP/1.1 200 OK" && $headers[0] != "HTTP/1.0 200 OK")
{
    # Load file locally / from elsewhere
    $flyer = IMG."/image.jpg";
}?>
PHP 5
Vanakkam !

Compressing HTML

Compress might not be the right word, as it doesn't really compress / zip the output HTML code, but combines all the HTML lines to a single line and removes trailing / leading whitespaces.

This has to be your index.php and am assuming you are routing all incoming page requests to index.php which will be handled by either rewrites in htaccess or direct query strings. For example, I would have either

RewriteRule ^about\/vision$ index.html?page=about-vision [QSA,L]
or
RewriteRule ^register$ index.php?module=Register

PHP
<?php
ob_start();
require_once "index.phtml";
$html = ob_get_clean();
$htmls = explode("\n", $html);
for ($i = 0; $i < count($htmls); $i++)
{
    $htmls[$i] = trim($htmls[$i]);
}
$html = implode("", $htmls);
echo $html;
?>
PHP 5

There is one gotcha in this. You can't use single line comments start with // in inline Javascript code in the HTML. Either place the JavaScript code outside of the HTML page, in separate .js page or use multi-line comments /* */

Vanakkam !

Convert CSV to JSON

Sometimes I find myself converting google doc spreadsheets (CSV) to JSON format. I would later on use the JSON string to insert it into a JavaScript file.
So here is a simple script to convert a CSV file to JSON format.

Python
import sys, csv, json

if len(sys.argv) == 1:
    print "1 argument for filename required"
    sys.exit()

gdoc = csv.reader(open(sys.argv[1]))

# Get the 1st line, assuming it contains the column titles
fieldnames = gdoc.next() 

# Get the total number of columns
fieldnames_len = len(fieldnames)

data = [] # Empty list
i = 0

for row in gdoc:
    
    # Add an empty dict to the list
    data.append({})
    
    for j in range(0, len(row)):
        data[i][fieldnames[j]] = row[j]
    
    # What if the last few cells are empty ? There may not be commas
    for j in range(len(row), fieldnames_len):
        data[i][fieldnames[j]] = ""
    
    i = i + 1

print json.dumps(data)
sys.exit()
Python 2.7.2
Vanakkam !

Using WebP image format for browsers that support it

The WebP image format for the web is starting to get a lot of hype for its massive file compression. Chrome supports it since ver 9 and Opera since ver 11.10.
Image file sizes are reduced by more than 50% in WebPs when compared to JPGs.
Looking at Google Analytics on various sites, Chrome 9+ users constitute 20% - 25%. So providing one-fifth or one-fourth of the users with a much reduced page load is worth the extra effort.

1. Convert JPGs / PNGs to WebPs :

for i in *.jpg; do j=`echo "$i" | cut -d . -f 1`; convert -colorspace RGB "$i" "$i"; ~/libwebp/cwebp -q 75 "$i" -o "${j}.webp"; done

convert is used to convert the colourspace from RGB to CMYK since as of now, webp encoder doesn't support CMYK colourspace.

2. Use 2 different CSS files for background images - one for standard jpg/png and another for webp. Chrome 9+ and Opera 11.10+ would be served webp.css while all others img.css.

get_browser is pretty useful for browser detection, but requires to set browscap in php.ini which is a PHP_INI_SYSTEM directive and can't be set in using ini_set(), .htaccess or user php.ini which rules out almost all shared hosting enviroments.
But interestingly, this is possible in WebFaction where I could set browscap in a user php.ini file which was parsed.

PHP
<?php
$imgCSS = "img";
$browser = get_browser(null, true);
if (($browser['browser'] = "Chrome" && $browser['version'] >= 9) || ($browser['browser'] = "Opera" && $browser['version'] >= 11.10))
$imgCSS = "webp";
?>
.
.
.
<link rel="stylesheet" type="text/css" media="all" href="<?php echo $imgCSS ?>.css" />
PHP 5.2

If you are getting something like ...

<b>Warning</b>:  get_browser() [<a href='function.get-browser'>function.get-browser</a>]: browscap ini directive not set in <b>/path/script.php</b> on line <b>7</b>

and if there is no way to set browscap, then you need to find an alternative browser detection like phpbrowscap.

I benchmarked this on a graphically heavy wordpress site, having 11 photographs in a slider on the homepage which takes a megabyte alone.

BrowserRequestsSizeTime
FireFox 3.6311.9 MB21.48s (onload: 20.62s)
Chrome 1132577.90KB6.51s (onload: 6.51s, DOMContentLoaded: 2.88s)

(I could not find the option to view total bytes downloaded in Opera dragonfly's network tab)

Side Note : When uploading webp images to google storage, S3 or any other cloud service, specify the content-type / mime-type.

gsutil -h "Cache-Control:public,max-age=31536000" -h "Content-Type: image/webp" cp -a public-read kitten.webp gs://[bucket]/images/kitten.webp
s3cmd put kitten.webp s3://[bucket]/images/ --acl-public --mime-type "image/webp" --add-header="Cache-Control:max-age=315360000"
Vanakkam !

Alexa Rank function in Google Apps Script

Google Apps Script is JavaScript based scripting for many of Google's services, most noteably, Google Spreadsheets, like VB Macros for Excel.
There are some subtilities in JavaScript, like window and document not being defined. Though the scripting is mostly JavaScript, it is not 100% native JavaScript since it runs in an app and not in the browser.
Currently Google Apps Script has a computeHmacSha256Signature() function but Amazon's AWIS requires a SHA-1 key for its signature which can be obtained from jsSHA. I've clipped the jsSHA code from here as its too lengthy. You can download it from sourceforge, copy-paste the code from /src/sha1.js. A small change is required though. Change b64pad="" to b64pad="=".

Google Apps Script
/*
* When defining a local function 'foo' in a closure, at the end, it is declared global by window.foo = foo
* Google Apps Script has window and document undefined
*/
var window = {};

// Amazon Web Information Services
var AWIS =
{
SERVICE_ENDPOINT : 'http://awis.amazonaws.com/?',
ACTION : 'UrlInfo',
RESPONSE_GROUP : 'Rank',
ACCESS_KEY_ID : "your-access-id",
SECRET_ACCESS_KEY : "your-secret-key"
}

/*
* Validate if a string is of proper domain
* @param String domain
* @return Boolean false if invalid, true if valid
* There are many more tests, this one is just to avoid a bad cell-value
*/
function isValidDomain(domain)
{
if (domain.indexOf(".") < 1) return false;
if (domain.indexOf(":") != -1) return false;
return true;
}

/*
* Get just the hostname from a URL
* @param String URL
* @return String hostname
* http://en.wikipedia.org/wiki/URI_scheme#Examples
*/
function getHostname(url)
{
return url.replace(/http(s*):\/\/(.[^/\?]+)(\/*)(.*)$/g, "$2").toLowerCase();
}

/*
* Validate cellvalue to be of a proper domain
* @param String domain cell-value
* @return Integer Alexa Rank
*/
function getAlexaRank(domain)
{
if (!isValidDomain(domain)) return domain + " is not a valid domain";
var hostname = getHostname(domain);

if (AWIS.ACCESS_KEY_ID == "" || AWIS.ACCESS_KEY_ID == "undefined") return "Error : AWS AccessKey not yet set";
if (AWIS.SECRET_ACCESS_KEY == "" || AWIS.SECRET_ACCESS_KEY == "undefined") return "Error : AWS SecretKey not yet set";

var timestamp = generate_timestamp();
var shaObj = new window.jsSHA(AWIS.ACTION + timestamp, 'ASCII');
var signature = shaObj.getHMAC(AWIS.SECRET_ACCESS_KEY, 'ASCII', 'B64');
var awis_url = AWIS.SERVICE_ENDPOINT + "AWSAccessKeyId=" + AWIS.ACCESS_KEY_ID + "&Action=" + AWIS.ACTION + "&ResponseGroup=" + AWIS.RESPONSE_GROUP + "&Timestamp=" + encodeURIComponent(timestamp) + "&Signature=" + encodeURIComponent(signature) + "&Url=" + encodeURIComponent(hostname);

try
{
var response = UrlFetchApp.fetch(awis_url);
}
catch (err)
{
return "Error in URL : " + awis_url;
}

var sXML = response.getContentText();
var oXML = Xml.parse(sXML, false);
var root = oXML.getElement();
var Response = root.getElements("http://awis.amazonaws.com/doc/2005-07-11","Response");
var UrlInfoResult = Response[0].getElements("http://awis.amazonaws.com/doc/2005-07-11","UrlInfoResult");
var Alexa = UrlInfoResult[0].getElements("http://awis.amazonaws.com/doc/2005-07-11","Alexa");
var TrafficData = Alexa[0].getElements("http://awis.amazonaws.com/doc/2005-07-11","TrafficData");
var Rank = TrafficData[0].getElements("http://awis.amazonaws.com/doc/2005-07-11","Rank");
return Rank[0].getText();
}

/*
* Generate Timestamp of current time
* http://www.w3.org/TR/xmlschema-2/#dateTime
* @return String UTC time in yyyy-MM-dd'T'HH:mm:ss.SSS'Z' format
*/
function generate_timestamp()
{
var now = new Date();

var yyyy = now.getUTCFullYear();
var mm = now.getUTCMonth() < 9 ? "0" + (now.getUTCMonth() + 1) : (now.getUTCMonth() + 1);
var dd = now.getUTCDate() < 10 ? "0" + now.getUTCDate() : now.getUTCDate();

var HH = now.getUTCHours() < 10 ? "0" + now.getUTCHours() : now.getUTCHours();
var ii = now.getUTCMinutes() < 10 ? "0" + now.getUTCMinutes() : now.getUTCMinutes();
var ss = now.getUTCSeconds() < 10 ? "0" + now.getUTCSeconds() : now.getUTCSeconds();

return yyyy + "-" + mm + "-" + dd + "T" + HH + ":" + ii + ":" + ss + ".000Z";
}

/*
* Copy /src/sha1.js from jsSHA (http://jssha.sourceforge.net)
* Currently Google Apps Script has a computeHmacSha256Signature() function but Amazon's AWIS requires a SHA-1 key for its signature
* Also, change b64pad="" to b64pad="="
*/
(function(){var charSize=8,b64pad="=",hexCase=0,str2binb=function(a) ... ;window.jsSHA=jsSHA}());
JavaScript
Vanakkam !