I have been making a login/register system and I am drawing close to finishing my register portion of code. The only problem I am running into is how to make it so that users cannot register with duplicated usernames. I want it to work so that my database won't accept the information, and it will tell the user about the error.
My PHP
<?php
include 'database_connection.php';
if (isset($_POST['formsubmitted'])) {
$error = array(); //Declare An Array to store any error message
if (empty($_POST['name'])) {//if no name has been supplied
$error[] = 'Please Enter a name '; //add to array "error"
} else {
$name = $_POST['name']; //else assign it a variable
}
if (empty($_POST['e-mail'])) {
$error[] = 'Please Enter your Email ';
} else {
if (preg_match("/^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*#([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$/", $_POST['e-mail'])) {
//regular expression for email validation
$Email = $_POST['e-mail'];
} else {
$error[] = 'Your EMail Address is invalid ';
}
}
if (empty($_POST['Password'])) {
$error[] = 'Please Enter Your Password ';
} else {
$Password = $_POST['Password'];
}
if (empty($error)) {
//send to Database if there's no error '
}
}
The best way to prevent duplicate usernames in the database is to add a UNIQUE index for the column
-- Make it unique
ALTER TABLE users ADD UNIQUE (username);
This will prevent duplicate records in the table with the same username. When you try to insert the same one then an error will be generated.
You can then catch the exception in PHP and check the reason. The duplicate constraint SQL error code is 1062.
Here is an example of how to catch this error when using PDO:
$error = [];
$username = 'Dharman';
$pdo = new \PDO("mysql:host=localhost;dbname=test;charset=utf8mb4", 'user', 'password', [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, // make sure the error reporting is enabled!
\PDO::ATTR_EMULATE_PREPARES => false
]);
try {
$stmt = $pdo->prepare('INSERT INTO users(username) VALUE(?)');
$stmt->execute([$username]);
} catch (\PDOException $e) {
if ($e->errorInfo[1] === 1062) {
$error[] = "This username is already taken!";
} else {
throw $e; // let the exception to be processed further
}
}
Here is an example of how to catch this error when using mysqli:
$error = [];
$username = 'Dharman';
// make sure the error reporting is enabled!
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli('localhost', 'user', 'password', 'test');
$mysqli->set_charset('utf8mb4');
try {
$stmt = $mysqli->prepare('INSERT INTO users(username) VALUE(?)');
$stmt->bind_param('s', $username);
$stmt->execute();
} catch (\mysqli_sql_exception $e) {
if ($e->getCode() === 1062) {
$error[] = "This username is already taken!";
} else {
throw $e; // let the exception to be processed further
}
}
You can do it like this when the user post the username for example and click submit you can write this code using mysqli:
<?php
// make sure the error reporting is enabled!
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli('localhost', 'user', 'password', 'test');
$mysqli->set_charset('utf8mb4');
$username = $_POST['username'];
$stmt = $conn->prepare("SELECT 1 FROM table_name where username=?");
$stmt->bind_param("s", $username);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_row();
if ($user) {
$error[] = "This username is already taken!";
}
When you create the column of the user you can make it unique, for example create table users(username varchar(350) not null unique).
There are two things you should do.
Make the user name a primary key in the database table. This is easily done using phpmyadmin.
Validate prior to insert.
For the second step, here's an algorithm. You may implement it in your own way using pdo, mysqli or even mysql (although it's not recommended now).
Algorithm at the end of your code (i.e., if there aren't errors)...
Select records that match the USERNAME supplied in the post.
If it exists, give out an error.
If it doesn't exist, insert it.
I used a PDO and class method, may be of some use.
//function for checking if the user already exists in the database
public function userExists($username)
{
//prepared statements for added security
$query = $this->db->prepare("SELECT COUNT(`id`) FROM `users` WHERE `username`= ?");
//execute the query
$query->execute([$username]);
$rows = $query->fetchColumn();
//if a row is returned...user already exists
return ($rows > 0);
}
note the prepared statements too - definitely the way forward
Related
While writing a pdo statement, is it possible to repeat the value of a variable? I mean:
$query = "UPDATE users SET firstname = :name WHERE firstname = :name";
$stmt = $dbh -> prepare($query);
$stmt -> execute(array(":name" => "Jackie"));
Please note that I repeat the ":name" nameholder whereas I provide the value only once. How can I make this work?
The simple answer is: You can't. PDO uses an abstraction for prepared statements which has some limitations. Unfortunately this is one, you have to work-around using something like
$query = "UPDATE users SET firstname = :name1 WHERE firstname = :name2";
$stmt = $dbh -> prepare($query);
$stmt -> execute(array(":name1" => "Jackie", ":name2" => "Jackie"));
In certain cases, such as emulated prepared statements with some versions of the PDO/MySQL driver, repeated named parameters are supported; however, this shouldn't be relied upon, as it's brittle (it can make upgrades require more work, for example).
If you want to support multiple appearances of a named parameter, you can always extend PDO and PDOStatement (by classical inheritance or by composition), or just PDOStatement and set your class as the statement class by setting the PDO::ATTR_STATEMENT_CLASS attribute. The extended PDOStatement (or PDO::prepare) could extract the named parameters, look for repeats and automatically generate replacements. It would also record these duplicates. The bind and execute methods, when passed a named parameter, would test whether the parameter is repeated and bind the value to each replacement parameter.
Note: the following example is untested and likely has bugs (some related to statement parsing are noted in code comments).
class PDO_multiNamed extends PDO {
function prepare($stmt) {
$params = array_count_values($this->_extractNamedParams());
# get just named parameters that are repeated
$repeated = array_filter($params, function ($count) { return $count > 1; });
# start suffixes at 0
$suffixes = array_map(function ($x) {return 0;}, $repeated);
/* Replace repeated named parameters. Doesn't properly parse statement,
* so may replacement portions of the string that it shouldn't. Proper
* implementation left as an exercise for the reader.
*
* $param only contains identifier characters, so no need to escape it
*/
$stmt = preg_replace_callback(
'/(?:' . implode('|', array_keys($repeated)) . ')(?=\W)/',
function ($matches) use (&$suffixes) {
return $matches[0] . '_' . $suffixes[$matches[0]]++;
}, $stmt);
$this->prepare($stmt,
array(
PDO::ATTR_STATEMENT_CLASS => array('PDOStatement_multiNamed', array($repeated)))
);
}
protected function _extractNamedParams() {
/* Not actually sufficient to parse named parameters, but it's a start.
* Proper implementation left as an exercise.
*/
preg_match_all('/:\w+/', $stmt, $params);
return $params[0];
}
}
class PDOStatement_multiNamed extends PDOStatement {
protected $_namedRepeats;
function __construct($repeated) {
# PDOStatement::__construct doesn't like to be called.
//parent::__construct();
$this->_namedRepeats = $repeated;
}
/* 0 may not be an appropriate default for $length, but an examination of
* ext/pdo/pdo_stmt.c suggests it should work. Alternatively, leave off the
* last two arguments and rely on PHP's implicit variadic function feature.
*/
function bindParam($param, &$var, $data_type=PDO::PARAM_STR, $length=0, $driver_options=array()) {
return $this->_bind(__FUNCTION__, $param, func_get_args());
}
function bindValue($param, $var, $data_type=PDO::PARAM_STR) {
return $this->_bind(__FUNCTION__, $param, func_get_args());
}
function execute($input_parameters=NULL) {
if ($input_parameters) {
$params = array();
# could be replaced by array_map_concat, if it existed
foreach ($input_parameters as $name => $val) {
if (isset($this->_namedRepeats[$param])) {
for ($i=0; $i < $this->_namedRepeats[$param], ++$i) {
$params["{$name}_{$i}"] = $val;
}
} else {
$params[$name] = $val;
}
}
return parent::execute($params);
} else {
return parent::execute();
}
}
protected function _bind($method, $param, $args) {
if (isset($this->_namedRepeats[$param])) {
$result = TRUE;
for ($i=0; $i < $this->_namedRepeats[$param], ++$i) {
$args[0] = "{$param}_{$i}";
# should this return early if the call fails?
$result &= call_user_func_array("parent::$method", $args);
}
return $result;
} else {
return call_user_func_array("parent::$method", $args);
}
}
}
In my case this error appeared when I switched from dblib freedts to sqlsrv PDO driver. Dblib driver handled duplicate parameters names with no errors. I have quite complicated dynamic queries with lots of unions and a lot of duplicated params so I used following helper as a workaround:
function prepareMsSqlQueryParams($query, $params): array
{
$paramsCount = [];
$newParams = [];
$pattern = '/(:' . implode('|:', array_keys($params)) . ')/';
$query = preg_replace_callback($pattern, function ($matches) use ($params, &$newParams, &$paramsCount) {
$key = ltrim($matches[0], ':');
if (isset($paramsCount[$key])) {
$paramsCount[$key]++;
$newParams[$key . $paramsCount[$key]] = $params[$key];
return $matches[0] . $paramsCount[$key];
} else {
$newParams[$key] = $params[$key];
$paramsCount[$key] = 0;
return $matches[0];
}
}, $query);
return [$query, $newParams];
}
Then you can use it this way:
$query = "UPDATE users SET firstname = :name WHERE firstname = :name";
$params = [":name" => "Jackie"];
// It will return "UPDATE users SET firstname = :name WHERE firstname = :name1"; with appropriate parameters array
list($query, $params) = prepareMsSqlQueryParams($query, $params);
$stmt = $dbh->prepare($query);
$stmt->execute(params);
I want to make user permissions based of values in an SQL Database. I currently store user permissions in a $_SESSION['Userpermissions'] array, however when I go to check these permissions, in_array always returns false.
I've already tried using the isset method and the array_search method.
Here is login.php
function loginById($user_id)
{
global $conn;
$sql = "SELECT u.id, u.role_id, u.username, r.name as role FROM users u LEFT JOIN roles r ON u.role_id=r.id WHERE u.id=? LIMIT 1";
$user = getSingleRecord($sql, 'i', [$user_id]);
if (!empty($user)) {
// put logged in user into session array
$_SESSION['user'] = $user;
$_SESSION['success_msg'] = "You are now logged in";
// if user is admin, redirect to dashboard, otherwise to homepage
if (isAdmin($user_id)) {
$permissionsSql = "SELECT p.name as permission_name FROM permissions as p
JOIN permission_role as pr ON p.id=pr.permission_id
WHERE pr.role_id=?";
$userPermissions = getMultipleRecords($permissionsSql, "i", [$user['role_id']]);
$_SESSION['userPermissions'] = $userPermissions;
header('location: ' . BASE_URL . 'admin/dashboard.php');
} else {
header('location: ' . BASE_URL . 'index.php');
}
exit(0);
}
}
Here is the function to check the permissions
function canDeletePost() {
if(in_array('delete-post', $_SESSION['userPermissions'])){
return true;
} else {
return false;
}
}
Here is the output of print_r:
Array
(
[0] => Array
(
[permission_name] => delete-user
)
[1] => Array
(
[permission_name] => create-user
)
)
And here's the code im using to test
<?php if (canDeleteUser()){
echo "CAN DELETE USER";
}else{
echo "CANT DELETE USER";
}?>
The result is always "CANT DELETE USER"
You are searching against the values in $_SESSION['userPermissions'], but those are arrays, not strings. Instead, use array_column to get the permission values out of the arrays:
in_array('delete-post', array_column($_SESSION['userPermissions'], 'permission_name'))
Good Afternoon,
I am currently in the process of creating a website using HTML, PHP and JavaScript, and I have run into an issue when trying to check for duplicate usernames in a database. Currently, I have a form which uses the following code to call two javascript functions on submit.
<form onsubmit="return (checkValid() && usernameCheck())" action="create.php" method="post" id="registerForm">
The javascript code is then present at the bottom of the body tag, within script tags, and is the following.
<script>
var pass = document.getElementById('passVal').value;
var confPass = document.getElementById('confPassVal').value;
var error = document.getElementById('errorMsg');
var result = true;
function checkValid(){
if(pass !== confPass)
{
error.type = "text";
error.value = "Passwords do not match!";
error.style.backgroundColor = "#E34234";
document.getElementById('passVal').style.borderColor = "#E34234";
document.getElementById('confPassVal').style.borderColor = "#E34234";
result = false;
}
return result;
}
function usernameCheck()
{
var username = document.getElementById('userName').value;
var j = checkName(username);
console.log(j);
if ( j == "taken" ) {
error.type = "text";
error.value = "Username Already Exists, Please choose an alternative.";
error.style.backgroundColor = "#E34234";
document.getElementById('userName').style.borderColor = "#E34234";
console.log(j);
result = false;
}
if ( j == "available" ) {
console.log(j);
result = true;
}
return result;
}
function checkName(uname) {
if (window.XMLHttpRequest) {
xhttp = new XMLHttpRequest();
} else {
xhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
var httpURL = "checkUserName.php?n=" + uname;
xhttp.open("GET",httpURL,false);
xhttp.send();
return xhttp.responseText;
}
</script>
The issue I am having is that the second JavaScript function is not executing, even though it is being called in the onsubmit function. Because of this, the third function is also not executing, as this function is called within the second function. The second function submits a GET value with the username input in the HTML form to a PHP script. The PHP script then checks the database to see if this user exists, and returns a value based on if this is true or not. The code below shows the PHP script.
<?php
session_start();
require 'sql.php';
header('Content-type: text/plain');
$userName = $_GET['n'];
$query = "SELECT username FROM users where username='$username'";
$checkun = $conn->query($query);
while ($row = $checkun->fetch_assoc()) {
if ($row > 0)
{
exit('taken');
} else
{
exit('available');
}
}
?>
The first JavaScript function successfully executes, which is used to check if the password fields match. This function either returns a false value if the password fields do not match (hence the second function will not execute), or a true value if they do. Even if the first function returns true, the next function does not execute, and the form submits without running this second function.
I would be grateful if someone more experienced than me (I have only been practising web development for a few months) could highlight if there are any errors in my method, and possibly help me with a solution.
Thank you for taking the time to read this!
When you say "the second function does not run" - do you mean that you get no response in the JS? I would imagine that to be true as you will NEVER have a match in your MySQL query - and, you don't have your 'catchall' in the right place.
This looks like a first learning experience for you to get data from a database, so I won't harp on the fact that you are WIDE OPEN for SQL injection attacks, though I will say that you must NOT use this code on any open system where there are real users (you need to learn how to protect against those attacks - there's loads of data on that on SO)!
So, your first step in at least getting the 'second function' to 'work'.....
<?php
session_start();
require 'sql.php';
header('Content-type: text/plain');
// $userName = $_GET['n']; // <<<==== here you use with a capital
$username = $_GET['n']; // let's change it to be the same
$query = "SELECT username FROM users where username='$username'"; // now, we have a chance...
$checkun = $conn->query($query);
while ($row = $checkun->fetch_assoc()) {
if ($row > 0)
{
exit('taken');
}
}
// Here is where to put your 'catchall' in case you don't get anything from the query (what you have won't ever give 'available')
exit('available');
?>
I'm adding new data to database table with jQuery. Then i'm getting all content from that table. Problem is i'm getting wrong array size every odd time.If i refresh the page it's getting right array size but when i'm clicking button with jQuery function it's all messed up.
I figure out that
$n = mysqli_num_rows($result);
already return wrong num of rows
Here is the script:
$(document).ready(function(){
$.getallentries = function(){
$.getJSON("entries.php",{action : "getall"},function(data) {
var content_array = $.map(data, function(e) { return e;});
console.log(content_array.length); // to check array size
});
$.addstatic = function(){
$.post("entries.php",{action : "addstatic"});
};
};
$("#adds").on("click",function(){
$.addstatic();
$.getallentries();
});
$.getallentries();
And here is entries.php:
function getall(){
$link = db_connect();
$query= "SELECT * FROM jq";
$result = mysqli_query($link,$query, MYSQLI_STORE_RESULT);// Smart people said MYSQLI_STORE_RESULT should help but it didn't
$n = mysqli_num_rows($result);
for($i = 0 ;$i<$n;$i++)
{
$row=mysqli_fetch_assoc($result);
$b[]=$row;
}
echo json_encode(array($b));
}
function addstatic(){
$link = db_connect();
$statin_Entry = "Static Entry";
$query = "INSERT INTO jq (Entry) VALUES ('$statin_Entry')";
$result = mysqli_query($link,$query);
}
if(isset($_GET['action']) && !empty($_GET['action'])) {
$action = $_GET['action'];
switch($action) {
case 'getall' : getall(); break;
}
}
else {
if(isset($_POST['action']) && !empty($_POST['action'])) {
$action = $_POST['action'];
switch($action) {
case 'addstatic' : addstatic();break;
case 'removelast' : removelast();break;
// ...etc...
}
}
}
This is log of array length
So the problem again, same array size 143,146,151 and where is 156??
You should call $.getallentries in the callback function of the $.addstatic AJAX call, so that it runs after $.addstatic has finished updating the database.
$.addstatic = function(){
$.post("entries.php",{action : "addstatic"}, $.getallentries);
};
There's no need to use mysqli_num_rows before fetching the rows. You should use a loop like this:
while ($row = mysqli_fetch_assoc($result)) {
$b[] = $row;
}
Also, you're wrapping your array in another array. Just do:
echo json_encode($b);
The way you've written it, console.log(content_array.length) should always log 1, I don't understand how you're getting higher numbers. Are you sure you posted the actual code?
There's no point in use $.map, all it's doing is making a copy of the data array. Just use data itself.
And in your PHP, you don't need to test both isset() and !empty(), because empty() checks if the variable is set first.
You don't need to use MYSQLI_STORE_RESULT, it's the default for that option.
I dont know where my mistake is but i want to store an oracle query inside a function and return that function inside an array.
JobDrop.php
class JobDrop {
private $jobSql = "SELECT VMI.PROJECT_NO JOB FROM VW_MTO_INFO VMI ORDER BY VMI.PROJECT_NO ASC";
function _construct($jobSql){
$this->jobSql = $jobSql;
}
function JobDropdown($conn){
$jobParse = oci_parse($conn, $this->jobSql);
$jobExcErr = oci_execute($jobParse);
if (!$jobExcErr){
$e = oci_error($jobParse);
print htmlentities($e['message']);
print "\n<pre>\n";
print htmlentities($e['sqltext']);
printf("\n%".($e['offset']+1)."s", "^");
print "\n</pre>\n";
} else {
$res = array();
while ($row = oci_fetch_assoc($jobParse)){
$res[] = $row;
}
$listVendor = json_encode($res, JSON_PRETTY_PRINT);
return $listVendor;
}
}
}
and in test.php
include './job_drop.php';
require_once('../../lib/dbinfo.inc.php');
$conn = oci_connect(ORA_CON_UN, ORA_CON_PW, ORA_CON_DB);
$jobdrop = new JobDrop();
$jobdrop->JobDropdown($conn);
var_dump($jobdrop);
but it doesnt show the array inside the browser. it shows the query string instead,
object(JobDrop)#1 (1) { ["jobSql":"JobDrop":private]=> string(74) "SELECT VMI.PROJECT_NO JOB FROM VW_MTO_INFO VMI ORDER BY VMI.PROJECT_NO ASC" }
Please help me where I am doing wrong here
If you want to see the array, do:
$res = $jobdrop->JobDropdown($conn);
var_dump($res);