Sometimes, after user request, is necesary to keep run current script in background to process some events about updates. Specific events must update some keys on user session.
My code:
<php
ini_set('session.save_path',__DIR__ . '/../!PHPSESSID');
session_start();
// ...... page content - db queries and other stuff. here can be generated some events
//close user connection and keep running script in background
ignore_user_abort(true);
//fastcgi_finish_request(); -- no need in my case
header('Connection: close');
header('Content-Length: '.ob_get_length());
session_write_close();
ob_end_flush();
flush();
//OK, user connection are closed. This script is running now in bacground
set_time_limit(120); // for me is more than enough - OR set 0
//... my database store all sids by each user id for each connections (e.g. same user, by id 123, have connections: 2 from pc(difrent browser), 1 from mobile .... ). I always know how ~ many connections user have and how to find him to send some events
if($haveSomeSpecificEvents){
foreach($specificEvents as $item){
if(!file_exists(session_save_path().'/sess_'.$item['sid'])){
continue; //skip
}
session_id($item['sid']); // generate warning: headers already sent
session_start(); // generate warning: headers already sent
//check if session is what i am looking for like $_SESSION['id'] == $item['user_id'], if not - just skip this
//place some updates (flag) in $_SESSION, but is always NULL.
session_write_close(); // save changes
}
}
Of cource, i can do request using CURL to my domain - i think this is not a good ideea.
P.S: English is not my first language
Well, I waited for someone to answer my question, but I had to answer it myself. Maybe this will help someone. My solution: turn on output buffering. Just add ob_start(); on top the script.
Related
I'm working on a system where an android app needs to send periodic updates to a server to say it is running okay. If 4 checks pass and an update hasn't been sent, a text message needs to be sent. I want to use a counter in the PHP file that checks how many 'checks' have passed without an update. However every time the android application contacts the server the counter is reset and never increases. I have it working but I don't want the message to be sent until the counter is 4. Does anyone have any suggestions on how to retain the value of the counter when the file is 'reopened'. Thanks.
<?php
//...
// check the value sent from the android application
if(isset($_REQUEST['alert'])){
echo "alert";
// everything is ok, reset the counter
$counter = 0;
}
else echo "no alert";
// increase the counter
$counter++;
if($counter >= 4) {
while($row = mysql_fetch_array($result)) {
$phNum = $row['mobile'];
}
// an update has not been sent in 4 attempts, send a text message
send_sms($phNum);
}
//...
?>
Perhaps you could:
Use SharedPreferences in Android App which would keep count of checks? then send it to server to say all 4 checks were passed?
PHP script is executed from beginning every time you visit website, another method would be to use sessions but that will get a bit messy, and another way: when request is called, it would send your data + device ID (unique ID to each device), then it would add number of checks into database and keep track of checks for each device, that would slow down your execution time by not that much.
But as I said already, Shared Preferences would work.
If your android app is properly sending and storing cookies you can use the php $_SESSION or $_CCOKIE variable for persistent data.
I am trying to create a PHP trigger for when a user views certain pages on my website it will update the user table in the points section.
I understand the process would work something like this
on page view > update user > where user id is (**get username from session**) > add 5 to points row
Anyone have any idea how to set up something simple like this for giving users simple points for viewing pages?
My site is using PHP and mySQL for the database.
Use cookies or session variables to keep track of the user details like the username or ID. So making a pageview trigger would be as easy as adding a mysql query at the top of every page which would update the database table for views. Kinda the same way that forums operate.
E.g
<?php
session_start();
$db_connection = mysqli_connect('host','username','password','db');
$user_id = $_SESSION['userid']; //That is asssuming that you had gotten the user id on login
mysqli_query($db_connection, 'UPDATE page_views SET views_column=views_column+1 WHERE userid=$user_id');
?>
Yes, you could do something like (if you own the page the user has to visit):
<?php
$pointsForThisSite = 5;
include "points_adder.php";
?>
While Points_adder looks whether $pointsForThisSite is defined and > 0, then adds the Points to the database as you descripbed.
Is that what you are looking for?
Create a php function and call it everytime the user enter the page.
You don't need a mysql trigger because, the action is at the webpage.
function add_points($user, $page){
//If users visits too many maybe you don't want to gave him some points.
//add points
}
and invoke the function in that pages you want to score
The most unobtrusive way to do this is with an AJAX call after the page has loaded. The call should be to an include file that performs the database update operation and returns a 204 response so that the visitor's browser doesn't wait for response content.
For an Apache server;
header('HTTP/1.0 204 No Content');
header('Content-Length: 0', true);
header('Content-Type: text/html', true);
flush();
// do the table update here
I have been tracking emails for years using a "beacon" image and for those clients that allow the images to download it has worked great to track how many people have opened the email.
I came across the service "DidTheyReadIt" which shows how long the client actually read the email, I tested it with their free service and it is actually pretty close to the times I opened the email.
I am very curious in how they achieve the ability to track this, I am certain that whatever solution is chosen it will put a lot of load on the server / database and that many of the community will reply with "Stop, No and Dont" but I do want to investigate this and try it out, even if its just enough for me to run a test on the server and say "hell no".
I did some googling and found this article which has a basic solution http://www.re-cycledair.com/tracking-email-open-time-with-php
I made a test using sleep() within the beacon image page:
<?php
set_time_limit(300); //1000 seconds
ignore_user_abort(false);
$hostname_api = "*";
$database_api = "*";
$username_api = "*";
$password_api = "*";
$api = mysql_pconnect($hostname_api, $username_api, $password_api) or trigger_error(mysql_error(),E_USER_ERROR);
mysql_select_db($database_api, $api);
$fileName = "logo.png";
$InsertSQL = "INSERT INTO tracker (FileName,Time_Start,Time_End) VALUES ('$fileName',Now(),Now()+1)";
mysql_select_db($database_api, $api);
$Result1 = mysql_query($InsertSQL, $api) or die(mysql_error());
$TRID = mysql_insert_id();
//Open the file, and send to user.
$fp = fopen($fileName, "r");
header("Content-type: image/png");
header('Content-Length: ' . filesize($fileName));
readfile($fileName);
set_time_limit(60);
$start = time();
for ($i = 0; $i < 59; ++$i) {
// Update Read Time
$UpdateSQL = "UPDATE tracker SET Time_End = Now() WHERE TRID = '$TRID'";
mysql_select_db($database_api, $api);
$Result1 = mysql_query($UpdateSQL, $api) or die(mysql_error());
time_sleep_until($start + $i + 1);
}
?>
The problem with the code above (other than updating the database every second) is that once the script runs it continues to run even if the user disconnects (or moves to another email in this case).
I added "ignore_user_abort(false);", however as there is no connection to the mail client and the headers are already written I dont think the "ignore_user_abort(false);" can fire.
I looked at the post Track mass email campaigns and one up from the bottom "Haragashi" says:
"You can simply build a tracking handler which returns the tracking image byte by byte. After every byte flush the response and sleep for a period of time.
If you encounter a stream closed exception the client has closed the e-mail (deleted or changed to another e-mail who knows).
At the time of the exception you know how long the client 'read' the e-mail."
Does anyone know how I could "simply build a tracking handler" like this or know of a solution I can implement into my code that will force the code to stop running when the user disconnects?
I think the problem is that you aren't doing a header redirect every so often. The reason that it is necessary is because once a script starts executing in PHP+Apache, it basically disregards the client until finished. If you force a redirect every X seconds, it makes the server re-evaluate if the client is still connected. If the client isn't connected, it can't force the redirect, and therefore stops tracking the time.
When I played around with this stuff, my code looked like:
header("Content-type: image/gif");
while(!feof($fp)) {
sleep(2);
if(isset($_GET['clientID'])) {
$redirect = $_SERVER['REQUEST_URI'];
} else {
$redirect = $_SERVER['REQUEST_URI'] . "&clientID=" . $clientID;
}
header("Location: $redirect");
exit;
}
If the client id was set, then above this block of code I would log this attempt at reading the beacon in the database. It was easy to simply increment the time on email column by 2 seconds every time the server forced a redirect.
Would you not do something more like this:
<?php
// Time the request
$time = time();
// Ignore user aborts and allow the script
// to run forever
ignore_user_abort(true);
set_time_limit(0);
// Run a pointless loop that sometime
// hopefully will make us click away from
// page or click the "Stop" button.
while(1)
{
// Did the connection fail?
if(connection_status() != CONNECTION_NORMAL)
{
break;
}
// Sleep for 1 seconds
sleep(1);
}
// Connention is now terminated, so insert the amount of seconds since start
$duration = time() - $time;
I have a web application using PHP and PDO with SQLSRV prepared statements to display links to files for users to download. The back-end PHP script 'download.php' checks various items before serving the PDF to the user to download. The download.php file then should update a few SQL tables, and serve the PDF file to the user.
Please read my
Previous Question
and the troubleshooting completed there, if you need more information.
After troubleshooting, the error I thought was occurring (and thus the previous question I had asked) was incorrect. My download script is getting executed more than once for every file download.
I have searched the server logs and while debugging with Firebug, I can see my download.php script making multiple GET requests to the server. Sometimes the script completes only once as expected. Other times the script executes three to four request for the one click of the download link.
Now that I more fully understand what error is occurring, I need a bit of help fixing it.
I need to prevent the script from running multiple times, and thus updating the SQL table with records that are within a few milliseconds of each other.
The view page checks the SQL database for files the current user is allowed access to, and displays a list of links:
<a href='download.php?f={$item['name']}&t={$type}' target='_blank'>{$item['name']}</a>
Because the values are needed for the download.php script to work, I cannot change the request to a $_POST instead of $_GET.
What I have tried:
Checking/setting a session variable for 'downloading' state, before the getfile() which unsets right before the exit(0)
Putting the SQL statements in a separate PHP file and require'ing that
Adding a sleep(1) after the getfile()
Commenting out the header/PDF information
The first three measures did not work to prevent the double/triple execution of the PHP download script. However, the last measure DOES prevent the double/triple execution of the PHP script, but of course the PDF is never delivered to the client browser!
Question: How can I ensure that only ONE insert/update PER DOWNLOAD is inserted into the database, or at the least, how can I prevent the PHP script from being executed multiple times?
UPDATE
Screenshot of issue in firebug:
One request:
Two requests:
download.php script
<?php
session_start();
require("cgi-bin/auth.php");
// Don't timeout when downloading large files
#ignore_user_abort(1);
#set_time_limit(0);
//error_reporting(E_ALL);
//ini_set('display_errors',1);
function getfile() {
if (!isset($_GET['f']) || !isset($_GET['t'])) {
echo "Nothing to do!";
exit(0);
}
require('cgi-bin/connect_db_pdf.php');
//Update variables
$vuname = strtolower(trim($_SESSION['uname']));
$file = trim(basename($_GET['f'])); //Filename we're looking for
$type = trim($_GET['t']);//Filetype
if (!preg_match('/^[a-zA-Z0-9_\-\.]{1,60}$/', $file) || !preg_match('/^av|ds|cr|dp$/', $type)) {
header('Location: error.php');
exit(0);
}
try {
$sQuery = "SELECT TOP 1 * FROM pdf_info WHERE PDF_name=:sfile AND type=:stype";
$statm = $conn->prepare($sQuery);
$statm->execute(array(':sfile'=>$file,':stype'=>$type));
$result = $statm->fetchAll();
$count = count($result);
$sQuery = null;
$statm = null;
if ($count == 1 ){ //File was found in the database so let them download it. Update the time as well
$result = $result[0];
$sQuery = "INSERT INTO access (PDF_name,PDF_type,PDF_time,PDF_access) VALUES (:ac_file, :ac_type, GetDate(), :ac_vuname); UPDATE pdf_info SET last_view=GetDate(),viewed_uname=:vuname WHERE PDF_name=:file AND PDF_type=:type";
$statm = $conn->prepare($sQuery);
$statm->execute(array( ':ac_vuname'=>$vuname, ':ac_file'=>$file, ':ac_type'=>$type,':vuname'=>$vuname, ':file'=>$file, ':type'=>$type));
$count = $statm->rowCount();
$sQuery = null;
$statm = null;
//$result is the first element from the SELECT query outside the 'if' scope.
$file_loc = $result['floc'];
$file_name = $result['PDF_name'];
// Commenting from this line to right after the exit(0) updates the database only ONCE, but then the PDF file is never sent to the browser!
header("Content-Type: application/pdf");
header("Pragma: no-cache");
header("Cache-Control: no-cache");
header("Content-Length: " . filesize($file_loc));
header("Accept-Ranges: bytes");
header("Content-Disposition: inline; filename={$file_name}");
ob_clean();
flush();
readfile($file_loc);
exit(0);
} else { //We did not find a file in the database. Redirect the user to the view page.
header("Location: view.php");
exit(0);
}
} catch(PDOException $err) {//PDO SQL error.
//echo $err;
header('Location: error.php');
exit(0);
}
}
getfile();
?>
If you really need to make sure that a link only creates an event once, then you need to implement a token system, where when a hyperlink (or a form post target) is generated, a use once token is generated and stored (in the session or wherever), and then is checked in the calling script.
So your hyperlink may look like this:
<a href='download.php?token={some-token}&f={$item['name']}&t={$type}' target='_blank'>{$item['name']}</a>
On the php side this is a really simplified idea of what you might do:
<?php
session_start();
if (!isset($_REQUEST['token']) die(); // or fail better
if (!isset($_SESSION['oneTimeTokens'][$_REQUEST['token']) die(); // or fail better
if ($_SESSION['oneTimeTokens'][$_REQUEST['token']=='used') die(); // or fail better
$_SESSION['oneTimeTokens'][$_REQUEST['token']='used';
// we're good from this point
This would solve the effects of your problem, though not the double running itself. However since you want to make sure a link is firing an event only once NO MATTER WHAT, you probably implement this in some form or another as it's the only way to guarantee that any link generated has a one real use life that I can think of.
When generating the link you would do something like this in your code:
<?php
$tokenID = {random id generation here};
$_SESSION['oneTimeTokens'][$tokenID] = 'not used';
I'd also somewhere put a cleanup routine to remove all used tokens. Also, it's not a bad idea to expire tokens beyond a certain age, but I think this explains it.
The following code is within an ajax call. I'm trying to make sure people don't vote on questions with a certain id too often using sessions.
So they click a button, which executes the following php code:
$id=$_GET["id"];
if ((isset($_SESSION["$id"]) && ((time() - $_SESSION["$id"]) > 180)) || (!isset($_SESSION["$id"]))) {
// last vote was more than 3 minutes ago
$_SESSION["$id"] = time(); // update/create vote time stamp
//there is code here to add the vote to the database
}
else{
echo "sorry, you've already voted recently";
}
So I'm creating a session variable for each question id which holds the time() of their last vote. I would do this with cookies, but they can be disabled.
Currently, there is a bug somewhere with my logic, because it allows the user to keep clicking the button and adding as many votes as they want.
Can anyone see an error that I have made?
using sessions to prevent multiple voting makes very little sense.
sessions do use cookies with the same drawbacks
unlike strings, variables in PHP should be addressed without quotes. such a false usage WILL cause an error someday.
I see no point in checking for isset($_SESSION[$id]) twice.
There was a bug in PHP which disallowed numerical indices for the $_SESSION array. Dunno if it was corrected nowadays.
As it was pointed out by Sajid, you have to call session_start() before using $_SESSION array.
now to the logic.
to me, it seems the code won't let anyone to vote at all. as it won't pass isset($_SESSION[$id]) condition for the first time and won't let $_SESSION[$id] to be set and so on.
it seems correct condition would be
if ( (!isset($_SESSION['vote'][$id]) OR (time() - $_SESSION['vote'][$id]) > 180) )
You need to call session_start() to start the session before any headers are sent. Otherwise, sessions will not be enabled unless the ini setting to autostart sessions is on. Also, your server must be correctly configured to be able to store session files (usually a writable tmp dir is needed). See more about sessions here: http://www.php.net/manual/en/ref.session.php
There might be a problem with the if statement. Try the following
$id=$_GET["id"];
if (((isset($_SESSION[$id]) && ((time() - $_SESSION[$id]) > 180))) || (!isset($_SESSION[$id]))) {
// last vote was more than 3 minutes ago
$_SESSION[$id] = time(); // update/create vote time stamp
//there is code here to add the vote to the database
}
else{
echo "sorry, you've already voted recently";
}
Perhaps time() returns milliseconds and you should compare to 180000 instead of 180.