640 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			640 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * Name: Image Comments
 | |
|  * Author: Shish <webmaster@shishnet.org>
 | |
|  * Link: http://code.shishnet.org/shimmie2/
 | |
|  * License: GPLv2
 | |
|  * Description: Allow users to make comments on images
 | |
|  * Documentation:
 | |
|  *  Formatting is done with the standard formatting API (normally BBCode)
 | |
|  */
 | |
| 
 | |
| require_once "vendor/ifixit/php-akismet/akismet.class.php";
 | |
| 
 | |
| class CommentPostingEvent extends Event
 | |
| {
 | |
|     /** @var  int */
 | |
|     public $image_id;
 | |
|     /** @var User */
 | |
|     public $user;
 | |
|     /** @var string  */
 | |
|     public $comment;
 | |
| 
 | |
|     public function __construct(int $image_id, User $user, string $comment)
 | |
|     {
 | |
|         $this->image_id = $image_id;
 | |
|         $this->user = $user;
 | |
|         $this->comment = $comment;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A comment is being deleted. Maybe used by spam
 | |
|  * detectors to get a feel for what should be deleted
 | |
|  * and what should be kept?
 | |
|  */
 | |
| class CommentDeletionEvent extends Event
 | |
| {
 | |
|     /** @var  int */
 | |
|     public $comment_id;
 | |
| 
 | |
|     public function __construct(int $comment_id)
 | |
|     {
 | |
|         $this->comment_id = $comment_id;
 | |
|     }
 | |
| }
 | |
| 
 | |
| class CommentPostingException extends SCoreException
 | |
| {
 | |
| }
 | |
| 
 | |
| class Comment
 | |
| {
 | |
|     public $owner;
 | |
|     public $owner_id;
 | |
|     public $owner_name;
 | |
|     public $owner_email;
 | |
|     public $owner_class;
 | |
|     public $comment;
 | |
|     public $comment_id;
 | |
|     public $image_id;
 | |
|     public $poster_ip;
 | |
|     public $posted;
 | |
| 
 | |
|     public function __construct($row)
 | |
|     {
 | |
|         $this->owner = null;
 | |
|         $this->owner_id = $row['user_id'];
 | |
|         $this->owner_name = $row['user_name'];
 | |
|         $this->owner_email = $row['user_email']; // deprecated
 | |
|         $this->owner_class = $row['user_class'];
 | |
|         $this->comment =  $row['comment'];
 | |
|         $this->comment_id =  $row['comment_id'];
 | |
|         $this->image_id =  $row['image_id'];
 | |
|         $this->poster_ip =  $row['poster_ip'];
 | |
|         $this->posted =  $row['posted'];
 | |
|     }
 | |
| 
 | |
|     public static function count_comments_by_user(User $user): int
 | |
|     {
 | |
|         global $database;
 | |
|         return $database->get_one("
 | |
| 			SELECT COUNT(*) AS count
 | |
| 			FROM comments
 | |
| 			WHERE owner_id=:owner_id
 | |
| 		", ["owner_id"=>$user->id]);
 | |
|     }
 | |
| 
 | |
|     public function get_owner(): User
 | |
|     {
 | |
|         if (empty($this->owner)) {
 | |
|             $this->owner = User::by_id($this->owner_id);
 | |
|         }
 | |
|         return $this->owner;
 | |
|     }
 | |
| }
 | |
| 
 | |
| class CommentList extends Extension
 | |
| {
 | |
|     /** @var CommentListTheme $theme */
 | |
|     public $theme;
 | |
| 
 | |
|     public function onInitExt(InitExtEvent $event)
 | |
|     {
 | |
|         global $config, $database;
 | |
|         $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);
 | |
| 
 | |
|         if ($config->get_int("ext_comments_version") < 3) {
 | |
|             // shortcut to latest
 | |
|             if ($config->get_int("ext_comments_version") < 1) {
 | |
|                 $database->create_table("comments", "
 | |
| 					id SCORE_AIPK,
 | |
| 					image_id INTEGER NOT NULL,
 | |
| 					owner_id INTEGER NOT NULL,
 | |
| 					owner_ip SCORE_INET NOT NULL,
 | |
| 					posted SCORE_DATETIME DEFAULT NULL,
 | |
| 					comment TEXT NOT NULL,
 | |
| 					FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE,
 | |
| 					FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT
 | |
| 				");
 | |
|                 $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)", []);
 | |
|                 $config->set_int("ext_comments_version", 3);
 | |
|             }
 | |
| 
 | |
|             // the whole history
 | |
|             if ($config->get_int("ext_comments_version") < 1) {
 | |
|                 $database->create_table("comments", "
 | |
| 					id SCORE_AIPK,
 | |
| 					image_id INTEGER NOT NULL,
 | |
| 					owner_id INTEGER NOT NULL,
 | |
| 					owner_ip CHAR(16) NOT NULL,
 | |
| 					posted SCORE_DATETIME DEFAULT NULL,
 | |
| 					comment TEXT NOT NULL
 | |
| 				");
 | |
|                 $database->execute("CREATE INDEX comments_image_id_idx ON comments(image_id)", []);
 | |
|                 $config->set_int("ext_comments_version", 1);
 | |
|             }
 | |
| 
 | |
|             if ($config->get_int("ext_comments_version") == 1) {
 | |
|                 $database->Execute("CREATE INDEX comments_owner_ip ON comments(owner_ip)");
 | |
|                 $database->Execute("CREATE INDEX comments_posted ON comments(posted)");
 | |
|                 $config->set_int("ext_comments_version", 2);
 | |
|             }
 | |
| 
 | |
|             if ($config->get_int("ext_comments_version") == 2) {
 | |
|                 $config->set_int("ext_comments_version", 3);
 | |
|                 $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");
 | |
|             }
 | |
| 
 | |
|             // FIXME: add foreign keys, bump to v3
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public function onPageRequest(PageRequestEvent $event)
 | |
|     {
 | |
|         if ($event->page_matches("comment")) {
 | |
|             switch ($event->get_arg(0)) {
 | |
|                 case "add": $this->onPageRequest_add(); break;
 | |
|                 case "delete": $this->onPageRequest_delete($event); break;
 | |
|                 case "bulk_delete": $this->onPageRequest_bulk_delete(); break;
 | |
|                 case "list": $this->onPageRequest_list($event); break;
 | |
|                 case "beta-search": $this->onPageRequest_beta_search($event); break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private function onPageRequest_add()
 | |
|     {
 | |
|         global $user, $page;
 | |
|         if (isset($_POST['image_id']) && isset($_POST['comment'])) {
 | |
|             try {
 | |
|                 $i_iid = int_escape($_POST['image_id']);
 | |
|                 $cpe = new CommentPostingEvent($_POST['image_id'], $user, $_POST['comment']);
 | |
|                 send_event($cpe);
 | |
|                 $page->set_mode("redirect");
 | |
|                 $page->set_redirect(make_link("post/view/$i_iid#comment_on_$i_iid"));
 | |
|             } catch (CommentPostingException $ex) {
 | |
|                 $this->theme->display_error(403, "Comment Blocked", $ex->getMessage());
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private function onPageRequest_delete(PageRequestEvent $event)
 | |
|     {
 | |
|         global $user, $page;
 | |
|         if ($user->can("delete_comment")) {
 | |
|             // FIXME: post, not args
 | |
|             if ($event->count_args() === 3) {
 | |
|                 send_event(new CommentDeletionEvent($event->get_arg(1)));
 | |
|                 flash_message("Deleted comment");
 | |
|                 $page->set_mode("redirect");
 | |
|                 if (!empty($_SERVER['HTTP_REFERER'])) {
 | |
|                     $page->set_redirect($_SERVER['HTTP_REFERER']);
 | |
|                 } else {
 | |
|                     $page->set_redirect(make_link("post/view/" . $event->get_arg(2)));
 | |
|                 }
 | |
|             }
 | |
|         } else {
 | |
|             $this->theme->display_permission_denied();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private function onPageRequest_bulk_delete()
 | |
|     {
 | |
|         global $user, $database, $page;
 | |
|         if ($user->can("delete_comment") && !empty($_POST["ip"])) {
 | |
|             $ip = $_POST['ip'];
 | |
| 
 | |
|             $comment_ids = $database->get_col("
 | |
| 				SELECT id
 | |
| 				FROM comments
 | |
| 				WHERE owner_ip=:ip
 | |
| 			", ["ip" => $ip]);
 | |
|             $num = count($comment_ids);
 | |
|             log_warning("comment", "Deleting $num comments from $ip");
 | |
|             foreach ($comment_ids as $cid) {
 | |
|                 send_event(new CommentDeletionEvent($cid));
 | |
|             }
 | |
|             flash_message("Deleted $num comments");
 | |
| 
 | |
|             $page->set_mode("redirect");
 | |
|             $page->set_redirect(make_link("admin"));
 | |
|         } else {
 | |
|             $this->theme->display_permission_denied();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private function onPageRequest_list(PageRequestEvent $event)
 | |
|     {
 | |
|         $page_num = int_escape($event->get_arg(1));
 | |
|         $this->build_page($page_num);
 | |
|     }
 | |
| 
 | |
|     private function onPageRequest_beta_search(PageRequestEvent $event)
 | |
|     {
 | |
|         $search = $event->get_arg(1);
 | |
|         $page_num = int_escape($event->get_arg(2));
 | |
|         $duser = User::by_name($search);
 | |
|         $i_comment_count = Comment::count_comments_by_user($duser);
 | |
|         $com_per_page = 50;
 | |
|         $total_pages = ceil($i_comment_count / $com_per_page);
 | |
|         $page_num = clamp($page_num, 1, $total_pages);
 | |
|         $comments = $this->get_user_comments($duser->id, $com_per_page, ($page_num - 1) * $com_per_page);
 | |
|         $this->theme->display_all_user_comments($comments, $page_num, $total_pages, $duser);
 | |
|     }
 | |
| 
 | |
|     public function onAdminBuilding(AdminBuildingEvent $event)
 | |
|     {
 | |
|         $this->theme->display_admin_block();
 | |
|     }
 | |
| 
 | |
|     public function onPostListBuilding(PostListBuildingEvent $event)
 | |
|     {
 | |
|         global $config, $database;
 | |
|         $cc = $config->get_int("comment_count");
 | |
|         if ($cc > 0) {
 | |
|             $recent = $database->cache->get("recent_comments");
 | |
|             if (empty($recent)) {
 | |
|                 $recent = $this->get_recent_comments($cc);
 | |
|                 $database->cache->set("recent_comments", $recent, 60);
 | |
|             }
 | |
|             if (count($recent) > 0) {
 | |
|                 $this->theme->display_recent_comments($recent);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     public function onUserPageBuilding(UserPageBuildingEvent $event)
 | |
|     {
 | |
|         $i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
 | |
|         $i_comment_count = Comment::count_comments_by_user($event->display_user);
 | |
|         $h_comment_rate = sprintf("%.1f", ($i_comment_count / $i_days_old));
 | |
|         $event->add_stats("Comments made: $i_comment_count, $h_comment_rate per day");
 | |
| 
 | |
|         $recent = $this->get_user_comments($event->display_user->id, 10);
 | |
|         $this->theme->display_recent_user_comments($recent, $event->display_user);
 | |
|     }
 | |
| 
 | |
|     public function onDisplayingImage(DisplayingImageEvent $event)
 | |
|     {
 | |
|         global $user;
 | |
|         $this->theme->display_image_comments(
 | |
|             $event->image,
 | |
|             $this->get_comments($event->image->id),
 | |
|             $user->can("create_comment")
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     // TODO: split akismet into a separate class, which can veto the event
 | |
|     public function onCommentPosting(CommentPostingEvent $event)
 | |
|     {
 | |
|         $this->add_comment_wrapper($event->image_id, $event->user, $event->comment);
 | |
|     }
 | |
| 
 | |
|     public function onCommentDeletion(CommentDeletionEvent $event)
 | |
|     {
 | |
|         global $database;
 | |
|         $database->Execute("
 | |
| 			DELETE FROM comments
 | |
| 			WHERE id=:comment_id
 | |
| 		", ["comment_id"=>$event->comment_id]);
 | |
|         log_info("comment", "Deleting Comment #{$event->comment_id}");
 | |
|     }
 | |
| 
 | |
|     public function onSetupBuilding(SetupBuildingEvent $event)
 | |
|     {
 | |
|         $sb = new SetupBlock("Comment Options");
 | |
|         $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");
 | |
|         $event->panel->add_block($sb);
 | |
|     }
 | |
| 
 | |
|     public function onSearchTermParse(SearchTermParseEvent $event)
 | |
|     {
 | |
|         $matches = [];
 | |
| 
 | |
|         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)) {
 | |
|             $user = User::by_name($matches[1]);
 | |
|             if (!is_null($user)) {
 | |
|                 $user_id = $user->id;
 | |
|             } else {
 | |
|                 $user_id = -1;
 | |
|             }
 | |
| 
 | |
|             $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)"));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // page building {{{
 | |
|     private function build_page(int $current_page)
 | |
|     {
 | |
|         global $database, $user;
 | |
| 
 | |
|         $where = SPEED_HAX ? "WHERE posted > now() - interval '24 hours'" : "";
 | |
|         
 | |
|         $total_pages = $database->cache->get("comment_pages");
 | |
|         if (empty($total_pages)) {
 | |
|             $total_pages = (int)($database->get_one("
 | |
| 				SELECT COUNT(c1)
 | |
| 				FROM (SELECT COUNT(image_id) AS c1 FROM comments $where GROUP BY image_id) AS s1
 | |
| 			") / 10);
 | |
|             $database->cache->set("comment_pages", $total_pages, 600);
 | |
|         }
 | |
|         $total_pages = max($total_pages, 1);
 | |
| 
 | |
|         $current_page = clamp($current_page, 1, $total_pages);
 | |
|         
 | |
|         $threads_per_page = 10;
 | |
|         $start = $threads_per_page * ($current_page - 1);
 | |
| 
 | |
|         $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 = ext_is_live("Ratings") ? Ratings::get_user_privs($user) : "";
 | |
| 
 | |
|         $images = [];
 | |
|         while ($row = $result->fetch()) {
 | |
|             $image = Image::by_id($row["image_id"]);
 | |
|             if (
 | |
|                 ext_is_live("Ratings") && !is_null($image) &&
 | |
|                 strpos($user_ratings, $image->rating) === false
 | |
|             ) {
 | |
|                 $image = null; // this is "clever", I may live to regret it
 | |
|             }
 | |
|             if (!is_null($image)) {
 | |
|                 $comments = $this->get_comments($image->id);
 | |
|                 $images[] = [$image, $comments];
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $this->theme->display_comment_list($images, $current_page, $total_pages, $user->can("create_comment"));
 | |
|     }
 | |
|     // }}}
 | |
| 
 | |
|     // get comments {{{
 | |
|     /**
 | |
|      * #return Comment[]
 | |
|      */
 | |
|     private function get_generic_comments(string $query, array $args): array
 | |
|     {
 | |
|         global $database;
 | |
|         $rows = $database->get_all($query, $args);
 | |
|         $comments = [];
 | |
|         foreach ($rows as $row) {
 | |
|             $comments[] = new Comment($row);
 | |
|         }
 | |
|         return $comments;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * #return Comment[]
 | |
|      */
 | |
|     private function get_recent_comments(int $count): array
 | |
|     {
 | |
|         return $this->get_generic_comments("
 | |
| 			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
 | |
| 		", ["limit"=>$count]);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * #return Comment[]
 | |
|      */
 | |
|     private function get_user_comments(int $user_id, int $count, int $offset=0): array
 | |
|     {
 | |
|         return $this->get_generic_comments("
 | |
| 			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
 | |
| 			WHERE users.id = :user_id
 | |
| 			ORDER BY comments.id DESC
 | |
| 			LIMIT :limit OFFSET :offset
 | |
| 		", ["user_id"=>$user_id, "offset"=>$offset, "limit"=>$count]);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * #return Comment[]
 | |
|      */
 | |
|     private function get_comments(int $image_id): array
 | |
|     {
 | |
|         return $this->get_generic_comments("
 | |
| 			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
 | |
| 			WHERE comments.image_id=:image_id
 | |
| 			ORDER BY comments.id ASC
 | |
| 		", ["image_id"=>$image_id]);
 | |
|     }
 | |
|     // }}}
 | |
| 
 | |
|     // add / remove / edit comments {{{
 | |
|     private function is_comment_limit_hit(): bool
 | |
|     {
 | |
|         global $config, $database;
 | |
| 
 | |
|         // sqlite fails at intervals
 | |
|         if ($database->get_driver_name() === "sqlite") {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         $window = int_escape($config->get_int('comment_window'));
 | |
|         $max = int_escape($config->get_int('comment_limit'));
 | |
| 
 | |
|         if ($database->get_driver_name() == "mysql") {
 | |
|             $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("
 | |
| 			SELECT *
 | |
| 			FROM comments
 | |
| 			WHERE owner_ip = :remote_ip AND posted > now() - $window_sql
 | |
| 		", ["remote_ip"=>$_SERVER['REMOTE_ADDR']]);
 | |
| 
 | |
|         return (count($result) >= $max);
 | |
|     }
 | |
| 
 | |
|     private function hash_match(): bool
 | |
|     {
 | |
|         return ($_POST['hash'] == $this->get_hash());
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * 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
 | |
|     {
 | |
|         return md5($_SERVER['REMOTE_ADDR'] . date("%Y%m%d"));
 | |
|     }
 | |
| 
 | |
|     private function is_spam_akismet(string $text): bool
 | |
|     {
 | |
|         global $config, $user;
 | |
|         if (strlen($config->get_string('comment_wordpress_key')) > 0) {
 | |
|             $comment = [
 | |
|                 'author'       => $user->name,
 | |
|                 'email'        => $user->email,
 | |
|                 'website'      => '',
 | |
|                 'body'         => $text,
 | |
|                 'permalink'    => '',
 | |
|                 ];
 | |
| 
 | |
|             # akismet breaks if there's no referrer in the environment; so if there
 | |
|             # isn't, supply one manually
 | |
|             if (!isset($_SERVER['HTTP_REFERER'])) {
 | |
|                 $comment['referrer'] = 'none';
 | |
|                 log_warning("comment", "User '{$user->name}' commented with no referrer: $text");
 | |
|             }
 | |
|             if (!isset($_SERVER['HTTP_USER_AGENT'])) {
 | |
|                 $comment['user_agent'] = 'none';
 | |
|                 log_warning("comment", "User '{$user->name}' commented with no user-agent: $text");
 | |
|             }
 | |
| 
 | |
|             $akismet = new Akismet(
 | |
|                 $_SERVER['SERVER_NAME'],
 | |
|                 $config->get_string('comment_wordpress_key'),
 | |
|                 $comment
 | |
|             );
 | |
| 
 | |
|             if ($akismet->errorsExist()) {
 | |
|                 return false;
 | |
|             } else {
 | |
|                 return $akismet->isSpam();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     private function is_dupe(int $image_id, string $comment): bool
 | |
|     {
 | |
|         global $database;
 | |
|         return (bool)$database->get_row("
 | |
| 			SELECT *
 | |
| 			FROM comments
 | |
| 			WHERE image_id=:image_id AND comment=:comment
 | |
| 		", ["image_id"=>$image_id, "comment"=>$comment]);
 | |
|     }
 | |
|     // do some checks
 | |
| 
 | |
|     private function add_comment_wrapper(int $image_id, User $user, string $comment)
 | |
|     {
 | |
|         global $database, $page;
 | |
| 
 | |
|         if (!$user->can("bypass_comment_checks")) {
 | |
|             // will raise an exception if anything is wrong
 | |
|             $this->comment_checks($image_id, $user, $comment);
 | |
|         }
 | |
| 
 | |
|         // all checks passed
 | |
|         if ($user->is_anonymous()) {
 | |
|             $page->add_cookie("nocache", "Anonymous Commenter", time()+60*60*24, "/");
 | |
|         }
 | |
|         $database->Execute(
 | |
|             "INSERT INTO comments(image_id, owner_id, owner_ip, posted, comment) ".
 | |
|                 "VALUES(:image_id, :user_id, :remote_addr, now(), :comment)",
 | |
|             ["image_id"=>$image_id, "user_id"=>$user->id, "remote_addr"=>$_SERVER['REMOTE_ADDR'], "comment"=>$comment]
 | |
|         );
 | |
|         $cid = $database->get_last_insert_id('comments_id_seq');
 | |
|         $snippet = substr($comment, 0, 100);
 | |
|         $snippet = str_replace("\n", " ", $snippet);
 | |
|         $snippet = str_replace("\r", " ", $snippet);
 | |
|         log_info("comment", "Comment #$cid added to Image #$image_id: $snippet", null, ["image_id"=>$image_id, "comment_id"=>$cid]);
 | |
|     }
 | |
| 
 | |
|     private function comment_checks(int $image_id, User $user, string $comment)
 | |
|     {
 | |
|         global $config, $page;
 | |
| 
 | |
|         // basic sanity checks
 | |
|         if (!$user->can("create_comment")) {
 | |
|             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
 | |
|         elseif (strlen($comment)/strlen(gzcompress($comment)) > 10) {
 | |
|             throw new CommentPostingException("Comment too repetitive~");
 | |
|         } elseif ($user->is_anonymous() && !$this->hash_match()) {
 | |
|             $page->add_cookie("nocache", "Anonymous Commenter", time()+60*60*24, "/");
 | |
|             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.");
 | |
|         }
 | |
|     }
 | |
|     // }}}
 | |
| }
 |