Make your own blog
New post creation
	Let's turn our attention to another major feature: writing a new article. We start off with
	setting up a new page, laying out the necessary form input controls, and adding a new logged-in
	menu item. There's also a change here to add a generic class user-form, so our
	forms across the whole application can easily acquire a common look-and-feel; see how I've
	gone back to existing form comment-form.php to update that too.
 
		 
	- assets/main.css assets/main.css
- edit-post.php edit-post.php
- templates/comment-form.php templates/comment-form.php
- templates/title.php templates/title.php
 
				82
					83
					84
					85
					86
					87
					88
					89
					90
					91
					92
					93
						margin-bottom: 8px;
					}
					.comment-form input,
					.comment-form textarea {
						margin: 4px;
					}
					.comment-form label {
						font-size: 0.95em;
						margin: 7px;
						width: 100px;
					82
					83
					84
					85
					86
					87
					88
					89
					90
					91
					92
					93
						margin-bottom: 8px;
					}
					.user-form input,
					.user-form textarea {
						margin: 4px;
					}
					.user-form label {
						font-size: 0.95em;
						margin: 7px;
						width: 100px;
					1
					2
					3
					4
					5
					6
					7
					8
					9
					10
					11
					12
					13
					14
					15
					16
					17
					18
					19
					20
					21
					22
					23
					24
					25
					26
					27
					28
					29
					30
					31
					32
					33
					34
					35
					36
					37
					38
					39
					40
					41
					<?php
					require_once 'lib/common.php';
					session_start();
					?>
					<html>
						<head>
							<title>A blog application | New post</title>
							<?php require 'templates/head.php' ?>
						</head>
						<body>
							<?php require 'templates/title.php' ?>
							<form method="post" class="post-form user-form">
								<div>
									<label for="post-title">Title:</label>
									<input
										id="post-title"
										name="post-title"
										type="text"
									/>
								</div>
								<div>
									<label for="post-body">Body:</label>
									<textarea
										id="post-body"
										name="post-body"
										rows="12"
										cols="70"
									></textarea>
								</div>
								<div>
									<input
										type="submit"
										value="Save post"
									/>
								</div>
							</form>
						</body>
					</html>
					18
					19
					20
					21
					22
					23
					24
					<h3>Add your comment</h3>
					<form method="post" class="comment-form">
						<div>
							<label for="comment-name">
								Name:
					18
					19
					20
					21
					22
					23
					24
					<h3>Add your comment</h3>
					<form method="post" class="comment-form user-form">
						<div>
							<label for="comment-name">
								Name:
					1
					2
					3
					 
					 
					4
					5
					6
					<div class="top-menu">
						<div class="menu-options">
							<?php if (isLoggedIn()): ?>
								Hello <?php echo htmlspecialchars(getAuthUser()) ?>.
								<a href="logout.php">Log out</a>
							<?php else: ?>
					1
					2
					3
					4
					5
					6
					7
					8
					<div class="top-menu">
						<div class="menu-options">
							<?php if (isLoggedIn()): ?>
								<a href="edit-post.php">New post</a>
								|
								Hello <?php echo htmlspecialchars(getAuthUser()) ?>.
								<a href="logout.php">Log out</a>
							<?php else: ?>
					Now, we only want to show this page for users who are logged in. Thus, if a user who is not logged in tries to access it (by typing the URL in directly) we must redirect them elsewhere; in this case, the home page will do fine.
 
		 
	- edit-post.php edit-post.php
 
				3
					4
					5
					 
					 
					 
					 
					 
					 
					6
					7
					8
					session_start();
					?>
					<html>
						<head>
					3
					4
					5
					6
					7
					8
					9
					10
					11
					12
					13
					14
					session_start();
					// Don't let non-auth users see this screen
					if (!isLoggedIn())
					{
						redirectAndExit('index.php');
					}
					?>
					<html>
						<head>
					
	While we are here, let's take a look at how redirectAndExit() works; this is in
	common.php. It starts by reading the domain we are running
	on (such as localhost), since it is good not to hardwire this into our code.
	It then sends a Location HTTP header to the browser, which will cause it to
	request the specified address. Since the PHP script would happily carry on running at this
	point (until it has detected that the browser has disconnected) we also need to forcibly
	exit and wait for the redirect.
Although we now have the structure of the new post feature, it does not work yet. So let us fix that now:
 
		 
	- edit-post.php edit-post.php
- lib/common.php lib/common.php
- lib/edit-post.php lib/edit-post.php
 
				1
					2
					 
					3
					4
					5
					10
					11
					12
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					13
					14
					15
					58
					59
					60
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					61
					62
					63
					<?php
					require_once 'lib/common.php';
					session_start();
						redirectAndExit('index.php');
					}
					?>
					<html>
						<head>
						<body>
							<?php require 'templates/title.php' ?>
							<form method="post" class="post-form user-form">
								<div>
									<label for="post-title">Title:</label>
					1
					2
					3
					4
					5
					6
					10
					11
					12
					13
					14
					15
					16
					17
					18
					19
					20
					21
					22
					23
					24
					25
					26
					27
					28
					29
					30
					31
					32
					33
					34
					35
					36
					37
					38
					39
					40
					41
					42
					43
					44
					45
					46
					47
					48
					49
					50
					51
					52
					53
					54
					58
					59
					60
					61
					62
					63
					64
					65
					66
					67
					68
					69
					70
					71
					72
					73
					<?php
					require_once 'lib/common.php';
					require_once 'lib/edit-post.php';
					session_start();
						redirectAndExit('index.php');
					}
					// Handle the post operation here
					$errors = array();
					if ($_POST)
					{
						// Validate these first
						$title = $_POST['post-title'];
						if (!$title)
						{
							$errors[] = 'The post must have a title';
						}
						$body = $_POST['post-body'];
						if (!$body)
						{
							$errors[] = 'The post must have a body';
						}
						if (!$errors)
						{
							$pdo = getPDO();
							$userId = getAuthUserId($pdo);
							$postId = addPost(
								getPDO(),
								$title,
								$body,
								$userId
							);
							if ($postId === false)
							{
								$errors[] = 'Post operation failed';
							}
						}
						if (!$errors)
						{
							redirectAndExit('edit-post.php?post_id=' . $postId);
						}
					}
					?>
					<html>
						<head>
						<body>
							<?php require 'templates/title.php' ?>
							<?php if ($errors): ?>
								<div class="error box">
									<ul>
										<?php foreach ($errors as $error): ?>
											<li><?php echo $error ?></li>
										<?php endforeach ?>
									</ul>
								</div>
							<?php endif ?>
							<form method="post" class="post-form user-form">
								<div>
									<label for="post-title">Title:</label>
					173
					174
					175
					{
						return isset($_SESSION['logged_in_username']);
					}
					173
					174
					175
					176
					177
					178
					179
					180
					181
					182
					183
					184
					185
					186
					187
					188
					189
					190
					191
					192
					193
					194
					195
					196
					197
					198
					199
					200
					201
					202
					203
					204
					{
						return isset($_SESSION['logged_in_username']);
					}
					/**
					 * Looks up the user_id for the current auth user
					 */
					function getAuthUserId(PDO $pdo)
					{
						// Reply with null if there is no logged-in user
						if (!isLoggedIn())
						{
							return null;
						}
						$sql = "
							SELECT
								id
							FROM
								user
							WHERE
								username = :username
						";
						$stmt = $pdo->prepare($sql);
						$stmt->execute(
							array(
								'username' => getAuthUser()
							)
						);
						return $stmt->fetchColumn();
					}
					1
					2
					3
					4
					5
					6
					7
					8
					9
					10
					11
					12
					13
					14
					15
					16
					17
					18
					19
					20
					21
					22
					23
					24
					25
					26
					27
					28
					29
					30
					31
					32
					33
					34
					<?php
					function addPost(PDO $pdo, $title, $body, $userId)
					{
						// Prepare the insert query
						$sql = "
							INSERT INTO
								post
								(title, body, user_id, created_at)
								VALUES
								(:title, :body, :user_id, :created_at)
						";
						$stmt = $pdo->prepare($sql);
						if ($stmt === false)
						{
							throw new Exception('Could not prepare post insert query');
						}
						// Now run the query, with these parameters
						$result = $stmt->execute(
							array(
								'title' => $title,
								'body' => $body,
								'user_id' => $userId,
								'created_at' => getSqlDateForNow(),
							)
						);
						if ($result === false)
						{
							throw new Exception('Could not run post insert query');
						}
						return $pdo->lastInsertId();
					}
					
	In a similar way to saving comments, we first test if we are in a post operation, using
	if ($_POST). This contains an array of input values that will be only be present
	if a user has submitted the form.
We then make some simple checks to ensure the form data is acceptable prior to our attempting to insert it into the database. This is a process known as form validation and is a frequent task within web application development. If any checks fail, we allow the page to be rendered in the POST request itself, plus error messages as appropriate, and it is only if the checks succeed that we try to save the data and redirect back to the newly committed article.
However, as it stands this redirect will just display a new, empty post, since we have not added the logic to render the edit facility for a specific row. Let's add that now:
 
		 
	- edit-post.php edit-post.php
 
				1
					2
					3
					 
					4
					5
					6
					11
					12
					13
					 
					 
					 
					 
					 
					 
					14
					15
					16
					38
					39
					40
					41
					42
					43
					44
					55
					56
					57
					 
					 
					 
					 
					 
					 
					 
					 
					 
					58
					59
					60
					91
					92
					93
					 
					94
					95
					96
					101
					102
					103
					104
					105
					106
					107
					<?php
					require_once 'lib/common.php';
					require_once 'lib/edit-post.php';
					session_start();
						redirectAndExit('index.php');
					}
					// Handle the post operation here
					$errors = array();
					if ($_POST)
							$pdo = getPDO();
							$userId = getAuthUserId($pdo);
							$postId = addPost(
								getPDO(),
								$title,
								$body,
								$userId
							redirectAndExit('edit-post.php?post_id=' . $postId);
						}
					}
					?>
					<html>
										id="post-title"
										name="post-title"
										type="text"
									/>
								</div>
								<div>
										name="post-body"
										rows="12"
										cols="70"
									></textarea>
								</div>
								<div>
									<input
					1
					2
					3
					4
					5
					6
					7
					11
					12
					13
					14
					15
					16
					17
					18
					19
					20
					21
					22
					38
					39
					40
					41
					42
					43
					44
					55
					56
					57
					58
					59
					60
					61
					62
					63
					64
					65
					66
					67
					68
					69
					91
					92
					93
					94
					95
					96
					97
					101
					102
					103
					104
					105
					106
					107
					<?php
					require_once 'lib/common.php';
					require_once 'lib/edit-post.php';
					require_once 'lib/view-post.php';
					session_start();
						redirectAndExit('index.php');
					}
					// Empty defaults
					$title = $body = '';
					// Init database and get handle
					$pdo = getPDO();
					// Handle the post operation here
					$errors = array();
					if ($_POST)
							$pdo = getPDO();
							$userId = getAuthUserId($pdo);
							$postId = addPost(
								$pdo,
								$title,
								$body,
								$userId
							redirectAndExit('edit-post.php?post_id=' . $postId);
						}
					}
					elseif (isset($_GET['post_id']))
					{
						$post = getPostRow($pdo, $_GET['post_id']);
						if ($post)
						{
							$title = $post['title'];
							$body = $post['body'];
						}
					}
					?>
					<html>
										id="post-title"
										name="post-title"
										type="text"
										value="<?php echo htmlspecialchars($title) ?>"
									/>
								</div>
								<div>
										name="post-body"
										rows="12"
										cols="70"
									><?php echo htmlspecialchars($body) ?></textarea>
								</div>
								<div>
									<input
					
	That was nice and easy: if we find we are not in a post operation and we have a
	post primary key and that row exists, then show it in the edit form. You can see that
	now we need the database connection (in $pdo) for two things, I've moved
	that line so that it is always executed.
	As we have done before, this code uses htmlspecialchars() to prevent users
	from entering HTML, which could break our page layout or introduce security problems. It is
	perhaps less of a worry here, since at least these users are authenticated, and hence
	they might be considered more trustworthy than anonymous commenters.
If you tried editing a post, you'll have found that this created a new post, rather than updating the old one. So let's fix that also:
 
		 
	- edit-post.php edit-post.php
- lib/edit-post.php lib/edit-post.php
 
				17
					18
					19
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					20
					21
					22
					48
					49
					50
					51
					52
					53
					54
					55
					56
					57
					58
					59
					60
					61
					 
					 
					 
					 
					 
					 
					 
					 
					 
					 
					62
					63
					64
					70
					71
					72
					73
					74
					75
					76
					77
					78
					79
					80
					81
					82
					83
					84
					// Init database and get handle
					$pdo = getPDO();
					// Handle the post operation here
					$errors = array();
					if ($_POST)
						if (!$errors)
						{
							$pdo = getPDO();
							$userId = getAuthUserId($pdo);
							$postId = addPost(
								$pdo,
								$title,
								$body,
								$userId
							);
							if ($postId === false)
							{
								$errors[] = 'Post operation failed';
							}
						}
							redirectAndExit('edit-post.php?post_id=' . $postId);
						}
					}
					elseif (isset($_GET['post_id']))
					{
						$post = getPostRow($pdo, $_GET['post_id']);
						if ($post)
						{
							$title = $post['title'];
							$body = $post['body'];
						}
					}
					?>
					<html>
					17
					18
					19
					20
					21
					22
					23
					24
					25
					26
					27
					28
					29
					30
					31
					32
					33
					34
					48
					49
					50
					51
					52
					 
					 
					 
					 
					 
					 
					 
					53
					54
					55
					56
					57
					58
					59
					60
					61
					62
					63
					64
					65
					66
					67
					70
					71
					72
					 
					 
					 
					 
					 
					 
					 
					 
					 
					73
					74
					75
					// Init database and get handle
					$pdo = getPDO();
					$postId = null;
					if (isset($_GET['post_id']))
					{
						$post = getPostRow($pdo, $_GET['post_id']);
						if ($post)
						{
							$postId = $_GET['post_id'];
							$title = $post['title'];
							$body = $post['body'];
						}
					}
					// Handle the post operation here
					$errors = array();
					if ($_POST)
						if (!$errors)
						{
							$pdo = getPDO();
							// Decide if we are editing or adding
							if ($postId)
							{
								editPost($pdo, $title, $body, $postId);
							}
							else
							{
								$userId = getAuthUserId($pdo);
								$postId = addPost($pdo, $title, $body, $userId);
								if ($postId === false)
								{
									$errors[] = 'Post operation failed';
								}
							}
						}
							redirectAndExit('edit-post.php?post_id=' . $postId);
						}
					}
					?>
					<html>
					32
					33
					34
						return $pdo->lastInsertId();
					}
					32
					33
					34
					35
					36
					37
					38
					39
					40
					41
					42
					43
					44
					45
					46
					47
					48
					49
					50
					51
					52
					53
					54
					55
					56
					57
					58
					59
					60
					61
					62
					63
					64
					65
					66
					67
					68
						return $pdo->lastInsertId();
					}
					function editPost(PDO $pdo, $title, $body, $postId)
					{
						// Prepare the insert query
						$sql = "
							UPDATE
								post
							SET
								title = :title,
								body = :body
							WHERE
								id = :post_id
						";
						$stmt = $pdo->prepare($sql);
						if ($stmt === false)
						{
							throw new Exception('Could not prepare post update query');
						}
						// Now run the query, with these parameters
						$result = $stmt->execute(
							array(
								'title' => $title,
								'body' => $body,
								'post_id' => $postId,
							)
						);
						if ($result === false)
						{
							throw new Exception('Could not run post update query');
						}
						return true;
					}
					
	I've moved the code to read the current post towards the start of the page, since this is
	now useful in two situations. The first is when displaying an article for
	editing, and the second is when submitting the edit form to save any changes.
	In both cases, $_GET['post_id'] will be available, and we can look up that row
	from the post table, and obtain title/body data if it is read successfully.
	Within a POST operation, we can then check $postId, and if it has a value we
	know we are editing an existing article rather than creating a new one. Thus, if we are editing,
	we call the new function editPost(), which will run the necessary
	UPDATE command against the database, rather than addPost(), which
	would run an INSERT.
You might have noticed that there has been no attempt to check whether the user editing a post is the same as the user who wrote it. Whether one person may edit other person's posts is a feature decision, but for the time being it is one that I have deliberately omitted, to keep things simple.
 Download
						Download