Make your own blog
Comment admin
In this section, we'll allow authorised users to delete comments from posts. To kick us off, here's a nice item of refactoring — the comments list within article pages is quite large, and it would be more modular to store it in a separate file.
- templates/list-comments.php templates/list-comments.php
- view-post.php view-post.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
<?php
/**
* @var $pdo PDO
* @var $postId integer
*/
?>
<div class="comment-list">
<h3><?php echo countCommentsForPost($pdo, $postId) ?> comments</h3>
<?php foreach (getCommentsForPost($pdo, $postId) as $comment): ?>
<div class="comment">
<div class="comment-meta">
Comment from
<?php echo htmlspecialchars($comment['name']) ?>
on
<?php echo convertSqlDate($comment['created_at']) ?>
</div>
<div class="comment-body">
<?php // This is already escaped ?>
<?php echo convertNewlinesToParagraphs($comment['text']) ?>
</div>
</div>
<?php endforeach ?>
</div>
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<?php echo convertNewlinesToParagraphs($row['body']) ?>
</div>
<div class="comment-list">
<h3><?php echo countCommentsForPost($pdo, $postId) ?> comments</h3>
<?php foreach (getCommentsForPost($pdo, $postId) as $comment): ?>
<div class="comment">
<div class="comment-meta">
Comment from
<?php echo htmlspecialchars($comment['name']) ?>
on
<?php echo convertSqlDate($comment['created_at']) ?>
</div>
<div class="comment-body">
<?php // This is already escaped ?>
<?php echo convertNewlinesToParagraphs($comment['text']) ?>
</div>
</div>
<?php endforeach ?>
</div>
<?php require 'templates/comment-form.php' ?>
</body>
79
80
81
82
83
84
85
<?php echo convertNewlinesToParagraphs($row['body']) ?>
</div>
<?php require 'templates/list-comments.php' ?>
<?php require 'templates/comment-form.php' ?>
</body>
Let us now add a delete button next to comments on blog posts, for authorised users only. I've added a bit of extra CSS to get it to float to the right-hand side.
- assets/main.css assets/main.css
- templates/list-comments.php templates/list-comments.php
69
70
71
72
73
74
80
81
82
83
84
85
padding-top: 8px;
}
.comment-body p {
margin: 8px 4px;
}
.comment-list {
border-bottom: 1px dotted silver;
margin-bottom: 12px;
}
.comment-margin {
69
70
71
72
73
74
75
76
77
78
80
81
82
83
84
85
86
padding-top: 8px;
}
.comment .comment-meta input {
float: right;
}
.comment-body p {
margin: 8px 4px;
}
.comment-list {
border-bottom: 1px dotted silver;
margin-bottom: 12px;
max-width: 900px;
}
.comment-margin {
14
15
16
17
18
19
<?php echo htmlspecialchars($comment['name']) ?>
on
<?php echo convertSqlDate($comment['created_at']) ?>
</div>
<div class="comment-body">
<?php // This is already escaped ?>
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php echo htmlspecialchars($comment['name']) ?>
on
<?php echo convertSqlDate($comment['created_at']) ?>
<?php if (isLoggedIn()): ?>
<input
type="submit"
name="delete-comment[<?php echo $comment['id'] ?>]"
value="Delete"
/>
<?php endif ?>
</div>
<div class="comment-body">
<?php // This is already escaped ?>
Since we want to do more than one thing with comments (add and delete), we need to make a change to explain what we are doing within the form submission. To do this, I've used the query string, which sends these items of information:
- The primary key of the post (
post_id
) upon which a comment is being added or deleted - A name for the action (
add-comment
anddelete-comment
), which can be read by our code to determine what the user wishes to do
- templates/comment-form.php templates/comment-form.php
- templates/list-comments.php templates/list-comments.php
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:
18
19
20
21
22
23
24
25
26
27
28
<h3>Add your comment</h3>
<form
action="view-post.php?action=add-comment&post_id=<?php echo $postId?>"
method="post"
class="comment-form user-form"
>
<div>
<label for="comment-name">
Name:
4
5
6
7
8
9
10
32
33
34
35
* @var $postId integer
*/
?>
<div class="comment-list">
<h3><?php echo countCommentsForPost($pdo, $postId) ?> comments</h3>
<?php foreach (getCommentsForPost($pdo, $postId) as $comment): ?>
</div>
</div>
<?php endforeach ?>
</div>
4
5
6
7
8
9
10
11
12
13
14
32
33
34
35
* @var $postId integer
*/
?>
<form
action="view-post.php?action=delete-comment&post_id=<?php echo $postId?>&"
method="post"
class="comment-list"
>
<h3><?php echo countCommentsForPost($pdo, $postId) ?> comments</h3>
<?php foreach (getCommentsForPost($pdo, $postId) as $comment): ?>
</div>
</div>
<?php endforeach ?>
</form>
Let us now make some room in our view post page, by moving some logic to a new function.
- lib/view-post.php lib/view-post.php
- view-post.php view-post.php
1
2
3
4
5
<?php
/**
* Retrieves a single post
*
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
<?php
/**
* Called to handle the comment form, redirects upon success
*
* @param PDO $pdo
* @param integer $postId
* @param array $commentData
*/
function handleAddComment(PDO $pdo, $postId, array $commentData)
{
$errors = addCommentToPost(
$pdo,
$postId,
$commentData
);
// If there are no errors, redirect back to self and redisplay
if (!$errors)
{
redirectAndExit('view-post.php?post_id=' . $postId);
}
return $errors;
}
/**
* Retrieves a single post
*
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
71
72
73
74
75
76
'website' => $_POST['comment-website'],
'text' => $_POST['comment-text'],
);
$errors = addCommentToPost(
$pdo,
$postId,
$commentData
);
// If there are no errors, redirect back to self and redisplay
if (!$errors)
{
redirectAndExit('view-post.php?post_id=' . $postId);
}
}
else
{
<?php require 'templates/list-comments.php' ?>
<?php require 'templates/comment-form.php' ?>
</body>
</html>
33
34
35
36
37
38
39
71
72
73
74
75
76
77
'website' => $_POST['comment-website'],
'text' => $_POST['comment-text'],
);
$errors = handleAddComment($pdo, $postId, $commentData);
}
else
{
<?php require 'templates/list-comments.php' ?>
<?php // We use $commentData in this HTML fragment ?>
<?php require 'templates/comment-form.php' ?>
</body>
</html>
We now need to read the action key we set up, so we can decide what feature to call.
If the user is deleting a comment, the new function deleteComment
in the next set
of changes is called; although we could just delete by comment.id
, to be sure of
deleting the right thing we filter additionally by comment.post_id
.
Note that we also add an if()
clause to ensure that the delete operation is skipped if
the user is not logged in. Although we do not render delete buttons for anonymous users, it is
still possible for security crackers to fake a form submission that contains a valid deletion
request — this clause prevents that.
- lib/view-post.php lib/view-post.php
- view-post.php view-post.php
24
25
26
27
28
29
return $errors;
}
/**
* Retrieves a single post
*
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
return $errors;
}
/**
* Delete the specified comment on the specified post
*
* @param PDO $pdo
* @param integer $postId
* @param integer $commentId
* @return boolean True if the command executed without errors
* @throws Exception
*/
function deleteComment(PDO $pdo, $postId, $commentId)
{
// The comment id on its own would suffice, but post_id is a nice extra safety check
$sql = "
DELETE FROM
comment
WHERE
post_id = :post_id
AND id = :comment_id
";
$stmt = $pdo->prepare($sql);
if ($stmt === false)
{
throw new Exception('There was a problem preparing this query');
}
$result = $stmt->execute(
array(
'post_id' => $postId,
'comment_id' => $commentId,
)
);
return $result !== false;
}
/**
* Retrieves a single post
*
28
29
30
31
32
33
34
35
36
37
38
39
$errors = null;
if ($_POST)
{
$commentData = array(
'name' => $_POST['comment-name'],
'website' => $_POST['comment-website'],
'text' => $_POST['comment-text'],
);
$errors = handleAddComment($pdo, $postId, $commentData);
}
else
{
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
$errors = null;
if ($_POST)
{
switch ($_GET['action'])
{
case 'add-comment':
$commentData = array(
'name' => $_POST['comment-name'],
'website' => $_POST['comment-website'],
'text' => $_POST['comment-text'],
);
$errors = handleAddComment($pdo, $postId, $commentData);
break;
case 'delete-comment':
// Don't do anything if the user is not authorised
if (isLoggedIn())
{
$deleteResponse = $_POST['delete-comment'];
$keys = array_keys($deleteResponse);
$deleteCommentId = $keys[0];
deleteComment($pdo, $postId, $deleteCommentId);
redirectAndExit('view-post.php?post_id=' . $postId);
}
break;
}
}
else
{
Finally, I noticed that the logic to add a comment is contained within a function, but the
logic to delete one is still in the switch()
block in
view-post.php. The following change just tidies that up:
- lib/view-post.php lib/view-post.php
- view-post.php view-post.php
24
25
26
27
28
29
return $errors;
}
/**
* Delete the specified comment on the specified post
*
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
return $errors;
}
/**
* Called to handle the delete comment form, redirects afterwards
*
* The $deleteResponse array is expected to be in the form:
*
* Array ( [6] => Delete )
*
* which comes directly from input elements of this form:
*
* name="delete-comment[6]"
*
* @param PDO $pdo
* @param integer $postId
* @param array $deleteResponse
*/
function handleDeleteComment(PDO $pdo, $postId, array $deleteResponse)
{
if (isLoggedIn())
{
$keys = array_keys($deleteResponse);
$deleteCommentId = $keys[0];
if ($deleteCommentId)
{
deleteComment($pdo, $postId, $deleteCommentId);
}
redirectAndExit('view-post.php?post_id=' . $postId);
}
}
/**
* Delete the specified comment on the specified post
*
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
$errors = handleAddComment($pdo, $postId, $commentData);
break;
case 'delete-comment':
// Don't do anything if the user is not authorised
if (isLoggedIn())
{
$deleteResponse = $_POST['delete-comment'];
$keys = array_keys($deleteResponse);
$deleteCommentId = $keys[0];
deleteComment($pdo, $postId, $deleteCommentId);
redirectAndExit('view-post.php?post_id=' . $postId);
}
break;
}
}
39
40
41
42
43
44
45
46
$errors = handleAddComment($pdo, $postId, $commentData);
break;
case 'delete-comment':
$deleteResponse = $_POST['delete-comment'];
handleDeleteComment($pdo, $postId, $deleteResponse);
break;
}
}
As ever, test everything to check it works, before we proceed to the final chapter!