HOW TO BIND USSD TRANSCEIVER USING PHP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;
I've been trying to implement USSD using SMPP protocol before I shared my problem, I will talk about what've been able to achieve so far:
- I have been able to successfully connect with the Telco via VPN
- I have been able to initiate SMPP Connection and returned an appropriate response of Response_Code '0' which is 'OK'
- Binding of Receiver, Transceiver & Transmitter with command_id '0x00000001', '0x00000009', '0x00000002' respectively, and they all return expected Response_Code which is 'OK'.
But the following are my problems:
- I want to be able to bind, and when the user dials the USSD ShortCode, they should be able to see at least 'Hello World'
- Unable to implement 'submit_sm' after successful 'transceiver' bind
- My current code implementation is using SMPP to send SMS, of which is running fine, but will like to have another method e.g. $tx->runUSSD() instead of $tx->sendSMS()
Below are my codes, and I believe with this code I should be able to do it, but have been so freaking stress with research and research, I've read and have read a lot, but can't get it working:
transciever.php
<?php
print "<pre>";
require_once "./smpp.php";
$tx=new SMPP('Ip Address',Port);
$tx->debug=true;
$tx->addr_npi=1;
print "open status: ".$tx->state."n";
$tx->bindTransceiver("system_id","password");
$tx->sms_source_addr_npi=0;
$tx->sms_source_addr_ton=5;
$tx->sms_dest_addr_ton=1;
$tx->sms_dest_addr_npi=1;
$status = $tx->sendSMS("Hello","08023828392","Hello World");
$tx->close();
unset($tx);
echo $status;
print "</pre>";
?>
smpp.php
<?php
class SMPP
var $debug = true;
//SMPP bind parameters
//var $system_type="WWW";
var $interface_version=0x34;
var $addr_ton=0;
var $addr_npi=0;
var $address_range="";
//ESME transmitter parameters
var $sms_service_type="";
var $sms_source_addr_ton=0;
var $sms_source_addr_npi=0;
var $sms_dest_addr_ton=0;
var $sms_dest_addr_npi=0;
var $sms_esm_class=0;
var $sms_protocol_id=0;
var $sms_priority_flag=0;
var $sms_schedule_delivery_time="";
var $sms_validity_period="";
var $sms_registered_delivery_flag=0;
var $sms_replace_if_present_flag=0;
var $sms_data_coding=0;
var $sms_sm_default_msg_id=0;
/**
* Constructs the smpp class
* @param $host - SMSC host name or host IP
* @param $port - SMSC port
*/
function SMPP($host, $port=5016)
//internal parameters
$this->sequence_number=1;
$this->debug=false;
$this->pdu_queue=array();
$this->host=$host;
$this->port=$port;
$this->state="closed";
//open the socket
$this->socket=fsockopen($this->host, $this->port, $errno, $errstr, 30);
if($this->socket)$this->state="open";
/**
* Binds the receiver. One object can be bound only as receiver or only as trancmitter.
* @param $login - ESME system_id
* @param $port - ESME password
* @return true when bind was successful
*/
function bindTransceiver($login, $pass)
if($this->state!="open")return false;
if($this->debug)
echo "Binding transciever...nn";
$status=$this->_bind($login, $pass, 0x00000009);
if($this->debug)
echo "Binding status : $statusnn";
if($status===0)$this->state="bind_trx";
return ($status===0);
/**
* Binds the receiver. One object can be bound only as receiver or only as trancmitter.
* @param $login - ESME system_id
* @param $port - ESME password
* @return true when bind was successful
*/
function bindReceiver($login, $pass)
if($this->state!="open")return false;
if($this->debug)
echo "Binding receiver...nn";
$status=$this->_bind($login, $pass, 0x00000001);
if($this->debug)
echo "Binding status : $statusnn";
if($status===0)$this->state="bind_rx";
return ($status===0);
/**
* Binds the transmitter. One object can be bound only as receiver or only as trancmitter.
* @param $login - ESME system_id
* @param $port - ESME password
* @return true when bind was successful
*/
function bindTransmitter($login, $pass)
if($this->state!="open")return false;
if($this->debug)
echo "Binding transmitter...nn";
$status=$this->_bind($login, $pass, 0x00000002);
if($this->debug)
echo "Binding status : $statusnn";
if($status===0)$this->state="bind_tx";
return ($status===0);
/**
* Closes the session on the SMSC server.
*/
function close()
if($this->state=="closed")return;
if($this->debug)
echo "Unbinding...nn";
$status=$this->sendCommand(0x00000006,"");
if($this->debug)
echo "Unbind status : $statusnn";
fclose($this->socket);
$this->state="closed";
/**
* Read USSD response from SMSC.
* @return
*/
function readResponse ()
if($this->state!="bind_trx")return false;
//set command id
$command_id = 0x00000005;
/**
* Read one SMS from SMSC. Can be executed only after bindReceiver() call.
* This method bloks. Method returns on socket timeout or enquire_link signal from SMSC.
* @return sms associative array or false when reading failed or no more sms.
*/
function readSMS()
if($this->state!="bind_rx")return false;
//stream_set_timeout($this->socket, 10);
$command_id=0x00000005;
//check the queue
for($i=0;$i<count($this->pdu_queue);$i++)
$pdu=$this->pdu_queue[$i];
if($pdu['id']==$command_id)
//remove responce
array_splice($this->pdu_queue, $i, 1);
return parseSMS($pdu);
//read pdu
do
if($this->debug)
echo "read sms...nn";
$pdu=$this->readPDU();
//check for enquire link command
if($pdu['id']==0x00000015)
$this->sendPDU(0x80000015, "", $pdu['sn']);
return false;
array_push($this->pdu_queue, $pdu);
while($pdu && $pdu['id']!=$command_id);
if($pdu)
array_pop($this->pdu_queue);
return $this->parseSMS($pdu);
return false;
/**
* Read one SMS from SMSC. Can be executed only after bindTransmitter() call.
* @return true on succesfull send, false if error encountered
*/
function sendSMS($from, $to, $message)
////////////////private functions///////////////
/**
* @private function
* Binds the socket and opens the session on SMSC
* @param $login - ESME system_id
* @param $port - ESME password
* @return bind status or false on error
*/
function _bind($login, $pass, $command_id)
//make PDU
$pdu = pack(
'a'.(strlen($login)+1).
'a'.(strlen($pass)+1).
'a'.(strlen($this->system_type)+1).
'CCCa'.(strlen($this->address_range)+1),
$login, $pass, $this->system_type,
$this->interface_version, $this->addr_ton,
$this->addr_npi, $this->address_range);
$status=$this->sendCommand($command_id,$pdu);
return $status;
/**
* @private function
* Parse deliver PDU from SMSC.
* @param $pdu - deliver PDU from SMSC.
* @return parsed PDU as array.
*/
function parseSMS($pdu)
//check command id
if($pdu['id']!=0x00000005)return false;
//unpack PDU
$ar=unpack("C*",$pdu['body']);
$sms=array('service_type'=>$this->getString($ar,6),
'source_addr_ton'=>array_shift($ar),
'source_addr_npi'=>array_shift($ar),
'source_addr'=>$this->getString($ar,21),
'dest_addr_ton'=>array_shift($ar),
'dest_addr_npi'=>array_shift($ar),
'destination_addr'=>$this->getString($ar,21),
'esm_class'=>array_shift($ar),
'protocol_id'=>array_shift($ar),
'priority_flag'=>array_shift($ar),
'schedule_delivery_time'=>array_shift($ar),
'validity_period'=>array_shift($ar),
'registered_delivery'=>array_shift($ar),
'replace_if_present_flag'=>array_shift($ar),
'data_coding'=>array_shift($ar),
'sm_default_msg_id'=>array_shift($ar),
'sm_length'=>array_shift($ar),
'short_message'=>$this->getString($ar,255)
);
if($this->debug)
echo "Delivered sms:n";
print_r($sms);
echo "n";
//send responce of recieving sms
$this->sendPDU(0x80000005, "", $pdu['sn']);
return $sms;
/**
* @private function
* Sends the PDU command to the SMSC and waits for responce.
* @param $command_id - command ID
* @param $pdu - PDU body
* @return PDU status or false on error
*/
function sendCommand($command_id, $pdu)
if($this->state=="closed")return false;
$this->sendPDU($command_id, $pdu, $this->sequence_number);
$status=$this->readPDU_resp($this->sequence_number, $command_id);
$this->sequence_number=$this->sequence_number+1;
return $status;
/**
* @private function
* Prepares and sends PDU to SMSC.
* @param $command_id - command ID
* @param $pdu - PDU body
* @param $seq_number - PDU sequence number
*/
function sendPDU($command_id, $pdu, $seq_number)
$length=strlen($pdu) + 16;
$header=pack("NNNN", $length, $command_id, 0, $seq_number);
if($this->debug)
echo "Send PDU : $length bytesn";
$this->printHex($header.$pdu);
echo "command_id : ".$command_id."n";
echo "sequence number : $seq_numbernn";
fwrite($this->socket, $header.$pdu, $length);
/**
* @private function
* Waits for SMSC responce on specific PDU.
* @param $seq_number - PDU sequence number
* @param $command_id - PDU command ID
* @return PDU status or false on error
*/
function readPDU_resp($seq_number, $command_id)0x80000000;
//check queue
for($i=0;$i<count($this->pdu_queue);$i++)
$pdu=$this->pdu_queue[$i];
if($pdu['sn']==$seq_number && $pdu['id']==$command_id)
//remove responce
array_splice($this->pdu_queue, $i, 1);
return $pdu['status'];
//read pdu
do
$pdu=$this->readPDU();
if($pdu)array_push($this->pdu_queue, $pdu);
while($pdu && ($pdu['sn']!=$seq_number
/**
* @private function
* Reads incoming PDU from SMSC.
* @return readed PDU or false on error.
*/
function readPDU()
//read PDU length
$tmp=fread($this->socket, 4);
if(!$tmp)return false;
extract(unpack("Nlength", $tmp));
//read PDU headers
$tmp2=fread($this->socket, 12);
if(!$tmp2)return false;
extract(unpack("Ncommand_id/Ncommand_status/Nsequence_number", $tmp2));
//read PDU body
if($length-16>0)
$body=fread($this->socket, $length-16);
if(!$body)return false;
else
$body="";
if($this->debug)
echo "Read PDU : $length bytesn";
$this->printHex($tmp.$tmp2.$body);
echo "body len : " . strlen($body) . "n";
echo "Command id : $command_idn";
echo "Command status : $command_statusn";
echo "sequence number : $sequence_numbernn";
$pdu=array(
'id'=>$command_id,
'status'=>$command_status,
'sn'=>$sequence_number,
'body'=>$body);
return $pdu;
/**
* @private function
* Reads C style zero padded string from the char array.
* @param $ar - input array
* @param $maxlen - maximum length to read.
* @return readed string.
*/
function getString(&$ar, $maxlen=255)
$s="";
$i=0;
do
$c=array_shift($ar);
if($c!=0)$s.=chr($c);
$i++;
while($i<$maxlen && $c!=0);
return $s;
/**
* @private function
* Prints the binary string as hex bytes.
* @param $maxlen - maximum length to read.
*/
function printHex($pdu)
$ar=unpack("C*",$pdu);
foreach($ar as $v)
$s=dechex($v);
if(strlen($s)<2)$s="0$s";
print "$s ";
print "n";
?>
I hope to hear from you guys.
Thank you in advance.
php smpp ussd pdu
add a comment |
I've been trying to implement USSD using SMPP protocol before I shared my problem, I will talk about what've been able to achieve so far:
- I have been able to successfully connect with the Telco via VPN
- I have been able to initiate SMPP Connection and returned an appropriate response of Response_Code '0' which is 'OK'
- Binding of Receiver, Transceiver & Transmitter with command_id '0x00000001', '0x00000009', '0x00000002' respectively, and they all return expected Response_Code which is 'OK'.
But the following are my problems:
- I want to be able to bind, and when the user dials the USSD ShortCode, they should be able to see at least 'Hello World'
- Unable to implement 'submit_sm' after successful 'transceiver' bind
- My current code implementation is using SMPP to send SMS, of which is running fine, but will like to have another method e.g. $tx->runUSSD() instead of $tx->sendSMS()
Below are my codes, and I believe with this code I should be able to do it, but have been so freaking stress with research and research, I've read and have read a lot, but can't get it working:
transciever.php
<?php
print "<pre>";
require_once "./smpp.php";
$tx=new SMPP('Ip Address',Port);
$tx->debug=true;
$tx->addr_npi=1;
print "open status: ".$tx->state."n";
$tx->bindTransceiver("system_id","password");
$tx->sms_source_addr_npi=0;
$tx->sms_source_addr_ton=5;
$tx->sms_dest_addr_ton=1;
$tx->sms_dest_addr_npi=1;
$status = $tx->sendSMS("Hello","08023828392","Hello World");
$tx->close();
unset($tx);
echo $status;
print "</pre>";
?>
smpp.php
<?php
class SMPP
var $debug = true;
//SMPP bind parameters
//var $system_type="WWW";
var $interface_version=0x34;
var $addr_ton=0;
var $addr_npi=0;
var $address_range="";
//ESME transmitter parameters
var $sms_service_type="";
var $sms_source_addr_ton=0;
var $sms_source_addr_npi=0;
var $sms_dest_addr_ton=0;
var $sms_dest_addr_npi=0;
var $sms_esm_class=0;
var $sms_protocol_id=0;
var $sms_priority_flag=0;
var $sms_schedule_delivery_time="";
var $sms_validity_period="";
var $sms_registered_delivery_flag=0;
var $sms_replace_if_present_flag=0;
var $sms_data_coding=0;
var $sms_sm_default_msg_id=0;
/**
* Constructs the smpp class
* @param $host - SMSC host name or host IP
* @param $port - SMSC port
*/
function SMPP($host, $port=5016)
//internal parameters
$this->sequence_number=1;
$this->debug=false;
$this->pdu_queue=array();
$this->host=$host;
$this->port=$port;
$this->state="closed";
//open the socket
$this->socket=fsockopen($this->host, $this->port, $errno, $errstr, 30);
if($this->socket)$this->state="open";
/**
* Binds the receiver. One object can be bound only as receiver or only as trancmitter.
* @param $login - ESME system_id
* @param $port - ESME password
* @return true when bind was successful
*/
function bindTransceiver($login, $pass)
if($this->state!="open")return false;
if($this->debug)
echo "Binding transciever...nn";
$status=$this->_bind($login, $pass, 0x00000009);
if($this->debug)
echo "Binding status : $statusnn";
if($status===0)$this->state="bind_trx";
return ($status===0);
/**
* Binds the receiver. One object can be bound only as receiver or only as trancmitter.
* @param $login - ESME system_id
* @param $port - ESME password
* @return true when bind was successful
*/
function bindReceiver($login, $pass)
if($this->state!="open")return false;
if($this->debug)
echo "Binding receiver...nn";
$status=$this->_bind($login, $pass, 0x00000001);
if($this->debug)
echo "Binding status : $statusnn";
if($status===0)$this->state="bind_rx";
return ($status===0);
/**
* Binds the transmitter. One object can be bound only as receiver or only as trancmitter.
* @param $login - ESME system_id
* @param $port - ESME password
* @return true when bind was successful
*/
function bindTransmitter($login, $pass)
if($this->state!="open")return false;
if($this->debug)
echo "Binding transmitter...nn";
$status=$this->_bind($login, $pass, 0x00000002);
if($this->debug)
echo "Binding status : $statusnn";
if($status===0)$this->state="bind_tx";
return ($status===0);
/**
* Closes the session on the SMSC server.
*/
function close()
if($this->state=="closed")return;
if($this->debug)
echo "Unbinding...nn";
$status=$this->sendCommand(0x00000006,"");
if($this->debug)
echo "Unbind status : $statusnn";
fclose($this->socket);
$this->state="closed";
/**
* Read USSD response from SMSC.
* @return
*/
function readResponse ()
if($this->state!="bind_trx")return false;
//set command id
$command_id = 0x00000005;
/**
* Read one SMS from SMSC. Can be executed only after bindReceiver() call.
* This method bloks. Method returns on socket timeout or enquire_link signal from SMSC.
* @return sms associative array or false when reading failed or no more sms.
*/
function readSMS()
if($this->state!="bind_rx")return false;
//stream_set_timeout($this->socket, 10);
$command_id=0x00000005;
//check the queue
for($i=0;$i<count($this->pdu_queue);$i++)
$pdu=$this->pdu_queue[$i];
if($pdu['id']==$command_id)
//remove responce
array_splice($this->pdu_queue, $i, 1);
return parseSMS($pdu);
//read pdu
do
if($this->debug)
echo "read sms...nn";
$pdu=$this->readPDU();
//check for enquire link command
if($pdu['id']==0x00000015)
$this->sendPDU(0x80000015, "", $pdu['sn']);
return false;
array_push($this->pdu_queue, $pdu);
while($pdu && $pdu['id']!=$command_id);
if($pdu)
array_pop($this->pdu_queue);
return $this->parseSMS($pdu);
return false;
/**
* Read one SMS from SMSC. Can be executed only after bindTransmitter() call.
* @return true on succesfull send, false if error encountered
*/
function sendSMS($from, $to, $message)
////////////////private functions///////////////
/**
* @private function
* Binds the socket and opens the session on SMSC
* @param $login - ESME system_id
* @param $port - ESME password
* @return bind status or false on error
*/
function _bind($login, $pass, $command_id)
//make PDU
$pdu = pack(
'a'.(strlen($login)+1).
'a'.(strlen($pass)+1).
'a'.(strlen($this->system_type)+1).
'CCCa'.(strlen($this->address_range)+1),
$login, $pass, $this->system_type,
$this->interface_version, $this->addr_ton,
$this->addr_npi, $this->address_range);
$status=$this->sendCommand($command_id,$pdu);
return $status;
/**
* @private function
* Parse deliver PDU from SMSC.
* @param $pdu - deliver PDU from SMSC.
* @return parsed PDU as array.
*/
function parseSMS($pdu)
//check command id
if($pdu['id']!=0x00000005)return false;
//unpack PDU
$ar=unpack("C*",$pdu['body']);
$sms=array('service_type'=>$this->getString($ar,6),
'source_addr_ton'=>array_shift($ar),
'source_addr_npi'=>array_shift($ar),
'source_addr'=>$this->getString($ar,21),
'dest_addr_ton'=>array_shift($ar),
'dest_addr_npi'=>array_shift($ar),
'destination_addr'=>$this->getString($ar,21),
'esm_class'=>array_shift($ar),
'protocol_id'=>array_shift($ar),
'priority_flag'=>array_shift($ar),
'schedule_delivery_time'=>array_shift($ar),
'validity_period'=>array_shift($ar),
'registered_delivery'=>array_shift($ar),
'replace_if_present_flag'=>array_shift($ar),
'data_coding'=>array_shift($ar),
'sm_default_msg_id'=>array_shift($ar),
'sm_length'=>array_shift($ar),
'short_message'=>$this->getString($ar,255)
);
if($this->debug)
echo "Delivered sms:n";
print_r($sms);
echo "n";
//send responce of recieving sms
$this->sendPDU(0x80000005, "", $pdu['sn']);
return $sms;
/**
* @private function
* Sends the PDU command to the SMSC and waits for responce.
* @param $command_id - command ID
* @param $pdu - PDU body
* @return PDU status or false on error
*/
function sendCommand($command_id, $pdu)
if($this->state=="closed")return false;
$this->sendPDU($command_id, $pdu, $this->sequence_number);
$status=$this->readPDU_resp($this->sequence_number, $command_id);
$this->sequence_number=$this->sequence_number+1;
return $status;
/**
* @private function
* Prepares and sends PDU to SMSC.
* @param $command_id - command ID
* @param $pdu - PDU body
* @param $seq_number - PDU sequence number
*/
function sendPDU($command_id, $pdu, $seq_number)
$length=strlen($pdu) + 16;
$header=pack("NNNN", $length, $command_id, 0, $seq_number);
if($this->debug)
echo "Send PDU : $length bytesn";
$this->printHex($header.$pdu);
echo "command_id : ".$command_id."n";
echo "sequence number : $seq_numbernn";
fwrite($this->socket, $header.$pdu, $length);
/**
* @private function
* Waits for SMSC responce on specific PDU.
* @param $seq_number - PDU sequence number
* @param $command_id - PDU command ID
* @return PDU status or false on error
*/
function readPDU_resp($seq_number, $command_id)0x80000000;
//check queue
for($i=0;$i<count($this->pdu_queue);$i++)
$pdu=$this->pdu_queue[$i];
if($pdu['sn']==$seq_number && $pdu['id']==$command_id)
//remove responce
array_splice($this->pdu_queue, $i, 1);
return $pdu['status'];
//read pdu
do
$pdu=$this->readPDU();
if($pdu)array_push($this->pdu_queue, $pdu);
while($pdu && ($pdu['sn']!=$seq_number
/**
* @private function
* Reads incoming PDU from SMSC.
* @return readed PDU or false on error.
*/
function readPDU()
//read PDU length
$tmp=fread($this->socket, 4);
if(!$tmp)return false;
extract(unpack("Nlength", $tmp));
//read PDU headers
$tmp2=fread($this->socket, 12);
if(!$tmp2)return false;
extract(unpack("Ncommand_id/Ncommand_status/Nsequence_number", $tmp2));
//read PDU body
if($length-16>0)
$body=fread($this->socket, $length-16);
if(!$body)return false;
else
$body="";
if($this->debug)
echo "Read PDU : $length bytesn";
$this->printHex($tmp.$tmp2.$body);
echo "body len : " . strlen($body) . "n";
echo "Command id : $command_idn";
echo "Command status : $command_statusn";
echo "sequence number : $sequence_numbernn";
$pdu=array(
'id'=>$command_id,
'status'=>$command_status,
'sn'=>$sequence_number,
'body'=>$body);
return $pdu;
/**
* @private function
* Reads C style zero padded string from the char array.
* @param $ar - input array
* @param $maxlen - maximum length to read.
* @return readed string.
*/
function getString(&$ar, $maxlen=255)
$s="";
$i=0;
do
$c=array_shift($ar);
if($c!=0)$s.=chr($c);
$i++;
while($i<$maxlen && $c!=0);
return $s;
/**
* @private function
* Prints the binary string as hex bytes.
* @param $maxlen - maximum length to read.
*/
function printHex($pdu)
$ar=unpack("C*",$pdu);
foreach($ar as $v)
$s=dechex($v);
if(strlen($s)<2)$s="0$s";
print "$s ";
print "n";
?>
I hope to hear from you guys.
Thank you in advance.
php smpp ussd pdu
I cannot believe that no one has given any comment.
– Emmanuel Osinnowo
Dec 5 '18 at 18:32
add a comment |
I've been trying to implement USSD using SMPP protocol before I shared my problem, I will talk about what've been able to achieve so far:
- I have been able to successfully connect with the Telco via VPN
- I have been able to initiate SMPP Connection and returned an appropriate response of Response_Code '0' which is 'OK'
- Binding of Receiver, Transceiver & Transmitter with command_id '0x00000001', '0x00000009', '0x00000002' respectively, and they all return expected Response_Code which is 'OK'.
But the following are my problems:
- I want to be able to bind, and when the user dials the USSD ShortCode, they should be able to see at least 'Hello World'
- Unable to implement 'submit_sm' after successful 'transceiver' bind
- My current code implementation is using SMPP to send SMS, of which is running fine, but will like to have another method e.g. $tx->runUSSD() instead of $tx->sendSMS()
Below are my codes, and I believe with this code I should be able to do it, but have been so freaking stress with research and research, I've read and have read a lot, but can't get it working:
transciever.php
<?php
print "<pre>";
require_once "./smpp.php";
$tx=new SMPP('Ip Address',Port);
$tx->debug=true;
$tx->addr_npi=1;
print "open status: ".$tx->state."n";
$tx->bindTransceiver("system_id","password");
$tx->sms_source_addr_npi=0;
$tx->sms_source_addr_ton=5;
$tx->sms_dest_addr_ton=1;
$tx->sms_dest_addr_npi=1;
$status = $tx->sendSMS("Hello","08023828392","Hello World");
$tx->close();
unset($tx);
echo $status;
print "</pre>";
?>
smpp.php
<?php
class SMPP
var $debug = true;
//SMPP bind parameters
//var $system_type="WWW";
var $interface_version=0x34;
var $addr_ton=0;
var $addr_npi=0;
var $address_range="";
//ESME transmitter parameters
var $sms_service_type="";
var $sms_source_addr_ton=0;
var $sms_source_addr_npi=0;
var $sms_dest_addr_ton=0;
var $sms_dest_addr_npi=0;
var $sms_esm_class=0;
var $sms_protocol_id=0;
var $sms_priority_flag=0;
var $sms_schedule_delivery_time="";
var $sms_validity_period="";
var $sms_registered_delivery_flag=0;
var $sms_replace_if_present_flag=0;
var $sms_data_coding=0;
var $sms_sm_default_msg_id=0;
/**
* Constructs the smpp class
* @param $host - SMSC host name or host IP
* @param $port - SMSC port
*/
function SMPP($host, $port=5016)
//internal parameters
$this->sequence_number=1;
$this->debug=false;
$this->pdu_queue=array();
$this->host=$host;
$this->port=$port;
$this->state="closed";
//open the socket
$this->socket=fsockopen($this->host, $this->port, $errno, $errstr, 30);
if($this->socket)$this->state="open";
/**
* Binds the receiver. One object can be bound only as receiver or only as trancmitter.
* @param $login - ESME system_id
* @param $port - ESME password
* @return true when bind was successful
*/
function bindTransceiver($login, $pass)
if($this->state!="open")return false;
if($this->debug)
echo "Binding transciever...nn";
$status=$this->_bind($login, $pass, 0x00000009);
if($this->debug)
echo "Binding status : $statusnn";
if($status===0)$this->state="bind_trx";
return ($status===0);
/**
* Binds the receiver. One object can be bound only as receiver or only as trancmitter.
* @param $login - ESME system_id
* @param $port - ESME password
* @return true when bind was successful
*/
function bindReceiver($login, $pass)
if($this->state!="open")return false;
if($this->debug)
echo "Binding receiver...nn";
$status=$this->_bind($login, $pass, 0x00000001);
if($this->debug)
echo "Binding status : $statusnn";
if($status===0)$this->state="bind_rx";
return ($status===0);
/**
* Binds the transmitter. One object can be bound only as receiver or only as trancmitter.
* @param $login - ESME system_id
* @param $port - ESME password
* @return true when bind was successful
*/
function bindTransmitter($login, $pass)
if($this->state!="open")return false;
if($this->debug)
echo "Binding transmitter...nn";
$status=$this->_bind($login, $pass, 0x00000002);
if($this->debug)
echo "Binding status : $statusnn";
if($status===0)$this->state="bind_tx";
return ($status===0);
/**
* Closes the session on the SMSC server.
*/
function close()
if($this->state=="closed")return;
if($this->debug)
echo "Unbinding...nn";
$status=$this->sendCommand(0x00000006,"");
if($this->debug)
echo "Unbind status : $statusnn";
fclose($this->socket);
$this->state="closed";
/**
* Read USSD response from SMSC.
* @return
*/
function readResponse ()
if($this->state!="bind_trx")return false;
//set command id
$command_id = 0x00000005;
/**
* Read one SMS from SMSC. Can be executed only after bindReceiver() call.
* This method bloks. Method returns on socket timeout or enquire_link signal from SMSC.
* @return sms associative array or false when reading failed or no more sms.
*/
function readSMS()
if($this->state!="bind_rx")return false;
//stream_set_timeout($this->socket, 10);
$command_id=0x00000005;
//check the queue
for($i=0;$i<count($this->pdu_queue);$i++)
$pdu=$this->pdu_queue[$i];
if($pdu['id']==$command_id)
//remove responce
array_splice($this->pdu_queue, $i, 1);
return parseSMS($pdu);
//read pdu
do
if($this->debug)
echo "read sms...nn";
$pdu=$this->readPDU();
//check for enquire link command
if($pdu['id']==0x00000015)
$this->sendPDU(0x80000015, "", $pdu['sn']);
return false;
array_push($this->pdu_queue, $pdu);
while($pdu && $pdu['id']!=$command_id);
if($pdu)
array_pop($this->pdu_queue);
return $this->parseSMS($pdu);
return false;
/**
* Read one SMS from SMSC. Can be executed only after bindTransmitter() call.
* @return true on succesfull send, false if error encountered
*/
function sendSMS($from, $to, $message)
////////////////private functions///////////////
/**
* @private function
* Binds the socket and opens the session on SMSC
* @param $login - ESME system_id
* @param $port - ESME password
* @return bind status or false on error
*/
function _bind($login, $pass, $command_id)
//make PDU
$pdu = pack(
'a'.(strlen($login)+1).
'a'.(strlen($pass)+1).
'a'.(strlen($this->system_type)+1).
'CCCa'.(strlen($this->address_range)+1),
$login, $pass, $this->system_type,
$this->interface_version, $this->addr_ton,
$this->addr_npi, $this->address_range);
$status=$this->sendCommand($command_id,$pdu);
return $status;
/**
* @private function
* Parse deliver PDU from SMSC.
* @param $pdu - deliver PDU from SMSC.
* @return parsed PDU as array.
*/
function parseSMS($pdu)
//check command id
if($pdu['id']!=0x00000005)return false;
//unpack PDU
$ar=unpack("C*",$pdu['body']);
$sms=array('service_type'=>$this->getString($ar,6),
'source_addr_ton'=>array_shift($ar),
'source_addr_npi'=>array_shift($ar),
'source_addr'=>$this->getString($ar,21),
'dest_addr_ton'=>array_shift($ar),
'dest_addr_npi'=>array_shift($ar),
'destination_addr'=>$this->getString($ar,21),
'esm_class'=>array_shift($ar),
'protocol_id'=>array_shift($ar),
'priority_flag'=>array_shift($ar),
'schedule_delivery_time'=>array_shift($ar),
'validity_period'=>array_shift($ar),
'registered_delivery'=>array_shift($ar),
'replace_if_present_flag'=>array_shift($ar),
'data_coding'=>array_shift($ar),
'sm_default_msg_id'=>array_shift($ar),
'sm_length'=>array_shift($ar),
'short_message'=>$this->getString($ar,255)
);
if($this->debug)
echo "Delivered sms:n";
print_r($sms);
echo "n";
//send responce of recieving sms
$this->sendPDU(0x80000005, "", $pdu['sn']);
return $sms;
/**
* @private function
* Sends the PDU command to the SMSC and waits for responce.
* @param $command_id - command ID
* @param $pdu - PDU body
* @return PDU status or false on error
*/
function sendCommand($command_id, $pdu)
if($this->state=="closed")return false;
$this->sendPDU($command_id, $pdu, $this->sequence_number);
$status=$this->readPDU_resp($this->sequence_number, $command_id);
$this->sequence_number=$this->sequence_number+1;
return $status;
/**
* @private function
* Prepares and sends PDU to SMSC.
* @param $command_id - command ID
* @param $pdu - PDU body
* @param $seq_number - PDU sequence number
*/
function sendPDU($command_id, $pdu, $seq_number)
$length=strlen($pdu) + 16;
$header=pack("NNNN", $length, $command_id, 0, $seq_number);
if($this->debug)
echo "Send PDU : $length bytesn";
$this->printHex($header.$pdu);
echo "command_id : ".$command_id."n";
echo "sequence number : $seq_numbernn";
fwrite($this->socket, $header.$pdu, $length);
/**
* @private function
* Waits for SMSC responce on specific PDU.
* @param $seq_number - PDU sequence number
* @param $command_id - PDU command ID
* @return PDU status or false on error
*/
function readPDU_resp($seq_number, $command_id)0x80000000;
//check queue
for($i=0;$i<count($this->pdu_queue);$i++)
$pdu=$this->pdu_queue[$i];
if($pdu['sn']==$seq_number && $pdu['id']==$command_id)
//remove responce
array_splice($this->pdu_queue, $i, 1);
return $pdu['status'];
//read pdu
do
$pdu=$this->readPDU();
if($pdu)array_push($this->pdu_queue, $pdu);
while($pdu && ($pdu['sn']!=$seq_number
/**
* @private function
* Reads incoming PDU from SMSC.
* @return readed PDU or false on error.
*/
function readPDU()
//read PDU length
$tmp=fread($this->socket, 4);
if(!$tmp)return false;
extract(unpack("Nlength", $tmp));
//read PDU headers
$tmp2=fread($this->socket, 12);
if(!$tmp2)return false;
extract(unpack("Ncommand_id/Ncommand_status/Nsequence_number", $tmp2));
//read PDU body
if($length-16>0)
$body=fread($this->socket, $length-16);
if(!$body)return false;
else
$body="";
if($this->debug)
echo "Read PDU : $length bytesn";
$this->printHex($tmp.$tmp2.$body);
echo "body len : " . strlen($body) . "n";
echo "Command id : $command_idn";
echo "Command status : $command_statusn";
echo "sequence number : $sequence_numbernn";
$pdu=array(
'id'=>$command_id,
'status'=>$command_status,
'sn'=>$sequence_number,
'body'=>$body);
return $pdu;
/**
* @private function
* Reads C style zero padded string from the char array.
* @param $ar - input array
* @param $maxlen - maximum length to read.
* @return readed string.
*/
function getString(&$ar, $maxlen=255)
$s="";
$i=0;
do
$c=array_shift($ar);
if($c!=0)$s.=chr($c);
$i++;
while($i<$maxlen && $c!=0);
return $s;
/**
* @private function
* Prints the binary string as hex bytes.
* @param $maxlen - maximum length to read.
*/
function printHex($pdu)
$ar=unpack("C*",$pdu);
foreach($ar as $v)
$s=dechex($v);
if(strlen($s)<2)$s="0$s";
print "$s ";
print "n";
?>
I hope to hear from you guys.
Thank you in advance.
php smpp ussd pdu
I've been trying to implement USSD using SMPP protocol before I shared my problem, I will talk about what've been able to achieve so far:
- I have been able to successfully connect with the Telco via VPN
- I have been able to initiate SMPP Connection and returned an appropriate response of Response_Code '0' which is 'OK'
- Binding of Receiver, Transceiver & Transmitter with command_id '0x00000001', '0x00000009', '0x00000002' respectively, and they all return expected Response_Code which is 'OK'.
But the following are my problems:
- I want to be able to bind, and when the user dials the USSD ShortCode, they should be able to see at least 'Hello World'
- Unable to implement 'submit_sm' after successful 'transceiver' bind
- My current code implementation is using SMPP to send SMS, of which is running fine, but will like to have another method e.g. $tx->runUSSD() instead of $tx->sendSMS()
Below are my codes, and I believe with this code I should be able to do it, but have been so freaking stress with research and research, I've read and have read a lot, but can't get it working:
transciever.php
<?php
print "<pre>";
require_once "./smpp.php";
$tx=new SMPP('Ip Address',Port);
$tx->debug=true;
$tx->addr_npi=1;
print "open status: ".$tx->state."n";
$tx->bindTransceiver("system_id","password");
$tx->sms_source_addr_npi=0;
$tx->sms_source_addr_ton=5;
$tx->sms_dest_addr_ton=1;
$tx->sms_dest_addr_npi=1;
$status = $tx->sendSMS("Hello","08023828392","Hello World");
$tx->close();
unset($tx);
echo $status;
print "</pre>";
?>
smpp.php
<?php
class SMPP
var $debug = true;
//SMPP bind parameters
//var $system_type="WWW";
var $interface_version=0x34;
var $addr_ton=0;
var $addr_npi=0;
var $address_range="";
//ESME transmitter parameters
var $sms_service_type="";
var $sms_source_addr_ton=0;
var $sms_source_addr_npi=0;
var $sms_dest_addr_ton=0;
var $sms_dest_addr_npi=0;
var $sms_esm_class=0;
var $sms_protocol_id=0;
var $sms_priority_flag=0;
var $sms_schedule_delivery_time="";
var $sms_validity_period="";
var $sms_registered_delivery_flag=0;
var $sms_replace_if_present_flag=0;
var $sms_data_coding=0;
var $sms_sm_default_msg_id=0;
/**
* Constructs the smpp class
* @param $host - SMSC host name or host IP
* @param $port - SMSC port
*/
function SMPP($host, $port=5016)
//internal parameters
$this->sequence_number=1;
$this->debug=false;
$this->pdu_queue=array();
$this->host=$host;
$this->port=$port;
$this->state="closed";
//open the socket
$this->socket=fsockopen($this->host, $this->port, $errno, $errstr, 30);
if($this->socket)$this->state="open";
/**
* Binds the receiver. One object can be bound only as receiver or only as trancmitter.
* @param $login - ESME system_id
* @param $port - ESME password
* @return true when bind was successful
*/
function bindTransceiver($login, $pass)
if($this->state!="open")return false;
if($this->debug)
echo "Binding transciever...nn";
$status=$this->_bind($login, $pass, 0x00000009);
if($this->debug)
echo "Binding status : $statusnn";
if($status===0)$this->state="bind_trx";
return ($status===0);
/**
* Binds the receiver. One object can be bound only as receiver or only as trancmitter.
* @param $login - ESME system_id
* @param $port - ESME password
* @return true when bind was successful
*/
function bindReceiver($login, $pass)
if($this->state!="open")return false;
if($this->debug)
echo "Binding receiver...nn";
$status=$this->_bind($login, $pass, 0x00000001);
if($this->debug)
echo "Binding status : $statusnn";
if($status===0)$this->state="bind_rx";
return ($status===0);
/**
* Binds the transmitter. One object can be bound only as receiver or only as trancmitter.
* @param $login - ESME system_id
* @param $port - ESME password
* @return true when bind was successful
*/
function bindTransmitter($login, $pass)
if($this->state!="open")return false;
if($this->debug)
echo "Binding transmitter...nn";
$status=$this->_bind($login, $pass, 0x00000002);
if($this->debug)
echo "Binding status : $statusnn";
if($status===0)$this->state="bind_tx";
return ($status===0);
/**
* Closes the session on the SMSC server.
*/
function close()
if($this->state=="closed")return;
if($this->debug)
echo "Unbinding...nn";
$status=$this->sendCommand(0x00000006,"");
if($this->debug)
echo "Unbind status : $statusnn";
fclose($this->socket);
$this->state="closed";
/**
* Read USSD response from SMSC.
* @return
*/
function readResponse ()
if($this->state!="bind_trx")return false;
//set command id
$command_id = 0x00000005;
/**
* Read one SMS from SMSC. Can be executed only after bindReceiver() call.
* This method bloks. Method returns on socket timeout or enquire_link signal from SMSC.
* @return sms associative array or false when reading failed or no more sms.
*/
function readSMS()
if($this->state!="bind_rx")return false;
//stream_set_timeout($this->socket, 10);
$command_id=0x00000005;
//check the queue
for($i=0;$i<count($this->pdu_queue);$i++)
$pdu=$this->pdu_queue[$i];
if($pdu['id']==$command_id)
//remove responce
array_splice($this->pdu_queue, $i, 1);
return parseSMS($pdu);
//read pdu
do
if($this->debug)
echo "read sms...nn";
$pdu=$this->readPDU();
//check for enquire link command
if($pdu['id']==0x00000015)
$this->sendPDU(0x80000015, "", $pdu['sn']);
return false;
array_push($this->pdu_queue, $pdu);
while($pdu && $pdu['id']!=$command_id);
if($pdu)
array_pop($this->pdu_queue);
return $this->parseSMS($pdu);
return false;
/**
* Read one SMS from SMSC. Can be executed only after bindTransmitter() call.
* @return true on succesfull send, false if error encountered
*/
function sendSMS($from, $to, $message)
////////////////private functions///////////////
/**
* @private function
* Binds the socket and opens the session on SMSC
* @param $login - ESME system_id
* @param $port - ESME password
* @return bind status or false on error
*/
function _bind($login, $pass, $command_id)
//make PDU
$pdu = pack(
'a'.(strlen($login)+1).
'a'.(strlen($pass)+1).
'a'.(strlen($this->system_type)+1).
'CCCa'.(strlen($this->address_range)+1),
$login, $pass, $this->system_type,
$this->interface_version, $this->addr_ton,
$this->addr_npi, $this->address_range);
$status=$this->sendCommand($command_id,$pdu);
return $status;
/**
* @private function
* Parse deliver PDU from SMSC.
* @param $pdu - deliver PDU from SMSC.
* @return parsed PDU as array.
*/
function parseSMS($pdu)
//check command id
if($pdu['id']!=0x00000005)return false;
//unpack PDU
$ar=unpack("C*",$pdu['body']);
$sms=array('service_type'=>$this->getString($ar,6),
'source_addr_ton'=>array_shift($ar),
'source_addr_npi'=>array_shift($ar),
'source_addr'=>$this->getString($ar,21),
'dest_addr_ton'=>array_shift($ar),
'dest_addr_npi'=>array_shift($ar),
'destination_addr'=>$this->getString($ar,21),
'esm_class'=>array_shift($ar),
'protocol_id'=>array_shift($ar),
'priority_flag'=>array_shift($ar),
'schedule_delivery_time'=>array_shift($ar),
'validity_period'=>array_shift($ar),
'registered_delivery'=>array_shift($ar),
'replace_if_present_flag'=>array_shift($ar),
'data_coding'=>array_shift($ar),
'sm_default_msg_id'=>array_shift($ar),
'sm_length'=>array_shift($ar),
'short_message'=>$this->getString($ar,255)
);
if($this->debug)
echo "Delivered sms:n";
print_r($sms);
echo "n";
//send responce of recieving sms
$this->sendPDU(0x80000005, "", $pdu['sn']);
return $sms;
/**
* @private function
* Sends the PDU command to the SMSC and waits for responce.
* @param $command_id - command ID
* @param $pdu - PDU body
* @return PDU status or false on error
*/
function sendCommand($command_id, $pdu)
if($this->state=="closed")return false;
$this->sendPDU($command_id, $pdu, $this->sequence_number);
$status=$this->readPDU_resp($this->sequence_number, $command_id);
$this->sequence_number=$this->sequence_number+1;
return $status;
/**
* @private function
* Prepares and sends PDU to SMSC.
* @param $command_id - command ID
* @param $pdu - PDU body
* @param $seq_number - PDU sequence number
*/
function sendPDU($command_id, $pdu, $seq_number)
$length=strlen($pdu) + 16;
$header=pack("NNNN", $length, $command_id, 0, $seq_number);
if($this->debug)
echo "Send PDU : $length bytesn";
$this->printHex($header.$pdu);
echo "command_id : ".$command_id."n";
echo "sequence number : $seq_numbernn";
fwrite($this->socket, $header.$pdu, $length);
/**
* @private function
* Waits for SMSC responce on specific PDU.
* @param $seq_number - PDU sequence number
* @param $command_id - PDU command ID
* @return PDU status or false on error
*/
function readPDU_resp($seq_number, $command_id)0x80000000;
//check queue
for($i=0;$i<count($this->pdu_queue);$i++)
$pdu=$this->pdu_queue[$i];
if($pdu['sn']==$seq_number && $pdu['id']==$command_id)
//remove responce
array_splice($this->pdu_queue, $i, 1);
return $pdu['status'];
//read pdu
do
$pdu=$this->readPDU();
if($pdu)array_push($this->pdu_queue, $pdu);
while($pdu && ($pdu['sn']!=$seq_number
/**
* @private function
* Reads incoming PDU from SMSC.
* @return readed PDU or false on error.
*/
function readPDU()
//read PDU length
$tmp=fread($this->socket, 4);
if(!$tmp)return false;
extract(unpack("Nlength", $tmp));
//read PDU headers
$tmp2=fread($this->socket, 12);
if(!$tmp2)return false;
extract(unpack("Ncommand_id/Ncommand_status/Nsequence_number", $tmp2));
//read PDU body
if($length-16>0)
$body=fread($this->socket, $length-16);
if(!$body)return false;
else
$body="";
if($this->debug)
echo "Read PDU : $length bytesn";
$this->printHex($tmp.$tmp2.$body);
echo "body len : " . strlen($body) . "n";
echo "Command id : $command_idn";
echo "Command status : $command_statusn";
echo "sequence number : $sequence_numbernn";
$pdu=array(
'id'=>$command_id,
'status'=>$command_status,
'sn'=>$sequence_number,
'body'=>$body);
return $pdu;
/**
* @private function
* Reads C style zero padded string from the char array.
* @param $ar - input array
* @param $maxlen - maximum length to read.
* @return readed string.
*/
function getString(&$ar, $maxlen=255)
$s="";
$i=0;
do
$c=array_shift($ar);
if($c!=0)$s.=chr($c);
$i++;
while($i<$maxlen && $c!=0);
return $s;
/**
* @private function
* Prints the binary string as hex bytes.
* @param $maxlen - maximum length to read.
*/
function printHex($pdu)
$ar=unpack("C*",$pdu);
foreach($ar as $v)
$s=dechex($v);
if(strlen($s)<2)$s="0$s";
print "$s ";
print "n";
?>
I hope to hear from you guys.
Thank you in advance.
php smpp ussd pdu
php smpp ussd pdu
edited Nov 15 '18 at 8:28
Emmanuel Osinnowo
asked Nov 15 '18 at 8:19
Emmanuel OsinnowoEmmanuel Osinnowo
65
65
I cannot believe that no one has given any comment.
– Emmanuel Osinnowo
Dec 5 '18 at 18:32
add a comment |
I cannot believe that no one has given any comment.
– Emmanuel Osinnowo
Dec 5 '18 at 18:32
I cannot believe that no one has given any comment.
– Emmanuel Osinnowo
Dec 5 '18 at 18:32
I cannot believe that no one has given any comment.
– Emmanuel Osinnowo
Dec 5 '18 at 18:32
add a comment |
0
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53315058%2fhow-to-bind-ussd-transceiver-using-php%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53315058%2fhow-to-bind-ussd-transceiver-using-php%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
I cannot believe that no one has given any comment.
– Emmanuel Osinnowo
Dec 5 '18 at 18:32