Facebook Ajax.post fails after calling Facebook.showPermissionDialog - php

I have a situation where I call Facebook.showPermissionDialog('offline_access'...) then later I make an ajax.post call. The ajax.post call fails if the call to the permission dialog was made prior. But it succeeds when the permission dialog was not called prior. Is anyone aware of some relationship between this dialog and ajax.post?
If you want to check out the problem firsthand, visit my app at http://apps.facebook.com/rails_dev (THIS IS A FACEBOOK APP SO YOU MUST GRANT ACCESS TO YOUR PROFILE).
Here's the code that calls Facebook.showPermissionDialog():
<?php
echo $this->jsInit($config);
if(!$userNamespace->newGame) {
$log->debug('NOT new game, calling turnResume()');
echo 'setVarBalance(' . $this->gamePlayerData['funds'] . ');'."\n";
echo 'turnResume();'."\n";
}
echo $this->drawTrack($this->routeData, $this->trainData);
echo $this->drawCityGoods($this->cityGoodsData);
//$link = 'startSetCity()'; //$config->url->absolute->fb->canvas . '/turn/start-set-city';
echo $this->drawCitiesAjax($this->cityDescData);
$log->debug('view: end start-select-city');
if(!$facebook->api_client->users_hasAppPermission('offline_access', $this->fbUserId)):
?>
var dialog = new Dialog().showMessage('Constant Authorization', 'Rails Across Europe is about to request \'Constant Authorization\' to your account. If you don\'t give us constant authorization, Facebook will eventually cause your game to timeout, thereby losing all game information. By granting this authorization, Facebook will not cause your game to timeout. This is the only reason we need this authorization.');
dialog.onconfirm = function() {
Facebook.showPermissionDialog('offline_access', null, false, null);
}
<?php
endif;
?>[
Here's the FBJS code that calls ajax.post:
switch(state) {
case START_SET_CITY:
//new Dialog().showMessage('test', 'START_SET_CITY');
//console.time('start_set_city');
ajax.responseType = Ajax.JSON;
ajax.ondone = function(data) {
//console.time('ondone');
//new Dialog().showMessage('in ajaxSetCity.ondone');
//new Dialog().showMessage('test', 'city=' + dump(data.city, 3) + '::: train=' + dump(data.train, 3));
drawCityAjax(data.city, data.train);
setVarBalance(data.funds);
ajax.responseType = Ajax.JSON;
ajax.post(baseURL + '/turn/start');
//console.timeEnd('ondone');
};
ajax.post(baseURL + '/turn/start-set-city', param); // <=== THIS IS THE AJAX CALL THAT FAILS
var actionPrompt = document.getElementById('action-prompt');
var innerHtml = '<span><div id="action-text">Build Track: Select a city where track building should begin</div>'+
'<div id="action-end">'+
'<input type="button" value="End Track Building" id="next-phase" onClick="moveTrainAuto();" />'+
'</div></span>';
actionPrompt.setInnerXHTML(innerHtml);
var btn = document.getElementById('next-phase');
btn.addEventListener('click', moveTrainAutoEvent);
state = TRACK_CITY_START;
//console.timeEnd('start_set_city');
// get funds balance from backend and call setVarBalance()
break;

I had the same problem if ajax.requireLogin parameter was true. Since you are already asking for extended permissions you can set it to false. Code below works for me:
Facebook.showPermissionDialog("publish_stream", function(permissions) {
var form_data = form.serialize();
var ajax = new Ajax();
ajax.responseType = Ajax.RAW;
ajax.requireLogin = false;
ajax.ondone = function(data) {
console.log("onerror")
};
ajax.onerror = function() {
console.log("onerror")
};
ajax.post("http://foo.example.com/submit", form_data);
return false;
});

Related

Firebase Auth JS/PHP

I've been tasked to build a web interface for an Android app based on firebase.
I've got a handful of endpoints, that interact with the database (Cloud functions). To access those endpoints I need to authenticate an user with email and password[1], retrieve an accessToken[2] und authorize every request to the endpoints with an Authorization: Bearer {accessToken} header.
I use php and struggle to wrap my mind around how to manage authenticated user in my app.
TL;DR please see my final solution in php only. https://stackoverflow.com/a/52119600/814031
I transfer the accessToken via ajax in a php session, to sign the cURL requests to the endpoints.
Apparently there is no other way around than use the firebase JS auth (not as far as I understand[4]).
My question is: Is it enough to save the accessToken in a php session and compare it with every page load via an ajax POST request (see code below)?
What would be a more robust strategy to handle that in php?
Edit: A user pointed out that using classic php sessions with JWT tokens don't make much sense and I read up about that topic.
So regarding Firebase - is this something to consider?
https://firebase.google.com/docs/auth/admin/manage-cookies
Firebase Auth provides server-side session cookie management for traditional websites that rely on session cookies. This solution has several advantages over client-side short-lived ID tokens, which may require a redirect mechanism each time to update the session cookie on expiration:
Here is what I got:
1. Login Page
As described in the Firebase examples[3]
function initApp() {
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
// User is signed in.
// obtain token, getIdToken(false) = no forced refresh
firebase.auth().currentUser.getIdToken(false).then(function (idToken) {
// Send token to your backend via HTTPS
$.ajax({
type: 'POST',
url: '/auth/check',
data: {'token': idToken},
complete: function(data){
// data = {'target' => '/redirect/to/route'}
if(getProperty(data, 'responseJSON.target', false)){
window.location.replace(getProperty(data, 'responseJSON.target'));
}
}
});
// ...
}).catch(function (error) {
console.log(error);
});
} else {
// User Signed out
$.ajax({
type: 'POST',
url: '/auth/logout',
complete: function(data){
// data = {'target' => '/redirect/to/route'}
if(getProperty(data, 'responseJSON.target', false)){
// don't redirect to itself
// logout => /
if(window.location.pathname != getProperty(data, 'responseJSON.target', false)){
window.location.replace(getProperty(data, 'responseJSON.target'));
}
}
}
});
// User is signed out.
}
});
}
window.onload = function () {
initApp();
};
2. a php controller to handle the auth requests
public function auth($action)
{
switch($action) {
// auth/logout
case 'logout':
unset($_SESSION);
// some http status header and mime type header
echo json_encode(['target' => '/']); // / => index page
break;
case 'check':
// login.
if(! empty($_POST['token']) && empty($_SESSION['token'])){
// What if I send some bogus data here? The call to the Endpoint later would fail anyway
// But should it get so far?
$_SESSION['token'] = $_POST['token'];
// send a redirect target back to the JS
echo json_encode(['target' => '/dashboard']);
break;
}
if($_POST['token'] == $_SESSION['token']){
// do nothing;
break;
}
break;
}
}
3. the Main controller
// pseudo code
class App
{
public function __construct()
{
if($_SESSION['token']){
$client = new \GuzzleHttp\Client();
// $user now holds all custom access rights within the app.
$this->user = $client->request(
'GET',
'https://us-centralx-xyz.cloudfunctions.net/user_endpoint',
['headers' =>
[
'Authorization' => "Bearer {$_SESSION['token']}"
]
]
)->getBody()->getContents();
}else{
$this->user = null;
}
}
public function dashboard(){
if($this->user){
var_dump($this->user);
}else{
unset($_SESSION);
// redirect to '/'
}
}
}
Note: I'm aware of this sdk https://github.com/kreait/firebase-php and I read a lot in the issues there and in posts here on SO, but I got confused, since there is talk about full admin rights etc. and I really only interact with the endpoints that build upon firebase (plus firebase auth and firestore). And I'm still on php 5.6 :-/
Thanks for your time!
[1]: https://firebase.google.com/docs/auth/web/password-auth
[2]: https://firebase.google.com/docs/reference/js/firebase.User#getIdToken
[3]: https://github.com/firebase/quickstart-js/blob/master/auth/email-password.html
[4]: https://github.com/kreait/firebase-php/issues/159#issuecomment-360225655
I have to admit, the complexity of the firebase docs and examples and different services, got me so confused, that I thought, authentication for the web is only possible via JavaScript. That was wrong. At least for my case, where I just login with email and password to retrieve a Json Web Token (JWT), to sign all calls to the Firebase cloud functions. Instead of juggling with weird Ajax requests or set the token cookie via JavaScript, I just needed to call the Firebase Auth REST API
Here is a minimal case using the Fatfreeframework:
Login form
<form action="/auth" method="post">
<input name="email">
<input name="password">
<input type="submit">
</form>
Route
$f3->route('POST /auth', 'App->auth');
Controller
class App
{
function auth()
{
$email = $this->f3->get('POST.email');
$password = $this->f3->get('POST.password');
$apiKey = 'API_KEY'; // see https://firebase.google.com/docs/web/setup
$auth = new Auth($apiKey);
$result = $auth->login($email,$password);
if($result['success']){
$this->f3->set('COOKIE.token',$result['idToken']);
$this->f3->reroute('/dashboard');
}else{
$this->f3->clear('COOKIE.token');
$this->f3->reroute('/');
}
}
}
Class
<?php
use GuzzleHttp\Client;
class Auth
{
protected $apiKey;
public function __construct($apiKey){
$this->apiKey = $apiKey;
}
public function login($email,$password)
{
$client = new Client();
// Create a POST request using google api
$key = $this->apiKey;
$responsee = $client->request(
'POST',
'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=' . $key,
[
'headers' => [
'content-type' => 'application/json',
'Accept' => 'application/json'
],
'body' => json_encode([
'email' => $email,
'password' => $password,
'returnSecureToken' => true
]),
'exceptions' => false
]
);
$body = $responsee->getBody();
$js = json_decode($body);
if (isset($js->error)) {
return [
'success' => false,
'message' => $js->error->message
];
} else {
return [
'success' => true,
'localId' => $js->localId,
'idToken' => $js->idToken,
'email' => $js->email,
'refreshToken' => $js->refreshToken,
'expiresIn' => $js->expiresIn,
];
}
}
}
Credits
Sounds like #Chad K is getting you on the right track (cookies and ajax - breakfast of champions... :), though I thought to share my code from my working system (with some 'privacy' things, of course!)
Look for /**** type comments for things you need to set up yourself (you may want to do some other firebase things differently as well - see the docs...)
LOGIN.php page (I found it simpler overall to keep this separate - see notes to learn why....)
<script>
/**** I picked this up somewhere off SO - kudos to them - I use it a lot!.... :) */
function setCookie(name, value, days = 7, path = '/') {
var expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = name + '=' + encodeURIComponent(value) + '; expires=' + expires + '; path=' + path;
}
function getCookie(c_name) {
if (document.cookie.length > 0) {
c_start = document.cookie.indexOf(c_name + "=");
if (c_start !== -1) {
c_start = c_start + c_name.length + 1;
c_end = document.cookie.indexOf(";", c_start);
if (c_end === -1) {
c_end = document.cookie.length;
}
return unescape(document.cookie.substring(c_start, c_end));
}
}
return "";
}
</script>
<script>
var config = {
apiKey: "your_key",
authDomain: "myapp.firebaseapp.com",
databaseURL: "https://myapp.firebaseio.com",
projectId: "myapp",
storageBucket: "myapp.appspot.com",
messagingSenderId: "the_number"
};
firebase.initializeApp(config);
</script>
<script src="https://cdn.firebase.com/libs/firebaseui/2.7.0/firebaseui.js"></script>
<link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/2.7.0/firebaseui.css"/>
<script type="text/javascript">
/**** set this url to the 'logged in' page (mine goes to a dashboard) */
var url = 'https://my.app/index.php#dashboard';
/**** by doing this signOut first, then it is simple to send any 'logout' request in the app to 'login.php' - one page does it.... :) */
firebase.auth().signOut().then(function () {
}).catch(function (error) {
console.log(error);
});
var signInFlow = 'popup';
if (('standalone' in window.navigator)
&& window.navigator.standalone) {
signInFlow = 'redirect';
}
var uiConfig = {
callbacks: {
signInSuccessWithAuthResult: function (authResult, redirectUrl) {
/**** here you can see the logged in user */
var firebaseUser = authResult.user;
var credential = authResult.credential;
var isNewUser = authResult.additionalUserInfo.isNewUser;
var providerId = authResult.additionalUserInfo.providerId;
var operationType = authResult.operationType;
/**** I like to force emailVerified...... */
if (firebaseUser.emailVerified !== true) {
firebase.auth().currentUser.sendEmailVerification().then(function () {
/**** if using this, you can set up your own usermgmt.php page for the user verifications (see firebase docs) */
window.location.replace("https://my.app/usermgmt.php?mode=checkEmail");
}).catch(function (error) {
console.log("an error has occurred in sending verification email " + error)
});
}
else {
var accessToken = firebaseUser.qa;
/**** set the Cookie (yes, I found this best, too) */
setCookie('firebaseRegistrationID', accessToken, 1);
/**** set up the AJAX call to PHP (where you will store this data for later lookup/processing....) - I use "function=....." and "return=....." to have options for all functions and what to select for the return so that ajax.php can be called for 'anything' (you can just call a special page if you like instead of this - if you use this idea, be sure to secure the ajax.php 'function' call to protect from non-authorized use!) */
var elements = {
function: "set_user_data",
user: JSON.stringify(firebaseUser),
return: 'page',
accessToken: accessToken
};
$.ajaxSetup({cache: false});
$.post("data/ajax.php", elements, function (data) {
/**** this calls ajax and gets the 'page' to set (this is from a feature where I store the current page the user is on, then when they log in again here, we go back to the same page - no need for cookies, etc. - only the login cookie is needed (and available for 'prying eyes' to see!) */
url = 'index.php#' + data;
var form = $('<form method="post" action="' + url + '"></form>');
$('body').append(form);
form.submit();
});
}
return false;
},
signInFailure: function (error) {
console.log("error - signInFailure", error);
return handleUIError(error);
},
uiShown: function () {
var loader = document.getElementById('loader');
if (loader) {
loader.style.display = 'none';
}
}
},
credentialHelper: firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM,
queryParameterForWidgetMode: 'mode',
queryParameterForSignInSuccessUrl: 'signInSuccessUrl',
signInFlow: signInFlow,
signInSuccessUrl: url,
signInOptions: [
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
// firebase.auth.FacebookAuthProvider.PROVIDER_ID,
// firebase.auth.TwitterAuthProvider.PROVIDER_ID,
{
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
requireDisplayName: true,
customParameters: {
prompt: 'select_account'
}
}
/* {
provider: firebase.auth.PhoneAuthProvider.PROVIDER_ID,
// Invisible reCAPTCHA with image challenge and bottom left badge.
recaptchaParameters: {
type: 'image',
size: 'invisible',
badge: 'bottomleft'
}
}
*/
],
tosUrl: 'https://my.app/login.php'
};
var ui = new firebaseui.auth.AuthUI(firebase.auth());
(function () {
ui.start('#firebaseui-auth-container', uiConfig);
})();
</script>
Now, on every page you want the user to see (in my case, it all goes through index.php#something - which makes it easier.... :)
<script src="https://www.gstatic.com/firebasejs/4.12.0/firebase.js"></script>
<script>
// Initialize Firebase - from https://github.com/firebase/firebaseui-web
var firebaseUser;
var config = {
apiKey: "your_key",
authDomain: "yourapp.firebaseapp.com",
databaseURL: "https://yourapp.firebaseio.com",
projectId: "yourapp",
storageBucket: "yourapp.appspot.com",
messagingSenderId: "the_number"
};
firebase.initializeApp(config);
initFBApp = function () {
firebase.auth().onAuthStateChanged(function (firebaseuser) {
if (firebaseuser) {
/**** here, I have another ajax call that sets up some select boxes, etc. (I chose to call it here, you can call it anywhere...) */
haveFBuser();
firebaseUser = firebaseuser;
// User is signed in.
var displayName = firebaseuser.displayName;
var email = firebaseuser.email;
var emailVerified = firebaseuser.emailVerified;
var photoURL = firebaseuser.photoURL;
if (firebaseuser.photoURL.length) {
/**** set the profile picture (presuming you are showing it....) */
$(".profilepic").prop('src', firebaseuser.photoURL);
}
var phoneNumber = firebaseuser.phoneNumber;
var uid = firebaseuser.uid;
var providerData = firebaseuser.providerData;
var string = "";
firebaseuser.getIdToken().then(function (accessToken) {
// document.getElementById('sign-in-status').textContent = 'Signed in';
// document.getElementById('sign-in').textContent = 'Sign out';
/**** set up another ajax call.... - to store things (yes, again.... - though this time it may be due to firebase changing the token, so we need it twice...) */
string = JSON.stringify({
displayName: displayName,
email: email,
emailVerified: emailVerified,
phoneNumber: phoneNumber,
photoURL: photoURL,
uid: uid,
accessToken: accessToken,
providerData: providerData
});
if (accessToken !== '<?php echo $_COOKIE['firebaseRegistrationID']?>') {
console.log("RESETTING COOKIE with new accessToken ");
setCookie('firebaseRegistrationID', accessToken, 1);
var elements = 'function=set_user_data&user=' + string;
$.ajaxSetup({cache: false});
$.post("data/ajax.php", elements, function (data) {
<?php
/**** leave this out for now and see if anything weird happens - should be OK but you might want to use it (refreshes the page when firebase changes things..... I found it not very user friendly as they reset at 'odd' times....)
/*
// var url = 'index.php#<?php echo(!empty($user->userNextPage) ? $user->userNextPage : 'dashboard'); ?>';
// var form = $('<form action="' + url + '" method="post">' + '</form>');
// $('body').append(form);
// console.log('TODO - leave this form.submit(); out for now and see if anything weird happens - should be OK');
// form.submit();
*/
?>
});
}
});
} else {
console.log("firebase user CHANGED");
document.location.href = "../login.php";
}
}, function (error) {
console.log(error);
}
);
};
window.addEventListener('load', function () {
initFBApp();
});
</script>
Hope this helps. It is from my working system, which includes some extra features I've put in there along the way, but mostly it is directly from firebase so you should be able to follow along well enough.
Seems a much simpler route to take than your original one.
You really aren't supposed to use sessions in PHP when using tokens. Tokens should be sent in the header on every request (or a cookie works too).
Tokens work like this:
1. You sign in, the server mints a token with some information encoded
2. You send that token back on every request
Based on the information encoded in the token, the server can get information about the user. Typically a User ID of some sort is encoded in it. The server knows it's a valid token because of the way it's encoded.
Send the token on every request you need to make, then in PHP you can just pass that token to the other API

Web Chat with Facebook Integration

I need your help guys, I'm building my own web chat for my online radio site. I already have a AJAX PHP Web chat from Tutorialzine. I want to modify it. But i don't know where to start. I want it to integrate with Facebook. I want it instead of asking for username and email, there will be a button that says 'Connect to Facebook'. and the Profile Picture and Name of the user will automatically saved to the database. I really need it. And i want it to be moderated. Thank You! and God bless everyone! :)
ajax.php
<?php
/* Database Configuration. Add your details below */
$dbOptions = array(
'db_host' => 'localhost',
'db_user' => 'root',
'db_pass' => '',
'db_name' => 'chat'
);
/* Database Config End */
error_reporting(E_ALL ^ E_NOTICE);
require "classes/DB.class.php";
require "classes/Chat.class.php";
require "classes/ChatBase.class.php";
require "classes/ChatLine.class.php";
require "classes/ChatUser.class.php";
session_name('webchat');
session_start();
if(get_magic_quotes_gpc()){
// If magic quotes is enabled, strip the extra slashes
array_walk_recursive($_GET,create_function('&$v,$k','$v = stripslashes($v);'));
array_walk_recursive($_POST,create_function('&$v,$k','$v = stripslashes($v);'));
}
try{
// Connecting to the database
DB::init($dbOptions);
$response = array();
// Handling the supported actions:
switch($_GET['action']){
case 'login':
$response = Chat::login($_POST['name'],$_POST['email']);
break;
case 'checkLogged':
$response = Chat::checkLogged();
break;
case 'logout':
$response = Chat::logout();
break;
case 'submitChat':
$response = Chat::submitChat($_POST['chatText']);
break;
case 'getUsers':
$response = Chat::getUsers();
break;
case 'getChats':
$response = Chat::getChats($_GET['lastID']);
break;
default:
throw new Exception('Wrong action');
}
echo json_encode($response);
}
catch(Exception $e){
die(json_encode(array('error' => $e->getMessage())));
}
?>
script.js
$(document).ready(function(){
// Run the init method on document ready:
chat.init();
});
var chat = {
// data holds variables for use in the class:
data : {
lastID : 0,
noActivity : 0
},
// Init binds event listeners and sets up timers:
init : function(){
// Using the defaultText jQuery plugin, included at the bottom:
$('#name').defaultText('Nickname');
$('#email').defaultText('Email (Gravatars are Enabled)');
// Converting the #chatLineHolder div into a jScrollPane,
// and saving the plugin's API in chat.data:
chat.data.jspAPI = $('#chatLineHolder').jScrollPane({
verticalDragMinHeight: 12,
verticalDragMaxHeight: 12
}).data('jsp');
// We use the working variable to prevent
// multiple form submissions:
var working = false;
// Logging a person in the chat:
$('#loginForm').submit(function(){
if(working) return false;
working = true;
// Using our tzPOST wrapper function
// (defined in the bottom):
$.tzPOST('login',$(this).serialize(),function(r){
working = false;
if(r.error){
chat.displayError(r.error);
}
else chat.login(r.name,r.gravatar);
});
return false;
});
// Submitting a new chat entry:
$('#submitForm').submit(function(){
var text = $('#chatText').val();
if(text.length == 0){
return false;
}
if(working) return false;
working = true;
// Assigning a temporary ID to the chat:
var tempID = 't'+Math.round(Math.random()*1000000),
params = {
id : tempID,
author : chat.data.name,
gravatar : chat.data.gravatar,
text : text.replace(/</g,'<').replace(/>/g,'>')
};
// Using our addChatLine method to add the chat
// to the screen immediately, without waiting for
// the AJAX request to complete:
chat.addChatLine($.extend({},params));
// Using our tzPOST wrapper method to send the chat
// via a POST AJAX request:
$.tzPOST('submitChat',$(this).serialize(),function(r){
working = false;
$('#chatText').val('');
$('div.chat-'+tempID).remove();
params['id'] = r.insertID;
chat.addChatLine($.extend({},params));
});
return false;
});
// Logging the user out:
$('a.logoutButton').live('click',function(){
$('#chatTopBar > span').fadeOut(function(){
$(this).remove();
});
$('#submitForm').fadeOut(function(){
$('#loginForm').fadeIn();
});
$.tzPOST('logout');
return false;
});
// Checking whether the user is already logged (browser refresh)
$.tzGET('checkLogged',function(r){
if(r.logged){
chat.login(r.loggedAs.name,r.loggedAs.gravatar);
}
});
// Self executing timeout functions
(function getChatsTimeoutFunction(){
chat.getChats(getChatsTimeoutFunction);
})();
(function getUsersTimeoutFunction(){
chat.getUsers(getUsersTimeoutFunction);
})();
},
// The login method hides displays the
// user's login data and shows the submit form
login : function(name,gravatar){
chat.data.name = name;
chat.data.gravatar = gravatar;
$('#chatTopBar').html(chat.render('loginTopBar',chat.data));
$('#loginForm').fadeOut(function(){
$('#submitForm').fadeIn();
$('#chatText').focus();
});
},
// The render method generates the HTML markup
// that is needed by the other methods:
render : function(template,params){
var arr = [];
switch(template){
case 'loginTopBar':
arr = [
'<span><img src="',params.gravatar,'" width="23" height="23" />',
'<span class="name">',params.name,
'</span>Logout</span>'];
break;
case 'chatLine':
arr = [
'<div class="chat chat-',params.id,' rounded"><span class="gravatar"><img src="',params.gravatar,
'" width="23" height="23" onload="this.style.visibility=\'visible\'" />','</span><span class="author">',params.author,
':</span><span class="text">',params.text,'</span><span class="time">',params.time,'</span></div>'];
break;
case 'user':
arr = [
'<div class="user" title="',params.name,'"><img src="',
params.gravatar,'" width="30" height="30" onload="this.style.visibility=\'visible\'" /></div>'
];
break;
}
// A single array join is faster than
// multiple concatenations
return arr.join('');
},
// The addChatLine method ads a chat entry to the page
addChatLine : function(params){
// All times are displayed in the user's timezone
var d = new Date();
if(params.time) {
// PHP returns the time in UTC (GMT). We use it to feed the date
// object and later output it in the user's timezone. JavaScript
// internally converts it for us.
d.setUTCHours(params.time.hours,params.time.minutes);
}
params.time = (d.getHours() < 10 ? '0' : '' ) + d.getHours()+':'+
(d.getMinutes() < 10 ? '0':'') + d.getMinutes();
var markup = chat.render('chatLine',params),
exists = $('#chatLineHolder .chat-'+params.id);
if(exists.length){
exists.remove();
}
if(!chat.data.lastID){
// If this is the first chat, remove the
// paragraph saying there aren't any:
$('#chatLineHolder p').remove();
}
// If this isn't a temporary chat:
if(params.id.toString().charAt(0) != 't'){
var previous = $('#chatLineHolder .chat-'+(+params.id - 1));
if(previous.length){
previous.after(markup);
}
else chat.data.jspAPI.getContentPane().append(markup);
}
else chat.data.jspAPI.getContentPane().append(markup);
// As we added new content, we need to
// reinitialise the jScrollPane plugin:
chat.data.jspAPI.reinitialise();
chat.data.jspAPI.scrollToBottom(true);
},
// This method requests the latest chats
// (since lastID), and adds them to the page.
getChats : function(callback){
$.tzGET('getChats',{lastID: chat.data.lastID},function(r){
for(var i=0;i<r.chats.length;i++){
chat.addChatLine(r.chats[i]);
}
if(r.chats.length){
chat.data.noActivity = 0;
chat.data.lastID = r.chats[i-1].id;
}
else{
// If no chats were received, increment
// the noActivity counter.
chat.data.noActivity++;
}
if(!chat.data.lastID){
chat.data.jspAPI.getContentPane().html('<p class="noChats">No chats yet</p>');
}
// Setting a timeout for the next request,
// depending on the chat activity:
var nextRequest = 1000;
// 2 seconds
if(chat.data.noActivity > 3){
nextRequest = 2000;
}
if(chat.data.noActivity > 10){
nextRequest = 5000;
}
// 15 seconds
if(chat.data.noActivity > 20){
nextRequest = 15000;
}
setTimeout(callback,nextRequest);
});
},
// Requesting a list with all the users.
getUsers : function(callback){
$.tzGET('getUsers',function(r){
var users = [];
for(var i=0; i< r.users.length;i++){
if(r.users[i]){
users.push(chat.render('user',r.users[i]));
}
}
var message = '';
if(r.total<1){
message = 'No one is online';
}
else {
message = r.total+' '+(r.total == 1 ? 'person':'people')+' online';
}
users.push('<p class="count">'+message+'</p>');
$('#chatUsers').html(users.join(''));
setTimeout(callback,15000);
});
},
// This method displays an error message on the top of the page:
displayError : function(msg){
var elem = $('<div>',{
id : 'chatErrorMessage',
html : msg
});
elem.click(function(){
$(this).fadeOut(function(){
$(this).remove();
});
});
setTimeout(function(){
elem.click();
},5000);
elem.hide().appendTo('body').slideDown();
}
};
// Custom GET & POST wrappers:
$.tzPOST = function(action,data,callback){
$.post('php/ajax.php?action='+action,data,callback,'json');
}
$.tzGET = function(action,data,callback){
$.get('php/ajax.php?action='+action,data,callback,'json');
}
// A custom jQuery method for placeholder text:
$.fn.defaultText = function(value){
var element = this.eq(0);
element.data('defaultText',value);
element.focus(function(){
if(element.val() == value){
element.val('').removeClass('defaultText');
}
}).blur(function(){
if(element.val() == '' || element.val() == value){
element.addClass('defaultText').val(value);
}
});
return element.blur();
}
If you only want to connect with facebook for the user name and picture then all you need to do is include the Facebook Javascript SDK, and then either use the Login Button plugin or use the Client-Side authentication.
If you want to connect with the Facebook internal chat, then you can use the Chat API which has two authentication methods: Facebook Platform and Username/Password.
If you want the first method (sounds like what you want) then you'll need to authenticate the user, either with the client side flow or the server side flow and ask for the "xmpp_login" permission.
There are php examples in the chat API documentation.

Using JS setTimeout to post AJAX requests in a loop, but setTimeout gets called far too often

This question has been answered. The problem was that myEventMoveTrainManaul() was being called from other locations within the code. Thanks to everyone who offered their help.
Please forgive me for re-posting this, but it was getting almost no attention and it's very important that I find someone to help me with this. Thank you for your kind understanding.
I have been working on a new feature for a facebook game I have written. The game allows a player to travel between cities in Europe by train and deliver goods for profit. This feature that I'm adding adds pathfinding AI to the game: it allows a player to select a city to travel to, then the game automatically moves the player's train along track from it's starting city to the destination city. I am using AJAX and setTimeout() to get the data from the backend and to enable the movement of the train along the track that connects cities. Please refer to the code which will (hopefully) contain a better understanding of what I am attempting to do.
The problem is that setTimeout() gets called too often. I put a global variable called statusFinalDest which may contain two values: ENROUTE and ARRIVED. While the train is ENROUTE, the JS train movement function calls itself using setTimeout until the backend returns a statusFinalDest of ARRIVED, at which time the train movement timeout loop is (supposedly) terminated. However, instead of calling myEventMoveTrainManual() once for each turn the backend processes, it gets called exceedingly more often, as if it is not recognizing the changed state of statusFinalDest. I have attempted to put more limiting structures into the code to put an end to this excessive behavior, the they don't appear to be working.
FYI - myEventMoveTrainManual() is not an event handler, but it does get called from an event handler.
Here's the relevant FBJS (Facebook JS) code:
function myEventMoveTrainManual(evt) {
if(mutexMoveTrainManual == 'CONTINUE') {
//mutexMoveTrainManual = 'LOCKED';
var ajax = new Ajax();
var param = {};
if(evt) {
var cityId = evt.target.getParentNode().getId();
var param = { "city_id": cityId };
}
ajax.responseType = Ajax.JSON;
ajax.ondone = function(data) {
statusFinalDest = data.status_final_dest;
if(data.code != 'ERROR_FINAL_DEST') {
// Draw train at new location
trackAjax = new Ajax();
trackAjax.responseType = Ajax.JSON;
trackAjax.ondone = function(trackData) {
var trains = [];
trains[0] = trackData.train;
removeTrain(trains);
drawTrack(trackData.y1, trackData.x1, trackData.y2, trackData.x2, '#FF0', trains);
if(data.code == 'UNLOAD_CARGO') {
unloadCargo();
} else if (data.code == 'MOVE_TRAIN_AUTO' || data.code == 'TURN_END') {
moveTrainAuto();
} else {
/* handle error */
}
mutexMoveTrainManual = 'CONTINUE';
}
trackAjax.post(baseURL + '/turn/get-track-data');
}
}
ajax.post(baseURL + '/turn/move-train-set-destination', param);
}
// If we still haven't ARRIVED at our final destination, we are ENROUTE so continue
// moving the train until final destination is reached
// statusFinalDest is a global var
if(statusFinalDest == 'ENROUTE') {
setTimeout(myEventMoveTrainManual, 1000);
}
}
And here is the backend PHP code:
public function moveTrainSetDestinationAction() {
require_once 'Train.php';
$trainModel = new Train();
$userNamespace = new Zend_Session_Namespace('User');
$gameNamespace = new Zend_Session_Namespace('Game');
$this->_helper->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender();
$trainRow = $trainModel->getTrain($userNamespace->gamePlayerId);
$statusFinalDest = $trainRow['status_final_dest'];
if($statusFinalDest == 'ARRIVED') {
$originCityId = $trainRow['dest_city_id'];
$destCityId = $this->getRequest()->getPost('city_id');
if(empty($destCityId)) {
// If we arrived at final dest but user supplied no city then this method got called
// incorrectly so return an error
echo Zend_Json::encode(array('code' => 'ERROR_FINAL_DEST', 'status_final_dest' => $statusFinalDest));
exit;
}
$gameNamespace->itinerary = $this->_helper->getTrainItinerary($originCityId, $destCityId);
array_shift($gameNamespace->itinerary); //shift-off the current train city location
$trainModel->setStatusFinalDest('ENROUTE', $userNamespace->gamePlayerId);
$statusFinalDest = 'ENROUTE';
}
$cityId = $trainRow['dest_city_id'];
if($trainRow['status'] == 'ARRIVED') {
if(count($gameNamespace->itinerary) > 0) {
$cityId = array_shift($gameNamespace->itinerary);
}
}
$trainRow = $this->_helper->moveTrain($cityId);
if(count($trainRow) > 0) {
if($trainRow['status'] == 'ARRIVED') {
// If there are no further cities on the itinerary, we have arrived at our final destination
if(count($gameNamespace->itinerary) == 0) {
$trainModel->setStatusFinalDest('ARRIVED', $userNamespace->gamePlayerId);
$statusFinalDest = 'ARRIVED';
}
echo Zend_Json::encode(array('code' => 'UNLOAD_CARGO', 'status_final_dest' => $statusFinalDest));
exit;
// Pass id for last city user selected so we can return user to previous map scroll postion
} else if($trainRow['track_units_remaining'] > 0) {
echo Zend_Json::encode(array('code' => 'MOVE_TRAIN_AUTO', 'status_final_dest' => $statusFinalDest));
exit;
} else { /* Turn has ended */
echo Zend_Json::encode(array('code' => 'TURN_END', 'status_final_dest' => $statusFinalDest));
exit;
}
}
echo Zend_Json::encode(array('code' => 'MOVE_TRAIN_AUTO_ERROR'));
}
Based on #brad's suggestion, I have modified myEventMoveTrainManual() as follows:
function myEventMoveTrainManual(evt) {
//debugger;
if(mutexMoveTrainManual == 'CONTINUE') {
//mutexMoveTrainManual = 'LOCKED';
//statusFinalDest = 'ARRIVED';
var ajax = new Ajax();
var param = {};
if(evt) {
var cityId = evt.target.getParentNode().getId();
var param = { "city_id": cityId };
}
ajax.responseType = Ajax.JSON;
ajax.ondone = function(data) {
statusFinalDest = data.status_final_dest;
//debugger;
consoleLog('statusFinalDest='+statusFinalDest+', data.code='+data.code);
if(data.code != 'ERROR_FINAL_DEST') {
consoleLog('data.code != ERROR_FINAL_DEST');
// Draw train at new location
trackAjax = new Ajax();
trackAjax.responseType = Ajax.JSON;
trackAjax.ondone = function(trackData) {
consoleLog('drawing track');
var trains = [];
trains[0] = trackData.train;
removeTrain(trains);
drawTrack(trackData.y1, trackData.x1, trackData.y2, trackData.x2, '#FF0', trains);
consoleLog('processing data.code = '+data.code);
if(data.code == 'UNLOAD_CARGO') {
unloadCargo();
consoleLog('returned from unloadCargo()');
} else if (data.code == 'MOVE_TRAIN_AUTO' || data.code == 'TURN_END') {
moveTrainAuto();
consoleLog('returned from moveTrainAuto()');
/*
} else if (data.code == 'TURN_END') {
consoleLog('moveTrainManual::turnEnd');
turnEnd();
*/
} else {
/* handle error */
}
mutexMoveTrainManual = 'CONTINUE';
// If we still haven't ARRIVED at our final destination, we are ENROUTE so continue
// moving the train until final destination is reached
if(statusFinalDest == 'ENROUTE') {
myEventMoveTrainManual(null);
}
}
trackAjax.post(baseURL + '/turn/get-track-data');
}
}
ajax.post(baseURL + '/turn/move-train-set-destination', param);
}
// If we still haven't ARRIVED at our final destination, we are ENROUTE so continue
// moving the train until final destination is reached
//if(statusFinalDest == 'ENROUTE') {
// clearTimeout(timerId);
// timerId = setTimeout(myEventMoveTrainManual, 1000);
//}
}
However, the original problem still manifests: myEventMoveTrainManual() gets called too many times.
you need your setTimeout to be within the callback of your ajax call (ajax.onDone if I'm reading this correctly)
I'm assuming you want your ajax call to be called again only after the first call has completed. Currently, this code will execute your function once a second, unconcerned with the pending asynchronous calls.
Is that what you want? Or do you want it to be executed one second after your ajax returns? If the latter, put your setTimeout within that callback and you'll only get the next request 1s after your ajax returns.
edit with adjusted example:
I still don't see your setTimeout within the ajax call. Here's some pseudo code and an explanation:
function myFunc(){
var ajax = new Ajax();
ajax.onDone = function(data){
// do some stuff here (ie modify mutex)
// now trigger your setTimeout within this onDone to call myFunc() again:
setTimeout(myFunc,1000);
}
ajax.post("someURL")
}
explanation
Here's what happens, you call myFunc(), it instantiates your ajax object and makes the call. When that ajax returns, you do whatever you want to do, then call myFunc() again (the setTimeout) after x amount of milliseconds (inside onDone). This instantiates your ajax object and makes the call...
I am not sure of the code but, but problem seems like this:
You are checking if statusFinalDest == 'ENROUTE' at the client side, which does not work.
Place a timer based counter on the server side before setting the global value to ENROUT and not setting it every time ie set 1 sec delay in setting the value also, as any client side method would get overridden by a fresh copy of js code.
Your Ajax-calls are asynchronous. I think even if the back end is ready, the response needs some time to get back to the client. Meanwhile the client sends more Ajax-requests.
(edit: Your changes approve that this is not the problem)
it seems that you registered an event listener for a DOM-event like mouse-click, because you are checking the evt-argument.Please post the code of your event handler and the part of the code, where you register the event.
Who says that there are too much calls? I can't see count-variables in the server/client scripts; Note that log-items are sometimes added very late to the console, they are not a indicator to my eyes.I think it's very important to know your indicators!

Grab session variable from php page via jquery?

I edited my original text to demostrate my entire set of code for those that weren't understanding my question. All this works perfect when I had my database use MyISAM but when I changed over to InnoDB I now have to account for my foreign key or the mysql_queries won't successfully execute. I have the user_id in a session variable that gets created at the time a user logs in. I would figure I need to relay that number (int) from this session variable and append it to the $_GET so that it can be transferred to the todo.class.php for processing right?
the final get() would perhaps need to look like this ?action=new&user_id=1 (or what ever number the user is)&text=text type by user...
if there is a better way to do this, i'm all ears and ready to learn! ;-)
todo.js
$(document).ready(function(){
$(".todoList").sortable({
axis : 'y',
containment : 'window',
update : function(){
var arr = $(".todoList").sortable('toArray');
arr = $.map(arr,function(val,key){
return val.replace('todo-','');
});
$.get('././process/todo/todo.ajax.php',{action:'rearrange',positions:arr});
},
/* Opera fix: */
stop: function(e,ui) {
ui.item.css({'top':'0','left':'0'});
}
});
var currentTODO;
$("#dialog-confirm").dialog({
resizable: false,
height:130,
modal: true,
autoOpen:false,
buttons: {
'Delete item': function() {
$.get("././process/todo/todo.ajax.php",{"action":"delete","id":currentTODO.data('id')},function(msg){
currentTODO.fadeOut('fast');
})
$(this).dialog('close');
},
Cancel: function() {
$(this).dialog('close');
}
}
});
$('.todo').live('dblclick',function(){
$(this).find('a.edit').click();
});
$('.todo a').live('click',function(e){
currentTODO = $(this).closest('.todo');
currentTODO.data('id',currentTODO.attr('id').replace('todo-',''));
e.preventDefault();
});
$('.todo a.delete').live('click',function(){
$("#dialog-confirm").dialog('open');
});
$('.todo a.edit').live('click',function(){
var container = currentTODO.find('.text');
if(!currentTODO.data('origText'))
{
currentTODO.data('origText',container.text());
}
else
{
return false;
}
$('<input type="text">').val(container.text()).appendTo(container.empty());
container.append(
'<div class="editTodo">'+
'<a class="saveChanges" href="#">Save</a> or <a class="discardChanges" href="#">Cancel</a>'+
'</div>'
);
});
$('.todo a.discardChanges').live('click',function(){
currentTODO.find('.text')
.text(currentTODO.data('origText'))
.end()
.removeData('origText');
});
$('.todo a.saveChanges').live('click',function(){
var text = currentTODO.find("input[type=text]").val();
$.get("././process/todo/todo.ajax.php",{'action':'edit','id':currentTODO.data('id'),'text':text});
currentTODO.removeData('origText')
.find(".text")
.text(text);
});
var timestamp=0;
$('#addButton-todo').click(function(e){
if((new Date()).getTime() - timestamp<5000) return false;
$.get("././process/todo/todo.ajax.php",{'action':'new','text':'New Todo Item. Doubleclick to Edit.','rand':Math.random()},function(msg){
$(msg).hide().appendTo('.todoList').fadeIn();
});
timestamp = (new Date()).getTime();
e.preventDefault();
});
});
todo.class.php
<?php
class ToDo{
private $data;
public function __construct($par){
if(is_array($par))
$this->data = $par;
}
public function __toString(){
return '
<li id="todo-' . $this->data['id'] . '" class="todo">
<div class="text">' . $this->data['text'] . '</div>
<div class="actions">
Edit
Delete
</div>
</li>';
}
public static function edit($id, $text){
$text = self::esc($text);
if(!$text) throw new Exception("Wrong update text!");
mysql_query("UPDATE `todo` SET `text` = '".$text."' WHERE `id`=".$id );
if(mysql_affected_rows($GLOBALS['link'])!=1)
throw new Exception("Couldn't update item!");
}
public static function delete($id){
mysql_query("DELETE FROM `todo` WHERE `id` = ".$id);
if(mysql_affected_rows($GLOBALS['link'])!=1)
throw new Exception("Couldn't delete item!");
}
public static function rearrange($key_value){
$updateVals = array();
foreach($key_value as $k=>$v)
{
$strVals[] = 'WHEN '.(int)$v.' THEN '.((int)$k+1).PHP_EOL;
}
if(!$strVals) throw new Exception("No data!");
mysql_query("UPDATE `todo` SET `position` = CASE `id`".join($strVals)." ELSE `position` END");
if(mysql_error($GLOBALS['link']))
throw new Exception("Error updating positions!");
}
public static function createNew($uid,$text){
$text = self::esc($text);
if(!$text) throw new Exception("Wrong input data!");
$posResult = mysql_query("SELECT MAX(`position`)+1 FROM `todo`");// WHERE `user_id` = 1");
if(mysql_num_rows($posResult))
list($position) = mysql_fetch_array($posResult);
if(!$position) $position = 1;
mysql_query("INSERT INTO `todo` SET /*`user_id` = {$uid},*/ `text` = '".$text."', `position` = ".$position);
if(mysql_affected_rows($GLOBALS['link'])!=1)
throw new Exception("Error inserting TODO!");
echo (new ToDo(array(
'id' => mysql_insert_id($GLOBALS['link']),
'text' => $text
)));
exit;
}
public static function esc($str){
if(ini_get('magic_quotes_gpc'))
$str = stripslashes($str);
return mysql_real_escape_string(strip_tags($str));
}
}
?>
todo.ajax.php
<?php
require "../../dbc.php";
require "../../resources/classes/todo.class.php";
$id = (int)$_GET['id'];
try{
switch($_GET['action'])
{
case 'delete':
ToDo::delete($id);
break;
case 'rearrange':
ToDo::rearrange($_GET['positions']);
break;
case 'edit':
ToDo::edit($id,$_GET['text']);
break;
case 'new':
ToDo::createNew($_GET['text']);
break;
}
}
catch(Exception $e){
echo $e->getMessage();
die("0");
}
echo "1";
?>
Why do you need the session id on the client side? jQuery is sending a GET request to a PHP script on your server. To your PHP script it looks like any other request. The $_SESSION array will be in place and all the session-related functions will work just fine.
Trusting the client to provide a session id is a really bad idea.
I don't follow your script entirely, but to my knowledge the only way to get the current session ID reliably into JavaScript space is
(... head section of the HTML document ...)
<script type="text/javascript">
php_session_id = "<?php echo session_id(); ?>"
alert("The PHP session ID is "+php_session_id);
</script>
#s2xi I realize you are looking for an answer to a simple question, "How do I get the PHP session id into my javascript?" and Unicron's answer is a foolproof way of doing that.
I think we are just trying to figure out why you need to put the PHP session id in your GET request. Your PHP script will always know the user's session id, you just need to call session_id(). There's no need to put it in your GET request. (Let's ignore the cookies-disabled edge case for now, I think it's clear we have bigger fish to fry)
Other things I'm worried about:
Tying data in your database to the session id doesn't make a whole lot of sense. As soon as that user's session expires, you will never be able to tie that data back to them. Am I missing something here?
You are using GET requests to perform actions and modify data. This is a really bad idea.

How to alert or warn a user that session will be expiring soon in php codeigniter

Basically I'm looking for a solution where a user is notified five minutes before the session expires.
The ideal solution will be count down notification that will have an option to renew the session.
If the countdown timer expires without the user refreshing the page, I need to log them out.
Since the session will be refreshed as soon as you go back server-side and the script calls session_start() you really need to do this in Javascript. However if the user has two browser windows open with a split session and one is inactive, while the user is still generating traffic with the other, then the javascript in the idle window would incorrectly report that the session was about to expire. So you'd need to implement your own ajax wrapper to detect the age of the session without calling session_start().
Something like:
$session_id=$_REQUEST[session_name()];
// if you use the default handler:
$session_last_access=filemtime(session_save_path() . '/' . $session_id);
$time_left=time() + session_cache_expire() - $session_last_access;
C.
Depends on what exactly you want to achieve. When someone uses multiple tabs/windows, a window can stay open for very long without the session expiring. AJAX operations complicate things even further. If you want accurate notifications, you will have to set up a timer, and when it fires, check via an AJAX request (taking care not to renew the session) whether the estimate is still accurate.
Added this script in view:
`
if(isSessionAlive >0)
{
var timer = {
time: 0,
now: function(){ return (new Date()).getTime(); },
start: function(){ this.time = this.now(); },
since: function(){ return this.now()-this.time; }
}
var timerId;
sess_expiration = <?=($this->config->config["sess_expiration"]*1000)?>;
alertTime = <?=($this->config->config["sess_time_to_alert"])?>;
timerId = window.setTimeout("pingCI()",sess_expiration-((alertTime*1000)));
jsBaseurl = "<?=($this->config->config["base_url"])?>";
}
function resetTimer(resetTime)
{
//alert('RESET Time'+resetTime);
window.clearTimeout(timerId);
timerId = window.setTimeout("pingCI()", resetTime);
return;
}
function pingCI()
{
if(isSessionAlive > 0)
{
$.ajax({
type: "POST",
url: "<?= site_url('users/getSessionTimeLeft') ?>/",
data: "sessid=<?=$this->session->userdata("session_id")?>",
success: function(transport)
{
response = transport;
if(response=='')
{
parent.location.assign(jsBaseurl+'users/logout');
}
else if((response<=(alertTime*1000)) || (response-1000<=(alertTime*1000)))
{
alertSessionTimeOut(response);
}
else
{
resetTime = eval((response - alertTime)*1000);
resetTimer(resetTime);
}
}
});
}
}
function alertSessionTimeOut(alertTimeExp)
{
if(isSessionAlive>0)
{
var response='';
var timerIdEnd;
timerAlert = window.setTimeout("forceLogout()",alertTimeExp*1000);
timer.start(); // start counting my friend...
fConfirm = confirm('Your Session is about to time out. Please click OK to continue the session');
if(timer.since() >= (alertTime*1000))
{
parent.location.assign(jsBaseurl+'users/logout');
}
if(fConfirm ==true)
{
$.ajax({
type: "POST",
url: "<?= site_url('users/keepAlive') ?>/",
data: "sessid=<?=$this->session->userdata("session_id")?>",
success: function(transport)
{
response = transport;
if(response=='')
{
parent.location.assign(jsBaseurl+'users/logout');
}
window.clearTimeout(timerAlert);
resetTimer(sess_expiration-((alertTime)*1000));
}
});
}
else
{
//parent.location.assign(jsBaseurl+'users/logout');
window.clearTimeout(timerAlert);
window.clearTimeout(timerId);
}
}
}
function forceLogout()
{
parent.location.assign(jsBaseurl+'users/logout');
}
And in Users Controller:
function getSessionTimeLeft()
{
$ci = & get_instance();
$SessTimeLeft = 0;
$SessExpTime = $ci->config->config["sess_expiration"];
$CurrTime = time();
$lastActivity = $this->session->userdata['last_activity'];
$SessTimeLeft = ($SessExpTime - ($CurrTime - $lastActivity))*1000;
print $SessTimeLeft;
}
function keepAlive()
{
$this->load->library('session');
$this->session->set_userdata(array('last_activity'=>time()));
if(isset($this->session->userdata["user_id"])) print 'ALIVE';
else print '';
}
`
One way is to store in one javascript variable the time remaining, and update the variable in every page refresh
Create one javascript function with one settimeout that verifies the value of the variable that you set in 1.)
Regards,
Pedro

Categories