1<?php 2/* 3 This script acts as the backend communication API for the user notes vote feature. 4 Requests come in here from the php.net to update the database with new votes. 5 master.php.net should respond with a JSON object (with one required property [status] and two optional properties 6 [votes] and [message]). 7 The JSON object [status] property contains a status returned by the server for that request. 8 It's value may be either a boolean true or false. If the status is true the php.net will know the vote went through successfully. 9 The optional [votes] property may then be supplied to update the php.net with the new value of the votes for that note. 10 If the status is false the php.net will know the request for voting failed and an optional [message] property may be 11 set to supply the php.net with a message string, explaining why the request failed. 12 13 Example Success: 14 15 { "status": true, "votes": 1 } 16 17 Example Failed: 18 19 { "status": false, "message": "You have already voted today!" } 20 { "status": false, "message": "Invalid request..." } 21*/ 22 23// Validate that the request to vote on a user note is OK (ip limits, post variables, and db info must pass validation) 24function vote_validate_request(PDO $dbh) { 25 // Initialize local variables 26 $ip = $hostip = $id = $vote = 0; 27 $ts = date("Y-m-d H:i:s"); 28 29 // Validate POST variables 30 if (isset($_POST['ip']) && 31 filter_var($_POST['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE | 32 FILTER_FLAG_NO_PRIV_RANGE | 33 FILTER_FLAG_IPV4)) 34 { 35 $ip = sprintf("%u", ip2long($_POST['ip'])); 36 } else { 37 // If the IP can't be validated use a non routable IP for loose validation (i.e. IPv6 and clients that couldn't send back proper IPs) 38 $ip = 0; 39 } 40 41 if (isset($_SERVER['REMOTE_ADDR']) && 42 filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE | 43 FILTER_FLAG_NO_PRIV_RANGE | 44 FILTER_FLAG_IPV4)) 45 { 46 $hostip = sprintf("%u", ip2long($_SERVER['REMOTE_ADDR'])); 47 } else { 48 // If the IP can't be validated use a non routable IP for loose validation (i.e. IPv6 and clients that couldn't send back proper IPs) 49 $hostip = 0; 50 51 } 52 53 if (!empty($_POST['noteid']) && filter_var($_POST['noteid'], FILTER_VALIDATE_INT)) 54 { 55 $id = filter_var($_POST['noteid'], FILTER_VALIDATE_INT); 56 } else { 57 return false; 58 } 59 60 if (!empty($_POST['vote']) && ($_POST['vote'] === 'up' || $_POST['vote'] === 'down')) 61 { 62 $vote = $_POST['vote'] === 'up' ? 1 : 0; 63 } 64 65 if (empty($_POST['sect'])) { 66 return false; 67 } 68 69 70 // Validate the note exists and is in the requested section 71 $noteStmt = $dbh->prepare("SELECT COUNT(*) AS num, sect FROM note WHERE id = :id"); 72 if (!$noteStmt) { 73 return false; 74 } 75 if (!$noteStmt->execute(['id' => $id])) { 76 return false; 77 } 78 if (false === $noteResult = $noteStmt->fetch(PDO::FETCH_ASSOC)) { 79 return false; 80 } 81 if ($noteResult['sect'] !== $_POST['sect']) { 82 return false; 83 } 84 85 // Validate remote IP has not exceeded voting limits 86 $remoteStmt = $dbh->prepare("SELECT COUNT(*) AS num FROM votes WHERE ip = :ip AND ts >= (NOW() - INTERVAL 1 DAY) AND note_id = :id"); 87 if (!$remoteStmt) { 88 return false; 89 } 90 if (!$remoteStmt->execute(['ip' => $ip, 'id' => $id])) { 91 return false; 92 } 93 if (false === $remoteResult = $remoteStmt->fetch(PDO::FETCH_ASSOC)) { 94 return false; 95 } 96 if ($remoteResult['num'] >= 1) { // Limit of 1 vote, per note, per remote IP, per day. 97 return false; 98 } 99 100 // Validate host IP has not exceeded voting limits 101 $hostStmt = $dbh->prepare("SELECT COUNT(*) AS num FROM votes WHERE hostip = :ip AND ts >= (NOW() - INTERVAL 1 HOUR) AND note_id = :id"); 102 if (!$hostStmt) { 103 return false; 104 } 105 if (!$hostStmt->execute(['ip' => $ip, 'id' => $id])) { 106 return false; 107 } 108 if (false === $hostResult = $hostStmt->fetch(PDO::FETCH_ASSOC)) { 109 return false; 110 } 111 if ($hostResult['num'] >= 100) { // Limit of 100 votes, per note, per host IP, per hour. 112 return false; 113 } 114 115 // Inser the new vote 116 $voteStmt = $dbh->prepare("INSERT INTO votes(note_id,ip,hostip,ts,vote) VALUES(:id,:ip,:host,:ts,:vote)"); 117 if (!$voteStmt) { 118 return false; 119 } 120 if (!$voteStmt->execute(['id' => $id, 'ip' => $ip, 'host' => $hostip, 'ts' => $ts, 'vote' => $vote])) { 121 return false; 122 } 123 124 125 // Get latest vote tallies for this note 126 $voteStmt = $dbh->prepare("SELECT SUM(votes.vote) AS up, (COUNT(votes.vote) - SUM(votes.vote)) AS down FROM votes WHERE votes.note_id = :id"); 127 if (!$voteStmt) { 128 return false; 129 } 130 if (!$voteStmt->execute(['id' => $id])) { 131 return false; 132 } 133 if (false === $voteResult = $voteStmt->fetch(PDO::FETCH_ASSOC)) { 134 return false; 135 } 136 // Return the new vote tally for this note 137 return $voteResult['up'] - $voteResult['down']; 138} 139 140// Initialize global JSON response object 141$jsonResponse = new stdclass; 142$jsonResponse->status = false; 143 144 145// Validate the request 146if (!isset($_SERVER['REQUEST_METHOD']) || strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') { 147 $jsonResponse->message = "Invalid request..."; 148 echo json_encode($jsonResponse); 149 exit; 150} 151 152// Initialize global PDO database handle 153try { 154 $dbh = new PDO('mysql:host=localhost;dbname=phpmasterdb', 'nobody', ''); 155} catch(PDOException $e) { 156 $jsonResponse->message = "The server could not complete this request. Please try again later..."; 157 echo json_encode($jsonResponse); 158 exit; 159} 160 161// Check master DB for hostip and clientip limits and other validations 162if (($jsonResponse->votes = vote_validate_request($dbh)) === false) { 163 $jsonResponse->message = "Unable to complete your request at this time. Please try again later..."; 164 echo json_encode($jsonResponse); 165 exit; 166} 167 168// If everything passes the response should be the new jsonResponse object with updated votes and success status 169$jsonResponse->status = true; 170echo json_encode($jsonResponse); 171