Preventing the same IP from executing an action for 24 hours - php

So I've been programming this thing that allows a user to click a button once every 24 hours and is blocked to do so in the meantime. The concrete example is a vote script that should only allow a vote every 24 hours (albeit a user can vote for the same thing again).
So what I did was create a timelimit table in MySql
CREATE TABLE IF NOT EXISTS `timelimit` (
`ipaddress` VARCHAR(50) NOT NULL ,
`ipdate` DATETIME NOT NULL ,
PRIMARY KEY (`ipaddress`) )
ENGINE = MyIsam;
The IP-address is a string because I was too lazy to convert it to a proper integer format and it should allow for IPv6 addresses, too. Also there's no 128 bit integer, is there?
I'll leave the other table that saves the votings out, I don't think it's in the scope of this question.
So my issue is... random. Some people couldnt vote for days straight, for example. Also we saw a rapid increase (a few hundred) in one day once. We suppose though that was the work of a bot, since this script doesn't yet have any security in terms of verifying if that actually was a click by a user (and it's pointless, because everything can be done by bots. Just a matter of making it harder).
I just voted today and tried again, the program told me I can vote again in... 0 hours and 59 minutes... It should rather be 23 hours and 59 minutes!
Something is so wrong here and I can't put my finger on it. At all!
$oneday = 86400;
$ip = trim($_SERVER['REMOTE_ADDR']);
require_once "config.php";
$conn = mysql_connect($mysql_host, $mysql_user, $mysql_pass);
mysql_select_db($mysql_db);
//I apologize for still using mysql_
if (!$conn) {
die("Connection error!");
}
mysql_query("SET time_zone='+0:00';"); // This helped me fix some issues because the field is DateTime
$res = mysql_query("SELECT COUNT(*) FROM `timelimit` WHERE ipdate <= DATE_ADD(NOW(), INTERVAL 1 DAY) AND `ipaddress` = '$ip';");
/* supposed to count if there is any entry corresponding
to the IP address wihich is in the blocked timeframe.
Could be the source of an error, but I can't put my finger on it... */
if (mysql_result($res,0) > 0) {
$res2 = mysql_query("SELECT UNIX_TIMESTAMP(ipdate) as ipdate FROM timelimit WHERE ipaddress = '$ip';");
$lastvote = mysql_result($res2, 0, "ipdate");
$nextvote = $lastvote + $oneday;
$timebetween = time() - $lastvote;
$timetonext = $nextvote - time();
//Some calculations done for the sake of output.
die("You can only vote once a day. You can vote again in " . date("H", $timetonext) . " hours and " . date("i", $timetonext) . " minutes");
} else {
if (isset($_REQUEST['postid'])) {
$postid = $_REQUEST['postid'];
$postid = mysql_real_escape_string($postid);
if (!is_numeric($postid)) {
die("post id not valid");
} //The input would be sanitized by now (I hope?)
//VOTE IS VALID
//*SNIP* voting logic (increases the counter with an UPDATE table SET votes = votes + 1)
//Done with the voting, time limit logic again:
mysql_query("DELETE FROM `timelimit` WHERE ipaddress = '$ip';");
mysql_query("INSERT INTO `timelimit` (`ipaddress`, `ipdate`) VALUES ('$ip', UTC_TIMESTAMP);");
// Could probably be combined into an INSERT and ON EXIST UPDATE query.
}

Use PEAR FloodControl it does everything for you.
Example taken from http://www.tozalakyan.com/fc/index.php#db
CREATE TABLE fc_logs (
unique_id varchar(32) NOT NULL,
data text NOT NULL,
access int UNSIGNED NOT NULL,
PRIMARY KEY (unique_id)
)
.
require_once 'HTTP/FloodControl.php';
try {
$ip = HTTP_FloodControl::getUserIP();
} catch (HTTP_FloodControl_Exception $e) {
die($e);
}
try {
$fc =& new HTTP_FloodControl();
$fc->setContainer('MDB2', array(
'dsn' => 'mysql://username:password#localhost/dbname',
'table' => 'fc_logs',
'autooptimize' => true
));
$limits = array (
86400 => 10 // maximum 10 requests in 24 h
);
if (!$fc->check($limits, $ip)) {
die('Too many requests. Please try later.');
}
} catch (HTTP_FloodControl_Exception $e) {
die($e);
}
// Your code...
Requirements and Dependencies:
PHP 5
PEAR::Exception
PEAR::DB (optional)
PEAR::MDB (optional)
PEAR::MDB2 (optional)

Related

SQL insert Or update times out if large date in loop

i have some data with userid and date.
Sometimes there is large datas i need to loop through and update the sql database but the database times out.
is there any better way i can do this please, sample code below.
foreach($time[$re->userid][$today] as $t){
if(($re->time >= $t->in_from) && ($re->time < $t->in_to)
&& md5($t->WorkDay."_in".$re->date) != $in){//in
$tble = tools::sd("{$t->WorkDay} in");
}
if(($re->time >= $t->out_from) && ($re->time < $t->out_to)
&& md5($t->WorkDay."_out".$re->date) != $out){//out
$tble = tools::sd("{$t->WorkDay} out");
if($tble =='nout'){
$re->date2 = tools::ndate($re->date . "- 1");
}
}
if(!empty($tble)){
$q = array(
"id" => $re->userid
, "dt" => $re->date2
, "{$tble}" => $re->time
);
dump($q); // insert into sql
}
}
dump function:::
function dump($d ='')
{
if(!empty($d)){
end($d);
$tble = key($d);
$d['ld'] = "{$d['dt']} {$d[$tble]}";
$r = $GLOBALS['mssqldb']->get_results("
IF NOT EXISTS (select id,ld,dt,{$tble} from clockL
WHERE id = '{$d['id']}'
AND dt ='{$d['dt']}')
INSERT INTO clockL (id,ld,dt,{$tble})
VALUES ('{$d['id']}','{$d['ld']}','{$d['dt']}'
,'{$d[$tble]}')
ELSE IF EXISTS (select id,{$tble} from clockL
WHERE id = '{$d['id']}'
AND dt ='{$d['dt']}'
AND {$tble} = 'NOC'
)
update clockL SET {$tble} ='{$d[$tble]}', ld ='{$d['ld']}' WHERE id = '{$d['id']}'
AND dt ='{$d['dt']}' AND {$tble} ='NOC'
");
//print_r($GLOBALS['mssqldb']);
}
}
Thank You.
Do the insert/update outside of the loop. Enclose it in a transaction so that you don't get an inconsistent database state if the script dies prematurely. Using one big query is usually faster than making lots of small queries. You might also set higher values for time and memory limits, but be aware of the consequencies.
Are you aware of a PHP function called set_time_limit()? You can find the detailed documentation here.
This can manipulate the execution time, which is 30 seconds default. If you set it to 0, eg set_time_limit(0), there will be no execution time limit.
May be looping is the reason for time out.
Because when your performing the insert/update operations in side the loop, the connection to the database will be in open state until the loop is terminated which may cause the time out problem.
Try doing the insert/update operation outside of the loop.

Alter table after X minutes, even if user closed window, is this a good way?

I'm trying to make a mission system where users can accept a mission or not, and if it hasn't been accepted after X minutes it will be inactivated. Is this a good way to do it, would it be able to handle 10K missions per day?
<?php
$mission_id = htmlspecialchars($_POST["mission_id"]));
$user = $_SESSION["user"];
// Verify that user is same as mission agent
$verify = $conn->prepare("SELECT agent FROM missions WHERE id = ? AND active = 0 ORDER BY id limit 1");
$verify->bindParam(1, $user);
$verify->execute();
$verify = $verify->fetch(PDO::FETCH_ASSOC);
if($verify["agent"] == $user) {
unset($verify);
// Do time code.
ignore_user_abort(true);
set_time_limit(300);
$time = 0;
while(time < 300) {
sleep(15);
time += 15;
// check if mission was accepted
$verify = $conn->prepare("SELECT accepted FROM missions WHERE id = ? ORDER BY id LIMIT 1");
$verify->bindParam(1, $mission_id);
$verify->execute();
$verify = $verify->fetch(PDO::FETCH_ASSOC);
if($verify["accepted"] == 0) { // not accepted
unset($verify);
// Inactivate mission
$inactivate = $conn->prepare("UPDATE missions SET active = 0 WHERE id = ?");
$inactivate->bindParam(1, $id);
$inactivate->execute();
unset($inactivate);
}
else {
break;
}
}
}
else {
header("location: logout.php");
// Log user out
}
?>
Use mysql Create Event functionality that is perfect for these types of situations. Think of them as scheduled stored procedures (as complicated as you want) to fire often in very flexible re-occurring fashion.
This functionality was put in to do away with cron especially when database-only operations are to occur.
A high-level view of it can be seen here in an Answer I wrote up.
I would use an expire date timestamp and set it
To 17 minutes in the future when u create the mission
If they don't act upon it. It will automatically expire
If they do.. Update the expire record to 9999-12-31
And not use active/inactive. Just use current time to get all active missions.
Thrn no event needed. No crontab. No extra code

PHP: Online Offline Status

Hi since 3 hour I am trying to make this work but not getting the result as I want. I want to display user list with online and offline status.
Here is the table
and here what I tried to get status result.
$loggedtime = time() - 300; // 5 minutes
$query = 'SELECT userid, handle FROM ^users WHERE loggedin = '.$loggedtime.' ORDER BY userid ASC';
// below are scripts function qa_ pleses refer this http://www.question2answer.org/functions.php
$result = qa_db_query_sub($query);
$users = qa_db_read_all_assoc($result);
$row = mysql_fetch_array($result);
if($row['userid'] > $loggedtime){
echo $row['handle'].' is online';
} else {
echo $row['handle'].' is offline';
}
NOT THIS TOO
foreach($users as $user){
if($user['userid'] > $loggedtime){
echo $user['handle']. ' is online';
} else {
echo $row['handle'].' is offline';
}
}
None of above code working. I am new to MYSQL and PHP just know basic so please help me to solve this.
EDIT:
I have tried now this but not working
foreach($users as $user){
if($user['loggedin'] > $loggedtime){
echo $user['handle']. ' is online';
} else {
echo $row['handle'].' is offline';
}
}
EDIT 2
$query = "SELECT
userid, handle,
CASE
WHEN TIMESTAMPDIFF(SECOND, loggedin, NOW()) < 300
THEN 'Online'
ELSE 'Offline'
END AS 'status'
FROM ^users
ORDER BY userid";
$result = qa_db_query_sub($query);
while($user = mysql_fetch_array($result)){
echo $user['handle'] . '<BR/>';
}
NEW APPROACH
Please check this for new approach User online offline status - offline status issue
Since you fixed the user id comparison, let's address the next issue..
You're trying to compare a string DATE versus a unix timestamp. Let's make them the same type and compare:
foreach($users as $user)
{
$user_time = strtotime($user['loggedin']);
if($user_time > $loggedtime)
{
echo $user['handle']. ' is online';
} else {
echo $row['handle'].' is offline';
}
}
Overall not the best way to approach this problem, but it might get this working for you. The database solution above is probably best.
Your structure looks funny to answer the question. Your loggedin field actually looks more like a "the last time they logged in". Just because you know when they logged in doesn't necessarily mean they are "online".
The reason your query isn't working is because you are comparing a UNIX timestamp to a mysql datetime. In addition, you are using = so unless they logged in EXACTLY five minutes ago, this will not work.
At minimum.
SELECT userid, handle FROM ^users WHERE loggedin > '.date('Y-m-d h:i:s', time()-300).'ORDER BY....
Why not just check on the database side?
SELECT
userid, handle,
CASE
WHEN TIMESTAMPDIFF(SECOND, loggedin, NOW()) < 300
THEN 'Online'
ELSE 'Offline'
END AS 'status'
FROM ^users
ORDER BY userid
You can use TIMESTAMPDIFF(unit,datetime_expr1,datetime_expr2) to return datetime_expr2 – datetime_expr1, where datetime_expr1 and datetime_expr2 are date or datetime expressions. One expression may be a date and the other a datetime; a date value is treated as a datetime having the time part '00:00:00' where necessary. The unit for the result (an integer) is given by the unit argument. The legal values for unit are the same as those listed in the description of the TIMESTAMPADD() function.
Take a look at the MySQL Date and Time Functions.
Also, I strongly advise using reserved words for table names.

PHP MYSQL Insert statement in FOR loop Hanging

I have spent many hours debugging, and scouring the internet for a solution to this unusual problem. Heres the deal:
I am working on a Work Order Submission and Tracking system. There are two databases involved:
The database where the submissions data gets posted, which is located on the same physical machine, but on a separate virtual machine as the webserver serving the php. They are on the same class C subnet.
The database of our tracking system. Located on a different physical server on a different IP altogether, also a virtual machine.
Our work order system allows for multiple 'services requested', stored in an array. In our sumbissions database, this is stored as a comma separated string, i.e. "40,60,70" but in our tracking system database, each 'service requested' needs a separate entry, as to allow the different aspects of the project to be tracked and completed at different times, by different staff.
THE PROBLEM IS: When I place my second insert statement, the one destined for the tracking database, in a for loop, it completely hangs, and takes maybe 5 to 15 minutes, before it passes that point in the code, and sends the confirmation email. The data does not get inserted either.
When I take it out of the for loop and simply do one insert in the submissions database and one insert into the tracking system, it works fine.
First, Ill post the code that works, but only posts one 'service' to the tracking system:
public function insertOrder()
{
$services = implode( ',', $this->model->chk );
$curdate = $this->model->getMySQLDate( $this->model->curdate );
$dueDate = $this->model->getMySQLDate( $this->model->dueDate );
$sql = "INSERT INTO orders VALUES(DEFAULT,
{$this->sanitize($services)},
{$this->sanitize($curdate)},
{$this->sanitize($this->model->submittedBy)},
{$this->sanitize($this->model->shortDesc)},
{$this->sanitize($this->model->projDetails)},
{$this->sanitize($dueDate)},
{$this->sanitize($this->model->dueDateNotes)},
{$this->sanitize( $this->model->approveBy)},
{$this->sanitize( $this->model->cost )} )";
$this->execute( $sql );
$this->convertServicesToTracks();
$notes = $this->model->getTracksNotes();
$dueDate = $dueDate.' 12:00:00';
$shortDescNoQuotes = str_replace("\"","'",$this->model->shortDesc);
$sqlTracks = "INSERT INTO todos VALUES(DEFAULT,
{$this->sanitizeTracks($this->model->chk[0])},
NULL,
{$this->sanitizeTracks($shortDescNoQuotes)},
{$this->sanitizeTracks($notes)},
now(),
{$this->sanitizeTracks($dueDate)},
NULL,
12,
NULL,
'active',
NULL,
now() );";
//echo $sqlTracks;
$this->executeTracks( $sqlTacks );
} private function executeTracks( $sql )
{
$db = $this->getTracksDB( );
$this->check4Error( $db, $sql );
return $result;
}
private function getTracksDB()
{
if (!$this->tracksdb) $this->tracksdb = new mysqli(AbstractSQL::TRACKS_HOST, AbstractSQL::USER, AbstractSQL::PASS, AbstractSQL::TRACKS_SCHEMA);
return $this->tracksdb;
}
private function convertServicesToTracks()
{
//converts submission data to tracking system data
}
private function sanitizeTracks($arg)
{
if (!isset($arg)) return "NULL";
if (is_numeric($arg) && !is_double( $arg) ) return $arg;
return "'{$this->getTracksDB()->escape_string($arg)}'";
}
When I add this simple for loop around the second INSERT statement, it hangs, even if the array only has one item!
for($i = 0; $i < count($this->model->chk); ++$i)
{
$sqlTracks = "INSERT INTO todos VALUES(DEFAULT,
{$this->sanitizeTracks($this->model->chk[$i])},
NULL,
{$this->sanitizeTracks($shortDescNoQuotes)},
{$this->sanitizeTracks($notes)},
now(),
{$this->sanitizeTracks($dueDate)},
NULL,
12,
NULL,
'active',
NULL,
now() );";
//echo $sqlTracks;
$this->executeTracks( $sqlTacks );
}
Any help would be greatly appreciated. And I apologize for the long code snippets!!
Is it iterating through the for loop? I see you have an echo, did that write anything out? How many items does it have to iterate through? 5 min seems like a long time but if there are a lot of items that could be why it's taking so long. Are you seeing any errors in your logs?
Something you might try is hold the count in a variable so it doesn't have to calculate that each time. It might speed up your for loop but I'm not sure it will insert the data.
for($i = 0, $count = count($this->model->chk); $i < $count; ++$i)
{
$sqlTracks = "INSERT INTO todos VALUES(DEFAULT,
{$this->sanitizeTracks($this->model->chk[$i])},
NULL,
{$this->sanitizeTracks($shortDescNoQuotes)},
{$this->sanitizeTracks($notes)},
now(),
{$this->sanitizeTracks($dueDate)},
NULL,
12,
NULL,
'active',
NULL,
now() );";
//echo $sqlTracks;
$this->executeTracks( $sqlTacks );
}
I found this in the PHP for loop reference: http://php.net/manual/en/control-structures.for.php
Well, this may not be the problem, but shouldn't you generally use a foreach loop to avoid hitting parts of the array that may not exist? There is more about this here. If you loop through an empty index, it would break your SQL statement. Like this:
foreach($this->model->chk as $val)

Allowing users to bump an input they submitted every hour?

I want people to be able to "bump" what they've wrote in my database but at the same time only allow the input to be in the table ONCE at a time.
For Example:
Jim's code is 5555. Jim enters his code and it shoots to the very bottom of the table. After 34 minutes he tries to enter his code in again (Because various other people have inputted their code between now and then) but gets a display error letting him know he has 26 minutes to wait still.
Joe inputs his code and waits an hour and five minutes and is able to push his code back to the bottom again.
Basically, I'm displaying data from the bottom up in my table.
Is there any way to easily do this?
function some_more_custom_content() {
$output="<BR>";
ob_start();
if ($_REQUEST['code'] != "") {
$code = $_REQUEST['code'];
$query="INSERT INTO `fc` (`code`,`datetime`) values ('" . mysql_real_escape_string($code) . "', now())";
$result=mysql_query($query);
$entry['datetime'] = strtotime($entry['datetime']);
while ($fetch_array = mysql_fetch_array($result)) {
$seconds = time() - strtotime($fetch_array["datetime"]);
if ((time() - $entry['datetime']) < 60*60) {
echo ("The code " . htmlentities($code) ." was updated less than an hour ago.");
} else {
echo ("Inserted " . htmlentities($code) ." into the top.");
}
}
?>
I get a syntax error. Any idea where it is?
UPDATE: Getting error of:
Parse error: syntax error, unexpected $end
you should create a table with a unique index on the code field and then use a query like:
INSERT INTO CODES (code)
VALUES (555)
ON DUPLICATE KEY UPDATE lastUpdated =
case when NOW() - INTERVAL 5 MINUTE > lastUpdated
then NOW()
else lastUpdated end
this will update the lastUpdated field only in cases when it's older than 5 minutes
Not that hard:
Use a timestamp for the last "bump time".
When a code is entered, check if it already exists in the database.
If it doesn't, insert it and set the bump timestamp to now().
If it does exist, check if the timestamp was within the hour.
If it was, display error.
If it wasn't, reset it to now().
Sort your display data by bump timestamp.
EDIT:
$entry = /* get entry from database, assuming the case where it already exists */;
// depending on the format of timestamp you get from the database,
// you may have to convert it to a UNIX timestamp:
$entry['timestamp'] = strtotime($entry['timestamp']);
if ((time() - $entry['timestamp']) < 60*60) { // 60*60 is one hour in seconds
// display error
} else {
// reset bump
}

Categories