2021-12-14 18:32:47 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
2010-01-04 12:41:04 +00:00
|
|
|
|
2023-01-10 22:44:09 +00:00
|
|
|
namespace Shimmie2;
|
|
|
|
|
2023-02-04 13:27:27 +00:00
|
|
|
use GQLA\Type;
|
|
|
|
use GQLA\Field;
|
|
|
|
use GQLA\Query;
|
|
|
|
use GQLA\Mutation;
|
2023-02-01 12:50:00 +00:00
|
|
|
|
2016-05-12 14:25:30 +00:00
|
|
|
require_once "vendor/ifixit/php-akismet/akismet.class.php";
|
2007-04-16 11:58:25 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
class CommentPostingEvent extends Event
|
|
|
|
{
|
2021-03-14 23:43:50 +00:00
|
|
|
public int $image_id;
|
|
|
|
public User $user;
|
|
|
|
public string $comment;
|
2019-05-28 16:59:38 +00:00
|
|
|
|
|
|
|
public function __construct(int $image_id, User $user, string $comment)
|
|
|
|
{
|
2020-01-26 13:19:35 +00:00
|
|
|
parent::__construct();
|
2019-05-28 16:59:38 +00:00
|
|
|
$this->image_id = $image_id;
|
|
|
|
$this->user = $user;
|
|
|
|
$this->comment = $comment;
|
|
|
|
}
|
2007-08-24 17:02:45 +00:00
|
|
|
}
|
2009-08-04 16:45:09 +00:00
|
|
|
|
|
|
|
/**
|
2007-04-16 11:58:25 +00:00
|
|
|
* A comment is being deleted. Maybe used by spam
|
2010-01-04 12:41:04 +00:00
|
|
|
* detectors to get a feel for what should be deleted
|
2007-04-16 11:58:25 +00:00
|
|
|
* and what should be kept?
|
|
|
|
*/
|
2019-05-28 16:59:38 +00:00
|
|
|
class CommentDeletionEvent extends Event
|
|
|
|
{
|
2021-03-14 23:43:50 +00:00
|
|
|
public int $comment_id;
|
2019-05-28 16:59:38 +00:00
|
|
|
|
|
|
|
public function __construct(int $comment_id)
|
|
|
|
{
|
2020-01-26 13:19:35 +00:00
|
|
|
parent::__construct();
|
2019-05-28 16:59:38 +00:00
|
|
|
$this->comment_id = $comment_id;
|
|
|
|
}
|
|
|
|
}
|
2007-04-16 11:58:25 +00:00
|
|
|
|
2024-02-11 15:47:40 +00:00
|
|
|
class CommentPostingException extends InvalidInput
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2007-04-16 11:58:25 +00:00
|
|
|
}
|
2009-08-04 16:45:09 +00:00
|
|
|
|
2023-02-04 13:27:27 +00:00
|
|
|
#[Type(name: "Comment")]
|
2019-05-28 16:59:38 +00:00
|
|
|
class Comment
|
|
|
|
{
|
2021-03-14 23:43:50 +00:00
|
|
|
public ?User $owner;
|
|
|
|
public int $owner_id;
|
|
|
|
public string $owner_name;
|
|
|
|
public ?string $owner_email;
|
|
|
|
public string $owner_class;
|
2023-02-04 13:27:27 +00:00
|
|
|
#[Field]
|
2021-03-14 23:43:50 +00:00
|
|
|
public string $comment;
|
2023-02-07 13:27:20 +00:00
|
|
|
#[Field]
|
2021-03-14 23:43:50 +00:00
|
|
|
public int $comment_id;
|
|
|
|
public int $image_id;
|
|
|
|
public string $poster_ip;
|
2023-02-04 13:27:27 +00:00
|
|
|
#[Field]
|
2021-03-14 23:43:50 +00:00
|
|
|
public string $posted;
|
2019-05-28 16:59:38 +00:00
|
|
|
|
2024-01-20 14:10:59 +00:00
|
|
|
/**
|
|
|
|
* @param array<string,mixed> $row
|
|
|
|
*/
|
|
|
|
public function __construct(array $row)
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
$this->owner = null;
|
2021-03-14 23:43:50 +00:00
|
|
|
$this->owner_id = (int)$row['user_id'];
|
2019-05-28 16:59:38 +00:00
|
|
|
$this->owner_name = $row['user_name'];
|
|
|
|
$this->owner_email = $row['user_email']; // deprecated
|
|
|
|
$this->owner_class = $row['user_class'];
|
|
|
|
$this->comment = $row['comment'];
|
2021-03-14 23:43:50 +00:00
|
|
|
$this->comment_id = (int)$row['comment_id'];
|
|
|
|
$this->image_id = (int)$row['image_id'];
|
2019-05-28 16:59:38 +00:00
|
|
|
$this->poster_ip = $row['poster_ip'];
|
|
|
|
$this->posted = $row['posted'];
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function count_comments_by_user(User $user): int
|
|
|
|
{
|
|
|
|
global $database;
|
2020-01-26 13:19:35 +00:00
|
|
|
return (int)$database->get_one("
|
2015-09-26 18:14:11 +00:00
|
|
|
SELECT COUNT(*) AS count
|
|
|
|
FROM comments
|
|
|
|
WHERE owner_id=:owner_id
|
2023-11-11 21:49:12 +00:00
|
|
|
", ["owner_id" => $user->id]);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
2023-02-04 13:27:27 +00:00
|
|
|
#[Field(name: "owner")]
|
2019-05-28 16:59:38 +00:00
|
|
|
public function get_owner(): User
|
|
|
|
{
|
|
|
|
if (empty($this->owner)) {
|
|
|
|
$this->owner = User::by_id($this->owner_id);
|
|
|
|
}
|
|
|
|
return $this->owner;
|
|
|
|
}
|
2023-02-01 12:50:00 +00:00
|
|
|
|
2024-01-20 14:10:59 +00:00
|
|
|
/**
|
|
|
|
* @return Comment[]
|
|
|
|
*/
|
2023-02-04 21:08:14 +00:00
|
|
|
#[Field(extends: "Post", name: "comments", type: "[Comment!]!")]
|
2023-02-07 13:27:20 +00:00
|
|
|
public static function get_comments(Image $post): array
|
2023-02-02 16:50:09 +00:00
|
|
|
{
|
2023-02-01 12:50:00 +00:00
|
|
|
return CommentList::get_comments($post->id);
|
|
|
|
}
|
|
|
|
|
2023-02-04 13:27:27 +00:00
|
|
|
#[Mutation(name: "create_comment")]
|
2023-02-08 00:18:10 +00:00
|
|
|
public static function create_comment(int $post_id, string $comment): bool
|
2023-02-02 16:50:09 +00:00
|
|
|
{
|
2023-02-01 12:50:00 +00:00
|
|
|
global $user;
|
|
|
|
send_event(new CommentPostingEvent($post_id, $user, $comment));
|
|
|
|
return true;
|
|
|
|
}
|
2009-08-04 16:45:09 +00:00
|
|
|
}
|
2009-05-11 14:04:33 +00:00
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
class CommentList extends Extension
|
|
|
|
{
|
|
|
|
/** @var CommentListTheme $theme */
|
2023-06-27 14:56:49 +00:00
|
|
|
public Themelet $theme;
|
2019-05-28 16:59:38 +00:00
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onInitExt(InitExtEvent $event): void
|
2019-11-03 18:28:38 +00:00
|
|
|
{
|
2019-11-03 17:19:37 +00:00
|
|
|
global $config;
|
2019-05-28 16:59:38 +00:00
|
|
|
$config->set_default_int('comment_window', 5);
|
|
|
|
$config->set_default_int('comment_limit', 10);
|
|
|
|
$config->set_default_int('comment_list_count', 10);
|
|
|
|
$config->set_default_int('comment_count', 5);
|
|
|
|
$config->set_default_bool('comment_captcha', false);
|
2019-11-03 17:19:37 +00:00
|
|
|
}
|
2019-05-28 16:59:38 +00:00
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event): void
|
2019-11-03 17:19:37 +00:00
|
|
|
{
|
2020-01-26 13:19:35 +00:00
|
|
|
global $database;
|
2019-11-03 19:49:52 +00:00
|
|
|
if ($this->get_version("ext_comments_version") < 3) {
|
2019-05-28 16:59:38 +00:00
|
|
|
// shortcut to latest
|
2019-11-03 19:49:52 +00:00
|
|
|
if ($this->get_version("ext_comments_version") < 1) {
|
2019-05-28 16:59:38 +00:00
|
|
|
$database->create_table("comments", "
|
2009-08-04 16:45:09 +00:00
|
|
|
id SCORE_AIPK,
|
|
|
|
image_id INTEGER NOT NULL,
|
|
|
|
owner_id INTEGER NOT NULL,
|
|
|
|
owner_ip SCORE_INET NOT NULL,
|
2019-11-03 19:15:09 +00:00
|
|
|
posted TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
2009-08-04 16:45:09 +00:00
|
|
|
comment TEXT NOT NULL,
|
2012-03-11 01:52:25 +00:00
|
|
|
FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE,
|
|
|
|
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT
|
2009-08-04 16:45:09 +00:00
|
|
|
");
|
2019-05-28 16:59:38 +00:00
|
|
|
$database->execute("CREATE INDEX comments_image_id_idx ON comments(image_id)", []);
|
|
|
|
$database->execute("CREATE INDEX comments_owner_id_idx ON comments(owner_id)", []);
|
|
|
|
$database->execute("CREATE INDEX comments_posted_idx ON comments(posted)", []);
|
2019-11-03 19:49:52 +00:00
|
|
|
$this->set_version("ext_comments_version", 3);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// the whole history
|
2019-11-03 19:49:52 +00:00
|
|
|
if ($this->get_version("ext_comments_version") < 1) {
|
2019-05-28 16:59:38 +00:00
|
|
|
$database->create_table("comments", "
|
2012-06-23 23:57:55 +00:00
|
|
|
id SCORE_AIPK,
|
2009-08-04 16:45:09 +00:00
|
|
|
image_id INTEGER NOT NULL,
|
|
|
|
owner_id INTEGER NOT NULL,
|
|
|
|
owner_ip CHAR(16) NOT NULL,
|
2019-11-03 19:15:09 +00:00
|
|
|
posted TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
2014-02-23 04:02:11 +00:00
|
|
|
comment TEXT NOT NULL
|
2012-03-10 18:57:35 +00:00
|
|
|
");
|
2019-05-28 16:59:38 +00:00
|
|
|
$database->execute("CREATE INDEX comments_image_id_idx ON comments(image_id)", []);
|
2019-11-03 19:49:52 +00:00
|
|
|
$this->set_version("ext_comments_version", 1);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
2019-11-03 19:49:52 +00:00
|
|
|
if ($this->get_version("ext_comments_version") == 1) {
|
2020-10-25 21:34:52 +00:00
|
|
|
$database->execute("CREATE INDEX comments_owner_ip ON comments(owner_ip)");
|
|
|
|
$database->execute("CREATE INDEX comments_posted ON comments(posted)");
|
2019-11-03 19:49:52 +00:00
|
|
|
$this->set_version("ext_comments_version", 2);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
2019-11-03 19:49:52 +00:00
|
|
|
if ($this->get_version("ext_comments_version") == 2) {
|
|
|
|
$this->set_version("ext_comments_version", 3);
|
2020-10-25 21:34:52 +00:00
|
|
|
$database->execute("ALTER TABLE comments ADD FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE");
|
|
|
|
$database->execute("ALTER TABLE comments ADD FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT");
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: add foreign keys, bump to v3
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-02 19:54:48 +00:00
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onPageNavBuilding(PageNavBuildingEvent $event): void
|
2019-08-02 19:54:48 +00:00
|
|
|
{
|
|
|
|
$event->add_nav_link("comment", new Link('comment/list'), "Comments");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event): void
|
2019-08-02 19:54:48 +00:00
|
|
|
{
|
2023-11-11 21:49:12 +00:00
|
|
|
if ($event->parent == "comment") {
|
2019-08-02 19:54:48 +00:00
|
|
|
$event->add_nav_link("comment_list", new Link('comment/list'), "All");
|
|
|
|
$event->add_nav_link("comment_help", new Link('ext_doc/comment'), "Help");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onPageRequest(PageRequestEvent $event): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2024-02-10 23:03:14 +00:00
|
|
|
global $cache, $config, $database, $user, $page;
|
|
|
|
if ($event->page_matches("comment/add", method: "POST", permission: Permissions::CREATE_COMMENT)) {
|
2024-02-11 15:47:40 +00:00
|
|
|
$i_iid = int_escape($event->req_POST('image_id'));
|
|
|
|
send_event(new CommentPostingEvent($i_iid, $user, $event->req_POST('comment')));
|
|
|
|
$page->set_mode(PageMode::REDIRECT);
|
|
|
|
$page->set_redirect(make_link("post/view/$i_iid", null, "comment_on_$i_iid"));
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
2024-02-11 11:34:09 +00:00
|
|
|
if ($event->page_matches("comment/delete/{comment_id}/{image_id}", permission: Permissions::DELETE_COMMENT)) {
|
2019-05-28 16:59:38 +00:00
|
|
|
// FIXME: post, not args
|
2024-02-11 11:34:09 +00:00
|
|
|
send_event(new CommentDeletionEvent($event->get_iarg('comment_id')));
|
2024-02-10 23:03:14 +00:00
|
|
|
$page->flash("Deleted comment");
|
|
|
|
$page->set_mode(PageMode::REDIRECT);
|
2024-02-11 11:34:09 +00:00
|
|
|
$page->set_redirect(referer_or(make_link("post/view/" . $event->get_iarg('image_id'))));
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
2024-02-10 23:03:14 +00:00
|
|
|
if ($event->page_matches("comment/bulk_delete", method: "POST", permission: Permissions::DELETE_COMMENT)) {
|
2024-02-10 01:55:05 +00:00
|
|
|
$ip = $event->req_POST('ip');
|
2019-05-28 16:59:38 +00:00
|
|
|
|
|
|
|
$comment_ids = $database->get_col("
|
2024-02-10 23:03:14 +00:00
|
|
|
SELECT id
|
|
|
|
FROM comments
|
|
|
|
WHERE owner_ip=:ip
|
|
|
|
", ["ip" => $ip]);
|
2019-05-28 16:59:38 +00:00
|
|
|
$num = count($comment_ids);
|
|
|
|
log_warning("comment", "Deleting $num comments from $ip");
|
|
|
|
foreach ($comment_ids as $cid) {
|
|
|
|
send_event(new CommentDeletionEvent($cid));
|
|
|
|
}
|
2019-12-15 19:47:18 +00:00
|
|
|
$page->flash("Deleted $num comments");
|
2019-05-28 16:59:38 +00:00
|
|
|
|
2019-06-19 01:58:28 +00:00
|
|
|
$page->set_mode(PageMode::REDIRECT);
|
2019-05-28 16:59:38 +00:00
|
|
|
$page->set_redirect(make_link("admin"));
|
|
|
|
}
|
2024-02-11 11:34:09 +00:00
|
|
|
if ($event->page_matches("comment/list", paged: true)) {
|
2024-02-10 23:03:14 +00:00
|
|
|
$threads_per_page = 10;
|
|
|
|
|
2024-06-14 13:37:24 +00:00
|
|
|
$speed_hax = (Extension::is_enabled(SpeedHaxInfo::KEY) && $config->get_bool(SpeedHaxConfig::CACHE_TAG_LISTS));
|
|
|
|
$where = $speed_hax ? "WHERE posted > now() - interval '24 hours'" : "";
|
2024-02-10 23:03:14 +00:00
|
|
|
|
|
|
|
$total_pages = cache_get_or_set("comment_pages", fn () => (int)ceil($database->get_one("
|
|
|
|
SELECT COUNT(c1)
|
|
|
|
FROM (SELECT COUNT(image_id) AS c1 FROM comments $where GROUP BY image_id) AS s1
|
|
|
|
") / $threads_per_page), 600);
|
|
|
|
$total_pages = max($total_pages, 1);
|
|
|
|
|
2024-02-11 11:34:09 +00:00
|
|
|
$current_page = $event->get_iarg('page_num', 1) - 1;
|
2024-02-10 23:03:14 +00:00
|
|
|
$start = $threads_per_page * $current_page;
|
|
|
|
|
|
|
|
$result = $database->execute("
|
|
|
|
SELECT image_id,MAX(posted) AS latest
|
|
|
|
FROM comments
|
|
|
|
$where
|
|
|
|
GROUP BY image_id
|
|
|
|
ORDER BY latest DESC
|
|
|
|
LIMIT :limit OFFSET :offset
|
|
|
|
", ["limit" => $threads_per_page, "offset" => $start]);
|
|
|
|
|
|
|
|
$user_ratings = Extension::is_enabled(RatingsInfo::KEY) ? Ratings::get_user_class_privs($user) : [];
|
|
|
|
|
|
|
|
$images = [];
|
|
|
|
while ($row = $result->fetch()) {
|
|
|
|
$image = Image::by_id((int)$row["image_id"]);
|
|
|
|
if (
|
|
|
|
Extension::is_enabled(RatingsInfo::KEY) && !is_null($image) &&
|
|
|
|
!in_array($image['rating'], $user_ratings)
|
|
|
|
) {
|
|
|
|
$image = null; // this is "clever", I may live to regret it
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
Extension::is_enabled(ApprovalInfo::KEY) && !is_null($image) &&
|
|
|
|
$config->get_bool(ApprovalConfig::IMAGES) &&
|
|
|
|
$image['approved'] !== true
|
|
|
|
) {
|
|
|
|
$image = null;
|
|
|
|
}
|
|
|
|
if (!is_null($image)) {
|
|
|
|
$comments = $this->get_comments($image->id);
|
|
|
|
$images[] = [$image, $comments];
|
|
|
|
}
|
2020-03-27 14:41:24 +00:00
|
|
|
}
|
|
|
|
|
2024-02-10 23:03:14 +00:00
|
|
|
$this->theme->display_comment_list($images, $current_page + 1, $total_pages, $user->can(Permissions::CREATE_COMMENT));
|
|
|
|
}
|
2024-02-11 11:34:09 +00:00
|
|
|
if ($event->page_matches("comment/beta-search/{search}", paged: true)) {
|
|
|
|
$search = $event->get_arg('search');
|
|
|
|
$page_num = $event->get_iarg('page_num', 1) - 1;
|
2024-02-10 23:03:14 +00:00
|
|
|
$duser = User::by_name($search);
|
|
|
|
$i_comment_count = Comment::count_comments_by_user($duser);
|
|
|
|
$com_per_page = 50;
|
|
|
|
$total_pages = (int)ceil($i_comment_count / $com_per_page);
|
|
|
|
$comments = $this->get_user_comments($duser->id, $com_per_page, $page_num * $com_per_page);
|
|
|
|
$this->theme->display_all_user_comments($comments, $page_num + 1, $total_pages, $duser);
|
|
|
|
}
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
2024-02-10 23:03:14 +00:00
|
|
|
public function onRobotsBuilding(RobotsBuildingEvent $event): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2024-02-10 23:03:14 +00:00
|
|
|
// comment lists change all the time, crawlers should
|
|
|
|
// index individual image's comments
|
|
|
|
$event->add_disallow("comment");
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onAdminBuilding(AdminBuildingEvent $event): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
$this->theme->display_admin_block();
|
|
|
|
}
|
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onPostListBuilding(PostListBuildingEvent $event): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2019-10-02 10:23:57 +00:00
|
|
|
global $cache, $config;
|
2019-05-28 16:59:38 +00:00
|
|
|
$cc = $config->get_int("comment_count");
|
|
|
|
if ($cc > 0) {
|
2023-12-14 17:06:54 +00:00
|
|
|
$recent = cache_get_or_set("recent_comments", fn () => $this->get_recent_comments($cc), 60);
|
2019-05-28 16:59:38 +00:00
|
|
|
if (count($recent) > 0) {
|
|
|
|
$this->theme->display_recent_comments($recent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onUserPageBuilding(UserPageBuildingEvent $event): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2024-02-20 00:22:25 +00:00
|
|
|
$i_days_old = ((time() - \Safe\strtotime($event->display_user->join_date)) / 86400) + 1;
|
2019-05-28 16:59:38 +00:00
|
|
|
$i_comment_count = Comment::count_comments_by_user($event->display_user);
|
|
|
|
$h_comment_rate = sprintf("%.1f", ($i_comment_count / $i_days_old));
|
2024-03-28 12:42:39 +00:00
|
|
|
$event->add_part("Comments made: $i_comment_count, $h_comment_rate per day");
|
2019-05-28 16:59:38 +00:00
|
|
|
|
|
|
|
$recent = $this->get_user_comments($event->display_user->id, 10);
|
|
|
|
$this->theme->display_recent_user_comments($recent, $event->display_user);
|
|
|
|
}
|
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onDisplayingImage(DisplayingImageEvent $event): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
global $user;
|
|
|
|
$this->theme->display_image_comments(
|
|
|
|
$event->image,
|
|
|
|
$this->get_comments($event->image->id),
|
2019-07-09 14:10:21 +00:00
|
|
|
$user->can(Permissions::CREATE_COMMENT)
|
2019-05-28 16:59:38 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: split akismet into a separate class, which can veto the event
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onCommentPosting(CommentPostingEvent $event): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
$this->add_comment_wrapper($event->image_id, $event->user, $event->comment);
|
|
|
|
}
|
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onCommentDeletion(CommentDeletionEvent $event): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
global $database;
|
2020-10-25 21:34:52 +00:00
|
|
|
$database->execute("
|
2015-09-26 18:14:11 +00:00
|
|
|
DELETE FROM comments
|
|
|
|
WHERE id=:comment_id
|
2023-11-11 21:49:12 +00:00
|
|
|
", ["comment_id" => $event->comment_id]);
|
2019-05-28 16:59:38 +00:00
|
|
|
log_info("comment", "Deleting Comment #{$event->comment_id}");
|
|
|
|
}
|
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onSetupBuilding(SetupBuildingEvent $event): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2020-10-26 15:13:28 +00:00
|
|
|
$sb = $event->panel->create_new_block("Comment Options");
|
2019-05-28 16:59:38 +00:00
|
|
|
$sb->add_bool_option("comment_captcha", "Require CAPTCHA for anonymous comments: ");
|
|
|
|
$sb->add_label("<br>Limit to ");
|
|
|
|
$sb->add_int_option("comment_limit");
|
|
|
|
$sb->add_label(" comments per ");
|
|
|
|
$sb->add_int_option("comment_window");
|
|
|
|
$sb->add_label(" minutes");
|
|
|
|
$sb->add_label("<br>Show ");
|
|
|
|
$sb->add_int_option("comment_count");
|
|
|
|
$sb->add_label(" recent comments on the index");
|
|
|
|
$sb->add_label("<br>Show ");
|
|
|
|
$sb->add_int_option("comment_list_count");
|
|
|
|
$sb->add_label(" comments per image on the list");
|
|
|
|
$sb->add_label("<br>Make samefags public ");
|
|
|
|
$sb->add_bool_option("comment_samefags_public");
|
|
|
|
}
|
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onSearchTermParse(SearchTermParseEvent $event): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2020-01-26 16:38:26 +00:00
|
|
|
if (is_null($event->term)) {
|
|
|
|
return;
|
|
|
|
}
|
2019-05-28 16:59:38 +00:00
|
|
|
|
2020-01-26 13:19:35 +00:00
|
|
|
$matches = [];
|
2019-05-28 16:59:38 +00:00
|
|
|
if (preg_match("/^comments([:]?<|[:]?>|[:]?<=|[:]?>=|[:|=])(\d+)$/i", $event->term, $matches)) {
|
|
|
|
$cmp = ltrim($matches[1], ":") ?: "=";
|
|
|
|
$comments = $matches[2];
|
|
|
|
$event->add_querylet(new Querylet("images.id IN (SELECT DISTINCT image_id FROM comments GROUP BY image_id HAVING count(image_id) $cmp $comments)"));
|
|
|
|
} elseif (preg_match("/^commented_by[=|:](.*)$/i", $event->term, $matches)) {
|
2019-11-11 16:31:14 +00:00
|
|
|
$user_id = User::name_to_id($matches[1]);
|
2019-05-28 16:59:38 +00:00
|
|
|
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM comments WHERE owner_id = $user_id)"));
|
|
|
|
} elseif (preg_match("/^commented_by_userno[=|:]([0-9]+)$/i", $event->term, $matches)) {
|
|
|
|
$user_id = int_escape($matches[1]);
|
|
|
|
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM comments WHERE owner_id = $user_id)"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-15 11:52:35 +00:00
|
|
|
public function onHelpPageBuilding(HelpPageBuildingEvent $event): void
|
2019-08-02 20:05:49 +00:00
|
|
|
{
|
2023-11-11 21:49:12 +00:00
|
|
|
if ($event->key === HelpPages::SEARCH) {
|
2019-08-02 20:05:49 +00:00
|
|
|
$block = new Block();
|
|
|
|
$block->header = "Comments";
|
|
|
|
$block->body = $this->theme->get_help_html();
|
|
|
|
$event->add_block($block);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-28 16:59:38 +00:00
|
|
|
/**
|
2024-01-20 14:10:59 +00:00
|
|
|
* @param array<string,mixed> $args
|
2024-01-06 21:28:06 +00:00
|
|
|
* @return Comment[]
|
2019-05-28 16:59:38 +00:00
|
|
|
*/
|
2023-02-01 12:50:00 +00:00
|
|
|
private static function get_generic_comments(string $query, array $args): array
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
global $database;
|
|
|
|
$rows = $database->get_all($query, $args);
|
|
|
|
$comments = [];
|
|
|
|
foreach ($rows as $row) {
|
|
|
|
$comments[] = new Comment($row);
|
|
|
|
}
|
|
|
|
return $comments;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-01-06 21:28:06 +00:00
|
|
|
* @return Comment[]
|
2019-05-28 16:59:38 +00:00
|
|
|
*/
|
2023-02-01 12:50:00 +00:00
|
|
|
private static function get_recent_comments(int $count): array
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2023-02-01 12:50:00 +00:00
|
|
|
return CommentList::get_generic_comments("
|
2015-09-26 18:14:11 +00:00
|
|
|
SELECT
|
|
|
|
users.id as user_id, users.name as user_name, users.email as user_email, users.class as user_class,
|
|
|
|
comments.comment as comment, comments.id as comment_id,
|
|
|
|
comments.image_id as image_id, comments.owner_ip as poster_ip,
|
|
|
|
comments.posted as posted
|
|
|
|
FROM comments
|
|
|
|
LEFT JOIN users ON comments.owner_id=users.id
|
|
|
|
ORDER BY comments.id DESC
|
|
|
|
LIMIT :limit
|
2023-11-11 21:49:12 +00:00
|
|
|
", ["limit" => $count]);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2024-01-06 21:28:06 +00:00
|
|
|
* @return Comment[]
|
2019-05-28 16:59:38 +00:00
|
|
|
*/
|
2023-11-11 21:49:12 +00:00
|
|
|
private static function get_user_comments(int $user_id, int $count, int $offset = 0): array
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2023-02-01 12:50:00 +00:00
|
|
|
return CommentList::get_generic_comments("
|
2015-09-26 18:14:11 +00:00
|
|
|
SELECT
|
2013-09-09 12:41:08 +00:00
|
|
|
users.id as user_id, users.name as user_name, users.email as user_email, users.class as user_class,
|
2011-12-31 13:54:32 +00:00
|
|
|
comments.comment as comment, comments.id as comment_id,
|
|
|
|
comments.image_id as image_id, comments.owner_ip as poster_ip,
|
|
|
|
comments.posted as posted
|
2015-09-26 18:14:11 +00:00
|
|
|
FROM comments
|
|
|
|
LEFT JOIN users ON comments.owner_id=users.id
|
|
|
|
WHERE users.id = :user_id
|
|
|
|
ORDER BY comments.id DESC
|
|
|
|
LIMIT :limit OFFSET :offset
|
2023-11-11 21:49:12 +00:00
|
|
|
", ["user_id" => $user_id, "offset" => $offset, "limit" => $count]);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-02-01 12:50:00 +00:00
|
|
|
* public just for Image::get_comments()
|
2024-01-06 21:28:06 +00:00
|
|
|
* @return Comment[]
|
2019-05-28 16:59:38 +00:00
|
|
|
*/
|
2023-02-01 12:50:00 +00:00
|
|
|
public static function get_comments(int $image_id): array
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
2023-02-01 12:50:00 +00:00
|
|
|
return CommentList::get_generic_comments("
|
2015-09-26 18:14:11 +00:00
|
|
|
SELECT
|
2013-09-09 12:41:08 +00:00
|
|
|
users.id as user_id, users.name as user_name, users.email as user_email, users.class as user_class,
|
2007-04-29 02:43:41 +00:00
|
|
|
comments.comment as comment, comments.id as comment_id,
|
2007-07-21 12:11:41 +00:00
|
|
|
comments.image_id as image_id, comments.owner_ip as poster_ip,
|
|
|
|
comments.posted as posted
|
2015-09-26 18:14:11 +00:00
|
|
|
FROM comments
|
|
|
|
LEFT JOIN users ON comments.owner_id=users.id
|
|
|
|
WHERE comments.image_id=:image_id
|
|
|
|
ORDER BY comments.id ASC
|
2023-11-11 21:49:12 +00:00
|
|
|
", ["image_id" => $image_id]);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private function is_comment_limit_hit(): bool
|
|
|
|
{
|
|
|
|
global $config, $database;
|
|
|
|
|
|
|
|
// sqlite fails at intervals
|
2022-10-28 00:45:35 +00:00
|
|
|
if ($database->get_driver_id() === DatabaseDriverID::SQLITE) {
|
2019-05-28 16:59:38 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-01-26 17:39:55 +00:00
|
|
|
$window = $config->get_int('comment_window');
|
|
|
|
$max = $config->get_int('comment_limit');
|
2019-05-28 16:59:38 +00:00
|
|
|
|
2022-10-28 00:45:35 +00:00
|
|
|
if ($database->get_driver_id() == DatabaseDriverID::MYSQL) {
|
2019-05-28 16:59:38 +00:00
|
|
|
$window_sql = "interval $window minute";
|
|
|
|
} else {
|
|
|
|
$window_sql = "interval '$window minute'";
|
|
|
|
}
|
|
|
|
|
|
|
|
// window doesn't work as an SQL param because it's inside quotes >_<
|
|
|
|
$result = $database->get_all("
|
2015-09-26 18:14:11 +00:00
|
|
|
SELECT *
|
|
|
|
FROM comments
|
|
|
|
WHERE owner_ip = :remote_ip AND posted > now() - $window_sql
|
2023-11-11 21:49:12 +00:00
|
|
|
", ["remote_ip" => get_real_ip()]);
|
2019-05-28 16:59:38 +00:00
|
|
|
|
|
|
|
return (count($result) >= $max);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* get a hash which semi-uniquely identifies a submission form,
|
|
|
|
* to stop spam bots which download the form once then submit
|
|
|
|
* many times.
|
|
|
|
*
|
|
|
|
* FIXME: assumes comments are posted via HTTP...
|
|
|
|
*/
|
|
|
|
public static function get_hash(): string
|
|
|
|
{
|
2022-01-17 17:06:20 +00:00
|
|
|
return md5(get_real_ip() . date("%Y%m%d"));
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private function is_spam_akismet(string $text): bool
|
|
|
|
{
|
|
|
|
global $config, $user;
|
2023-12-15 19:01:15 +00:00
|
|
|
$key = $config->get_string('comment_wordpress_key');
|
|
|
|
if (!is_null($key) && strlen($key) > 0) {
|
2019-05-28 16:59:38 +00:00
|
|
|
$comment = [
|
|
|
|
'author' => $user->name,
|
|
|
|
'email' => $user->email,
|
|
|
|
'website' => '',
|
|
|
|
'body' => $text,
|
|
|
|
'permalink' => '',
|
2020-03-27 19:42:46 +00:00
|
|
|
'referrer' => $_SERVER['HTTP_REFERER'] ?? 'none',
|
|
|
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'none',
|
|
|
|
];
|
2019-05-28 16:59:38 +00:00
|
|
|
|
2023-02-03 16:44:16 +00:00
|
|
|
// @phpstan-ignore-next-line
|
2023-12-15 19:01:15 +00:00
|
|
|
$akismet = new \Akismet($_SERVER['SERVER_NAME'], $key, $comment);
|
2019-05-28 16:59:38 +00:00
|
|
|
|
2023-02-03 20:03:04 +00:00
|
|
|
// @phpstan-ignore-next-line
|
2019-05-28 16:59:38 +00:00
|
|
|
if ($akismet->errorsExist()) {
|
|
|
|
return false;
|
|
|
|
} else {
|
2023-02-03 20:03:04 +00:00
|
|
|
// @phpstan-ignore-next-line
|
2019-05-28 16:59:38 +00:00
|
|
|
return $akismet->isSpam();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function is_dupe(int $image_id, string $comment): bool
|
|
|
|
{
|
|
|
|
global $database;
|
|
|
|
return (bool)$database->get_row("
|
2015-09-26 18:14:11 +00:00
|
|
|
SELECT *
|
|
|
|
FROM comments
|
|
|
|
WHERE image_id=:image_id AND comment=:comment
|
2023-11-11 21:49:12 +00:00
|
|
|
", ["image_id" => $image_id, "comment" => $comment]);
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
// do some checks
|
|
|
|
|
2024-01-20 14:10:59 +00:00
|
|
|
private function add_comment_wrapper(int $image_id, User $user, string $comment): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
global $database, $page;
|
|
|
|
|
2019-07-09 14:10:21 +00:00
|
|
|
if (!$user->can(Permissions::BYPASS_COMMENT_CHECKS)) {
|
2019-05-28 16:59:38 +00:00
|
|
|
// will raise an exception if anything is wrong
|
|
|
|
$this->comment_checks($image_id, $user, $comment);
|
|
|
|
}
|
|
|
|
|
|
|
|
// all checks passed
|
|
|
|
if ($user->is_anonymous()) {
|
2023-11-11 21:49:12 +00:00
|
|
|
$page->add_cookie("nocache", "Anonymous Commenter", time() + 60 * 60 * 24, "/");
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
2023-06-25 20:31:11 +00:00
|
|
|
$database->execute(
|
|
|
|
"INSERT INTO comments(image_id, owner_id, owner_ip, posted, comment) ".
|
|
|
|
"VALUES(:image_id, :user_id, :remote_addr, now(), :comment)",
|
2023-11-11 21:49:12 +00:00
|
|
|
["image_id" => $image_id, "user_id" => $user->id, "remote_addr" => get_real_ip(), "comment" => $comment]
|
2019-05-28 16:59:38 +00:00
|
|
|
);
|
2023-06-25 20:31:11 +00:00
|
|
|
$cid = $database->get_last_insert_id('comments_id_seq');
|
2019-05-28 16:59:38 +00:00
|
|
|
$snippet = substr($comment, 0, 100);
|
|
|
|
$snippet = str_replace("\n", " ", $snippet);
|
|
|
|
$snippet = str_replace("\r", " ", $snippet);
|
2020-10-09 12:47:42 +00:00
|
|
|
log_info("comment", "Comment #$cid added to >>$image_id: $snippet");
|
2019-05-28 16:59:38 +00:00
|
|
|
}
|
|
|
|
|
2024-01-20 14:10:59 +00:00
|
|
|
private function comment_checks(int $image_id, User $user, string $comment): void
|
2019-05-28 16:59:38 +00:00
|
|
|
{
|
|
|
|
global $config, $page;
|
|
|
|
|
|
|
|
// basic sanity checks
|
2019-07-09 14:10:21 +00:00
|
|
|
if (!$user->can(Permissions::CREATE_COMMENT)) {
|
2019-05-28 16:59:38 +00:00
|
|
|
throw new CommentPostingException("Anonymous posting has been disabled");
|
|
|
|
} elseif (is_null(Image::by_id($image_id))) {
|
|
|
|
throw new CommentPostingException("The image does not exist");
|
|
|
|
} elseif (trim($comment) == "") {
|
|
|
|
throw new CommentPostingException("Comments need text...");
|
|
|
|
} elseif (strlen($comment) > 9000) {
|
|
|
|
throw new CommentPostingException("Comment too long~");
|
|
|
|
}
|
|
|
|
|
|
|
|
// advanced sanity checks
|
2024-02-20 00:22:25 +00:00
|
|
|
elseif (strlen($comment) / strlen(\Safe\gzcompress($comment)) > 10) {
|
2019-05-28 16:59:38 +00:00
|
|
|
throw new CommentPostingException("Comment too repetitive~");
|
2024-02-10 01:55:05 +00:00
|
|
|
} elseif ($user->is_anonymous() && ($_POST['hash'] != $this->get_hash())) {
|
2023-11-11 21:49:12 +00:00
|
|
|
$page->add_cookie("nocache", "Anonymous Commenter", time() + 60 * 60 * 24, "/");
|
2019-05-28 16:59:38 +00:00
|
|
|
throw new CommentPostingException(
|
|
|
|
"Comment submission form is out of date; refresh the ".
|
|
|
|
"comment form to show you aren't a spammer~"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// database-querying checks
|
|
|
|
elseif ($this->is_comment_limit_hit()) {
|
|
|
|
throw new CommentPostingException("You've posted several comments recently; wait a minute and try again...");
|
|
|
|
} elseif ($this->is_dupe($image_id, $comment)) {
|
|
|
|
throw new CommentPostingException("Someone already made that comment on that image -- try and be more original?");
|
|
|
|
}
|
|
|
|
|
|
|
|
// rate-limited external service checks last
|
|
|
|
elseif ($config->get_bool('comment_captcha') && !captcha_check()) {
|
|
|
|
throw new CommentPostingException("Error in captcha");
|
|
|
|
} elseif ($user->is_anonymous() && $this->is_spam_akismet($comment)) {
|
|
|
|
throw new CommentPostingException("Akismet thinks that your comment is spam. Try rewriting the comment, or logging in.");
|
|
|
|
}
|
|
|
|
}
|
2007-04-16 11:58:25 +00:00
|
|
|
}
|