Problem on PHP and SQL Databases Kevin Yank

Hi all,

I am following Kevin Yank’s book about PHP and SQL driven database websites. In this book, we create a joke website where users can connect and depending on their role, they can do various things like add/modify jokes, add authors and categories. Everything works fine for the book but I am trying to explore a little bit on my own now.

I have a joke form where the user (which has previously logged in) enters a new joke and select the category appropriated. My first problem is that when I test this, the joke and the category enter the database but do not display on my page and I do not know why. I think it has something to do with the fact that the newly added joke has no author linked to the joke, but on the page where it displays I see no where where I write about the authors so I do not see why.

That links to my second problem. When I tried to solve the first problem, I tried to add some code that would automatically add the author id who is currently logged in to his newly added joke.

Here is the joke.html.php that displays the joke

<?php include_once $_SERVER['DOCUMENT_ROOT']. '/includes/helpers.inc.php'; ?>


<!DOCTYPE html>
<html lang="en">  
  
	<head>  
    <title>List of Jokes</title>  
    <meta http-equiv="content-type"  
        content="text/html; charset=utf-8"/> 
			  <link rel="stylesheet" type="text/css" href="/includes/main1.css?version=53" />

		<style>
			li {display: inline-block;}
			
		</style>
    </head>  
  
	<body> 
		<?php include $_SERVER['DOCUMENT_ROOT'] .'/includes/logout.inc.html.php'; ?>
		<p><a href="../admin/">Administator's page</a></p><br>
	<a href="/home/"><h1>Home page</h1></a><br>	
		<p>Welcome to the best joke website in town ! See the other users jokes or add your own !</p><br><br>
		
	
		
	<div class = "content">	
	<p><a href="?addjoke" style="background-color: white; padding: 10px; border: solid 2px black;">Add your own joke</a></p><br><br>
    <h2>Here are all the jokes in the database:</h2>  
    
	<?php foreach ($jokes as $joke): ?>    
	  <ol><form action="?deletejoke" method="post">    
		<blockquote>    
		  <p>    
			<li><?php markdownout($joke['text']); ?>  </li>  
			<input type="hidden" name="id" value="<?php echo $joke['id']; ?>"/>    
			<li><input type="submit" value="Delete"/></li>
			
			<li><?php if ($joke['email']==! null && $joke['name'] ==! null) : ?>  
			  
				
				(by <a href="mailto:<?php echo htmlout($joke['email']); ?>"><?php echo htmlout($joke['name']); ?></a>) 
				<?php endif; ?> </li>
			  
		  </p>    
		</blockquote>    
		</form>  </ol>
	<?php endforeach; ?>
    
		</div> 
  </body>  
</html>[/CODE]

The form.html.php
[CODE]<?php include_once $_SERVER['DOCUMENT_ROOT'] .
 '/includes/helpers.inc.php'; ?>

<!DOCTYPE html>
<html lang="en">  
  <head>   
    <title>Add Joke</title>   
    <meta http-equiv="content-type"   
        content="text/html; charset=utf-8"/>   
    <style type="text/css">   
    textarea, fieldset {   
      display: block;   
      width: 50%;
		margin-left: 25%;
		border: 0px;
    }   
		
    </style>   
	  <link rel="stylesheet" type="text/css" href="/includes/main1.css?version=51" />
  </head>   
  <body>   
	  <h1>Add a new joke !</h1>
       <form action ="?joketext" method="post">   
      <div>   
        <label for="joketext">Type your joke here:</label>   
        <textarea id="joketext" name="joketext" rows="3" cols="42"></textarea>   
      </div>   
	

         
     <fieldset>
         <legend>Categories:</legend>
         <?php foreach ($categories as $category): ?>
         <div><label for="category<?php htmlout($category['id']);
         ?>"><input type="checkbox" name="categories[]"
         id="category<?php htmlout($category['id']); ?>"
         value="<?php htmlout($category['id']); ?>"<?php
         if ($category['selected'])
         {
         echo ' checked';
         }
         ?>><?php htmlout($category['name']); ?></label></div>
         <?php endforeach; ?>
     </fieldset>
      <div><input type="submit" value="Add"/></div>   
    </form>  
      
	  <p><a href= "/home/connect/">Return to home page</a></p>
      
  </body>   
</html>[/CODE]


The **index.php** behind those two

[CODE]<?php 
/*ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);*/

include $_SERVER['DOCUMENT_ROOT'] . '/includes/db.inc.php';

require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/access.inc.php';

	if (!userIsLoggedIn())
{
 $error = 'You must login or register to add new jokes';
 include $_SERVER['DOCUMENT_ROOT'] .'/includes/accessdenied.html.php';
 exit();
}
if (!userHasRole('Public user'))
{
 $error = 'You are not authorized';
 include $_SERVER['DOCUMENT_ROOT'] .'/includes/accessdenied.html.php';
 exit();
}


//if click in add a joke , it will open the form
if (isset($_GET['addjoke']))
{
	if (!userIsLoggedIn())
{
 $error = 'You must login or register to add new jokes';
 include $_SERVER['DOCUMENT_ROOT'] .'/includes/accessdenied.html.php';
 exit();
}
if (!userHasRole('Public user'))
{
 $error = 'You are not authorized';
 include $_SERVER['DOCUMENT_ROOT'] .'/includes/accessdenied.html.php';
 exit();
}
	
	//builds category list
	  try
 {
 $result = $pdo->query('SELECT id, name FROM category');
 }
 
    catch (PDOException $e)
 {
 $error = 'Error fetching list of categories.';
 include 'error.html.php';
 exit();
 }
 
    foreach ($result as $row)
 {
 $categories[] = array('id' => $row['id'],'name' => $row['name'],'selected' => FALSE); //sets selected to false be default
 }
	
 include 'form.html.php';
 exit();

}


//if some text is post in the form, insert it into table
if (isset($_POST['joketext']))
{
	 include $_SERVER['DOCUMENT_ROOT'] .'/includes/db.inc.php';
	//if some category is added

 try
 {
 $sql = 'INSERT INTO joke SET
 joketext = :joketext,
 jokedate = CURDATE()';
 $s = $pdo->prepare($sql);
 $s->bindValue(':joketext', $_POST['joketext']);
 $s->execute();
 }
 catch (PDOException $e)
 {
 $error = 'Error adding submitted joke: ' . $e->getMessage();
 include 'error.html.php';
 exit();
 }
	
	
	$jokeid = $pdo->lastInsertId();  //retrieves the ID of the newly inserted joke
	
//adds the associated category if there is 	
 if (isset($_POST['categories']))
 {
 
    try //insert into jokecategory the category id and joke id
 {
 $sql = 'INSERT INTO jokecategory SET
 jokeid = :jokeid,
 categoryid = :categoryid';
 $s = $pdo->prepare($sql);
     
 
   foreach ($_POST['categories'] as $categoryid)   //create a single variable from each of the array value (category id)
 {
 $s->bindValue(':jokeid', $jokeid);
 $s->bindValue(':categoryid', $categoryid);
 $s->execute();
 }
 }
 catch (PDOException $e)
 {
 $error = 'Error inserting joke into selected categories.';
 include 'error.html.php';
 exit();
 }
 
 header('Location: .');
 exit();
}
	
 header('Location: .');
 exit();
}




//delete a joke and category related
if (isset($_GET['deletejoke']))
{
    include $_SERVER['DOCUMENT_ROOT'] .'/includes/db.inc.php';
 try
 {
 $sql = 'DELETE FROM joke WHERE id = :id';
 $s = $pdo->prepare($sql);
 $s->bindValue(':id', $_POST['id']);
 $s->execute();
 }
 catch (PDOException $e)
 {
 $error = 'Error deleting joke: ' . $e->getMessage();
 include 'error.html.php';
 exit();
 }
 header('Location: .');
 exit();
}


//select all the jokes, puts them into an array 

try
{
	include $_SERVER['DOCUMENT_ROOT'] .'/includes/db.inc.php';
 $sql = 'SELECT joke.id, joketext, name, email FROM joke
 INNER JOIN author ON joke.authorid = author.id';
 $result = $pdo->query($sql);
}
catch (PDOException $e)
{
 $error = 'Error fetching jokes: ' . $e->getMessage();
 include 'error.html.php';
 exit();
}
foreach ($result as $row)
{
 $jokes[] = array(
 'id' => $row['id'],
 'text' => $row['joketext'],
 'name' => $row['name'],
 'email' => $row['email']
 );
}




//if nothing, display the jokes

include 'jokes.html.php';
?>[/CODE]


and the access.inc.php situated in another file which creates the functions to see if a user is logged in.

[CODE]<?php
/*ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);*/



function userIsLoggedIn()
{
	

	session_start();
	
 if (isset($_POST['action']) and $_POST['action'] == 'login')
 {
 if (!isset($_POST['email']) or $_POST['email'] == '' or
 !isset($_POST['password']) or $_POST['password'] == '')
 {
 $GLOBALS['loginError'] = 'Please fill in both fields';
 return FALSE;
 }
 $password = md5($_POST['password'] . 'ijdb');
	 
	 
 if (databaseContainsAuthor($_POST['email'], $password))
 {
 
 $_SESSION['loggedIn'] = TRUE;
 $_SESSION['email'] = $_POST['email'];
 $_SESSION['password'] = $password;	 
 return TRUE;
 }
 else
 {
 
 unset($_SESSION['loggedIn']);
 unset($_SESSION['email']);
 unset($_SESSION['password']);
 $GLOBALS['loginError'] =
 'The specified email address or password was incorrect.';
 return FALSE;
 }
 }
	
 if (isset($_POST['action']) and $_POST['action'] == 'logout')
 {

 
 unset($_SESSION['loggedIn']);
 unset($_SESSION['email']);
 unset($_SESSION['password']);
 header('Location: ' .$_POST['goto']);
 exit();
 }
 
 if (isset($_SESSION['loggedIn']))
 {
 return databaseContainsAuthor($_SESSION['email'],
 $_SESSION['password']);
 }
}


function databaseContainsAuthor($email, $password)
{
 include 'db.inc.php';
 try
 {
 $sql = 'SELECT COUNT(*) FROM author
 WHERE email = :email AND password = :password';
 $s = $pdo->prepare($sql);
 
 $s->bindValue(':email', $email);
 $s->bindValue(':password', $password);
	 
 $s->execute();
 
 }
 catch (PDOException $e)
 {
 $error = 'Error searching for author.';
 include 'error.html.php';
 exit();
 }
 $row = $s->fetch();
 if ($row[0] > 0)
 {
 return TRUE;
 }
 else
 {
 return FALSE;

 }
}

function userHasRole($role)
{
 include 'db.inc.php';
 try
 {
 $sql = "SELECT COUNT(*) FROM author
 INNER JOIN authorrole ON author.id = authorid
 INNER JOIN role ON roleid = role.id
 WHERE email = :email AND role.id = :roleId";
 $s = $pdo->prepare($sql);
 $s->bindValue(':email', $_SESSION['email']);
 $s->bindValue(':roleId', $role);
 $s->execute();
 }
 catch (PDOException $e)
 {
 $error = 'Error searching for author roles.';
 include 'error.html.php';
 exit();
 }
 $row = $s->fetch();
 if ($row[0] > 0)
 {
 return TRUE;
 }
 else
 {
 return FALSE;
 }
}


function currentUser(){
	
	echo $_SESSION['email'];
	
}
	
	
	?>[/CODE]


The best for my second problem I think would be to add a $_SESSION['id'] which would be the author id who is currently logged in. But for that, I have to select the author id who is currently connected in the part where check if an author has the correct authentification info : [CODE]$sql = "SELECT COUNT(*) FROM author
 INNER JOIN authorrole ON author.id = authorid
 INNER JOIN role ON roleid = role.id
 WHERE email = :email AND role.id = :roleId";
 $s = $pdo->prepare($sql);
 $s->bindValue(':email', $_SESSION['email']);
 $s->bindValue(':roleId', $role);
 $s->execute();

It certainly seems to be the case that the code to “insert joke” doesn’t set the author id. Does the book not talk about how that value should be set? It seems quite a glaring omission, or is the author / author id part of the changes you’ve made?

I’m not 100% sure, but it might not appear if the inner join on the author table cannot retrieve the value, which of course it won’t if you’re leaving it blank.

To me, the easiest way would be to modify the login code to store the author ID in a session as you said, but there may be security reasons for not doing that. You could add a “getAuthorId()” function to the access.inc.php file and call that whenever you need the author id. That function could do the permissions-checking internally, and return either the author id or false.

In your joke.html - I’ve never seen this operator written like ==! instead of !==. Could this be causing the conditional to not work properly?

1 Like

Seems as if an INNER JOIN will only retrieve the rows where the conditions match, so any rows in ‘jokes’ without a matching author id in the authors table will be dropped. An outer join would bring them all, regardless. I think.

Was wondering this myself but it appears to be valid:

<?php
declare(strict_types=1);
error_reporting(-1);

$joke['email'] = 'realdonaldtrump@silly.com';
$joke['name'] = 'Whiny Boy';

if ($joke['email'] ==! null && $joke['name'] ==! null) {
    echo "Both name and email are present\n";
}

$joke['name'] = null;
if ($joke['email'] ==! null && $joke['name'] ==! null) {
    echo "Both name and email are still present\n";
}
else {
    echo "Missing email or name\n";
}

$joke['name'] = 0; // Just for kicks
if ($joke['email'] ==! null && $joke['name'] ==! null) {
    echo "Both name and email are still present\n";
}
else {
    echo "Missing email or name\n";
}

Sort of like a yoda operator.

1 Like

Are the “==!” used in the PHP and SQL Databases by Kevin Yank book? I.e. written exactly like that? Actually let me rephrase that, is the context of “value ==! null” used in the book?

It is important to understand that by the underlying engine this is read as “== !” and not as “==!” and it has no relevance to the identical comparison (for more information on the comparison types: http://php.net/manual/en/language.operators.comparison.php)

What it does is that it change the value of the right side to the opposite.

Example:
“true ==! false” : this will evaluate as true, and should really be written like “true == !false” to make it easier to read.
“1 ==! 0” : this will evaluate as true etc.

The use of it makes the code much harder to read, and should not be used. In the if example you gave, instead use “!empty()” to check if the variables contain a value. Please note that 0, false etc. values will count as empty, so if you know the variable can contain those, use isset to check instead, or a identical comparison.

1 Like

@WebMachine , @ahundiak and @TheRedDevil I have switched the “==! nul” to a “!== nul” and it does not change anything. Anyway, I don’t think it is part of my problems since this part of code only displays the author email if there is one associated with the joke (which is supposed to skip if no author…)

@droopsnoot the book does not talk about it. It is a part a I added myself. Now, when someone logs in he enters an email and a password, the code check if there is a matching email/password in my author table and if there is one, the function returns true. I wanted to add a bit to that code that when the email/password is matched, it then takes the id of that matching author and stores it in $_SESSION[“id”]. Is it possible to do in in the same SELECT code ?

Otherwise, I will add a “getAuthorId()” function like you said. I did not think about it and it may seem the best solution. Thank you !

You could change the databaseContainsAuthor() function so that it returns the author id, or false if it not found, then go from there. All you’d need to do is change the query so that it selects the author_id column instead selecting the count, and use ->rowCount to decide whether any rows were found.

thank you for the help but I have another question, I can quite make it. I tried something with ->rowCount but I had lots of trouble trying to do so. Instead, I tried the following thing which I do not know if it could work:

[CODE]function databaseContainsAuthor($email, $password)
{
include ‘db.inc.php’;
try
{
$sql = ‘SELECT COUNT(*) FROM author
WHERE email = :email AND password = :password’;
$s = $pdo->prepare($sql);
$s->bindValue(‘:email’, $email);
$s->bindValue(‘:password’, $password);
$s->execute();
}
catch (PDOException $e)
{
$error = ‘Error searching for author.’;
include ‘error.html.php’;
exit();
}
$row = $s->fetch();
if ($row[0] > 0)
{
$result = $pdo->query('SELECT id FROM author WHERE email = AND password = ');

 foreach ($result as $row){
	 $author[] = array('id' => $row['id'],'name' => $row['name']);
	 
 }

return TRUE;
}
else
{
return FALSE;

}
}[/CODE]

I reasoned like this: the function select matching row with email and passwords . It fetches the result, and then check if it is greater than zero and returns true. All I would have to do is to add, “if it is greater than 0” return true but also select that author id. I tried to do so but in my select I do not understand how to select WHERE my email and password are the previous one. Do i need to hold their value or something ?

Otherwise, could you tell me how you would do it using ->rowCount ? I am new to this and a bit confused ! thanks

I looked on http://php.net/manual/fr/pdostatement.rowcount.php and it says that ->rowCount does not return the number of rows for a SELECT statement, which I am trying to do. It comes back to Count(*) but that does not work as we saw.I tried something else:

[CODE]function databaseContainsAuthor($email, $password)
{
include ‘db.inc.php’;
try
{
$sql = ‘SELECT id FROM author
WHERE email = :email AND password = :password’;
$s = $pdo->prepare($sql);
$s->bindValue(‘:email’, $email);
$s->bindValue(‘:password’, $password);
$s->execute();
}
catch (PDOException $e)
{
$error = ‘Error searching for author.’;
include ‘error.html.php’;
exit();
}
$row = $s->fetchAll();
if ($row[0] > 0)
{
foreach ($row as $authors){

$author[] = array('id' => $authors['id'],'name' => $authors['name']);
} 

return TRUE;
}
else
{
return FALSE;

}
}[/CODE]

Isn’t ->fetchAll supposed to return an object ? Then I could use that object to create a $author array containing the id and name of the author ? But it is not working … why ?

This:

if ($row[0] > 0)

Should just be this

if ($row)

Get rid of return TRUE and the else return FALSE. The if ($row) is already returning true and if it fails it will already return false.

See the manual

PDOStatement::fetchAll() returns an array containing all of the remaining rows in the result set. The array represents each row as either an array of column values or an object with properties corresponding to each column name. An empty array is returned if there are zero results to fetch, or FALSE on failure.

Also, you only SELECTED the id so you are not getting $authors[‘name’]

As @benanamen said above, you don’t retrieve the name, so you can’t return it. Also in your function you go to the trouble of building an array called $authors[], but then don’t do anything with it.

As I recall it, you only wanted the id to store in a session variable, because other bits of code are used to get the other information. And, if your database update routines have worked, your query should either recover zero rows, or one row, as duplicate emails won’t be allowed. So fetchAll() probably isn’t needed, you could just check that a row was found, if it was then return the id column, and if it was not, return false.

I apologise for the red herring of rowCount(), I was sure that worked properly on PDO but as you say, the documentation clearly says it only works sometimes.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.