✏️ 编辑:class-comments-logger.php
路径:
/home/forge/kingkrunch.com/wp/wp-content/plugins/simple-history/loggers/class-comments-logger.php
大小:25 KB · 修改:2026-09-25 00:42:29 · 权限:0644 · 可写
← 返回目录
👁 查看
⬇ 下载
<?php namespace Simple_History\Loggers; use Simple_History\Event_Details\Event_Details_Group; use Simple_History\Event_Details\Event_Details_Group_Table_Formatter; use Simple_History\Event_Details\Event_Details_Item; use Simple_History\Event_Details\Event_Details_Item_Table_Row_RAW_Formatter; use Simple_History\Helpers; use Simple_History\Log_Initiators; /** * Logs things related to comments */ class Comments_Logger extends Logger { /** @var string Logger slug */ public $slug = 'SimpleCommentsLogger'; /** * Get array with information about this logger * * @return array */ public function get_info() { return array( 'name' => __( 'Comments Logger', 'simple-history' ), 'description' => __( 'Logs comments, and modifications to them', 'simple-history' ), 'capability' => 'moderate_comments', 'messages' => array( // Comments. 'anon_comment_added' => _x( 'Added a comment to {comment_post_type} "{comment_post_title}"', 'A comment was added to the database by a non-logged in internet user', 'simple-history' ), 'user_comment_added' => _x( 'Added a comment to {comment_post_type} "{comment_post_title}"', 'A comment was added to the database by a logged in user', 'simple-history' ), 'comment_status_approve' => _x( 'Approved a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})', 'A comment was approved', 'simple-history' ), 'comment_status_hold' => _x( 'Unapproved a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})', 'A comment was was unapproved', 'simple-history' ), 'comment_status_spam' => _x( 'Marked a comment to post "{comment_post_title}" as spam', 'A comment was marked as spam', 'simple-history' ), 'comment_status_trash' => _x( 'Trashed a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})', 'A comment was marked moved to the trash', 'simple-history' ), 'comment_untrashed' => _x( 'Restored a comment to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash', 'A comment was restored from the trash', 'simple-history' ), 'comment_deleted' => _x( 'Deleted a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})', 'A comment was deleted', 'simple-history' ), 'comment_edited' => _x( 'Edited a comment to "{comment_post_title}" by {comment_author} ({comment_author_email})', 'A comment was edited', 'simple-history' ), // Trackbacks. 'anon_trackback_added' => _x( 'Added a trackback to {comment_post_type} "{comment_post_title}"', 'A trackback was added to the database by a non-logged in internet user', 'simple-history' ), 'user_trackback_added' => _x( 'Added a trackback to {comment_post_type} "{comment_post_title}"', 'A trackback was added to the database by a logged in user', 'simple-history' ), 'trackback_status_approve' => _x( 'Approved a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})', 'A trackback was approved', 'simple-history' ), 'trackback_status_hold' => _x( 'Unapproved a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})', 'A trackback was was unapproved', 'simple-history' ), 'trackback_status_spam' => _x( 'Marked a trackback to post "{comment_post_title}" as spam', 'A trackback was marked as spam', 'simple-history' ), 'trackback_status_trash' => _x( 'Trashed a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})', 'A trackback was marked moved to the trash', 'simple-history' ), 'trackback_untrashed' => _x( 'Restored a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash', 'A trackback was restored from the trash', 'simple-history' ), 'trackback_deleted' => _x( 'Deleted a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})', 'A trackback was deleted', 'simple-history' ), 'trackback_edited' => _x( 'Edited a trackback to "{comment_post_title}" by {comment_author} ({comment_author_email})', 'A trackback was edited', 'simple-history' ), // Pingbacks. 'anon_pingback_added' => _x( 'Added a pingback to {comment_post_type} "{comment_post_title}"', 'A trackback was added to the database by a non-logged in internet user', 'simple-history' ), 'user_pingback_added' => _x( 'Added a pingback to {comment_post_type} "{comment_post_title}"', 'A pingback was added to the database by a logged in user', 'simple-history' ), 'pingback_status_approve' => _x( 'Approved a pingback to "{comment_post_title}" by "{comment_author}"" ({comment_author_email})', 'A pingback was approved', 'simple-history' ), 'pingback_status_hold' => _x( 'Unapproved a pingback to "{comment_post_title}" by "{comment_author}" ({comment_author_email})', 'A pingback was was unapproved', 'simple-history' ), 'pingback_status_spam' => _x( 'Marked a pingback to post "{comment_post_title}" as spam', 'A pingback was marked as spam', 'simple-history' ), 'pingback_status_trash' => _x( 'Trashed a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})', 'A pingback was marked moved to the trash', 'simple-history' ), 'pingback_untrashed' => _x( 'Restored a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email}) from the trash', 'A pingback was restored from the trash', 'simple-history' ), 'pingback_deleted' => _x( 'Deleted a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})', 'A pingback was deleted', 'simple-history' ), 'pingback_edited' => _x( 'Edited a pingback to "{comment_post_title}" by {comment_author} ({comment_author_email})', 'A pingback was edited', 'simple-history' ), ), // end messages. 'labels' => array( 'search' => array( 'label' => _x( 'Comments', 'Comments logger: search', 'simple-history' ), 'label_all' => _x( 'All comments activity', 'Comments logger: search', 'simple-history' ), 'options' => array( _x( 'Added comments', 'Comments logger: search', 'simple-history' ) => array( 'anon_comment_added', 'user_comment_added', 'anon_trackback_added', 'user_trackback_added', 'anon_pingback_added', 'user_pingback_added', ), _x( 'Edited comments', 'Comments logger: search', 'simple-history' ) => array( 'comment_edited', 'trackback_edited', 'pingback_edited', ), _x( 'Approved comments', 'Comments logger: search', 'simple-history' ) => array( 'comment_status_approve', 'trackback_status_approve', 'pingback_status_approve', ), _x( 'Held comments', 'Comments logger: search', 'simple-history' ) => array( 'comment_status_hold', 'trackback_status_hold', 'pingback_status_hold', ), _x( 'Comments status changed to spam', 'Comments logger: search', 'simple-history' ) => array( 'comment_status_spam', 'trackback_status_spam', 'pingback_status_spam', ), _x( 'Trashed comments', 'Comments logger: search', 'simple-history' ) => array( 'comment_status_trash', 'trackback_status_trash', 'pingback_status_trash', ), _x( 'Untrashed comments', 'Comments logger: search', 'simple-history' ) => array( 'comment_untrashed', 'trackback_untrashed', 'pingback_untrashed', ), _x( 'Deleted comments', 'Comments logger: search', 'simple-history' ) => array( 'comment_deleted', 'trackback_deleted', 'pingback_deleted', ), ), ), // end search. ), // labels. ); } /** * Modify sql query to exclude comments of type spam * * @param string $where sql query where. */ public function maybe_modify_log_query_sql_where( $where ) { // since 19 sept 2016 we do include spam, to skip the subquery // spam comments should not be logged anyway since some time. $include_spam = true; /** * Filter option to include spam or not in the gui * By default spam is not included, because it can fill the log * with too much events * * @since 2.0 * * @param bool $include_spam Default false */ $include_spam = apply_filters( 'simple_history/comments_logger/include_spam', $include_spam ); if ( $include_spam ) { return $where; } $where .= sprintf( ' AND id NOT IN ( SELECT id # , c1.history_id, c2.history_id FROM %1$s AS h INNER JOIN %2$s AS c1 ON c1.history_id = h.id AND c1.key = "_message_key" AND c1.value IN ( "comment_deleted", "pingback_deleted", "trackback_deleted", "anon_comment_added", "anon_pingback_added", "anon_trackback_added" ) INNER JOIN %2$s AS c2 ON c2.history_id = h.id AND c2.key = "comment_approved" AND c2.value = "spam" WHERE logger = "%3$s" ) ', $this->db_table, $this->db_table_contexts, $this->get_slug() ); return $where; } /** * Called when logger is loaded. */ public function loaded() { // Add option to not show spam comments, because to much things getting logged. add_filter( 'simple_history/log_query_inner_where', array( $this, 'maybe_modify_log_query_sql_where' ) ); add_filter( 'simple_history/quick_stats_where', array( $this, 'maybe_modify_log_query_sql_where' ) ); /** * Fires immediately after a comment is inserted into the database. */ add_action( 'comment_post', array( $this, 'on_comment_post' ), 10, 2 ); /** * Fires after a comment status has been updated in the database. * The hook also fires immediately before comment status transition hooks are fired. */ add_action( 'wp_set_comment_status', array( $this, 'on_wp_set_comment_status' ), 10, 2 ); /** *Fires immediately after a comment is restored from the Trash. */ add_action( 'untrashed_comment', array( $this, 'on_untrashed_comment' ), 10, 1 ); /** * Fires immediately before a comment is deleted from the database. */ add_action( 'delete_comment', array( $this, 'on_delete_comment' ), 10, 1 ); /** * Fires immediately after a comment is updated in the database. * The hook also fires immediately before comment status transition hooks are fired. */ add_action( 'edit_comment', array( $this, 'on_edit_comment' ), 10, 1 ); } /** * Get comments context. * * @param int $comment_ID Comment ID. * @return mixed array with context if comment found, false if comment not found */ public function get_context_for_comment( $comment_ID ) { // get_comment passes comment_ID by reference, so it can be unset by that function. $comment_ID_original = $comment_ID; $comment_data = get_comment( $comment_ID ); if ( is_null( $comment_data ) ) { return false; } // The parent post can be gone — a comment orphaned by a deleted post, or an // import that brought comments without their posts. The comment action is // still worth logging, so fall back to empty strings rather than bailing. // See get_log_row_plain_text_output() for how those empties are presented. // // The id guard matters: get_post() falls back to $GLOBALS['post'] when its // argument is empty, so an unattached comment (post ID 0) would otherwise be // logged against whatever post happened to be global during the request. $comment_parent_post = $comment_data->comment_post_ID ? get_post( $comment_data->comment_post_ID ) : null; $context = array( 'comment_ID' => $comment_ID_original, 'comment_author' => $comment_data->comment_author, 'comment_author_email' => $comment_data->comment_author_email, 'comment_author_url' => $comment_data->comment_author_url, 'comment_author_IP' => $comment_data->comment_author_IP, 'comment_content' => $comment_data->comment_content, 'comment_approved' => $comment_data->comment_approved, 'comment_agent' => $comment_data->comment_agent, 'comment_type' => $comment_data->comment_type, 'comment_parent' => $comment_data->comment_parent, 'comment_post_ID' => $comment_data->comment_post_ID, 'comment_post_title' => $comment_parent_post->post_title ?? '', 'comment_post_type' => $comment_parent_post->post_type ?? '', ); // Note: comment type is empty for normal comments. if ( empty( $context['comment_type'] ) ) { $context['comment_type'] = 'comment'; } return $context; } /** * Fires immediately after a comment is updated in the database. * * @param int $comment_ID The comment ID. */ public function on_edit_comment( $comment_ID ) { $context = $this->get_context_for_comment( $comment_ID ); if ( ! $context ) { return; } $this->info_message( "{$context["comment_type"]}_edited", $context ); } /** * Fires immediately before a comment is deleted from the database. * * @param int $comment_ID The comment ID. */ public function on_delete_comment( $comment_ID ) { $context = $this->get_context_for_comment( $comment_ID ); if ( ! $context ) { return; } $comment_data = get_comment( $comment_ID ); // add occasions if comment was considered spam // if not added, spam comments can easily flood the log // Deletions of spam easily flood log. if ( isset( $comment_data->comment_approved ) && $comment_data->comment_approved === 'spam' ) { // Since 2.5.5 we don't log deletion of spam comments. return; } $this->info_message( "{$context["comment_type"]}_deleted", $context ); } /** * Fires immediately after a comment is restored from the Trash. * * @param int $comment_ID The comment ID. */ public function on_untrashed_comment( $comment_ID ) { $context = $this->get_context_for_comment( $comment_ID ); if ( ! $context ) { return; } $this->info_message( "{$context["comment_type"]}_untrashed", $context ); } /** * Fires after a comment status has been updated in the database. * The hook also fires immediately before comment status transition hooks are fired. * * do_action( 'wp_set_comment_status', $comment_id, $comment_status ); * * @param int $comment_ID The comment ID. * @param string|bool $comment_status The comment status. Possible values include 'hold', * 'approve', 'spam', 'trash', or false. */ public function on_wp_set_comment_status( $comment_ID, $comment_status ) { $context = $this->get_context_for_comment( $comment_ID ); if ( ! $context ) { return; } /* $comment_status: approve comment was approved spam comment was marked as spam trash comment was trashed hold comment was un-approved */ $message = "{$context["comment_type"]}_status_{$comment_status}"; $this->info_message( $message, $context ); } /** * Fires immediately after a comment is inserted into the database. * * @param int $comment_ID The comment ID. * @param int|string $comment_approved 1 if the comment is approved, 0 if not, 'spam' if spam. */ public function on_comment_post( $comment_ID, $comment_approved ) { $context = $this->get_context_for_comment( $comment_ID ); if ( ! $context ) { return; } // since 2.5.5: no more logging of spam comments. if ( $comment_approved === 'spam' ) { return; } $comment_data = get_comment( $comment_ID ); if ( $comment_data->user_id !== '' && $comment_data->user_id !== '0' ) { // comment was from a logged in user. $message = "user_{$context["comment_type"]}_added"; } else { // comment was from a non-logged in user. $message = "anon_{$context["comment_type"]}_added"; $context['_initiator'] = Log_Initiators::WEB_USER; // add occasions if comment is considered spam // if not added, spam comments can easily flood the log. if ( isset( $comment_data->comment_approved ) && $comment_data->comment_approved === 'spam' ) { $context['_occasionsID'] = self::class . '/' . __FUNCTION__ . "/anon_{$context["comment_type"]}_added/type:spam"; } } $this->info_message( $message, $context ); } /** * Modify plain output to include link to post * and link to comment * * @param object $row Log row. */ public function get_log_row_plain_text_output( $row ) { $message = $row->message; $context = $row->context; $message_key = $context['_message_key']; // Message is untranslated here, so get translated text // Can't call parent __FUNCTION__ because it will interpolate too, which we don't want. if ( ! empty( $message_key ) ) { $translated_message = $this->get_translated_message( $message_key ); if ( $translated_message !== null ) { $message = $translated_message; } } // Whether the parent post still exists decides both the placeholder below and // the link further down, so resolve it once. An empty title is NOT the same // thing as a missing post — attachments and the aside/status/quote post // formats legitimately have no title — so the post is looked up rather than // inferred from the stored title. Note get_post() falls back to the global // post when given 0, hence the id guard. $comment_post_ID = isset( $context['comment_post_ID'] ) ? (int) $context['comment_post_ID'] : 0; $parent_post = $comment_post_ID ? get_post( $comment_post_ID ) : null; // Substitute placeholders when the parent post is gone, so messages don't // render as 'Approved a comment to ""'. Done at render time rather than when // the context is stored, both to keep the strings translatable per viewer and // so events already logged with empty values render sensibly too. A post // deleted after the event was logged keeps its recorded title — only events // that never captured one get the placeholder. if ( ! $parent_post ) { if ( empty( $context['comment_post_title'] ) ) { $context['comment_post_title'] = _x( '(deleted)', 'Comment logger: parent post no longer exists', 'simple-history' ); } // The real post type is unknowable once the post is deleted, so use the // generic noun to keep 'Added a comment to {comment_post_type} …' readable. if ( empty( $context['comment_post_type'] ) ) { $context['comment_post_type'] = _x( 'post', 'Comment logger: parent post type is unknown', 'simple-history' ); } } // Wrap links around {comment_post_title}, but only while there is still // something to link to. if ( $parent_post ) { $edit_post_link = get_edit_post_link( $comment_post_ID ); if ( $edit_post_link ) { $message = str_replace( '"{comment_post_title}"', "<a href='{$edit_post_link}'>\"{comment_post_title}\"</a>", $message ); } } // The post title is the reachable one here: stored verbatim from the // parent post, so a user holding unfiltered_html can put script in it // that then fires for any administrator reading the log. $context = $this->esc_html_context_keys( $context, [ 'comment_post_title', 'comment_post_type', 'comment_author', 'comment_author_email' ] ); return helpers::interpolate( $message, $context, $row ); } /** * Get output for detailed log section * * @param object $row Log row. */ public function get_log_row_details_output( $row ) { $context = $row->context; $comment_type = $context['comment_type'] ?? ''; // Prepare comment text for content field. $comment_text = ''; if ( isset( $context['comment_content'] ) && $context['comment_content'] ) { $comment_text = $context['comment_content']; $comment_text = wp_trim_words( $comment_text, 20 ); // Escape before wpautop() so the only HTML left is the paragraph markup // wpautop() adds. Comment content is unauthenticated input and this string // goes to the RAW formatter, which the admin UI renders with // dangerouslySetInnerHTML. wp_trim_words() happens to strip tags today, but // that is an implementation detail of an unrelated helper — do not rely on it. $comment_text = wpautop( esc_html( $comment_text ) ); } // Keys to show. $arr_plugin_keys = array(); switch ( $comment_type ) { case 'trackback': $arr_plugin_keys = array( 'trackback_status' => _x( 'Status', 'comments logger - detailed output comment status', 'simple-history' ), 'trackback_author' => _x( 'Name', 'comments logger - detailed output author', 'simple-history' ), 'trackback_author_email' => _x( 'Email', 'comments logger - detailed output email', 'simple-history' ), 'trackback_content' => _x( 'Content', 'comments logger - detailed output content', 'simple-history' ), ); break; case 'pingback': $arr_plugin_keys = array( 'pingback_status' => _x( 'Status', 'comments logger - detailed output comment status', 'simple-history' ), 'pingback_author' => _x( 'Name', 'comments logger - detailed output author', 'simple-history' ), 'pingback_author_email' => _x( 'Email', 'comments logger - detailed output email', 'simple-history' ), 'pingback_content' => _x( 'Content', 'comments logger - detailed output content', 'simple-history' ), ); break; case 'comment': default: $arr_plugin_keys = array( 'comment_status' => _x( 'Status', 'comments logger - detailed output comment status', 'simple-history' ), 'comment_author' => _x( 'Name', 'comments logger - detailed output author', 'simple-history' ), 'comment_author_email' => _x( 'Email', 'comments logger - detailed output email', 'simple-history' ), 'comment_content' => _x( 'Comment', 'comments logger - detailed output content', 'simple-history' ), ); break; } /** * Filter the keys to show in the comments details output. * * @param array $arr_plugin_keys */ $arr_plugin_keys = apply_filters( 'simple_history/comments_logger/row_details_plugin_info_keys', $arr_plugin_keys ); $group = new Event_Details_Group(); $group->set_formatter( new Event_Details_Group_Table_Formatter() ); foreach ( $arr_plugin_keys as $key => $desc ) { $desc_output = ''; switch ( $key ) { case 'comment_content': case 'trackback_content': case 'pingback_content': $desc_output = $comment_text; break; case 'comment_author': case 'trackback_author': case 'pingback_author': if ( isset( $context[ $key ] ) ) { $desc_output = $context[ $key ]; } break; case 'comment_status': case 'trackback_status': case 'pingback_status': if ( isset( $context['comment_approved'] ) ) { if ( $context['comment_approved'] === 'spam' ) { $desc_output = __( 'Spam', 'simple-history' ); } elseif ( $context['comment_approved'] === '1' ) { $desc_output = __( 'Approved', 'simple-history' ); } elseif ( $context['comment_approved'] === '0' ) { $desc_output = __( 'Pending', 'simple-history' ); } } break; default: if ( isset( $context[ $key ] ) ) { $desc_output = $context[ $key ]; } break; } if ( empty( $desc_output ) ) { continue; } // Content fields contain HTML (from wpautop), use RAW formatter. $is_content_field = in_array( $key, [ 'comment_content', 'trackback_content', 'pingback_content' ], true ); $item = new Event_Details_Item( null, $desc ); if ( $is_content_field ) { $item->set_formatter( ( new Event_Details_Item_Table_Row_RAW_Formatter() ) ->set_html_output( $desc_output ) ); } else { $item->set_new_value( $desc_output ); } $group->add_item( $item ); } return $group; } /** * Get action links for a log row. * * @param object $row Log row object. * @return array Array of action link arrays. */ public function get_action_links( $row ) { $context = $row->context; $comment_ID = isset( $context['comment_ID'] ) && is_numeric( $context['comment_ID'] ) ? (int) $context['comment_ID'] : false; if ( ! $comment_ID ) { return []; } $comment = get_comment( $comment_ID ); if ( ! ( $comment instanceof \WP_Comment ) ) { return []; } $edit_comment_link = get_edit_comment_link( $comment_ID ); // Edit link sometimes does not contain comment ID. if ( ! $edit_comment_link || $edit_comment_link[-1] === '=' ) { return []; } return [ [ 'url' => $edit_comment_link, 'label' => _x( 'Edit comment', 'comments logger - edit comment action link', 'simple-history' ), 'action' => 'edit', ], ]; } /** * Get output for detailed log section. */ public function admin_css() { ?> <style> .SimpleCommentsLogger__gravatar { line-height: 1; border-radius: 50%; overflow: hidden; margin-right: .5em; margin-left: .5em; display: inline-block; } .SimpleCommentsLogger__gravatar img { display: block; } </style> <?php } }
💾 保存
取消
保存为 UTF-8,换行统一为 LF