hex2bin 7 php<5.4

Standard

Another annoying thing; working on a server that has an old version of php. If you cant update, you cant use all the lovely new core functions that, well, that make life a bit less of a headache.

I came across the need to use the Unirest ‘Lightweight HTTP Request Client Libraries’ (as it describes its self – basically Unirest is a set of lightweight HTTP libraries that can make talking to an API a whole lot easier – especially if it uses any forms of security.

Well this library needed to use hex2bin – which i couldn’t being on a server tied to 5.3.3 (or so).

PHP and the likes of stackoverflow to the rescue.

function hex2bin($hexstr)
{
	$n = strlen($hexstr);
	$sbin="";
	$i=0;
	while($i<$n)
	{
		$a =substr($hexstr,$i,2);
		$c = pack("H*",$a);
		if ($i==0){$sbin=$c;}
		else {$sbin.=$c;}
		$i+=2;
	}
	return $sbin;
 
}

so this…

return array_combine(array_map('hex2bin', array_keys($values)), $values);

became this…

return array_combine(array_map('self::hex2bin', array_keys($values)), $values);

allow_url_include=0ff

Standard

This is a highly annoying place to be, when you want to include from a url – which i know is a security scare, but when you know its safe, i.e. you know who could have access it is fine; but you cant because the server is locked down.

It was a seek and a find that led me to this

 

public function Getter($url){
 
	$ch = curl_init( $url );
	curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
	curl_setopt( $ch, CURLOPT_HEADER, false );
	curl_setopt( $ch, CURLOPT_NOBODY, false );
 
	$content = curl_exec( $ch );
 
	curl_close( $ch );
	return $content;
}

 

Basically, a curl to remove the need of the include. So my include went from…

include 'http://domain.site.com?query='.$_GET['query'];

to…

$url = 'http://domain.site.com?query='.$_GET['query'];
	echo ($this->Getter($url));

boom.

Hostel La Verde, Cienfuegos, Cuba

Standard

Hostal La Verde screenshotHostal La Verde

Hostal La Verde, a Cuban hostal website

A small website for a Hostal in Cuba.

Bookings are permitted as are contacting the site owner for enquiries etc.

This was a fair simple wordpress site with a custom booking system with a separate database.

Waveform Image Generation

Standard

A waveform was created using a PHP script (http://goo.gl/HXyitu) written by an Andrew Freiday, GitHub.

This allowed MP3 files to have their waveform drawn out and saved as a PNG image. This was achieved by converting MP3 it into a WAV (a higher audio quality than MP3), splitting it into samples and converting the samples into a drawing; with all sample drawings being stuck together to generate the waveform shape.

Below is the PHP code:

ini_set("max_execution_time", "30000");
// how much detail required. Larger number means less detail
// (basically, how many bytes/frames to skip processing)
// the lower the number means longer processing time
define("DETAIL", 1);
// Harcoded widths, heights and colours (colours will be swapped out for transparent colours)
define("DEFAULT_WIDTH", 600);
define("DEFAULT_HEIGHT", 120);
define("DEFAULT_FOREGROUND", "#000000");
define("DEFAULT_BACKGROUND", "#ffffff");
/**
* GENERAL FUNCTIONS
*/
function findValues($byte1, $byte2)
{
$byte1 = hexdec(bin2hex($byte1));
$byte2 = hexdec(bin2hex($byte2));
return ($byte1 + ($byte2 * 256));
}
/**
* Great function slightly modified as posted by Minux at
* http://forums.clantemplates.com/showthread.php?t=133805
*/
function html2rgb($input)
{
$input = ($input[0] == "#") ? substr($input, 1, 6) : substr($input, 0, 6);
return array(
hexdec(substr($input, 0, 2)),
hexdec(substr($input, 2, 2)),
hexdec(substr($input, 4, 2))
);
}
// function call is fired on the load of the audio track
function createWave($audioFile)
{
// if a wave image doesnt exist - the file is created
if (!file_exists("/path/to/musicplayer/" . $audioFile . ".png")) {
/**
* PROCESS THE FILE
*/
// temporary file name
$tmpname = substr(md5(time()), 0, 10);
// copy the audio file to a temp file and use that to create the waveform image
copy("/path/to/musicplayer/" . $audioFile, "{$tmpname}_o.mp3");
// array of wavs that need to be processed
$wavs_to_process = array();
/**
* convert mp3 to wav using lame decoder
* First, resample the original mp3 using as mono (-m m), 16 bit (-b 16), and 8 KHz (--resample 8)
* Secondly, convert that resampled mp3 into a wav
* We don't necessarily need high quality audio to produce a waveform, doing this process reduces the WAV
* to it's simplest form and makes processing significantly faster
*/
exec("lame {$tmpname}_o.mp3 -m m -S -f -b 16 --resample 8 {$tmpname}.mp3 && lame -S --decode {$tmpname}.mp3 {$tmpname}.wav");
$wavs_to_process[] = "{$tmpname}.wav";
// delete temporary files
unlink("{$tmpname}_o.mp3");
unlink("{$tmpname}.mp3");
// get user vars from form
$width = DEFAULT_WIDTH;
$height = DEFAULT_HEIGHT;
$foreground = DEFAULT_FOREGROUND;
$background = DEFAULT_BACKGROUND;
$img = false;
// generate foreground color
list($r, $g, $b) = html2rgb($foreground);
// process each wav sample individually
for ($wav = 1; $wav <= sizeof($wavs_to_process); $wav++) {
$filename = $wavs_to_process[$wav - 1];
/**
* Below as posted by "zvoneM" on
* http://forums.devshed.com/php-development-5/reading-16-bit-wav-file-318740.html
* as findValues() defined above
* Translated from Croation to English - July 11, 2011
*/
$handle = fopen($filename, "r");
// wav file header retrieval
$heading[] = fread($handle, 4);
$heading[] = bin2hex(fread($handle, 4));
$heading[] = fread($handle, 4);
$heading[] = fread($handle, 4);
$heading[] = bin2hex(fread($handle, 4));
$heading[] = bin2hex(fread($handle, 2));
$heading[] = bin2hex(fread($handle, 2));
$heading[] = bin2hex(fread($handle, 4));
$heading[] = bin2hex(fread($handle, 4));
$heading[] = bin2hex(fread($handle, 2));
$heading[] = bin2hex(fread($handle, 2));
$heading[] = fread($handle, 4);
$heading[] = bin2hex(fread($handle, 4));
// wav bitrate
$peek = hexdec(substr($heading[10], 0, 2));
$byte = $peek / 8;
$ratio = 80;
// start putting together the initial canvas
// $data_size = (size_of_file - header_bytes_read) / skipped_bytes + 1
$data_size = floor((filesize($filename) - 44) / ($ratio + $byte) + 1);
$data_point = 0;
// now that we have the data_size for a single channel (they both will be the same)
// we can initialize our image canvas
if (!$img) {
// create original image width based on amount of detail
// each waveform to be processed with be $height high, but will be condensed
// and resized later (if specified)
$img = imagecreatetruecolor($data_size / DETAIL, $height * sizeof($wavs_to_process));
// fill background of image
if ($background == "") {
// transparent background specified
imagesavealpha($img, true);
$transparentColor = imagecolorallocatealpha($img, 0, 0, 0, 127);
imagefill($img, 0, 0, $transparentColor);
} else {
list($br, $bg, $bb) = html2rgb($background);
imagefilledrectangle($img, 0, 0, (int) ($data_size / DETAIL), $height * sizeof($wavs_to_process), imagecolorallocate($img, $br, $bg, $bb));
}
}
while (!feof($handle) && $data_point < $data_size) {
if ($data_point++ % DETAIL == 0) {
$bytes = array();
// get number of bytes depending on bitrate
for ($i = 0; $i < $byte; $i++)
$bytes[$i] = fgetc($handle);
switch ($byte) {
// get value for 8-bit wav
case 1:
$data = findValues($bytes[0], $bytes[1]);
break;
// get value for 16-bit wav
case 2:
if (ord($bytes[1]) & 128)
$temp = 0;
else
$temp = 128;
$temp = chr((ord($bytes[1]) & 127) + $temp);
$data = floor(findValues($bytes[0], $temp) / 256);
break;
}
// skip bytes for memory optimization
fseek($handle, $ratio, SEEK_CUR);
// draw this data point
// relative value based on height of image being generated
// data values can range between 0 and 255
$v = (int) ($data / 255 * $height);
// don't print flat values on the canvas if not necessary
if (!($v / $height == 0.5))
// draw the line on the image using the $v value and centering it vertically on the canvas
imageline($img,
// x1
(int) ($data_point / DETAIL),
// y1: height of the image minus $v as a percentage of the height for the wave amplitude
$height * $wav - $v,
// x2
(int) ($data_point / DETAIL),
// y2: same as y1, but from the bottom of the image
$height * $wav - ($height - $v), imagecolorallocate($img, $r, $g, $b));
} else {
// skip this one due to lack of detail
fseek($handle, $ratio + $byte, SEEK_CUR);
}
}
// close and cleanup
fclose($handle);
// delete the processed wav file
unlink($filename);
}
//header("Content-Type: image/png");
// want it resized?
// resample the image to the proportions defined in the form
$rimg = imagecreatetruecolor($width * 1.5, $height);
$rimg2 = imagecreatetruecolor($width, $height);
// This part is to sort out the transparency so the waveform shape is transparent and the background colour is solid
// the script was initially set up to show a solid waveform with a transparent background, so it needed
$oTransparentColor = imagecolorallocate($rimg, 254, 254, 254);
imagecolortransparent($rimg, $oTransparentColor);
imagealphablending($rimg, false);
$oTransparentColor = imagecolorallocate($img, 0, 0, 0);
imagecolortransparent($img, $oTransparentColor);
imagealphablending($img, false);
imagecopyresampled($img, $img, 0, 0, 0, 0, $width, $height, $width, $height);
imagepng($img, $audioFile . ".png");
imagedestroy($rimg);
imagedestroy($img);
}
}

Google Site Map XML

Standard

An example of creating an on-the-fly generated Google Shopping XML.

The basic structure of a google shopping xml is below, with some explanations of a few parts.

<feed xmlns="http://www.w3.org/2005/Atom" xmlns:g="http://base.google.com/ns/1.0">
<!—Website information -->
<title>Site Title</title>
<link>http://siteurl.com</link>
<updated>2013-12-20T13:03:47+00:00</updated>
<author>Author name</author>
<id>Identifier of this sitemap for this website</id>
<!-- start of repeatable section for each product -->
<entry>
<g:id>Unique identifier for this product on this website</g:id>
<title>Product title</title>
<description>Product description</description>
<g:google_product_category>Product category*</g:google_product_category>
<g:product_type>Product type**</g:product_type>
<g:age_group>Age Group</g:age_group>
<g:image_link>Image 1 path</g:image_link>
<g:additional_image_link>Image 2 path</g:additional_image_link>
<g:additional_image_link>Image 3 path</g:additional_image_link>
<link>Url of product page</link> <g:condition>Condition***</g:condition>
<g:availability>Availability</g:availability> <g:price>Price****</g:price>
<g:sale_price/> <g:brand>Brand*</g:brand> <g:gender>Gender</g:gender>
<g:color>Colour</g:color>
<g:size>Size</g:size>
<g:mpn>Manufacture Part Number*****</g:mpn>
<g:shipping> <g:country>Country Code</g:country>
<g:service>Delivery means</g:service>
<g:price>Delivery Price******</g:price>
</g:shipping>
</entry>
<!-- end of repeatable section for each product -->
</feed>

 

Explanation of nodes

Most of the nodes have a namespace of ‘g’ attached; as this is how Google requires the xml to be.

*Product category

This is like taxonomy and is Google specific.  One would need to consult the Google Shopper API documentation to see which category your product(s) fit into.

**Product type

This is also like a taxonomy and again Google specific.  Consult the Google Shopper API documentation to see which you products(s) fit into.

***Price

This is the price in the local currency; if this XML is to be used for the UK, then this will be XX GBP.  The letters denote the currency; consult the documentation to see what your local currency is.

****Manufacturer Part Number

The Manufacturer Part Number (MPN) is used to identify a specific product in the ‘mpn’ attribute if it is accompanied by the manufacturer’s brand name in the ‘brand’ attribute. These values are very important for matching users’ queries to your products.

*****Delivery Price

This is the Fixed delivery price (including VAT). Only delivery-to-consumer rates are allowed.

Full Google Shopping XML details are here http://goo.gl/8QEWNA.

 

Get the XML Dataset

The XML Dataset was built using a rather large SELECT statement to gain the various datafields from the database

Below is the SQL Select.

All the fields have had the semicolon from the namespace removed because it would invalidate the SQL.  In it’s place, gn has been substituted, PHP will replace gn back with a semi-colon when the XML is constructed.

//Begin with select statement by assigning the product id
$query = "SELECT CONCAT('websitename-',products.id,'-',".time().") as gnsid,\n";
 
// the product’s title
$query .= "CONCAT(brands.name,' ',products.model) as title, \n";
 
// the product’s description field
$query .= "products.description as description, \n";
 
// all products were set to be the same product type and product category.  If they were different, a CASE SELECT could be used to check for the appropriate keywords for each product
$query .= "'Apparel & Accessories > Jewelry > Watches' as gnsgoogle_product_category, \n";
$query .= "'Apparel & Accessories > Jewelry > Watches' as gnsproduct_type, \n";
 
// all of these products where for adults in this case, so a select case could be used if the products had an Age field attached
$query .= "'Adult' as gnsage_group, \n";
// the products image – in the case of this website the URL of the images where built up by the baseUrl set from the php config and the various product attributes to build up the file name.
$query .= "CONCAT('" . $baseUrl . "','images/',products.id,'-',Lower(Replace(brands.name, ' ', '-')),'-',Lower(Replace(products.model, ' ', '-')),'-1-thumb.jpg') as gnsimage_link, \n";
 
// if there is more than one picture available for the product
for($i=2;$i<=4;$i++){
if ($i == 2){
$query .= "CONCAT('" . $baseUrl . "','images/',Replace(CONCAT(products.id,'-',Lower(Replace(brands.name, ' ', '-')),'-',Replace(Lower(products.model), ' ', '-')),'', ''),'-2-thumb.jpg') as gnsadditional_image_linkDistinct".$i.", \n";
}
if ($i == 3){
$query .= "CONCAT('" . $baseUrl . "','images/',Replace(CONCAT(products.id,'-',Lower(Replace(brands.name, ' ', '-')),'-',Replace(Lower(products.model), ' ', '-')),'', ''),'-3-thumb.jpg') as gnsadditional_image_linkDistinct".$i.", \n";
}
if ($i == 4){
$query .= "CONCAT('" . $baseUrl . "','images/',Replace(CONCAT(products.id,'-',Lower(Replace(brands.name, ' ', '-')),'-',Replace(Lower(products.model), ' ', '-')),'', ''),'-4-thumb.jpg') as gnsadditional_image_linkDistinct".$i.", \n";
}
}
 
// the URL of the product on the website made from building up the specific filename from the product attributes
$query .= "CONCAT('" . $baseUrl . "','products/',products.id,'/',Lower(Replace(brands.name, ' ', '-')),'-',Lower(Replace(products.model, ' ', '-'))) as link, \n";
 
// a select case to set the correct condition – all these products were used, so they were set to be translated in the same way, but this can be changed to New etc.
$query .= "CASE \n";
$query .= " WHEN Lower(products.grade) = 'unworn' then 'Used' \n";
$query .= " WHEN Lower(products.grade) = 'excellent' then 'Used' \n";
$query .= " WHEN Lower(products.grade) = 'good' then 'Used' \n";
$query .= " WHEN Lower(products.grade) = 'average' then 'Used' \n";
$query .= "END as gnscondition,\n";
 
// a select case to set the correct availability
$query .= "CASE \n";
$query .= " WHEN (products.reserved = 1) then 'Out of Stock' ELSE 'In Stock'\n";
$query .= " WHEN products.reserved = 0 then 'available for order' \n";
$query .= "END as gnsavailability,\n";
 
// the pricing information of the product
$query .= "(products.price) as gnsprice,\n";
$query .= "(products.sale_price) as gnssale_price,\n";
 
// the brand of the product
$query .= "brands.name as gnsbrand, \n";
 
// the gender the product is for
$query .= "products.gender as gnsgender, \n";
 
//the color of the product
$query .= "products.dial as gnscolor, \n";
 
// the size of the product
$query .= "products.size as gnssize, \n";
 
// the MPN was created by combining the product name and model and stripping away the spaces, dot and hyphens
$query .= "Replace(Replace(REPLACE(CONCAT(brands.name,products.model), ' ',''),'-',''),'.','') as gnsmpn \n";
 
// selected from a brand table and products table
$query .= "FROM brands INNER JOIN products ON brands.id = products.brand_id \n";
 
// this database table had been defined to allow alpha-numeric values in the products ‘price’ field, and consequently products as they have been entered and deactivated in the system had the price field set to ENQUIRE, POR, SOLD etc.
// Google requires all products to have a valid price, so all products that had no price had to be skipped. REGEX was used to enable this.
$query .= "WHERE (products.price REGEXP '(^[+-]?[0-9]+\.?[0-9]*e?[0-9]+$)|(0x[0-9A-F]+)') AND
 
// only active products (i.e. in-stock and available to purchase) can be added
products.status = '1'";

 

Below is the PHP to create the XML.

// create a new XML document to save to the server and download.
// the download means the administrator can take a copy as a backup.
// google shopper can read from your domain and consume the generated
// XML by a scheduled update.
$doc = new DomDocument('1.0', 'UTF-8');
// create the root node
$root = $doc->createElement('feed');
// add attribute information
$root = $doc->createElementNS('http://www.w3.org/2005/Atom', 'feed');
$root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:g', 'http://base.google.com/ns/1.0');
$root = $doc->appendChild($root);
// the website title
$title = $doc->createElement("title", "Website Title");
$title = $root->appendChild($title);
// the website url
$link = $doc->createElement("link", "http://website.url");
$link = $root->appendChild($link);
// the datetime of last update
$updated = $doc->createElement("updated", date(DATE_ATOM));
$updated = $root->appendChild($updated);
// name of author
$author = $doc->createElement("author", "Author name");
$author = $root->appendChild($author);
$id = $doc->createElement("id", "Unique ID for website shopper xml-use the full website url here appended with xml filename");
$id = $root->appendChild($id);
while($row = mysqli_fetch_assoc($sql)) {
// add a node for each product
$product_node = $doc->createElement("entry");
$product_node = $root->appendChild($product_node);
// add a child node for each field
foreach ($row as $fieldname => $fieldvalue) {
// if the current item is the description
if ($fieldname == "description")
{
// a search and replace to get rid of erroneous characters.
// this database had a few.  One can be cleverer in cleansing
// the information, but a simple string replace can do the job
$fieldvalue = str_replace('â','\'', $fieldvalue);
}
// remove numbers from incremented xml
// as field names needed to be enumerated
if (substr($fieldvalue, -9) == "thumb.jpg" && substr($fieldname,0,-9) == "gnsadditional_image_link")
{
$tempstring = str_replace(substr(BASE_URL,0,-1), BASE_URI, $fieldvalue);
if (file_exists($tempstring)){
$fieldname = substr($fieldname,0,-9);
} else {
$fieldname = substr($fieldname,0,-9);
$fieldvalue="";
}
}
if ($fieldname == "gnsprice" or $fieldname == "gnssale_price")
{
if ($fieldvalue != ""){
$fieldvalue = $fieldvalue . " GBP";
}
}
if (substr($fieldname, 0, 3) == "gns"){
$fieldname = str_replace("gns","g:",$fieldname);
}
$child = $doc->createElement($fieldname);
$child = $product_node->appendChild($child);
$value = $doc->createTextNode($fieldvalue);
$value = $child->appendChild($value);
} // foreach
$shipping = $doc->createElement("g:shipping");
$shipping = $product_node->appendChild($shipping);
$country = $doc->createElement("g:country", "GB");
$country = $shipping->appendChild($country);
$service = $doc->createElement("g:service", "courier");
$service = $shipping->appendChild($service);
$deliveryprice = $doc->createElement("g:price", "0 GBP");
$deliveryprice = $shipping->appendChild($deliveryprice);
} // while
// get completed xml document
$doc->formatOutput = true;
$xml_string = utf8_encode($doc->saveXML());
$newShopperFeed = "websiteurl_shop_feed.xml";
// save the xml to the server
file_put_contents("folder/path/".$newShopperFeed, $xml_string);