Skip to content

Commit

Permalink
Feature: Add akismet support to v3 forms (#7474)
Browse files Browse the repository at this point in the history
  • Loading branch information
kjohnson authored Aug 14, 2024
1 parent 7f4424b commit 89f4f62
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 0 deletions.
1 change: 1 addition & 0 deletions give.php
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ final class Give
Give\PaymentGateways\Gateways\ServiceProvider::class,
Give\EventTickets\ServiceProvider::class,
Give\BetaFeatures\ServiceProvider::class,
Give\DonationSpam\ServiceProvider::class,
];

/**
Expand Down
12 changes: 12 additions & 0 deletions src/DonationForms/Routes/DonateRoute.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use Exception;
use Give\DonationForms\Controllers\DonateController;
use Give\DonationForms\DataTransferObjects\DonateControllerData;
use Give\DonationForms\DataTransferObjects\DonateFormRouteData;
use Give\DonationForms\DataTransferObjects\DonateRouteData;
use Give\DonationForms\Exceptions\DonationFormFieldErrorsException;
Expand Down Expand Up @@ -55,6 +56,17 @@ public function __invoke(array $request)

try {
$data = $formData->validated();

/**
* Allow for additional validation of the donation form data.
* The donation flow can be interrupted by throwing an Exception.
*
* @unreleased
*
* @param DonateControllerData $data
*/
do_action('givewp_donate_form_data_validated', $data);

$this->donateController->donate($data, $data->getGateway());
} catch (DonationFormFieldErrorsException $exception) {
$type = DonationFormErrorTypes::VALIDATION;
Expand Down
21 changes: 21 additions & 0 deletions src/DonationSpam/Akismet/API.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Give\DonationSpam\Akismet;

use Akismet;
use Give\DonationSpam\Akismet\DataTransferObjects\CommentCheckArgs;

/**
* @unreleased
*/
class API
{
/**
* @unreleased
*/
public function commentCheck(CommentCheckArgs $args): array
{
// @phpstan-ignore class.notFound
return Akismet::http_post($args->toHttpQuery(), 'comment-check');
}
}
62 changes: 62 additions & 0 deletions src/DonationSpam/Akismet/Actions/ValidateDonation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace Give\DonationSpam\Akismet\Actions;

use Akismet;
use Give\DonationForms\DataTransferObjects\DonateControllerData;
use Give\DonationSpam\Akismet\API;
use Give\DonationSpam\Akismet\DataTransferObjects\CommentCheckArgs;
use Give\DonationSpam\Akismet\DataTransferObjects\SpamContext;
use Give\DonationSpam\EmailAddressWhiteList;
use Give\DonationSpam\Exceptions\SpamDonationException;
use Give\Log\Log;

/**
* @unreleased
*/
class ValidateDonation
{
/**
* @var API
*/
protected $akismet;

/**
* @var EmailAddressWhiteList
*/
protected $whitelist;

/**
* @unreleased
*/
public function __construct(API $akismet, EmailAddressWhiteList $whitelist)
{
$this->akismet = $akismet;
$this->whitelist = $whitelist;
}

/**
* @unreleased
*
* @param DonateControllerData $data
*
* @throws SpamDonationException
*/
public function __invoke(DonateControllerData $data): void
{
if(!$this->whitelist->validate($data->email)) {

$args = CommentCheckArgs::make($data);
$response = $this->akismet->commentCheck($args);
$spam = 'true' === $response[1];

if($spam) {
$message = "This donor's email ($data->firstName $data->lastName - $data->email) has been flagged as SPAM";
if(!give_akismet_is_email_logged($data->email)) {
Log::spam($message, (array) new SpamContext($args, $response));
}
throw new SpamDonationException($message);
}
}
}
}
60 changes: 60 additions & 0 deletions src/DonationSpam/Akismet/DataTransferObjects/CommentCheckArgs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace Give\DonationSpam\Akismet\DataTransferObjects;

use Give\DonationForms\DataTransferObjects\DonateControllerData;

/**
* @unreleased
*/
class CommentCheckArgs
{
public $blog;
public $blog_lang;
public $blog_charset;
public $user_ip;
public $user_agent;
public $referrer;
public $comment_type;
public $comment_content;
public $comment_author;
public $comment_author_email;

/**
* @unreleased
*/
public static function make(DonateControllerData $data): CommentCheckArgs
{
$self = new self();

$self->comment_type = 'contact-form';
$self->comment_content = $data->comment;
$self->comment_author = $data->firstName;
$self->comment_author_email = $data->email;

$self->blog = get_option('home');
$self->blog_lang = get_locale();
$self->blog_charset = get_option('blog_charset');

$self->user_ip = @$_SERVER['REMOTE_ADDR'];
$self->user_agent = @$_SERVER['HTTP_USER_AGENT'];
$self->referrer = @$_SERVER['HTTP_REFERER'];

// Append additional server variables.
foreach ( $_SERVER as $key => $value ) {
if ( ! in_array( $key, [ 'HTTP_COOKIE', 'HTTP_COOKIE2', 'PHP_AUTH_PW' ], true ) ) {
$self->$key = $value;
}
}

return $self;
}

/**
* @unreleased
*/
public function toHttpQuery(): string
{
return http_build_query(get_object_vars($this));
}
}
54 changes: 54 additions & 0 deletions src/DonationSpam/Akismet/DataTransferObjects/SpamContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Give\DonationSpam\Akismet\DataTransferObjects;

/**
* @unreleased
*/
class SpamContext
{
/**
* @var CommentCheckArgs
*/
protected $args;

/**
* @var array
*/
protected $response;

/**
* @unreleased
*/
public function __construct(CommentCheckArgs $args, array $response)
{
$this->args = $args;
$this->response = $response;
}

/**
* @unreleased
*/
public function __serialize(): array
{
return [
'donor_email' => $this->args->comment_author_email,
'filter' => 'akismet',
'message' => $this->formatMessage(),
];
}

/**
* @unreleased
*/
public function formatMessage(): string
{
return sprintf(
'<p><strong>%1$s</strong><pre>%2$s</pre></p><strong>%3$s</strong><pre>%4$s</pre><p>',
__( 'Request', 'give' ),
print_r( $this->args, true ),
__( 'Response', 'give' ),
print_r( $this->response, true )
);
}
}
30 changes: 30 additions & 0 deletions src/DonationSpam/EmailAddressWhiteList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Give\DonationSpam;

/**
* @unreleased
*/
class EmailAddressWhiteList
{
/**
* @var array
*/
protected $whitelistEmails;

/**
* @unreleased
*/
public function __construct($whitelistEmails = [])
{
$this->whitelistEmails = $whitelistEmails;
}

/**
* @unreleased
*/
public function validate($email): bool
{
return in_array($email, $this->whitelistEmails, true);
}
}
10 changes: 10 additions & 0 deletions src/DonationSpam/Exceptions/SpamDonationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Give\DonationSpam\Exceptions;

use Give\Framework\Exceptions\Primitives\Exception;

/**
* @unreleased
*/
class SpamDonationException extends Exception {}
49 changes: 49 additions & 0 deletions src/DonationSpam/ServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Give\DonationSpam;

use Give\Helpers\Hooks;
use Give\ServiceProviders\ServiceProvider as ServiceProviderInterface;

/**
* @unreleased
*/
class ServiceProvider implements ServiceProviderInterface
{
/**
* @unreleased
* @inheritDoc
*/
public function register(): void
{
give()->singleton(EmailAddressWhiteList::class, function () {
return new EmailAddressWhiteList(
apply_filters( 'give_akismet_whitelist_emails', give_akismet_get_whitelisted_emails() )
);
});
}

/**
* @unreleased
* @inheritDoc
*/
public function boot(): void
{
if($this->isAkismetEnabledAndConfigured()) {
Hooks::addAction('givewp_donate_form_data_validated', Akismet\Actions\ValidateDonation::class);
}
}

/**
* @unreleased
* @return bool
*/
public function isAkismetEnabledAndConfigured(): bool
{
return
give_check_akismet_key()
&& give_is_setting_enabled(
give_get_option( 'akismet_spam_protection', 'enabled')
);
}
}
Loading

0 comments on commit 89f4f62

Please sign in to comment.