Related
How to validate NHS number using check digit 11 (mod 11) using PHP.
After a long search on the internet trying to find a function that I can use to validate check digit 11 (mod 11) number, I couldn't find so I end up developing one myself and share it here. I hope someone my find it useful.
The code with comments:
###### Check Digit 11 Modulus
function mod11($nhs_no){
//Get Last Number
$checkDigit = (int)substr($nhs_no, -1);
//Get Remaining Numbers
$numbers = substr($nhs_no, 0, -1);
// Sort Numbers Into an Array
$numbers_array = str_split($numbers);
//Define the base for the weights
$base=2;
// Multiply the weights to the numbers
$numbers_array_reversed = array_reverse($numbers_array);
foreach($numbers_array_reversed as $number){$multiplier[$number.'x'.$base]=(float)$number * $base;$base++;}
//Get the total sum
$sum =(float) array_sum($multiplier);
$modulus = 11;
// Divide the sum by check digit 11
$divider = (float)($sum / $modulus);
// Get the remainder
$remainder = (int)(($divider - (int)$divider)*$modulus);
// Check if the remainder is matching with the last check digit
$results = (int) ($modulus-$remainder);
// Return true or false
return ($results == $checkDigit ? true : false);
}
I got a bit of a complex problem. At work we have to count our inventory every month. This is done with a scanner. At each location there can be up to 100 different items. Every item, even the same kind have to be scanned. When each location has been scanned, we print out the list of scanned items. The problem is that each scan has its own line in the txt file (it done not add/subtract multiple counts of the same item)
As the vendor of our system is notoriously slow implementing new functions I thought about a php script that does the following:
1: read every line from the txt file
2: add/substract the count of the same item
3: print out a list with the item number and count.
The txt file is as following:
01234+000001N
Where the first 5 digits is the item number. As it is possible to add and substract the next symbol is + or - then the next 5 digits is the count and the N is the "eol"
So somehow I have to put it all in some sort of array and the sort it by item number. And the add/substract and then finally print out the final list
Assuming you've loaded the file into a string, line by line, and is split by a new line, you can do the following; (read code comments)
$strTxtFile = <<<TXT
01234+000001N
01234+000001N
09876+000002N
01234+000001N
01234+000001N
09876+000002N
01234-000001N
09876+000002N
TXT;
/**
* 01234 should have 3 stock
* 09876 should have 6 stock
*/
$arrProducts = array();
$arrFileLines = explode(PHP_EOL, $strTxtFile);
foreach($arrFileLines as $strFileLine) {
//Split the lines by the action (+/-)
$arrStockAction = preg_split("/(\+|\-)/", $strFileLine, NULL, PREG_SPLIT_DELIM_CAPTURE);
$strProductCode = $arrStockAction[0]; //The first part is the product code
$strAction = $arrStockAction[1]; //The action (+/-) to the stock
$intStockAmount = (int) $arrStockAction[2]; //Cast it to an int to get the number
//Check if the product exists in our array, if not, create it with 0 stock
if( array_key_exists($strProductCode, $arrProducts) === FALSE ) {
$arrProducts[$strProductCode] = 0;
}
if($strAction === "+") {
//Add stock
$arrProducts[$strProductCode] += $intStockAmount;
} else {
//Minus stock
$arrProducts[$strProductCode] -= $intStockAmount;
}
}
print_r($arrProducts);
https://repl.it/ECrW
Similar to the other answer, maybe a little simpler:
foreach(file('/path/to/file.txt') as $line) {
$item = substr($line, 0, 5);
$sign = substr($line, 5, 1);
$qty = substr($line, 6, 6);
if(!isset($result[$item])) {
$result[$item] = $qty;
} else {
$result[$item] += $sign.$qty;
}
}
Or replace the substr() lines with:
preg_match('/(\d{5})(.)(\d{6})/', $line, $matches);
And use $matches[1], $matches[2] and $matches[3].
I just found out I had misread the txt file. The lines is as follow:
01234000001 N
And
01234000001-N
The blank space between the last number and the N represent addition and - substract
Having a bit of trouble with a PHP & MySql script I'm trying to create. It's a bit complex so hope a guru out there can take a stab.
I need to generate 100 random 8 digit numbers and insert them as new rows into a MySQL table. The numbers have to be unique (i.e. not already in the column) and I need to make sure that I create exactly 100 rows - the problem being that if the 8 digit number is already in the column, existing solutions that suggest INSERT IGNORE etc will just skip over a row so I would end up only adding eg. 98 rows if 2 rows already contain the number.
So far I have gotten to a point where I can generate a random number (within limits), check the DB for that number in the specified column, if it doesn't exist it creates a new row. Concurrently the unique number is used to generate and save a QR code - the ultimate goal being to simply run the script once to create 100 QR codes and store their info in the DB.
So far I can do it for one number - any ideas on the best way to loop this?
<?php
// Connection variables:
$hostname="mydbhost";
$user="user";
$pass="password";
$dbname="name";
// Create connection
$con=mysqli_connect($hostname,$user,$pass,$dbname);
// Check connection
if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
// Pull the data from the DB
$result = mysqli_query($con,"SELECT number FROM qrcodes");
$data=array();
// Fill array with the data from the DB
while($row = mysqli_fetch_array($result))
{
$data [] = $row[0];
}
//Min & Max values for the random numbers
$min = 80000000;
$max = 80009999;
//Set content to be included in QR Code and Label
$qrcontent = rand($min,$max);
if(in_array("$qrcontent", $data))
{
//echo "Error: It's already in the DB!";
}
else
{
//When not in the database
mysqli_query($con,"INSERT INTO qrcodes (id, number, status) VALUES ('', '$qrcontent', '0')");
// the rest of the else clause creates and saves the QR code - just included it for interest sake
$text = "$qrcontent"; // URL for code to
$label = "$qrcontent"; // message to display below barcode
//google chart API URL
// $image = "http://chart.apis.google.com/chart?cht=qr&chs=190x190&chl=" . $text . "&chld=h";
$image = "http://chart.apis.google.com/chart?cht=qr&chs=190x190&chl=" . $text . "&chld=h|2";
//Set path to font file to be used for label
$font = '/include/fonts/arialbd.ttf';
//Get the QR code image from google
$background = imagecreatefrompng($image);
$swidth = imagesx($background);
$sheight = imagesy($background);
//If label field isn't blank, draw it in below the code
if(isset($label))
{
$font_color = imagecolorallocate($background, 0, 0, 0); // set font color
if(strlen($label) >= 15)
{
//split string in two, down the middle
$half = (int) ( (strlen($label) / 2) ); // cast to int incase str length is odd
$left = trim(substr($label, 0, $half));
$right = trim(substr($label, $half));
// Write Text
imagettftext($background, 10, 0, 58, 181, $font_color, $font, $left);
imagettftext($background, 10, 0, 58, 190, $font_color, $font, $right);
} else {
// Write Text
imagettftext($background, 12, 0, 58, 190, $font_color, $font, $label);
}
}
// Output header and final image
header("Content-type: image/png");
header("Content-Disposition: filename=" . $image);
// Save image to specified file
imagepng($background, "/var/www/images/qrcodes/".$qrcontent.".png");
// Optional - print image to screen in browser too
imagepng($background);
// Destroy the images
imagedestroy($background);
}
mysqli_close($con);
Appreciate any insight
There's a lot of irrelevant code above, but the gist of what you want is to use a UNIQUE key on the number column and code like this:
// your connection code ....
// How many rows inserted so far?
$rows_inserted = 0;
$numbers_inserted = array();
// Loop until done
while($rows_inserted < 100) {
// Create random number
//Set content to be included in QR Code and Label
$qrcontent = rand($min,$max);
mysqli_query($con,"INSERT INTO qrcodes (id, number, status) VALUES ('', '$qrcontent', '0')");
if(mysqli_affected_rows($con) == 1) {
$rows_inserted++; // update the counter
mysqli_affected_rows($con);
// All your image manipulation code and so on ...
}
}
There are several factors at play here:
How often do you want to repeat the process? At some point in time, your 8 digits will be exhausted and also, 8 digits could be relatively easy to guess.
Do the numbers have to be truly random? Think about adding digits or alphanumeric characters.
These things aside, you want to do the following:
SELECT all existing QR-codes from the database
Create a new number in a loop:
do {
$random = myRandomNumberGenerator();
} while (!in_array($random, $existingNumbers) && !in_array($random, $newNumbers));
You probably want to set a limit to prevent infinite loops. This way, you don't fail if there's a duplicate.
Repeat this process 100 times and put all new numbers in an array $newNumbers
Turn your entire array into a single SQL statement.
foreach ($newNumbers as $new) {
$str .= "('', '$new', '0'),";
}
And finally build and execute the query:
"INSERT INTO (id, number, status) VALUES " . $str;
Just generating the random numbers:
$picked = array();
$result2 = mysqli_query($con,"SELECT * FROM qrcode");
for($i=0;$i<mysqli_num_rows($result2);$i++) {
$row = mysqli_fetch_array($result2);
$picked[] = $row['number'];
}
$randomn = array();
for($i=0;$i<100;$i++) {
$n = 00000000;
while(in_array($n,$randomn) || in_array($n,$picked)) {
$n = mt_rand($min,$max);
}
$randomn[] = $n;
}
Then use the $randomn array to get all the random numbers.
I know how to generate a random number in PHP but lets say I want a random number between 1-10 but I want more 3,4,5's then 8,9,10's. How is this possible? I would post what I have tried but honestly, I don't even know where to start.
Based on #Allain's answer/link, I worked up this quick function in PHP. You will have to modify it if you want to use non-integer weighting.
/**
* getRandomWeightedElement()
* Utility function for getting random values with weighting.
* Pass in an associative array, such as array('A'=>5, 'B'=>45, 'C'=>50)
* An array like this means that "A" has a 5% chance of being selected, "B" 45%, and "C" 50%.
* The return value is the array key, A, B, or C in this case. Note that the values assigned
* do not have to be percentages. The values are simply relative to each other. If one value
* weight was 2, and the other weight of 1, the value with the weight of 2 has about a 66%
* chance of being selected. Also note that weights should be integers.
*
* #param array $weightedValues
*/
function getRandomWeightedElement(array $weightedValues) {
$rand = mt_rand(1, (int) array_sum($weightedValues));
foreach ($weightedValues as $key => $value) {
$rand -= $value;
if ($rand <= 0) {
return $key;
}
}
}
For an efficient random number skewed consistently towards one end of the scale:
Choose a continuous random number between 0..1
Raise to a power γ, to bias it. 1 is unweighted, lower gives more of the higher numbers and vice versa
Scale to desired range and round to integer
eg. in PHP (untested):
function weightedrand($min, $max, $gamma) {
$offset= $max-$min+1;
return floor($min+pow(lcg_value(), $gamma)*$offset);
}
echo(weightedrand(1, 10, 1.5));
There's a pretty good tutorial for you.
Basically:
Sum the weights of all the numbers.
Pick a random number less than that
subtract the weights in order until the result is negative and return that number if it is.
This tutorial walks you through it, in PHP, with multiple cut and paste solutions. Note that this routine is slightly modified from what you'll find on that page, as a result of the comment below.
A function taken from the post:
/**
* weighted_random_simple()
* Pick a random item based on weights.
*
* #param array $values Array of elements to choose from
* #param array $weights An array of weights. Weight must be a positive number.
* #return mixed Selected element.
*/
function weighted_random_simple($values, $weights){
$count = count($values);
$i = 0;
$n = 0;
$num = mt_rand(1, array_sum($weights));
while($i < $count){
$n += $weights[$i];
if($n >= $num){
break;
}
$i++;
}
return $values[$i];
}
/**
* #param array $weightedValues
* #return string
*/
function getRandomWeightedElement(array $weightedValues)
{
$array = array();
foreach ($weightedValues as $key => $weight) {
$array = array_merge(array_fill(0, $weight, $key), $array);
}
return $array[array_rand($array)];
}
getRandomWeightedElement(array('A'=>10, 'B'=>90));
This is very easy method. How get random weighted element. I fill array variable $key. I get $key to array $weight x. After that, use array_rand to array. And I have random value ;).
Plain and fair.
Just copy/paste and test it.
/**
* Return weighted probability
* #param (array) prob=>item
* #return key
*/
function weightedRand($stream) {
$pos = mt_rand(1,array_sum(array_keys($stream)));
$em = 0;
foreach ($stream as $k => $v) {
$em += $k;
if ($em >= $pos)
return $v;
}
}
$item['30'] = 'I have more chances than everybody :]';
$item['10'] = 'I have good chances';
$item['1'] = 'I\'m difficult to appear...';
for ($i = 1; $i <= 10; $i++) {
echo weightedRand($item).'<br />';
}
Edit: Added missing bracket at the end.
You can use weightedChoice from Non-standard PHP library. It accepts a list of pairs (item, weight) to have the possibility to work with items that can't be array keys. You can use pairs function to convert array(item => weight) to the needed format.
use function \nspl\a\pairs;
use function \nspl\rnd\weightedChoice;
$weights = pairs(array(
1 => 10,
2 => 15,
3 => 15,
4 => 15,
5 => 15,
6 => 10,
7 => 5,
8 => 5,
9 => 5,
10 => 5
));
$number = weightedChoice($weights);
In this example, 2-5 will appear 3 times more often than 7-10.
i used Brad's answar and changed it a little to fit my situation and add more flexibility
i have an array with array value
$products = [
['id'=>1,'name'=> 'product1' , 'chance'=>2] ,
['id'=>2,'name'=> 'product2' , 'chance'=>7]
]
first i shuffle the products array
shuffle($products );
then you can pass it to the function
function getRandomWeightedElement(array $products) {
$chancesSum = 0;
foreach ($products as $product){
$chancesSum += (int) $product['chance'];
}
$rand = mt_rand(1, $chancesSum);
$range = 0;
foreach ($products as $product) {
$range += (int) $product['chance'];
$compare = $rand - $range;
if ($compare <= 0){
return (int) $product['id'];
}
}}
Since I used IainMH's solution, I may as well share my PHP code:
<pre><?php
// Set total number of iterations
$total = 1716;
// Set array of random number
$arr = array(1, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5);
$arr2 = array(0, 0, 1, 1, 2, 2, 2, 3, 3, 4, 5);
// Print out random numbers
for ($i=0; $i<$total; $i++){
// Pick random array index
$rand = array_rand($arr);
$rand2 = array_rand($arr2);
// Print array values
print $arr[$rand] . "\t" . $arr2[$rand2] . "\r\n";
}
?></pre>
I just released a class to perform weighted sorting easily.
It's based on the same algorithm mentioned in Brad's and Allain's answers, and is optimized for speed, unit-tested for uniform distribution, and supports elements of any PHP type.
Using it is simple. Instantiate it:
$picker = new Brick\Random\RandomPicker();
Then add elements as an array of weighted values (only if your elements are strings or integers):
$picker->addElements([
'foo' => 25,
'bar' => 50,
'baz' => 100
]);
Or use individual calls to addElement(). This method supports any kind of PHP values as elements (strings, numbers, objects, ...), as opposed to the array approach:
$picker->addElement($object1, $weight1);
$picker->addElement($object2, $weight2);
Then get a random element:
$element = $picker->getRandomElement();
The probability of getting one of the elements depends on its associated weight. The only restriction is that weights must be integers.
Many of the answers on this page seem to use array bloating, excessive iteration, a library, or a hard-to-read process. Of course, everyone thinks their own baby is the cutest, but I honestly think my approach is lean, simple and easy to read/modify...
Per the OP, I will create an array of values (declared as keys) from 1 to 10, with 3, 4, and 5 having double the weight of the other values (declared as values).
$values_and_weights=array(
1=>1,
2=>1,
3=>2,
4=>2,
5=>2,
6=>1,
7=>1,
8=>1,
9=>1,
10=>1
);
If you are only going to make one random selection and/or your array is relatively small* (do your own benchmarking to be sure), this is probably your best bet:
$pick=mt_rand(1,array_sum($values_and_weights));
$x=0;
foreach($values_and_weights as $val=>$wgt){
if(($x+=$wgt)>=$pick){
echo "$val";
break;
}
}
This approach involves no array modification and probably won't need to iterate the entire array (but may).
On the other hand, if you are going to make more than one random selection on the array and/or your array is sufficiently large* (do your own benchmarking to be sure), restructuring the array may be better.
The cost in memory for generating a new array will be increasingly justified as:
array size increases and
number of random selections increases.
The new array requires the replacement of "weight" with a "limit" for each value by adding the previous element's weight to the current element's weight.
Then flip the array so that the limits are the array keys and the values are the array values.
The logic is: the selected value will have the lowest limit that is >= $pick.
// Declare new array using array_walk one-liner:
array_walk($values_and_weights,function($v,$k)use(&$limits_and_values,&$x){$limits_and_values[$x+=$v]=$k;});
//Alternative declaration method - 4-liner, foreach() loop:
/*$x=0;
foreach($values_and_weights as $val=>$wgt){
$limits_and_values[$x+=$wgt]=$val;
}*/
var_export($limits_and_values);
Creates this array:
array (
1 => 1,
2 => 2,
4 => 3,
6 => 4,
8 => 5,
9 => 6,
10 => 7,
11 => 8,
12 => 9,
13 => 10,
)
Now to generate the random $pick and select the value:
// $x (from walk/loop) is the same as writing: end($limits_and_values); $x=key($limits_and_values);
$pick=mt_rand(1,$x); // pull random integer between 1 and highest limit/key
while(!isset($limits_and_values[$pick])){++$pick;} // smallest possible loop to find key
echo $limits_and_values[$pick]; // this is your random (weighted) value
This approach is brilliant because isset() is very fast and the maximum number of isset() calls in the while loop can only be as many as the largest weight (not to be confused with limit) in the array. For this case, maximum iterations = 2!
THIS APPROACH NEVER NEEDS TO ITERATE THE ENTIRE ARRAY
I used this:
mt_rand($min, mt_rand($min, $max));
it give more lower values and less higher values, since the more the value is high the more is cutted out by one of the mt_rand
The probability is linearly increasing in the lower values, forming a square diagonal (see maths lower)
PRO: easy and strightforward
CON: maybe too simple so not enough weightable or balanceable for some use case
Maths:
let i index of i-nth value from min to max,
let P(i) the probability of obtaining the i-nth value,
let N=max-min:
P(i)=(1+N-i)/sum(1,N)
Since N is equals for all terms:
P(i) is proportional to N-i
so, in facts, the probability is linearly increasing in the lower values, forming a square diagonal
Variants:
you can write variants:
mt_rand($min, mt_rand(1, mt_rand(1, $max))); //value more given in low part
mt_rand(mt_rand($min, $max), $max); //mirrored, more upper values than lower
...
function getBucketFromWeights($values) {
$total = $currentTotal = $bucket = 0;
foreach ($values as $amount) {
$total += $amount;
}
$rand = mt_rand(0, $total-1);
foreach ($values as $amount) {
$currentTotal += $amount;
if ($rand => $currentTotal) {
$bucket++;
}
else {
break;
}
}
return $bucket;
}
I ugh modified this from an answer here Picking random element by user defined weights
After I wrote this I saw someone else had an even more elegant answer. He he he he.
I'm trying to build some PHP code to send SMS through telnet to a SIM server, but I'm having trouble in sending concatenated messages.
I've read some things about using padding bits to make the coded message septets into octets, but I don't fully understand how it works.
I have a class that receives the phone number, the message (already split in 153chars maximum), the total number of SMSs and the order number of the present part of text.
It works as long as I have that '20' added before the $hexmessage. But I get a junk char in the beginning of the first part (before the 1st letter of my msg), and the same junk char replacing the first letter of the second part! (using '20' so it would show a blank space, but it shows a triangle)
I can't understand why, or what I'd had to change for it to work properly.
I hope that someone can help me understand what am I doing wrong.
Here's what I've got so far:
<?php
// Generate PDU string
public function generatePDUm($receiverNumber,$message, $sms_count, $msg_nr) {
//Append filler digit if number is national
if( strlen($receiverNumber)==9){
$nacional=1;
$receiverNumber = $receiverNumber."F";
$network = substr($receiverNumber, 0, 2); //NETWORK code, used to decide the SIM Card to be used
//Check for international flags and set the number type accordingly
}else{
$nacional=0;
if(substr($receiverNumber, 0, 1)=='+'){
$network = substr($receiverNumber, 4, 2); //NETWORK code, used to decide the SIM Card to be used
$receiverNumber = substr($receiverNumber, 1, 12); //remove international indicator
}
else if(substr($receiverNumber, 0, 2)== '00'){
$network = substr($receiverNumber, 5, 2); //NETWORK code, used to decide the SIM Card to be used
$receiverNumber = substr($receiverNumber, 2, 12); //remove international indicator
}
}
/* Flag the network to be used */
switch ($network){
case "92":
$network="TMN";
break;
case "96":
$network="TMN";
break;
case "91":
$network="VODAFONE";
break;
case "93":
$network="OPTIMUS";
break;
}
// Receiver number must be 10 characters long ('national nr' + filler digit) or less than 13 ('351'+'national nr'). (Portugal)
if( strlen($receiverNumber) < 10 || strlen($receiverNumber) > 12) {
// Error, not 10 or over 12 numbers long (Code 1)
$this->setErrorCode(1);
return false;
}
// Message must be 2 characters long at least
if( strlen($message) < 2 ) {
// Error, message too short (Code 2)
$this->setErrorCode(2);
return false;
}
// Message can't be longer than 153 characters. 3SMS.
if( strlen($message) > 153 ) {
// Error, message too long (Code 3)
$this->setErrorCode(3);
return false;
}
// Length of servicecenter number (00 = automatically fixed by phone)
$serviceCenterNumberLength = '00';
// SMS-? : 04=sms-deliver(recieve), 11=sms-submit, 01 = dont know but it works, 41 = SMS-SUBMIT + UDH bit (for extended/concatenated SMS)
// You can try to change this if your phone does not work with 01 command try to use 11 command
$smsType = '41';
// TP Message Reference: (placeholder), let the phone set the message reference number itself
$messageRef = '00';
// Number length. If national -> 9, if international -> 12
if($nacional==1){
$numberLength = '09';
}else{
$numberLength = '0C';
}
// Type of phone adress: (81=Unknown=10dec, 91=InternationalFormat, 92=National?)
if($nacional==1){
$numberType = '81';
}else{
$numberType = '91';
}
// Get the PDU version of the number
$number = $this->getNumberAsPDU( $receiverNumber );
// TP-PID (Protocol Identifier)
$protocolId = '00';
// TP-DCS (Data coding scheme)
$dataCodingScheme = '00';
// TP-Validity-Period (timestamp), AA=4days expiry, disabled for SonyEricsson support.
// $validityPeriod = 'A0';
// $validityPeriod = 'AA'; // Add this if the PDU command fails
/*user data header information (05 - User Data header info length
* 00 - Information element identifier for a concatenated short message
* 03 - Information element data length
* 00 - Reference number, auto
* 0.$sms_count - total SMS nr
* 0.$msg_nr - current SMS order */
$udhi = '050003000'.$sms_count.'0'.$msg_nr;
// echo 'UDHinfo: '.$udhi."\n";
// Data length of message (in hex format)
$dataLength = $this->strToHexLen($message);
// echo 'DATA LENGHT: '.$dataLength."\n\n";
// Convert message, string > 7bits > 8bits > hex
$hexMessage = $this->bit7tohex( $this->strto7bit( $message ) );
// Create the complete PDU string
$pdu = $serviceCenterNumberLength . $smsType . $messageRef . $numberLength .
$numberType . $number . $protocolId . $dataCodingScheme . $dataLength .
$udhi . '20' .$hexMessage;
/*
* Generate the length of var $pdu (pdu/2 minus 1) as pdu format requests
* The -1 is because we don't count the first two characters '00', needed for this command: 'cmgs=24'
*/
$cmgslen = strlen($pdu)/2-1;
// Build data array to return with required information
$data = array();
$data['pdu'] = $pdu;
$data['cmgslen'] = $cmgslen;
$data['rede'] = $network;
// Return the data array with PDU information
return $data;
}
// Generate PDU formatted cellphone number
private function getNumberAsPDU($number) {
// Length of number divided by 2 handle two characters each time
$length = strlen( $number )/2;
// Set counter to 1 for strlen
$i = 1;
$pduNumber = '';
// Loop to handle every 2 characters of the phone number. 06 12 34 56 78
while ($i <= $length) {
// Get 2 characters of the complete string depending on the number of the current loop.
// Then reverse these 2 characters and put them in var $pduNumber (06 = 60)
$pduNumber .= strrev( substr( $number,$i*2-2,2) );
// Counter + 1
$i++;
}
// Return the generated number
return $pduNumber;
}
/* Function to convert ascii character to 8 bits
* Much more efficient than holding a complete ASCII table
* Thanks to Mattijs F.
*/
private function asc2bin($input, $length=8) {
$bin_out = '';
// Loop through every character in the string
for($charCount=0; $charCount < strlen($input); $charCount++) {
$charAscii = ord($input{$charCount}); // ascii value of character
$charBinary = decbin($charAscii); // decimal to binary
$charBinary = str_pad($charBinary, $length, '0', STR_PAD_LEFT);
$bin_out .= $charBinary;
}
// Return complete generated string
return $bin_out;
}
// String to 7 bits array
private function strto7bit($message) {
$message = trim($message);
$length = strlen( $message );
$i = 1;
$bitArray = array();
// Loop through every character in the string
while ($i <= $length) {
// Convert this character to a 7 bits value and insert it into the array
$bitArray[] = $this->asc2bin( substr( $message ,$i-1,1) ,7);
$i++;
}
// Return array containing 7 bits values
return $bitArray;
}
// Convert 8 bits binary string to hex values (like F2)
private function bit8tohex($bin, $padding=false, $uppercase=true) {
$hex = '';
// Last item for counter (for-loop)
$last = strlen($bin)-1;
// Loop for every item
for($i=0; $i<=$last; $i++) {
$hex += $bin[$last-$i] * pow(2,$i);
}
// Convert from decimal to hexadecimal
$hex = dechex($hex);
// Add a 0 (zero) if there is only 1 value returned, like 'F'
if($padding && strlen($hex) < 2 ) {
$hex = '0'.$hex;
}
// If we want the output returned as UPPERCASE do this
if($uppercase) {
$hex = strtoupper($hex);
}
// Return the hexadecimal value
return $hex;
}
// Convert 7 bits binary to hex, 7 bits > 8 bits > hex
private function bit7tohex($bits) {
$i = 0;
$hexOutput = '';
$running = true;
// For every 7 bits character array item
while($running) {
if(count($bits)==$i+1) {
$running = false;
}
$value = $bits[$i];
if($value=='') {
$i++;
continue;
}
// Convert the 7 bits value to the 8 bits value
// Merge a part of the next array element and a part of the current one
// Default: new value is current value
$new = $value;
if(key_exists(($i+1), $bits)) {
// There is a next array item so make it 8 bits
$neededChar = 8 - strlen($value);
// Get the char;s from the next array item
$charFromNext = substr($bits[$i+1], -$neededChar);
// Remove used bit's from next array item
$bits[$i+1] = substr($bits[$i+1], 0, strlen($bits[$i+1])-$neededChar );
// New value is characters from next value and current value
$new = $charFromNext.$value;
}
if($new!='') {
// Always make 8 bits
$new = str_pad($new, 8, '0', STR_PAD_LEFT);
// The 8 bits to hex conversion
$hexOutput .= $this->bit8tohex($new, true);
}
$i++;
}
// Return the 7bits->8bits->hexadecimal generated value
return $hexOutput;
}
// String to length in Hex, String > StringLength > Hex
private function strToHexLen($message) {
// Length of the string (message)
$length = strlen( $message )+7; //+7 for UDH. the UDH is a total of (number of octets x bit size of octets) 6 x 8 = 48 bits long. Therefore a single bit of padding has to be prepended to the message. The UDH is therefore (bits for UDH / bits per septet) = (48 + 1)/7 = 7 septets in length.
// Hex value of this string length
$hex = dechex($length);
// Length of the hex value
$hexLength = strlen($hex);
// If the hex strng length is lower dan 2
if($hexLength < 2) {
// Add a 0 (zero) before it
$hex = '0'.$hex;
}
// Return the hex value in UPPERCASE characters
return strtoupper($hex);
}
}
?>
As you are already aware, creating concatenated SMS messages requires you to add a UDH before your text message. The UDH becomes part of your payload, thus reducing the number of characters you can send per segment.
As it has become part of your payload, it needs to confirm with your payloads data requirement - which is 7 bit. The UDH however, is 8 bit, which clearly complicates things.
Consider the UDH of the following:
050003000302
05 is the length of the UDH
00 is the IEI
03 is the IEDL (3 more octets)
00 is a reference (this number must be the same in each of your concatenated message UDH's)
03 is the maximum number of messages
02 is the current message number.
This is 6 octets in total - equating to 48 bits. This is all and well, but since the UDH is actually part of your SMS message, what you have to do is add more bits so that the actual message starts on a septet boundary. A septet boundary is every 7 bits, so in this case, we will have to add 1 more bit of data to make the UDH 49 bits, and then we can add our standard GSM-7 encoded characters.
You can read up more about this from Here
These questions are all over the place and no one seems to be able to answer them in way that makes any sense. Zero padding usually just makes things worse. I think easiest way to go around this design flaw in GMS standard is to use 8 bit encoding or 16-bit UCS2 even that it means less characters. In that way you don't need to care about difference in byte boundaries which is the reason creating concatenated SMS is so hard.