Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add report spam link #344

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 241 additions & 1 deletion antispam_bee.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ public static function init() {
)
);

add_action(
'rest_api_init',
array(
__CLASS__,
'register_rest_routes'
)
);

$disallow_ajax = apply_filters( 'antispam_bee_disallow_ajax_calls', true );

if ( defined( 'DOING_AJAX' ) && DOING_AJAX && $disallow_ajax ) {
Expand Down Expand Up @@ -251,6 +259,25 @@ public static function init() {
);

} elseif ( self::_current_page( 'edit-comments' ) ) {
require_once dirname( __FILE__ ) . '/inc/gui.class.php';
add_filter(
'comment_row_actions',
array(
'Antispam_Bee_GUI',
'report_comment_action_link',
),
10,
2
);

add_action(
'admin_enqueue_scripts',
array(
'Antispam_Bee_GUI',
'enqueue_report_comment_action_link_script',
)
);

// phpcs:disable WordPress.CSRF.NonceVerification.NoNonceVerification
if ( ! empty( $_GET['comment_status'] ) && 'spam' === $_GET['comment_status'] && ! self::get_option( 'no_notice' ) ) {
// phpcs:enable WordPress.CSRF.NonceVerification.NoNonceVerification
Expand Down Expand Up @@ -281,14 +308,22 @@ public static function init() {
'print_column_styles',
)
);

add_filter(
'manage_edit-comments_sortable_columns',
array(
'Antispam_Bee_Columns',
'register_sortable_columns',
)
);
add_filter(
'comment_row_actions',
array(
'Antispam_Bee_Columns',
'add_report_comment_action_link',
),
10,
2
);
add_action(
'pre_get_comments',
array(
Expand Down Expand Up @@ -2869,6 +2904,211 @@ private static function parse_url( $url, $component = 'host' ) {
return ( is_array( $parts ) && isset( $parts[ $component ] ) ) ? $parts[ $component ] : '';
}

/**
* Registering REST routes.
*
* @since 2.12.0
*/
public static function register_rest_routes() {
register_rest_route( 'antispam-bee/v1', 'report-spam', [
'methods' => 'POST',
'callback' => array( __CLASS__, 'report_spam_route_callback' ),
'permission_callback' => array( __CLASS__, 'report_spam_route_permission_callback' ),
'args' => array(
'comment_ids' => array(
'validate_callback' => array( __CLASS__, 'validate_comment_ids_array' ),
)
)
] );

register_rest_route( 'antispam-bee/v1', 'unrecognized-spam', [
'methods' => 'GET',
'callback' => array( __CLASS__, 'unrecognized_spam_comments' ),
'permission_callback' => array( __CLASS__, 'unrecognized_spam_route_permission_callback' ),
'args' => array(
'token' => array(
'validate_callback' => array( __CLASS__, 'validate_unrecognized_spam_token' ),
)
)
] );
}

/**
* Validation of comment_ids request param.
*
* @since 2.12.0
*
* @param mixed $ids Value of comment_ids request param.
*
* @return bool
*/
public static function validate_comment_ids_array( $ids ) {
if ( ! is_array( $ids ) || empty( $ids ) ) {
return false;
}

foreach ( $ids as $id ) {
if ( is_int( $id ) ) {
continue;
}

return false;
}

return true;
}

/**
* Checking permission for report-spam route.
*
* @since 2.12.0
*
* @return bool
*/
public static function report_spam_route_permission_callback() {
return current_user_can( 'moderate_comments' );
}

/**
* Handling successful request to report-spam route.
*
* @since 2.12.0
*
* @param WP_REST_Request $request
*
* @return WP_REST_Response
*/
public static function report_spam_route_callback( $request ) {
$comment_ids = $request->get_param( 'comment_ids' );
$marked_comments = self::report_spam_comments( $comment_ids );
if ( $marked_comments === false ) {
return new WP_REST_Response(
array(
'success' => false
)
);
}

return new WP_REST_Response(
array(
'success' => true
)
);
}

/**
* Reporting the unrecognized spam comments.
*
* @since 2.12.0
*
* @param array $comment_ids
*
* @return bool
*/
private static function report_spam_comments( $comment_ids ) {
$comments = get_comments(
array(
'comment__in' => $comment_ids !== null ? $comment_ids : [ 0 ],
)
);

if ( empty( $comments ) ) {
return false;
}

foreach ( $comments as $comment ) {
add_comment_meta( $comment->comment_ID, 'antispam_bee_unrecognized_spam', true, true );
}

$token = sodium_bin2hex( random_bytes( 6 ) );

update_option( 'antispam_bee_unrecognized_spam_token', $token );

wp_safe_remote_post(
'https://api.pluginkollektiv.org/spam-data/v1/',
array(
'body' => wp_json_encode(
array(
'host' => untrailingslashit( get_site_url() ),
'token' => $token
)
)
)
);

return true;
}

/**
* Returns the comments that are marked as unrecognized spam.
*
* @since 2.12.0
*
* @return array
*/
public static function unrecognized_spam_comments() {
$comments = get_comments(
array(
'meta_key' => 'antispam_bee_unrecognized_spam',
'meta_value' => 1
)
);

$result = [];

foreach ( $comments as $comment ) {
$result[] = [
'author' => $comment->comment_author,
'author_email' => $comment->comment_author_email,
'author_url' => $comment->comment_author_url,
'author_IP' => $comment->comment_author_IP,
'content' => $comment->comment_content,
'agent' => $comment->comment_agent,
'host' => gethostbyaddr( $comment->comment_author_IP ),
];

delete_comment_meta( $comment->comment_ID, 'antispam_bee_unrecognized_spam' );
}

delete_option( 'antispam_bee_unrecognized_spam_token' );

return $result;
}

/**
* Checks permission for unrecognized spam route.
*
* @since 2.12.0
*
* @param WP_REST_Request $request
*
* @return bool
*/
public static function unrecognized_spam_route_permission_callback( $request ) {
$token = $request->get_param( 'token' );
$stored_token = get_option( 'antispam_bee_unrecognized_spam_token', '' );
if ( $token !== $stored_token ) {
return false;
}
return true;
}

/**
* Validate token param of unrecognized spam route request.
*
* @since 2.12.0
*
* @param mixed $token The value of the token request param
*
* @return bool
*/
public static function validate_unrecognized_spam_token( $token ) {
if ( $token === '' || ! is_string( $token ) ) {
return false;
}
return true;
}

/**
* Updates the database structure if necessary
*
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,11 @@
"minify": [
"minifycss css/dashboard.css > css/dashboard.min.css",
"minifycss css/styles.css > css/styles.min.css",
"minifycss css/report-spam.css > css/report-spam.min.css",
"minifyjs js/dashboard.js > js/dashboard.min.js",
"minifyjs js/raphael.helper.js > js/raphael.helper.min.js",
"minifyjs js/a11y-dialog.js > js/a11y-dialog.min.js",
"minifyjs js/report-spam.js > js/report-spam.min.js",
"minifyjs js/scripts.js > js/scripts.min.js"
]
}
Expand Down
55 changes: 55 additions & 0 deletions css/report-spam.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.a11y-dialog-container,
div[data-a11y-dialog-hide] {
position: fixed; /* 1 */
top: 0; /* 1 */
right: 0; /* 1 */
bottom: 0; /* 1 */
left: 0; /* 1 */
}

.a11y-dialog-container {
z-index: 2; /* 1 */
display: flex; /* 2 */
}

.a11y-dialog-container[aria-hidden="true"] {
display: none; /* 1 */
}

.a11y-dialog-container > div {
width: 100%;
}

div[data-a11y-dialog-hide] {
background-color: rgba(0, 0, 0, 0.35); /* 1 */
}

.a11y-dialog-container .dialog-content {
margin: auto; /* 1 */
z-index: 2; /* 2 */
position: relative; /* 2 */
background-color: white; /* 3 */
width: 312px;
max-height: calc(100% - 100px);
overflow: auto;
top: 50px;
padding: 1rem;
box-sizing: border-box;
box-shadow: 0 10px 10px rgba(0,0,0,.25);
}

.a11y-dialog-container .dialog-content > :first-child {
margin-top: 0;
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;
font-size: 24px;
line-height: 1.4;
}

.asb-report-spam-button {
white-space: nowrap;
background: #007cba;
color: #fff;
text-decoration: none;
text-shadow: none;
outline: 1px solid transparent;
}
34 changes: 34 additions & 0 deletions inc/columns.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,38 @@ public static function print_column_styles() {
</style>
<?php
}

/**
* Add comment action link to report spam to ASB.
*
* @since 2.9.3
*
* @param string[] $actions Array of actions.
* @param WP_Comment $comment Comment object.
*/
public static function add_report_comment_action_link( $actions, $comment ) {

// URLencode comment data.
$name = rawurlencode( $comment->comment_author );
$email = rawurlencode( $comment->comment_author_email );
$ip = rawurlencode( $comment->comment_author_IP );
Comment on lines +171 to +174
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about the GDPR implications, when we collect those values.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already do this now. This is just a a helpful link to prefill the form.

But if we want to follow this path. My research says:

        Consent must be “freely given, specific, informed and unambiguous.”

        Requests for consent must be “clearly distinguishable from the other matters” and presented in “clear and plain language.”

        Data subjects can withdraw previously given consent whenever they want, and you have to honor their decision. You can’t simply change the legal basis of the processing to one of the other justifications.

        Children under 13 can only give consent with permission from their parent.

    You need to keep documentary evidence of consent.

The sixth legal basis is to have a “legitimate interest” to process the person’s data. 

I think the legitimate interest in fighting spam outweighs the rights of any spammer (or false positive user). But I am not a lawyer. I try to ping someone from the community to have look.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a legal point of view, I would have no problems with this.

In terms of data protection, this is possible without consent as it is within the context of a legitimate interest. (In the case of a legitimate interest, an opt-out option is required, but that is easily possible for the spammer, so you don't need to think of it.)

Also I don't see any major problems in terms of copyright law, because on the one hand, emails of this kind regularly do not have the necessary level of creation for copyright protection, on the other hand, the author has also consented to storage and processing by sending, because that is exactly the purpose of his mail.

So: just make it!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @PraetorIM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going through the discussion here, I get the impression that we are only looking at whether we as the pluginkollektiv get the user's consent to get access to this data.

However, what we are doing here is giving immediate access to a third-party (Google) via these unencrypted URL values. The way I see it, we would also need to inform the user about which third-parties we'd also give access to this data and need the user's consent for that as well.

So, even if we say that we don't need consent in this case, we still need to let the user know about the third-parties involved, no?

@PraetorIM It's not clear to me whether you consider this (we're sending unencrypted user data via a URL to a Google application), so I'd be grateful for any further clarification you can provide.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with mention and opt-out option in the data protection declaration or at the comment-form

What do you mean with that?

This PR adds a link to every spam comment in the admin area which is a link called "Report to Antispam Bee" which is going to a Google Form page and prepopulates it with the data from the comment.

It is just an external link IMHO. Everyone can see where it goes. If I need to mention it in a data protection declaration, I would need to mention every external link to a non-EU website ... or not?

And to which data protection declaration should we add it? On the website where it is used? This would not be working out I think. And why should we add it to the comment form? It is just added in the admin area and it does not do anything in the frontend. And how should I provide an opt-out for a link? You click it or you don't click it. How can we opt-out there?

I think we are all not in the same boat and speak about completely different things here.

Maybe we can talk about this in a chat. Maybe in German. To get this sorted out.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@schlessera @PraetorIM @websupporter I really would like to get 2.9.3 released soon. Any chance to get this legal question solved?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all: The reporting of a URL is irrelevant in terms of data protection law, since a URL (apart from extreme exceptional cases) is not a personal date. Even a URL like firstname-lastname.de is initially without privacy problems (hard to believe in today's hysteria, I know). This would only become personal data if the person of the commentator / spammer was also transmitted. But that's not what you've planned. So just get that feature in.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PraetorIM We are not just reporting an URL.

We are reporting what was entered as author name, e-mail and website url. Additionally the IP address used by the visitor, the host (for this IP), the content of the comment and the user agent used.

These values are send to a Google form via URL, so that the Google form is prepopulated.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Torsten, melde dich einfach einmal bei mir, ehe wir hier noch eine Brieffreundschaft anfangen ;-)

$host = rawurlencode( gethostbyaddr( $comment->comment_author_IP ) );
$url = rawurlencode( $comment->comment_author_url );
$content = rawurlencode( $comment->comment_content );
$agent = rawurlencode( $comment->comment_agent );

// Build action link.
$target = ' target="_blank" ';
$rel = ' rel="noopener noreferrer" ';
$href = 'href="https://docs.google.com/forms/d/e/1FAIpQLSeQlKVZZYsF1qkKz7U78B2wy_6s6I7aNSdQc-DGpjeqWx70-A/viewform?c=0&w=1&entry.437446945=' . $name . '&entry.462884433=' . $ip . '&entry.1346967038=' . $host . '&entry.121560485=' . $email . '&entry.1210529682=' . $url . '&entry.1837399577=' . $content . '&entry.372858475=' . $agent . '" ';

$action = '';
$action .= "<a $target $href $rel>";
$action .= __( 'Report to Antispam Bee', 'antispam-bee' );
$action .= '</a>';

$actions['report_spam trash'] = $action;

return $actions;
}
}
Loading