Make your own blog
All posts page
The next step is to add an All Posts screen for administrative users. We'll start with a static mock-up with hard-wired post values:
- assets/main.css assets/main.css
- list-posts.php list-posts.php
- templates/top-menu.php templates/top-menu.php
98
99
100
101
/* Some browsers make labels too tall, and as a result they incorrectly stack horizontally.
Let's reset each to the left-hand side to be sure. */
clear: left;
}
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/* Some browsers make labels too tall, and as a result they incorrectly stack horizontally.
Let's reset each to the left-hand side to be sure. */
clear: left;
}
#post-list {
border-collapse: collapse;
border: 1px solid silver;
}
#post-list td {
padding: 8px;
}
#post-list tbody tr:nth-child(odd) {
background-color: #f4f4f4;
}
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?php
require_once 'lib/common.php';
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<title>A blog application | Blog posts</title>
<?php require 'templates/head.php' ?>
</head>
<body>
<?php require 'templates/top-menu.php' ?>
<h1>Post list</h1>
<form method="post">
<table id="post-list">
<tbody>
<tr>
<td>Title of the first post</td>
<td>
<a href="edit-post.php?post_id=1">Edit</a>
</td>
<td>
<input
type="submit"
name="post[1]"
value="Delete"
/>
</td>
</tr>
<tr>
<td>Title of the second post</td>
<td>
<a href="edit-post.php?post_id=2">Edit</a>
</td>
<td>
<input
type="submit"
name="post[2]"
value="Delete"
/>
</td>
</tr>
<tr>
<td>Title of the third post</td>
<td>
<a href="edit-post.php?post_id=3">Edit</a>
</td>
<td>
<input
type="submit"
name="post[3]"
value="Delete"
/>
</td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
1
2
3
4
5
6
<div class="top-menu">
<div class="menu-options">
<?php if (isLoggedIn()): ?>
<a href="edit-post.php">New post</a>
|
Hello <?php echo htmlEscape(getAuthUser()) ?>.
1
2
3
4
5
6
7
8
<div class="top-menu">
<div class="menu-options">
<?php if (isLoggedIn()): ?>
<a href="list-posts.php">All posts</a>
|
<a href="edit-post.php">New post</a>
|
Hello <?php echo htmlEscape(getAuthUser()) ?>.
Of course, since this is a restricted screen, we must only allow authorised users to see it. To do so, we redirect back to the home page, and exit as usual:
- list-posts.php list-posts.php
3
4
5
6
7
8
session_start();
?>
<!DOCTYPE html>
<html>
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');
}
?>
<!DOCTYPE html>
<html>
Great stuff. Don't forget to test it by logging out, and then trying to visit the page manually!
What do square brackets in form elements do?
Aha! Glad you asked. First, let's consider this simple form element:
<input type="submit" name="save" value="Save" />
This is pretty simple. If the button is pressed, the form is submitted, and the submitted key
save
gets the value of the button, "Save".However, consider the scenario where we need several submit buttons carrying out a similar purpose, e.g. a delete button per database row. One solution is to add in the primary key as part of the name:
<input type="submit" name="delete_1" value="Delete" /> <input type="submit" name="delete_2" value="Delete" /> <input type="submit" name="delete_3" value="Delete" />
When this form gets submitted, we can search for keys matching this naming specific pattern, and delete the matching row as appropriate. However, this is rather fiddly, so PHP provides a cleaner solution - the square bracket syntax:
<input type="submit" name="delete[1]" value="Delete" /> <input type="submit" name="delete[2]" value="Delete" /> <input type="submit" name="delete[3]" value="Delete" />
This helps up cheat by doing some of our parsing for us. Here's what happens in that form if we press the second Delete button:
Array ( [delete] => Array ( [2] => Delete ) )
This makes things nice and easy: we just look up the name of the element (i.e.
delete
), and then grab the first key value.
Now, the logic we use to read the posts for the front page will be useful also for this new screen. Since we don't want to write this twice, let's move it to a function so we can reuse it later on.
- index.php index.php
- lib/common.php lib/common.php
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
26
27
28
29
30
31
32
51
52
53
54
55
56
57
// Connect to the database, run a query, handle errors
$pdo = getPDO();
$stmt = $pdo->query(
'SELECT
id, title, created_at, body
FROM
post
ORDER BY
created_at DESC'
);
if ($stmt === false)
{
throw new Exception('There was a problem running this query');
}
$notFound = isset($_GET['not-found']);
<?php endif ?>
<div class="post-list">
<?php while ($row = $stmt->fetch(PDO::FETCH_ASSOC)): ?>
<div class="post-synopsis">
<h2>
<?php echo htmlEscape($row['title']) ?>
<?php endif ?>
</div>
</div>
<?php endwhile ?>
</div>
</body>
5
6
7
8
9
10
11
26
27
28
29
30
31
32
51
52
53
54
55
56
57
// Connect to the database, run a query, handle errors
$pdo = getPDO();
$posts = getAllPosts($pdo);
$notFound = isset($_GET['not-found']);
<?php endif ?>
<div class="post-list">
<?php foreach ($posts as $row): ?>
<div class="post-synopsis">
<h2>
<?php echo htmlEscape($row['title']) ?>
<?php endif ?>
</div>
</div>
<?php endforeach ?>
</div>
</body>
73
74
75
76
77
78
return date('Y-m-d H:i:s');
}
/**
* Converts unsafe text to safe, paragraphed, HTML
*
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
return date('Y-m-d H:i:s');
}
/**
* Gets a list of posts in reverse order
*
* @param PDO $pdo
* @return array
*/
function getAllPosts(PDO $pdo)
{
$stmt = $pdo->query(
'SELECT
id, title, created_at, body
FROM
post
ORDER BY
created_at DESC'
);
if ($stmt === false)
{
throw new Exception('There was a problem running this query');
}
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Converts unsafe text to safe, paragraphed, HTML
*
While working on the home page, I noticed I'd used the variable name $row
. This
is rather generic — most things read from a database are rows — so I swapped to
a better name for it. This is more readable, and fits in with the common naming convention
of using a plural name for an array and the corresponding singular name for each item.
- index.php index.php
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
<?php endif ?>
<div class="post-list">
<?php foreach ($posts as $row): ?>
<div class="post-synopsis">
<h2>
<?php echo htmlEscape($row['title']) ?>
</h2>
<div class="meta">
<?php echo convertSqlDate($row['created_at']) ?>
(<?php echo countCommentsForPost($pdo, $row['id']) ?> comments)
</div>
<p>
<?php echo htmlEscape($row['body']) ?>
</p>
<div class="post-controls">
<a
href="view-post.php?post_id=<?php echo $row['id'] ?>"
>Read more...</a>
<?php if (isLoggedIn()): ?>
|
<a
href="edit-post.php?post_id=<?php echo $row['id'] ?>"
>Edit</a>
<?php endif ?>
</div>
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
<?php endif ?>
<div class="post-list">
<?php foreach ($posts as $post): ?>
<div class="post-synopsis">
<h2>
<?php echo htmlEscape($post['title']) ?>
</h2>
<div class="meta">
<?php echo convertSqlDate($post['created_at']) ?>
(<?php echo countCommentsForPost($pdo, $post['id']) ?> comments)
</div>
<p>
<?php echo htmlEscape($post['body']) ?>
</p>
<div class="post-controls">
<a
href="view-post.php?post_id=<?php echo $post['id'] ?>"
>Read more...</a>
<?php if (isLoggedIn()): ?>
|
<a
href="edit-post.php?post_id=<?php echo $post['id'] ?>"
>Edit</a>
<?php endif ?>
</div>
Next, we'll modify the mock-up by adding in a creation time for posts:
- list-posts.php list-posts.php
26
27
28
29
30
31
42
43
44
45
46
47
58
59
60
61
62
63
<tbody>
<tr>
<td>Title of the first post</td>
<td>
<a href="edit-post.php?post_id=1">Edit</a>
</td>
</tr>
<tr>
<td>Title of the second post</td>
<td>
<a href="edit-post.php?post_id=2">Edit</a>
</td>
</tr>
<tr>
<td>Title of the third post</td>
<td>
<a href="edit-post.php?post_id=3">Edit</a>
</td>
26
27
28
29
30
31
32
33
34
42
43
44
45
46
47
48
49
50
58
59
60
61
62
63
64
65
66
<tbody>
<tr>
<td>Title of the first post</td>
<td>
dd MM YYYY h:mi
</td>
<td>
<a href="edit-post.php?post_id=1">Edit</a>
</td>
</tr>
<tr>
<td>Title of the second post</td>
<td>
dd MM YYYY h:mi
</td>
<td>
<a href="edit-post.php?post_id=2">Edit</a>
</td>
</tr>
<tr>
<td>Title of the third post</td>
<td>
dd MM YYYY h:mi
</td>
<td>
<a href="edit-post.php?post_id=3">Edit</a>
</td>
As with our mock-up approach before, once a layout contains everything required, it is time to convert to a working version. So, let's do that now: we'll add a loop and render table data in the HTML.
- list-posts.php list-posts.php
9
10
11
12
13
14
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
redirectAndExit('index.php');
}
?>
<!DOCTYPE html>
<html>
<form method="post">
<table id="post-list">
<tbody>
<tr>
<td>Title of the first post</td>
<td>
dd MM YYYY h:mi
</td>
<td>
<a href="edit-post.php?post_id=1">Edit</a>
</td>
<td>
<input
type="submit"
name="post[1]"
value="Delete"
/>
</td>
</tr>
<tr>
<td>Title of the second post</td>
<td>
dd MM YYYY h:mi
</td>
<td>
<a href="edit-post.php?post_id=2">Edit</a>
</td>
<td>
<input
type="submit"
name="post[2]"
value="Delete"
/>
</td>
</tr>
<tr>
<td>Title of the third post</td>
<td>
dd MM YYYY h:mi
</td>
<td>
<a href="edit-post.php?post_id=3">Edit</a>
</td>
<td>
<input
type="submit"
name="post[3]"
value="Delete"
/>
</td>
</tr>
</tbody>
</table>
</form>
9
10
11
12
13
14
15
16
17
18
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
redirectAndExit('index.php');
}
// Connect to the database, run a query
$pdo = getPDO();
$posts = getAllPosts($pdo);
?>
<!DOCTYPE html>
<html>
<form method="post">
<table id="post-list">
<tbody>
<?php foreach ($posts as $post): ?>
<tr>
<td>
<?php echo htmlEscape($post['title']) ?>
</td>
<td>
<?php echo convertSqlDate($post['created_at']) ?>
</td>
<td>
<a href="edit-post.php?post_id=<?php echo $post['id']?>">Edit</a>
</td>
<td>
<input
type="submit"
name="delete-post[<?php echo $post['id']?>]"
value="Delete"
/>
</td>
</tr>
<?php endforeach ?>
</tbody>
</table>
</form>
As it stands, the user may click on a delete button to remove a post, but this is not presently handled. So, let's do that now:
- lib/list-posts.php lib/list-posts.php
- list-posts.php list-posts.php
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
<?php
/**
* Tries to delete the specified post
*
* @param PDO $pdo
* @param integer $postId
* @return boolean Returns true on successful deletion
* @throws Exception
*/
function deletePost(PDO $pdo, $postId)
{
$sql = "
DELETE FROM
post
WHERE
id = :id
";
$stmt = $pdo->prepare($sql);
if ($stmt === false)
{
throw new Exception('There was a problem preparing this query');
}
$result = $stmt->execute(
array('id' => $postId, )
);
return $result !== false;
}
1
2
3
4
5
10
11
12
13
14
15
<?php
require_once 'lib/common.php';
session_start();
redirectAndExit('index.php');
}
// Connect to the database, run a query
$pdo = getPDO();
$posts = getAllPosts($pdo);
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
<?php
require_once 'lib/common.php';
require_once 'lib/list-posts.php';
session_start();
redirectAndExit('index.php');
}
if ($_POST)
{
$deleteResponse = $_POST['delete-post'];
if ($deleteResponse)
{
$keys = array_keys($deleteResponse);
$deletePostId = $keys[0];
if ($deletePostId)
{
deletePost(getPDO(), $deletePostId);
redirectAndExit('list-posts.php');
}
}
}
// Connect to the database, run a query
$pdo = getPDO();
$posts = getAllPosts($pdo);
Finally let's add in a post count on this page:
- list-posts.php list-posts.php
41
42
43
44
45
46
<h1>Post list</h1>
<form method="post">
<table id="post-list">
<tbody>
41
42
43
44
45
46
47
48
<h1>Post list</h1>
<p>You have <?php echo count($posts) ?> posts.
<form method="post">
<table id="post-list">
<tbody>