I am using the following code to initiate calls. If the call is not answered, I want to capture this event (unanswered call) and call an alternative phone number. Currently with the below code, if a phone is not answered once, it is calling 3 times and then giving up without calling the StatusCallback URL at all. What am I doing wrong? (I can call the URL from browser with parameters.)
Thx a lot.
// Piece of PHP code making the call
$this->info('Calling providers:');
$account_sid = ...
$auth_token = ...
$client = new \Services_Twilio($account_sid, $auth_token);
try {
$this->info ('Calling phone... ');
// Make a call
$call = $client->account->calls->create('+448008021203', '+'.$phone_to_call, 'http://xyz/order_msg.html', array(
'Method' => 'GET',
"StatusCallback" => "http://xyz/phone_events?alt_phone=".$alternate_phone,
"StatusCallbackMethod" => "GET",
"StatusCallbackEvent" => array("completed"),
'Record' => 'false',
));
$this->info('Called with :' . $call);
}
}catch (\Exception $e) {
$this->error('Exception :'.$e->getMessage());
}
// StatusCallback URL = http://xyz/phone_events PHP-Laravel code
$status =Input::get('CallStatus');
$alternate_phone =Input::get('alt_phone');
if (! empty($alternate_phone) && ! empty($status)) {
if ($status != "completed" || $status != "queued") {
/* start the next call */
$account_sid = ...;
$auth_token = ...;
$client = new \Services_Twilio($account_sid, $auth_token);
try {
$client->account->calls->create('+448008021203', '+'.$alt_phone, 'http://xyz/order_msg.html', array(
'Method' => 'GET',
"StatusCallback" => "http://xyz/phone_events,
"StatusCallbackMethod" => "GET",
"StatusCallbackEvent" => array("completed"),
'Record' => 'false',
));
} catch (\Exception $e) {
$log->error('Phone Events Error: ' . $e);
}
}
}
Related
I am new to Laravel 9 and usage of Twilio API.
I am trying to create a scenario where a customer_care will call the first participant and after talking with the participant it will call the second participant with whom the first participant will communicate. While calling the second participant, the first participant's call will be kept on hold and after the second participant receives the call both their call will be merged so that they are connected to the same call.
I have written two separate function one for the first call and the other for the second call . The first function is:
public function twiliocall1(Request $request) {
$returnArray = array();
$status = "Error";
$msg = "Call not started";
$validator = Validator::make($request->all(), [
'participants' => 'required|array',
'twilloNo' => 'required',
'contact_id' => 'required'
]);
if ($validator->fails()) {
return response()->json(['error' => $validator->errors()], 401);
}
$twilloNo = $request->input('twilloNo');
$participants = $request->input('participants');
$sid = "xxxxx";
$token = "yyyy";
$client = new Client($sid, $token);
if (!empty($participants)) {
foreach($participants as $participant) {
$call1 = $client->account->calls->create(
$participant,
$twilloNo,
array("url" => "https://lbwr.operative.dev/multitenant/public/files/conference.php")
);
}
$status = "Success";
$msg = "Call started";
echo($call1->sid);
}
$returnArray['status'] = $status;
$returnArray['msg'] = $msg;
return response()->json($returnArray, 200);
}
The second function is:
public function twiliocall2(Request $request) {
$returnArray = array();
$status = "Error";
$msg = "Call not started";
$validator = Validator::make($request->all(), [
'participants' => 'required|array',
'twilloNo' => 'required',
'contact_id' => 'required',
'sid' => 'required'
]);
if ($validator->fails()) {
return response()->json(['error' => $validator->errors()], 401);
}
$twilloNo = $request->input('twilloNo');
$participants = $request->input('participants');
$parent_sid = $request->input('sid');
$sid = "xxxx";
$token = "yyyy";
$client = new Client($sid, $token);
foreach($participants as $participant) {
$call = $client->account->calls->get($parent_sid);
$call->update(
$participant,
$twilloNo,
array(
"Url" => "http://demo.twilio.com/docs/voice.xml",
"Method" => "POST"));
}
$status = "Success";
$msg = "Call started";
$returnArray['status'] = $status;
$returnArray['msg'] = $msg;
return response()->json($returnArray, 200);
}
$sid and $token value is same for both the function. I am trying to call the first function using the following json array:
{
"participants":["12345"],
"twilloNo":"68564",
"contact_id":"1234"
}
And on hitting I am getting the sid of this call which I am passing as a json array element to the function twiliocall2() as:
{
"participants":["+7777"],
"twilloNo":"111122",
"contact_id":"1023",
"sid":"fdthgfjkhbklnj"
}
All the values are changed.
The problem arises when I am calling the second function with this Json array as it throws an error:
Twilio\Exceptions\TwilioException: Unknown subresource get in file /opt/lampp/htdocs/multitenant/vendor/twilio/sdk/src/Twilio/Rest/Api/V2010/Account/CallList.php
Kindly suggest if possible where am I going wrong. I am not getting any idea how to implement this scenario any further.
The code in the Controller is as follows which parse an JSON and creates a Conference object where the error is occouring:
public function twilioConferenceCall(Request $request) {
$returnArray = array();
$status = "Error";
$msg = "Call not started";
$validator = Validator::make($request->all(), [
'participants' => 'required|array',
'fromNo' => 'required|numeric',
'twilloNo' => 'required',
'contact_id' => 'required'
]);
if ($validator->fails()) {
return response()->json(['error' => $validator->errors()], 401);
}
$fromNo = $request->input('fromNo');
$twilloNo = $request->input('twilloNo');
$sid = "someid";
$token = "sometoken";
$client = new Client($sid, $token);
$conference = $client->conferences->create([
'friendlyName' => 'Test Conference Call'
]);
$conference_sid = $conference->sid;
if ($fromNo != "" && !empty($request->participants)) {
foreach($request->participants as $participant) {
$concall = $client->account->calls->create(
$participant,
$twilloNo,
array(
"url" => "http://twimlets.com/conference?Name=Test%20Conference%20Call&ConferenceSid=".$conference_sid."&Moderators%5B0%5D=".$fromNo
)
);
$status = "Success";
$msg = "Started call to " . $participant;
}
}
$returnArray['status'] = $status;
$returnArray['msg'] = $msg;
return response()->json($returnArray, 200);
}
The JSON which am I sending is as follows:
{
"participants":["+1234","+9999"],
"fromNo":"+5678",
"twilloNo":"119988",
"contact_id":"1057"
}
The error which it throws is:
Error: Call to undefined method Twilio\Rest\Api\V2010\Account\ConferenceList::create()
I have included the following headers in my file
use Twilio\autoload;
use Twilio\Rest\Client;
Kindly suggest where am I doing wrong.
I am following the instructions here:
https://learn.microsoft.com/en-us/graph/auth-v2-user?view=graph-rest-1.0#3-get-a-token
I get the authorisation response and therefore the required code, but when I request the token I get a "HTTP/1.1 401 Unauthorized" response.
This is my code (not for production, just for me to get the flow/code right first):
<?php
/*
Auth
https://learn.microsoft.com/en-us/graph/auth-v2-user?view=graph-rest-1.0
*/
define("HOST", 'https://login.microsoftonline.com');
define("TENANT", 'common');
define("AUTH_ENDPOINT", HOST.'/'.TENANT.'/oauth2/v2.0/authorize');
define("TOKEN_ENDPOINT", HOST.'/'.TENANT.'/oauth2/v2.0/token');
define("CLIENT_ID", '<< FROM AZURE PORTAL >>');
define("CLIENT_SECRET", '<< FROM AZURE PORTAL >>');
define("ALL_SCOPES", rawurlencode('offline_access user.read mail.read'));
define("CALLBACK", rawurlencode('http://localhost:300/graph2/auth.php'));
// Build URL
$url = AUTH_ENDPOINT."?client_id=".CLIENT_ID;
$url .= "&response_type=code";
$url .= "&redirect_uri=".CALLBACK;
$url .= "&response_mode=query";
$url .= "&scope=".ALL_SCOPES;
echo "<a href=$url>$url</a>";
if (isset($_GET))
{
echo '<pre>';print_r($_GET);echo'</pre>';
if (isset($_GET['code']))
{
// Got authorisation code - now need token
$response = requestAccessTokenByVerifier($_GET['code']);
echo ">".$response;
}
}
function requestAccessTokenByVerifier($verifier)
{
return requestAccessToken(
array(
'client_id' => CLIENT_ID,
'redirect_uri' => CALLBACK,
'client_secret' => CLIENT_SECRET,
'code' => $verifier,
'grant_type' => 'authorization_code',
'scope' => ALL_SCOPES
)
);
}
function requestAccessToken($content)
{
$response = sendRequest(TOKEN_ENDPOINT, 'POST', $content);
if ($response !== false)
{
$authToken = json_decode($response);
if (!empty($authToken) && !empty($authToken->{ACCESSTOKEN})) return $authToken;
}
die("No access token");
return false;
}
function sendRequest($url, $method = 'GET', $data = array(), $headers = array('Content-Type: application/x-www-form-urlencoded'))
{
$context = stream_context_create(
array(
'http' => array(
'method' => $method,
'header' => $headers,
'content' => buildQueryString($data)
)
)
);
echo "URL: $url<br>";
echo '<pre>';print_r($data);echo '</pre>';
echo buildQueryString($data)."<br>";
$response = file_get_contents($url, false, $context);
echo '<pre>';var_dump($http_response_header);echo '</pre>';
return $response;
}
function buildQueryString($array)
{
$result = '';
foreach ($array as $k => $v)
{
if ($result == '') $prefix = ''; else $prefix = '&';
$result .= $prefix . $k . '=' . $v;
}
return $result;
}
?>
As you will see I've put a few basic diagnostics in there which show I am getting the code required successfully, but then not being able to get a token from the code.
Can anybody suggest what to try next, I wonder either from a debugging point of view or alternative approaches.
Thanks.
OK, after digging around I realised I'd made a stupid mistake, but just in case anybody else does the same - you need to use the client secret value not id!
I've deleted that client secret :)
Hope that helps somebody else bashing their head against a brick wall.
I'm using ZfcUser in my app and I need to control the timeout parameter. As it's not part of the configuration I would like to set my own Zend/Session object (with the remember_me_seconds param) to ZfcUser on bootstrap but I don't know how.
By chance, has anyone done this already?
The easiest way to set session params is as follows
config\autoload\global.php
return array(
'service_manager' => [
'factories' => [
// Configures the default SessionManager instance
'Zend\Session\ManagerInterface' => 'Zend\Session\Service\SessionManagerFactory',
// Provides session configuration to SessionManagerFactory
'Zend\Session\Config\ConfigInterface' => 'Zend\Session\Service\SessionConfigFactory',
],
],
'session_manager' => [
// SessionManager config: validators, etc
],
'session_config' => [
'cache_expire' => 86400,
'cookie_lifetime' => 86400,
'remember_me_seconds' => 86400,
'gc_probability' => 10,
'gc_divisor' => 1000,
'use_cookies' => true,
'cookie_httponly' => true,
'cookie_lifetime' => 0, // to reset lifetime to maximum at every click
'gc_maxlifetime' => 86400,
],
);
And add line to onBootstrap method at Module.php
public function onBootstrap(MvcEvent $e)
{
$manager = $e->getApplication()->getServiceManager()->get('Zend\Session\ManagerInterface');
}
This will affect session setting and phpinfo() shows it.
I found it in zfcuser github
I don't use zfcuser but try this
autoload/global.php
<?php
use Zend\Session\Config\SessionConfig;
use Zend\Session\SessionManager;
use Zend\Session\Container;
return array(
'service_manager' => array(
'factories' => array(
'SessionManager' => function($sm) {
$sessionConfig = new SessionConfig();
$sessionConfig->setOption('remember_me_seconds', 1440);
$sessionManager = new SessionManager($sessionConfig);
Container::setDefaultManager($sessionManager);
return $sessionManager;
},
),
),
);
Module.php
public function onBootstrap($event)
{
$serviceManager = $event->getApplication()->getServiceManager();
$serviceManager->get('SessionManager')->start();
}
Here is how I did it. I am not the worlds greatest coder but this seems to work. This is all in Module.php
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$sharedManager = $eventManager->getSharedManager();
$sm = $e->getApplication()->getServiceManager();
//This checks to see if the user is logged in.
$eventManager->attach(MvcEvent::EVENT_DISPATCH, array($this, 'checkLogin'), 100);
}
//This function is attached to a listener to see if the user is not currently logged in
//If they are not logged in they will be redirected to the login page. This check will happen through the
//application so there is no need to keep checking in other modules
public function checkLogin (MvcEvent $e)
{
$session = new Container('defaults');
$this->route = $e->getRouteMatch();
$this->matchedRouteName = explode('/', $this->route->getMatchedRouteName());
$this->route_root = $this->matchedRouteName[0];
$sm = $e->getApplication()->getServiceManager();
$zfcServiceEvents = $sm->get('ZfcUser\Authentication\Adapter\AdapterChain')->getEventManager();
$zfcServiceEvents->attach(
'authenticate',
function ($e) use ($session) {
$session->offsetSet('sessionstart', $_SERVER['REQUEST_TIME']);
}
);
$auth = $sm->get('zfcuser_auth_service');
if (!$auth->hasIdentity() && $this->route_root != 'zfcuser')
{
$response = new \Zend\Http\PhpEnvironment\Response();
$response->getHeaders()->addHeaderLine('Location', '/user/login');
$response->setStatusCode(302);
$response->sendHeaders();
$e->stopPropagation(true);
return $response;
}
else if ($auth->hasIdentity() && $session->offsetGet('sessionstart') < ($_SERVER['REQUEST_TIME'] - 10800) && $this->route_root != 'zfcuser')
{
$response = new \Zend\Http\PhpEnvironment\Response();
$response->getHeaders()->addHeaderLine('Location', '/user/logout');
$response->setStatusCode(302);
$response->sendHeaders();
$e->stopPropagation(true);
return $response;
}
else if ($auth->hasIdentity())
{
$session->offsetSet('sessionstart', $_SERVER['REQUEST_TIME']);
}
}
My autocomplete is not working and I can't spot the error.
Jquery:
<script>
$(function() {
$('#autoComplete').autocomplete({
//source: "/groceries/items/autoComplete", ///This works but response isn't formatted correctly'
//dataType: "json"
minLength: 2,
source: function( request, response ) {
$.ajax({
url: "/groceries/items/autoComplete",
dataType: "jsonp",
data: {
featureClass: "P",
style: "full",
maxRows: 12,
term: request.term
},
success: function( data ) {
response( $.map( data, function( el ) {
return { label: el.id, value: el.name }
}));
}
});
}
});
});
Controller:
public function autoComplete() {
Configure::write('debug', 0);
$this->layout = 'ajax';
$query = $_GET['term'];
$items = $this->Item->find('all', array(
'conditions' => array('Item.name LIKE' => $query . '%'),
'fields' => array('name', 'id', 'category_id'),
'group' => array('name')));
$this->set('items', $items);
}
Form:
<p>
<?php echo $this->Form->create('Item', array('model'=>'item','action' => 'addItem', 'name'=>'AddItem'));?>
<?php echo $this->Form->text('Item.name', array('size'=>'30', 'id'=>'autoComplete', 'autocomplete'=>'off')); ?>
<?php echo $this->Form->text('Item.category_id', array('type'=>'hidden', 'value'=>'0')); ?>
<?php echo $this->Form->text('Groclist.id', array('type'=>'hidden', 'value'=>$groclist['Groclist']['id'])); ?>
<?php echo $this->Form->end('Add'); ?>
</p>
Response:
0: {Item:{name:Cake, id:6, category_id:null}}
1: {Item:{name:Carrot Cake, id:9, category_id:null}}
2: {Item:{name:Carrots, id:8, category_id:null}}
3: {Item:{name:Casserole, id:11, category_id:null}}
4: {Item:{name:Cauliflower, id:10, category_id:null}}
Edited for clarification.
I realize JqueryUI expects label and value and that map should rearrange them, but for some reason it's not. Any ideas?
I found an even better solution. This is done completely in the controller. No view required.
public function autoComplete() {
Configure::write('debug', 0);
*$this->autoRender=false;*
$this->layout = 'ajax';
$query = $_GET['term'];
$items = $this->Item->find('all', array(
'conditions' => array('Item.name LIKE' => $query . '%'),
'fields' => array('name', 'id', 'category_id'),
'group' => array('name')));
*$i=0;
foreach($items as $item){
$response[$i]['value']="'".$item['Item']['id']."'";
$response[$i]['label']="'".$item['Item']['name']."'";
$i++;
}
echo json_encode($response);*
}
What if you reduce the array that is being returned by the Items model. So instead of $this->set('items', $items); you return the json encoded results like so:
foreach($items as $item) {
$data[] = $item['Item'];
}
$data = json_encode($data);
echo $data;
exit;
This is inside the auto_complete method in the controller.
UPDATE:
When querying for Cake for example, it would return a result like so:
[
{"name":"Cake Batter","id":"1","category_id":"3"},
{"name":"Cake Mix","id":"2","category_id":"3"}
]
if you are not wanting to return the json, you could just return $data without the json encoding.
Format Update:
I am not certain if this is to "sloppy", but you could change the foreach loop to:
foreach($items as $item) {
$data[]= array(
'label' => $item['Item']['name'],
'value' => $item['Item']['id']
);
}
Looks like you forgot the Item:
response($.map( data, function( el ) {
return { label: el.Item.id, value: el.Item.name }
});
You can also do it in the server side using Set:
$this->set('items', Set::extract('/Item', $items));