1506 lines
52 KiB
PHP
1506 lines
52 KiB
PHP
<?php
|
|
require_once "model".DIR."Comment.php";
|
|
|
|
class Comments extends Modules {
|
|
# Array: $caches
|
|
# Query caches for methods.
|
|
private $caches = array();
|
|
|
|
public static function __install() {
|
|
Comment::install();
|
|
|
|
Config::current()->set(
|
|
"module_comments",
|
|
array(
|
|
"notify_site_contact" => false,
|
|
"notify_post_author" => false,
|
|
"default_comment_status" => Comment::STATUS_DENIED,
|
|
"allowed_comment_html" => array(
|
|
"strong", "em", "blockquote", "code", "pre", "a"
|
|
),
|
|
"comments_per_page" => 25,
|
|
"enable_reload_comments" => false,
|
|
"auto_reload_comments" => 30
|
|
)
|
|
);
|
|
|
|
Group::add_permission("add_comment", "Add Comments");
|
|
Group::add_permission("add_comment_private", "Add Comments to Private Posts");
|
|
Group::add_permission("edit_comment", "Edit Comments");
|
|
Group::add_permission("edit_own_comment", "Edit Own Comments");
|
|
Group::add_permission("delete_comment", "Delete Comments");
|
|
Group::add_permission("delete_own_comment", "Delete Own Comments");
|
|
Group::add_permission("code_in_comments", "Can Use HTML in Comments");
|
|
|
|
Route::current()->add("comment/(id)/", "comment");
|
|
}
|
|
|
|
public static function __uninstall($confirm): void {
|
|
if ($confirm)
|
|
Comment::uninstall();
|
|
|
|
Config::current()->remove("module_comments");
|
|
|
|
Group::remove_permission("add_comment");
|
|
Group::remove_permission("add_comment_private");
|
|
Group::remove_permission("edit_comment");
|
|
Group::remove_permission("edit_own_comment");
|
|
Group::remove_permission("delete_comment");
|
|
Group::remove_permission("delete_own_comment");
|
|
Group::remove_permission("code_in_comments");
|
|
|
|
Route::current()->remove("comment/(id)/");
|
|
}
|
|
|
|
public function user_logged_in($user): void {
|
|
$_SESSION['comments'] = array();
|
|
}
|
|
|
|
public function user($user): void {
|
|
$user->has_many[] = "comments";
|
|
}
|
|
|
|
public function post($post): void {
|
|
$post->has_many[] = "comments";
|
|
}
|
|
|
|
public function list_permissions($names = array()): array {
|
|
$names["add_comment"] = __("Add Comments", "comments");
|
|
$names["add_comment_private"] = __("Add Comments to Private Posts", "comments");
|
|
$names["edit_comment"] = __("Edit Comments", "comments");
|
|
$names["edit_own_comment"] = __("Edit Own Comments", "comments");
|
|
$names["delete_comment"] = __("Delete Comments", "comments");
|
|
$names["delete_own_comment"] = __("Delete Own Comments", "comments");
|
|
$names["code_in_comments"] = __("Can Use HTML in Comments", "comments");
|
|
return $names;
|
|
}
|
|
|
|
public function main_comment($main): bool {
|
|
if (empty($_GET['id']) or !is_numeric($_GET['id']))
|
|
Flash::warning(
|
|
__("Please enter an ID to find a comment.", "comments"),
|
|
"/"
|
|
);
|
|
|
|
$comment = new Comment($_GET['id']);
|
|
|
|
if ($comment->no_results)
|
|
return false;
|
|
|
|
if ($comment->post->no_results)
|
|
return false;
|
|
|
|
redirect($comment->post->url()."#comment_".$comment->id);
|
|
}
|
|
|
|
public function main_most_comments($main): void {
|
|
$posts = Post::find(array("placeholders" => true));
|
|
|
|
usort($posts[0], function ($a, $b) {
|
|
$count_a = $this->get_post_comment_count($a["id"]);
|
|
$count_b = $this->get_post_comment_count($b["id"]);
|
|
|
|
if ($count_a == $count_b)
|
|
return 0;
|
|
|
|
return ($count_a > $count_b) ? -1 : 1 ;
|
|
});
|
|
|
|
$main->display(
|
|
array("pages".DIR."most_comments", "pages".DIR."index"),
|
|
array("posts" => new Paginator($posts, $main->post_limit)),
|
|
__("Most commented on posts", "comments")
|
|
);
|
|
}
|
|
|
|
public function parse_urls($urls): array {
|
|
$urls['|/comment/([0-9]+)/|'] = '/?action=comment&id=$1';
|
|
return $urls;
|
|
}
|
|
|
|
private function add_comment(): array {
|
|
if (!isset($_POST['hash']) or !Session::check_token($_POST['hash']))
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("Invalid authentication token.")
|
|
);
|
|
|
|
if (empty($_POST['post_id']) or !is_numeric($_POST['post_id']))
|
|
error(
|
|
__("No ID Specified"),
|
|
__("An ID is required to add a comment.", "comments"),
|
|
code:400
|
|
);
|
|
|
|
$post = new Post(
|
|
$_POST['post_id'], array("drafts" => true)
|
|
);
|
|
|
|
if ($post->no_results)
|
|
show_404(__("Not Found"), __("Post not found."));
|
|
|
|
if (!Comment::creatable($post))
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("You cannot comment on this post.", "comments")
|
|
);
|
|
|
|
if (empty($_POST['body']))
|
|
return array(
|
|
false,
|
|
__("Message can't be blank.", "comments")
|
|
);
|
|
|
|
if (empty($_POST['author']) or derezz($_POST['author']))
|
|
return array(
|
|
false,
|
|
__("Author can't be blank.", "comments")
|
|
);
|
|
|
|
if (empty($_POST['author_email']))
|
|
return array(
|
|
false,
|
|
__("Email address can't be blank.", "comments")
|
|
);
|
|
|
|
if (!is_email($_POST['author_email']))
|
|
return array(
|
|
false,
|
|
__("Invalid email address.", "comments")
|
|
);
|
|
|
|
if (!empty($_POST['author_url']) and !is_url($_POST['author_url']))
|
|
return array(
|
|
false,
|
|
__("Invalid website URL.", "comments")
|
|
);
|
|
|
|
if (!empty($_POST['author_url']))
|
|
$_POST['author_url'] = add_scheme($_POST['author_url']);
|
|
|
|
if (!logged_in() and !check_captcha())
|
|
return array(
|
|
false,
|
|
__("Incorrect captcha response.", "comments")
|
|
);
|
|
|
|
fallback($_POST['author_url'], "");
|
|
$parent = (int) fallback($_POST['parent_id'], 0);
|
|
$notify = (!empty($_POST['notify']) and logged_in());
|
|
|
|
$comment = Comment::create(
|
|
body:$_POST['body'],
|
|
author:$_POST['author'],
|
|
author_url:$_POST['author_url'],
|
|
author_email:$_POST['author_email'],
|
|
post:$post,
|
|
parent:$parent,
|
|
notify:$notify
|
|
);
|
|
|
|
return array(
|
|
true,
|
|
($comment->status == Comment::STATUS_APPROVED) ?
|
|
__("Comment added.", "comments") :
|
|
__("Your comment is awaiting moderation.", "comments")
|
|
);
|
|
}
|
|
|
|
private function update_comment(): array {
|
|
if (!isset($_POST['hash']) or !Session::check_token($_POST['hash']))
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("Invalid authentication token.")
|
|
);
|
|
|
|
if (empty($_POST['id']) or !is_numeric($_POST['id']))
|
|
error(
|
|
__("No ID Specified"),
|
|
__("An ID is required to update a comment.", "comments"),
|
|
code:400
|
|
);
|
|
|
|
$comment = new Comment($_POST['id']);
|
|
|
|
if ($comment->no_results)
|
|
show_404(
|
|
__("Not Found"),
|
|
__("Comment not found.", "comments")
|
|
);
|
|
|
|
if (!$comment->editable())
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("You do not have sufficient privileges to edit this comment.", "comments")
|
|
);
|
|
|
|
fallback($_POST['created_at']);
|
|
fallback($_POST['status'], $comment->status);
|
|
fallback($_POST['author_email'], $comment->author_email);
|
|
fallback($_POST['author_url'], $comment->author_url);
|
|
|
|
if (empty($_POST['body']))
|
|
return array(
|
|
false,
|
|
__("Message can't be blank.", "comments")
|
|
);
|
|
|
|
if (empty($_POST['author']) or derezz($_POST['author']))
|
|
return array(
|
|
false,
|
|
__("Author can't be blank.", "comments")
|
|
);
|
|
|
|
if (empty($_POST['author_email']) and $_POST['status'] != Comment::STATUS_PINGBACK)
|
|
return array(
|
|
false,
|
|
__("Email address can't be blank.", "comments")
|
|
);
|
|
|
|
if (!empty($_POST['author_email']) and !is_email($_POST['author_email']))
|
|
return array(
|
|
false,
|
|
__("Invalid email address.", "comments")
|
|
);
|
|
|
|
if (!empty($_POST['author_url']) and !is_url($_POST['author_url']))
|
|
return array(
|
|
false,
|
|
__("Invalid website URL.", "comments")
|
|
);
|
|
|
|
if (!empty($_POST['author_url']))
|
|
$_POST['author_url'] = add_scheme($_POST['author_url']);
|
|
|
|
$notify = (!empty($_POST['notify']) and logged_in());
|
|
$can_edit_comment = Visitor::current()->group->can("edit_comment");
|
|
|
|
$status = ($can_edit_comment) ?
|
|
$_POST['status'] :
|
|
$comment->status ;
|
|
|
|
|
|
$created_at = ($can_edit_comment) ?
|
|
datetime($_POST['created_at']) :
|
|
$comment->created_at ;
|
|
|
|
$comment = $comment->update(
|
|
body:$_POST['body'],
|
|
author:$_POST['author'],
|
|
author_url:$_POST['author_url'],
|
|
author_email:$_POST['author_email'],
|
|
status:$status,
|
|
notify:$notify,
|
|
created_at:$created_at
|
|
);
|
|
|
|
return array(
|
|
true,
|
|
__("Comment updated.", "comments")
|
|
);
|
|
}
|
|
|
|
public function admin_update_comment()/*: never */{
|
|
list($success, $message) = $this->update_comment();
|
|
|
|
if (!$success)
|
|
error(
|
|
__("Error"),
|
|
$message,
|
|
code:422
|
|
);
|
|
|
|
Flash::notice(
|
|
$message,
|
|
"manage_comments"
|
|
);
|
|
}
|
|
|
|
public function ajax_add_comment(): void {
|
|
list($success, $message) = $this->add_comment();
|
|
json_response($message, $success);
|
|
}
|
|
|
|
public function ajax_update_comment(): void {
|
|
list($success, $message) = $this->update_comment();
|
|
json_response($message, $success);
|
|
}
|
|
|
|
public function admin_edit_comment($admin): void {
|
|
if (empty($_GET['id']) or !is_numeric($_GET['id']))
|
|
error(
|
|
__("No ID Specified"),
|
|
__("An ID is required to edit a comment.", "comments"),
|
|
code:400
|
|
);
|
|
|
|
$comment = new Comment(
|
|
$_GET['id'],
|
|
array("filter" => false)
|
|
);
|
|
|
|
if ($comment->no_results)
|
|
show_404(
|
|
__("Not Found"),
|
|
__("Comment not found.", "comments")
|
|
);
|
|
|
|
if (!$comment->editable())
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("You do not have sufficient privileges to edit this comment.", "comments")
|
|
);
|
|
|
|
$admin->display(
|
|
"pages".DIR."edit_comment",
|
|
array("comment" => $comment)
|
|
);
|
|
}
|
|
|
|
public function admin_delete_comment($admin): void {
|
|
if (empty($_GET['id']) or !is_numeric($_GET['id']))
|
|
error(
|
|
__("No ID Specified"),
|
|
__("An ID is required to delete a comment.", "comments"),
|
|
code:400
|
|
);
|
|
|
|
$comment = new Comment($_GET['id']);
|
|
|
|
if ($comment->no_results)
|
|
show_404(
|
|
__("Not Found"),
|
|
__("Comment not found.", "comments")
|
|
);
|
|
|
|
if (!$comment->deletable())
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("You do not have sufficient privileges to delete this comment.", "comments")
|
|
);
|
|
|
|
$admin->display(
|
|
"pages".DIR."delete_comment",
|
|
array("comment" => $comment)
|
|
);
|
|
}
|
|
|
|
public function admin_destroy_comment()/*: never */{
|
|
if (!isset($_POST['hash']) or !Session::check_token($_POST['hash']))
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("Invalid authentication token.")
|
|
);
|
|
|
|
if (empty($_POST['id']) or !is_numeric($_POST['id']))
|
|
error(
|
|
__("No ID Specified"),
|
|
__("An ID is required to delete a comment.", "comments"),
|
|
code:400
|
|
);
|
|
|
|
if (!isset($_POST['destroy']) or $_POST['destroy'] != "indubitably")
|
|
redirect("manage_comments");
|
|
|
|
$comment = new Comment($_POST['id']);
|
|
|
|
if ($comment->no_results)
|
|
show_404(
|
|
__("Not Found"),
|
|
__("Comment not found.", "comments")
|
|
);
|
|
|
|
if (!$comment->deletable())
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("You do not have sufficient privileges to delete this comment.", "comments")
|
|
);
|
|
|
|
Comment::delete($comment->id);
|
|
|
|
$redirect = ($comment->status == Comment::STATUS_SPAM) ?
|
|
"manage_spam" :
|
|
"manage_comments" ;
|
|
|
|
Flash::notice(__("Comment deleted.", "comments"), $redirect);
|
|
}
|
|
|
|
public function admin_manage_comments($admin): void {
|
|
if (!Comment::any_editable() and !Comment::any_deletable())
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("You do not have sufficient privileges to manage any comments.", "comments")
|
|
);
|
|
|
|
# Redirect searches to a clean URL or dirty GET depending on configuration.
|
|
if (isset($_POST['query']))
|
|
redirect(
|
|
"manage_comments/query/".
|
|
str_ireplace(
|
|
array("%2F", "%5C"),
|
|
"%5F",
|
|
urlencode($_POST['query'])
|
|
).
|
|
"/"
|
|
);
|
|
|
|
fallback($_GET['query'], "");
|
|
list($where, $params, $order) = keywords(
|
|
$_GET['query'],
|
|
"body LIKE :query",
|
|
"comments"
|
|
);
|
|
|
|
$where[] = "status != '".Comment::STATUS_SPAM."'";
|
|
fallback($order, "post_id DESC, created_at ASC");
|
|
|
|
$visitor = Visitor::current();
|
|
|
|
if (!$visitor->group->can("edit_comment", "delete_comment", true))
|
|
$where["user_id"] = $visitor->id;
|
|
|
|
$admin->display(
|
|
"pages".DIR."manage_comments",
|
|
array(
|
|
"comments" => new Paginator(
|
|
Comment::find(
|
|
array(
|
|
"placeholders" => true,
|
|
"where" => $where,
|
|
"params" => $params,
|
|
"order" => $order
|
|
)
|
|
),
|
|
$admin->post_limit
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
public function admin_manage_spam($admin): void {
|
|
if (!Visitor::current()->group->can("edit_comment", "delete_comment", true))
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("You do not have sufficient privileges to manage any comments.", "comments")
|
|
);
|
|
|
|
# Redirect searches to a clean URL or dirty GET depending on configuration.
|
|
if (isset($_POST['query']))
|
|
redirect(
|
|
"manage_spam/query/".
|
|
str_ireplace(
|
|
array("%2F", "%5C"),
|
|
"%5F",
|
|
urlencode($_POST['query'])
|
|
).
|
|
"/"
|
|
);
|
|
|
|
fallback($_GET['query'], "");
|
|
list($where, $params, $order) = keywords(
|
|
$_GET['query'],
|
|
"body LIKE :query",
|
|
"comments"
|
|
);
|
|
|
|
$where[] = "status = '".Comment::STATUS_SPAM."'";
|
|
fallback($order, "post_id DESC, created_at ASC");
|
|
|
|
$admin->display(
|
|
"pages".DIR."manage_spam",
|
|
array(
|
|
"comments" => new Paginator(
|
|
Comment::find(
|
|
array(
|
|
"placeholders" => true,
|
|
"where" => $where,
|
|
"params" => $params,
|
|
"order" => $order
|
|
)
|
|
),
|
|
$admin->post_limit
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
public function admin_bulk_comments()/*: never */{
|
|
if (!isset($_POST['hash']) or !Session::check_token($_POST['hash']))
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("Invalid authentication token.")
|
|
);
|
|
|
|
if (!isset($_POST['comment']))
|
|
Flash::warning(
|
|
__("No comments selected."),
|
|
"manage_comments"
|
|
);
|
|
|
|
$trigger = Trigger::current();
|
|
$false_positives = array();
|
|
$false_negatives = array();
|
|
$comments = array_keys($_POST['comment']);
|
|
|
|
switch (fallback($_POST['task'])) {
|
|
case "delete":
|
|
$count_delete = 0;
|
|
|
|
foreach ($comments as $comment) {
|
|
$comment = new Comment(
|
|
$comment,
|
|
array("filter" => false)
|
|
);
|
|
|
|
if (!$comment->deletable())
|
|
continue;
|
|
|
|
Comment::delete($comment->id);
|
|
$count_delete++;
|
|
}
|
|
|
|
if (!empty($count_delete))
|
|
Flash::notice(
|
|
__("Selected comments deleted.", "comments")
|
|
);
|
|
|
|
break;
|
|
|
|
case "deny":
|
|
$count_deny = 0;
|
|
|
|
foreach ($comments as $comment) {
|
|
$comment = new Comment(
|
|
$comment,
|
|
array("filter" => false)
|
|
);
|
|
|
|
if (!$comment->editable())
|
|
continue;
|
|
|
|
if ($comment->status == Comment::STATUS_PINGBACK)
|
|
continue;
|
|
|
|
if ($comment->status == Comment::STATUS_SPAM)
|
|
$false_positives[] = $comment;
|
|
|
|
$comment->update(
|
|
body:$comment->body,
|
|
author:$comment->author,
|
|
author_url:$comment->author_url,
|
|
author_email:$comment->author_email,
|
|
status:Comment::STATUS_DENIED
|
|
);
|
|
|
|
$count_deny++;
|
|
}
|
|
|
|
if (!empty($count_deny))
|
|
Flash::notice(
|
|
__("Selected comments denied.", "comments")
|
|
);
|
|
|
|
break;
|
|
|
|
case "approve":
|
|
$count_approve = 0;
|
|
|
|
foreach ($comments as $comment) {
|
|
$comment = new Comment(
|
|
$comment,
|
|
array("filter" => false)
|
|
);
|
|
|
|
if (!$comment->editable())
|
|
continue;
|
|
|
|
if ($comment->status == Comment::STATUS_PINGBACK)
|
|
continue;
|
|
|
|
if ($comment->status == Comment::STATUS_SPAM)
|
|
$false_positives[] = $comment;
|
|
|
|
$comment->update(
|
|
body:$comment->body,
|
|
author:$comment->author,
|
|
author_url:$comment->author_url,
|
|
author_email:$comment->author_email,
|
|
status:Comment::STATUS_APPROVED
|
|
);
|
|
|
|
$count_approve++;
|
|
}
|
|
|
|
if (!empty($count_approve))
|
|
Flash::notice(
|
|
__("Selected comments approved.", "comments")
|
|
);
|
|
|
|
break;
|
|
|
|
case "spam":
|
|
$count_spam = 0;
|
|
|
|
foreach ($comments as $comment) {
|
|
$comment = new Comment(
|
|
$comment,
|
|
array("filter" => false)
|
|
);
|
|
|
|
if (!$comment->editable())
|
|
continue;
|
|
|
|
if ($comment->status == Comment::STATUS_PINGBACK)
|
|
continue;
|
|
|
|
$comment->update(
|
|
body:$comment->body,
|
|
author:$comment->author,
|
|
author_url:$comment->author_url,
|
|
author_email:$comment->author_email,
|
|
status:Comment::STATUS_SPAM
|
|
);
|
|
|
|
$count_spam++;
|
|
$false_negatives[] = $comment;
|
|
}
|
|
|
|
if (!empty($count_spam))
|
|
Flash::notice(
|
|
__("Selected comments marked as spam.", "comments")
|
|
);
|
|
|
|
break;
|
|
}
|
|
|
|
if (!empty($false_positives))
|
|
$trigger->call("comments_false_positives", $false_positives);
|
|
|
|
if (!empty($false_negatives))
|
|
$trigger->call("comments_false_negatives", $false_negatives);
|
|
|
|
redirect("manage_comments");
|
|
}
|
|
|
|
public function admin_comment_settings($admin): void {
|
|
if (!Visitor::current()->group->can("change_settings"))
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("You do not have sufficient privileges to change settings.")
|
|
);
|
|
|
|
$config = Config::current();
|
|
$comments_html = implode(
|
|
", ",
|
|
$config->module_comments["allowed_comment_html"]
|
|
);
|
|
$comments_status = array(
|
|
Comment::STATUS_APPROVED => __("Approved", "comments"),
|
|
Comment::STATUS_DENIED => __("Denied", "comments"),
|
|
Comment::STATUS_SPAM => __("Spam", "comments")
|
|
);
|
|
|
|
if (empty($_POST)) {
|
|
$admin->display(
|
|
"pages".DIR."comment_settings",
|
|
array(
|
|
"comments_html" => $comments_html,
|
|
"comments_status" => $comments_status
|
|
)
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!isset($_POST['hash']) or !Session::check_token($_POST['hash']))
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("Invalid authentication token.")
|
|
);
|
|
|
|
fallback($_POST['default_comment_status'], Comment::STATUS_DENIED);
|
|
fallback($_POST['allowed_comment_html'], "");
|
|
fallback($_POST['comments_per_page'], 25);
|
|
fallback($_POST['auto_reload_comments'], 30);
|
|
|
|
# Split at the comma.
|
|
$allowed_comment_html = explode(",", $_POST['allowed_comment_html']);
|
|
|
|
# Remove whitespace.
|
|
$allowed_comment_html = array_map("trim", $allowed_comment_html);
|
|
|
|
# Remove duplicates.
|
|
$allowed_comment_html = array_unique($allowed_comment_html);
|
|
|
|
# Remove empties.
|
|
$allowed_comment_html = array_diff($allowed_comment_html, array(""));
|
|
|
|
$config = Config::current();
|
|
$config->set(
|
|
"module_comments",
|
|
array(
|
|
"notify_site_contact" => isset($_POST['notify_site_contact']),
|
|
"notify_post_author" => isset($_POST['notify_post_author']),
|
|
"default_comment_status" => $_POST['default_comment_status'],
|
|
"allowed_comment_html" => $allowed_comment_html,
|
|
"comments_per_page" => abs((int) $_POST['comments_per_page']),
|
|
"enable_reload_comments" => isset($_POST['enable_reload_comments']),
|
|
"auto_reload_comments" => (int) $_POST['auto_reload_comments']
|
|
)
|
|
);
|
|
|
|
Flash::notice(
|
|
__("Settings updated."),
|
|
"comment_settings"
|
|
);
|
|
}
|
|
|
|
public function admin_determine_action($action): ?string {
|
|
if (
|
|
$action == "manage" and
|
|
(Comment::any_editable() or Comment::any_deletable())
|
|
)
|
|
return "manage_comments";
|
|
|
|
return null;
|
|
}
|
|
|
|
public function settings_nav($navs): array {
|
|
if (Visitor::current()->group->can("change_settings"))
|
|
$navs["comment_settings"] = array(
|
|
"title" => __("Comments", "comments")
|
|
);
|
|
|
|
return $navs;
|
|
}
|
|
|
|
public function manage_nav($navs): array {
|
|
if (!Comment::any_editable() and !Comment::any_deletable())
|
|
return $navs;
|
|
|
|
$sql = SQL::current();
|
|
$comment_count = $sql->count(
|
|
"comments",
|
|
array("status not" => Comment::STATUS_SPAM)
|
|
);
|
|
$spam_count = $sql->count(
|
|
"comments",
|
|
array("status" => Comment::STATUS_SPAM)
|
|
);
|
|
|
|
$navs["manage_comments"] = array(
|
|
"title" => _f("Comments (%d)", $comment_count, "comments"),
|
|
"selected" => array("edit_comment", "delete_comment")
|
|
);
|
|
|
|
if (Visitor::current()->group->can("edit_comment", "delete_comment"))
|
|
$navs["manage_spam"] = array(
|
|
"title" => _f("Spam (%d)", $spam_count, "comments")
|
|
);
|
|
|
|
return $navs;
|
|
}
|
|
|
|
public function manage_posts_column_header(): string {
|
|
return '<th class="post_comments value">'.
|
|
__("Comments", "comments").
|
|
'</th>';
|
|
}
|
|
|
|
public function manage_posts_column($post): string {
|
|
return '<td class="post_comments value"><a href="'.
|
|
url("manage_comments/query/".urlencode("post_id:".$post->id)).
|
|
'">'.
|
|
$post->comment_count.
|
|
'</a></td>';
|
|
}
|
|
|
|
public function manage_users_column_header(): string {
|
|
return '<th class="user_comments value">'.
|
|
__("Comments", "comments").
|
|
'</th>';
|
|
}
|
|
|
|
public function manage_users_column($user): string {
|
|
return '<td class="user_comments value"><a href="'.
|
|
url("manage_comments/query/".urlencode("user_id:".$user->id)).
|
|
'">'.
|
|
$user->comment_count.
|
|
'</a></td>';
|
|
}
|
|
|
|
public function ajax_reload_comments(): void {
|
|
if (empty($_POST['post_id']) or !is_numeric($_POST['post_id']))
|
|
error(
|
|
__("No ID Specified"),
|
|
__("An ID is required to reload comments.", "comments"),
|
|
code:400
|
|
);
|
|
|
|
$post = new Post(
|
|
$_POST['post_id'], array("drafts" => true)
|
|
);
|
|
|
|
if ($post->no_results)
|
|
show_404(
|
|
__("Not Found"),
|
|
__("Post not found.")
|
|
);
|
|
|
|
$last = (empty($_POST['last_comment'])) ?
|
|
$post->created_at :
|
|
$_POST['last_comment'] ;
|
|
|
|
$text = _f("Comments added since %s", when("%c", $last, true), "comments");
|
|
|
|
$ids = array();
|
|
|
|
if ($post->latest_comment > $last) {
|
|
$times = SQL::current()->select(
|
|
tables:"comments",
|
|
fields:array("id", "created_at"),
|
|
conds:array(
|
|
"post_id" => $post->id,
|
|
"created_at >" => $last,
|
|
Comment::redactions()
|
|
),
|
|
order:array("created_at ASC")
|
|
);
|
|
|
|
while ($row = $times->fetchObject()) {
|
|
$ids[] = $row->id;
|
|
$last = $row->created_at;
|
|
}
|
|
}
|
|
|
|
json_response(
|
|
$text,
|
|
array("comment_ids" => $ids, "last_comment" => $last)
|
|
);
|
|
}
|
|
|
|
public function ajax_show_comment(): void {
|
|
if (empty($_POST['comment_id']) or !is_numeric($_POST['comment_id']))
|
|
error(
|
|
__("Error"),
|
|
__("An ID is required to show a comment.", "comments"),
|
|
code:400
|
|
);
|
|
|
|
$comment = new Comment($_POST['comment_id']);
|
|
|
|
if ($comment->no_results)
|
|
show_404(
|
|
__("Not Found"),
|
|
__("Comment not found.", "comments")
|
|
);
|
|
|
|
$main = MainController::current();
|
|
$main->display(
|
|
"content".DIR."comment",
|
|
array("comment" => $comment)
|
|
);
|
|
}
|
|
|
|
public function ajax_edit_comment(): void {
|
|
if (!isset($_POST['hash']) or !Session::check_token($_POST['hash']))
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("Invalid authentication token.")
|
|
);
|
|
|
|
if (empty($_POST['comment_id']) or !is_numeric($_POST['comment_id']))
|
|
error(
|
|
__("Error"),
|
|
__("An ID is required to edit a comment.", "comments"),
|
|
code:400
|
|
);
|
|
|
|
$comment = new Comment(
|
|
$_POST['comment_id'],
|
|
array("filter" => false)
|
|
);
|
|
|
|
if ($comment->no_results)
|
|
show_404(
|
|
__("Not Found"),
|
|
__("Comment not found.", "comments")
|
|
);
|
|
|
|
if (!$comment->editable())
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("You do not have sufficient privileges to edit this comment.", "comments")
|
|
);
|
|
|
|
$main = MainController::current();
|
|
$main->display(
|
|
"forms".DIR."comment".DIR."edit",
|
|
array("comment" => $comment)
|
|
);
|
|
}
|
|
|
|
public function ajax_destroy_comment(): void {
|
|
if (!isset($_POST['hash']) or !Session::check_token($_POST['hash']))
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("Invalid authentication token.")
|
|
);
|
|
|
|
if (empty($_POST['id']) or !is_numeric($_POST['id']))
|
|
error(
|
|
__("Error"),
|
|
__("An ID is required to delete a comment.", "comments"),
|
|
code:400
|
|
);
|
|
|
|
$comment = new Comment($_POST['id']);
|
|
|
|
if ($comment->no_results)
|
|
show_404(
|
|
__("Not Found"),
|
|
__("Comment not found.", "comments")
|
|
);
|
|
|
|
if (!$comment->deletable())
|
|
show_403(
|
|
__("Access Denied"),
|
|
__("You do not have sufficient privileges to delete this comment.", "comments")
|
|
);
|
|
|
|
Comment::delete($comment->id);
|
|
json_response(
|
|
__("Comment deleted.", "comments"),
|
|
true
|
|
);
|
|
}
|
|
|
|
public function links($links): array {
|
|
$config = Config::current();
|
|
$route = Route::current();
|
|
$main = MainController::current();
|
|
|
|
if ($route->action == "view" and !empty($main->context["post"])) {
|
|
$post = $main->context["post"];
|
|
|
|
if (!$post->no_results) {
|
|
$feed_url = ($config->clean_urls) ?
|
|
rtrim($post->url(), "/")."/feed/" :
|
|
$post->url()."&feed" ;
|
|
|
|
$text = oneof($post->title(), ucfirst($post->feather));
|
|
$title = _f("Comments on “%s”", $text, "comments");
|
|
|
|
$links[] = array(
|
|
"href" => $feed_url,
|
|
"type" => BlogFeed::type(),
|
|
"title" => $title
|
|
);
|
|
}
|
|
}
|
|
|
|
return $links;
|
|
}
|
|
|
|
public function main_view(): bool {
|
|
if (isset($_POST['action'])) {
|
|
if ($_POST['action'] == "add_comment") {
|
|
list($success, $message) = $this->add_comment();
|
|
$type = ($success) ? "notice" : "warning" ;
|
|
Flash::$type($message);
|
|
|
|
if ($success) {
|
|
unset($_POST['body']);
|
|
unset($_POST['author']);
|
|
unset($_POST['author_email']);
|
|
unset($_POST['author_url']);
|
|
}
|
|
}
|
|
|
|
if ($_POST['action'] == "update_comment") {
|
|
list($success, $message) = $this->update_comment();
|
|
$type = ($success) ? "notice" : "warning" ;
|
|
Flash::$type($message);
|
|
|
|
if ($success) {
|
|
unset($_POST['body']);
|
|
unset($_POST['author']);
|
|
unset($_POST['author_email']);
|
|
unset($_POST['author_url']);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function main_unsubscribe($main)/*: never */{
|
|
fallback($_GET['email']);
|
|
fallback($_GET['token']);
|
|
|
|
if (empty($_GET['id']) or !is_numeric($_GET['id']))
|
|
show_404(
|
|
__("Not Found"),
|
|
__("Post not found.")
|
|
);
|
|
|
|
$post = new Post($_GET['id']);
|
|
|
|
if ($post->no_results)
|
|
show_404(
|
|
__("Not Found"),
|
|
__("Post not found.")
|
|
);
|
|
|
|
if (!is_email($_GET['email']))
|
|
Flash::warning(
|
|
__("Invalid email address."),
|
|
"/"
|
|
);
|
|
|
|
$hash = token($_GET['email']);
|
|
|
|
if (!hash_equals($hash, $_GET['token']))
|
|
Flash::warning(
|
|
__("Invalid authentication token."),
|
|
"/"
|
|
);
|
|
|
|
SQL::current()->update(
|
|
table:"comments",
|
|
conds:array(
|
|
"post_id" => $post->id,
|
|
"author_email" => $_GET['email']
|
|
),
|
|
data:array("notify" => false)
|
|
);
|
|
|
|
Flash::notice(
|
|
__("You have unsubscribed from the conversation.", "comments"),
|
|
$post->url()
|
|
);
|
|
}
|
|
|
|
public function view_feed($context): void {
|
|
$trigger = Trigger::current();
|
|
|
|
if (!isset($context["post"]))
|
|
show_404(
|
|
__("Not Found"),
|
|
__("Post not found.")
|
|
);
|
|
|
|
$post = $context["post"];
|
|
$comments = $post->comments;
|
|
$latest_timestamp = 0;
|
|
$text = oneof($post->title(), ucfirst($post->feather));
|
|
$title = _f("Comments on “%s”", $text, "comments");
|
|
|
|
foreach ($comments as $comment) {
|
|
$created_at = strtotime($comment->created_at);
|
|
|
|
if ($latest_timestamp < $created_at)
|
|
$latest_timestamp = $created_at;
|
|
}
|
|
|
|
$feed = new BlogFeed();
|
|
|
|
$feed->open(
|
|
title:$title,
|
|
subtitle:Config::current()->description,
|
|
updated:$latest_timestamp
|
|
);
|
|
|
|
foreach ($comments as $comment) {
|
|
$updated = ($comment->updated) ?
|
|
$comment->updated_at :
|
|
$comment->created_at ;
|
|
|
|
$feed->entry(
|
|
title:_f("Comment #%d", $comment->id, "comments"),
|
|
id:url("comment/".$comment->id),
|
|
content:$comment->body,
|
|
link:$comment->post->url()."#comment_".$comment->id,
|
|
published:$comment->created_at,
|
|
updated:$updated,
|
|
name:$comment->author,
|
|
uri:$comment->author_url
|
|
);
|
|
|
|
$trigger->call("comments_feed_item", $comment, $feed);
|
|
}
|
|
|
|
$feed->display();
|
|
}
|
|
|
|
public function webmention($post, $from, $to): void {
|
|
$count = SQL::current()->count(
|
|
tables:"comments",
|
|
conds:array(
|
|
"post_id" => $post->id,
|
|
"status" => Comment::STATUS_PINGBACK,
|
|
"author_url" => $from
|
|
)
|
|
);
|
|
|
|
if (!empty($count))
|
|
error(
|
|
__("Error"),
|
|
__("A ping from your URL is already registered.", "comments"),
|
|
code:422
|
|
);
|
|
|
|
if (strlen($from) > 2048)
|
|
error(
|
|
__("Error"),
|
|
__("Your URL is too long to be stored in our database.", "comments"),
|
|
code:413
|
|
);
|
|
|
|
Comment::create(
|
|
body:__("Mentioned this post.", "comments"),
|
|
author:preg_replace("~(https?://|^)([^/:]+).*~", "$2", $from),
|
|
author_url:$from,
|
|
author_email:"",
|
|
post:$post,
|
|
parent:0,
|
|
notify:false,
|
|
status:Comment::STATUS_PINGBACK
|
|
);
|
|
}
|
|
|
|
public function javascript(): void {
|
|
$config = Config::current();
|
|
include MODULES_DIR.DIR."comments".DIR."javascript.php";
|
|
}
|
|
|
|
public function post_options($fields, $post = null): array {
|
|
$statuses = array(
|
|
array(
|
|
"name" => __("Open", "comments"),
|
|
"value" => Comment::OPTION_OPEN,
|
|
"selected" => isset($post) ?
|
|
$post->comment_status == "open" :
|
|
true
|
|
),
|
|
array(
|
|
"name" => __("Closed", "comments"),
|
|
"value" => Comment::OPTION_CLOSED,
|
|
"selected" => isset($post) ?
|
|
$post->comment_status == "closed" :
|
|
false
|
|
),
|
|
array(
|
|
"name" => __("Private", "comments"),
|
|
"value" => Comment::OPTION_PRIVATE,
|
|
"selected" => isset($post) ?
|
|
$post->comment_status == "private" :
|
|
false
|
|
),
|
|
array(
|
|
"name" => __("Registered Only", "comments"),
|
|
"value" => Comment::OPTION_REG_ONLY,
|
|
"selected" => isset($post) ?
|
|
$post->comment_status == "registered_only" :
|
|
false
|
|
)
|
|
);
|
|
|
|
$fields[] = array(
|
|
"attr" => "option[comment_status]",
|
|
"label" => __("Comment Status", "comments"),
|
|
"type" => "select",
|
|
"options" => $statuses
|
|
);
|
|
|
|
return $fields;
|
|
}
|
|
|
|
public function delete_post($post): void {
|
|
SQL::current()->delete(
|
|
table:"comments",
|
|
conds:array("post_id" => $post->id)
|
|
);
|
|
}
|
|
|
|
public function delete_user($user): void {
|
|
SQL::current()->update(
|
|
table:"comments",
|
|
conds:array("user_id" => $user->id),
|
|
data:array("user_id" => 0)
|
|
);
|
|
}
|
|
|
|
private function get_post_comment_count($post_id): int {
|
|
if (!isset($this->caches["post_comment_counts"])) {
|
|
$counts = SQL::current()->select(
|
|
tables:"comments",
|
|
fields:array("COUNT(post_id) AS total", "post_id AS post_id"),
|
|
conds:array(Comment::redactions()),
|
|
group:"post_id"
|
|
);
|
|
|
|
$this->caches["post_comment_counts"] = array();
|
|
|
|
foreach ($counts->fetchAll() as $count) {
|
|
$id = $count["post_id"];
|
|
$total = (int) $count["total"];
|
|
$this->caches["post_comment_counts"][$id] = $total;
|
|
}
|
|
}
|
|
|
|
return fallback($this->caches["post_comment_counts"][$post_id], 0);
|
|
}
|
|
|
|
public function post_comment_count_attr($attr, $post): int {
|
|
if ($post->no_results)
|
|
return 0;
|
|
|
|
return $this->get_post_comment_count($post->id);
|
|
}
|
|
|
|
private function get_latest_comments($post_id): ?string {
|
|
if (!isset($this->caches["latest_comments"])) {
|
|
$times = SQL::current()->select(
|
|
tables:"comments",
|
|
fields:array("MAX(created_at) AS latest", "post_id"),
|
|
conds:array(Comment::redactions()),
|
|
group:"post_id"
|
|
);
|
|
|
|
$this->caches["latest_comments"] = array();
|
|
|
|
foreach ($times->fetchAll() as $row) {
|
|
$id = $row["post_id"];
|
|
$latest = $row["latest"];
|
|
$this->caches["latest_comments"][$id] = $latest;
|
|
}
|
|
}
|
|
|
|
return fallback($this->caches["latest_comments"][$post_id], null);
|
|
}
|
|
|
|
public function post_latest_comment_attr($attr, $post): ?string {
|
|
if ($post->no_results)
|
|
return null;
|
|
|
|
return $this->get_latest_comments($post->id);
|
|
}
|
|
|
|
private function get_user_comment_count($user_id): int {
|
|
if (!isset($this->caches["user_comment_counts"])) {
|
|
$this->caches["user_comment_counts"] = array();
|
|
|
|
$counts = SQL::current()->select(
|
|
tables:"comments",
|
|
fields:array("COUNT(user_id) AS total", "user_id as user_id"),
|
|
conds:array(Comment::redactions()),
|
|
group:"user_id"
|
|
);
|
|
|
|
foreach ($counts->fetchAll() as $count) {
|
|
$id = $count["user_id"];
|
|
$total = (int) $count["total"];
|
|
$this->caches["user_comment_counts"][$id] = $total;
|
|
}
|
|
}
|
|
|
|
return fallback($this->caches["user_comment_counts"][$user_id], 0);
|
|
}
|
|
|
|
public function user_comment_count_attr($attr, $user): int {
|
|
if ($user->no_results)
|
|
return 0;
|
|
|
|
return $this->get_user_comment_count($user->id);
|
|
}
|
|
|
|
public function visitor_comment_count_attr($attr, $visitor): int {
|
|
return ($visitor->id == 0) ?
|
|
count(fallback($_SESSION['comments'], array())) :
|
|
$this->user_comment_count_attr($attr, $visitor) ;
|
|
}
|
|
|
|
public function post_commentable_attr($attr, $post): bool {
|
|
if ($post->no_results)
|
|
return false;
|
|
|
|
return Comment::creatable($post);
|
|
}
|
|
|
|
public function import_chyrp_post($entry, $post): void {
|
|
$chyrp = $entry->children("http://chyrp.net/export/1.0/");
|
|
|
|
if (!isset($chyrp->comment))
|
|
return;
|
|
|
|
foreach ($chyrp->comment as $comment) {
|
|
$chyrp = $comment->children("http://chyrp.net/export/1.0/");
|
|
$comment = $comment->children("http://www.w3.org/2005/Atom");
|
|
|
|
$login = $comment->author->children(
|
|
"http://chyrp.net/export/1.0/"
|
|
)->login;
|
|
|
|
$user = new User(
|
|
array("login" => unfix((string) $login))
|
|
);
|
|
|
|
$updated = (
|
|
(string) $comment->updated != (string) $comment->published
|
|
);
|
|
|
|
Comment::add(
|
|
body:unfix((string) $comment->content),
|
|
author:unfix((string) $comment->author->name),
|
|
author_url:unfix((string) $comment->author->uri),
|
|
author_email:unfix((string) $comment->author->email),
|
|
ip:0,
|
|
agent:"",
|
|
status:unfix((string) $chyrp->status),
|
|
post_id:$post->id,
|
|
user_id:(!$user->no_results) ? $user->id : 0,
|
|
parent:0,
|
|
notify:false,
|
|
created_at:datetime((string) $comment->published),
|
|
updated_at:($updated) ? datetime((string) $comment->updated) : null
|
|
);
|
|
}
|
|
}
|
|
|
|
public function posts_export($atom, $post): string {
|
|
$comments = Comment::find(
|
|
array("where" => array("post_id" => $post->id)),
|
|
array("filter" => false)
|
|
);
|
|
|
|
foreach ($comments as $comment) {
|
|
$updated = ($comment->updated) ?
|
|
$comment->updated_at :
|
|
$comment->created_at ;
|
|
|
|
$atom.= '<chyrp:comment>'."\n".
|
|
'<updated>'.
|
|
when(DATE_ATOM, $updated).
|
|
'</updated>'."\n".
|
|
'<published>'.
|
|
when(DATE_ATOM, $comment->created_at).
|
|
'</published>'."\n".
|
|
'<chyrp:etag>'.
|
|
fix($comment->etag(), false, true).
|
|
'</chyrp:etag>'."\n".
|
|
'<author chyrp:user_id="'.$comment->user_id.'">'."\n".
|
|
'<name>'.
|
|
fix($comment->author, false, true).
|
|
'</name>'."\n".
|
|
'<uri>'.
|
|
fix($comment->author_url, false, true).
|
|
'</uri>'."\n".
|
|
'<email>'.
|
|
fix($comment->author_email, false, true).
|
|
'</email>'."\n".
|
|
'<chyrp:login>'.
|
|
($comment->user->no_results ?
|
|
"" :
|
|
fix($comment->user->login, false, true)
|
|
).
|
|
'</chyrp:login>'."\n".
|
|
'</author>'."\n".
|
|
'<content type="html">'.
|
|
fix($comment->body, false, true).
|
|
'</content>'."\n".
|
|
'<chyrp:status>'.
|
|
fix($comment->status, false, true).
|
|
'</chyrp:status>'."\n".
|
|
'</chyrp:comment>'."\n";
|
|
}
|
|
|
|
return $atom;
|
|
}
|
|
|
|
public static function email_site_new_comment($comment): bool {
|
|
$config = Config::current();
|
|
$trigger = Trigger::current();
|
|
$mailto = $config->email;
|
|
|
|
if ($trigger->exists("correspond_site_new_comment"))
|
|
return $trigger->call("correspond_site_new_comment", $comment);
|
|
|
|
$headers = array(
|
|
"Content-Type" => "text/plain; charset=UTF-8",
|
|
"From" => $config->email,
|
|
"X-Mailer" => CHYRP_IDENTITY
|
|
);
|
|
|
|
$subject = _f("New Comment at %s", $config->name, "comments");
|
|
$message = _f("%s commented on a blog post:", $comment->author, "comments").
|
|
"\r\n".
|
|
unfix($comment->url()).
|
|
"\r\n".
|
|
"\r\n".
|
|
truncate(strip_tags($comment->body), 998);
|
|
|
|
return email($mailto, $subject, $message, $headers);
|
|
}
|
|
|
|
public static function email_user_new_comment($comment, $user): bool {
|
|
$config = Config::current();
|
|
$trigger = Trigger::current();
|
|
$mailto = $user->email;
|
|
|
|
if ($trigger->exists("correspond_user_new_comment"))
|
|
return $trigger->call("correspond_user_new_comment", $comment, $user);
|
|
|
|
$headers = array(
|
|
"Content-Type" => "text/plain; charset=UTF-8",
|
|
"From" => $config->email,
|
|
"X-Mailer" => CHYRP_IDENTITY
|
|
);
|
|
|
|
$subject = _f("New Comment at %s", $config->name, "comments");
|
|
$message = _f("%s commented on a blog post:", $comment->author, "comments").
|
|
"\r\n".
|
|
unfix($comment->url()).
|
|
"\r\n".
|
|
"\r\n".
|
|
truncate(strip_tags($comment->body), 998);
|
|
|
|
return email($mailto, $subject, $message, $headers);
|
|
}
|
|
|
|
public static function email_peer_new_comment($comment, $peer): bool {
|
|
$config = Config::current();
|
|
$trigger = Trigger::current();
|
|
$mailto = $peer->author_email;
|
|
|
|
$url = $config->url."/?action=unsubscribe".
|
|
"&email=".urlencode($mailto).
|
|
"&id=".$comment->post_id.
|
|
"&token=".token($mailto);
|
|
|
|
if ($trigger->exists("correspond_peer_new_comment"))
|
|
return $trigger->call("correspond_peer_new_comment", $comment, $peer, $url);
|
|
|
|
$headers = array(
|
|
"Content-Type" => "text/plain; charset=UTF-8",
|
|
"From" => $config->email,
|
|
"X-Mailer" => CHYRP_IDENTITY
|
|
);
|
|
|
|
$subject = _f("New Comment at %s", $config->name, "comments");
|
|
$message = _f("%s commented on a blog post:", $comment->author, "comments").
|
|
"\r\n".
|
|
unfix($comment->url()).
|
|
"\r\n".
|
|
"\r\n".
|
|
truncate(strip_tags($comment->body), 998).
|
|
"\r\n".
|
|
"\r\n".
|
|
__("Unsubscribe from this conversation:", "comments").
|
|
"\r\n".
|
|
unfix($url);
|
|
|
|
return email($mailto, $subject, $message, $headers);
|
|
}
|
|
}
|