| 1 |
<?php |
| 2 |
/** |
| 3 |
* Re-sends the stored confirmation mail of an opt-in. |
| 4 |
* |
| 5 |
* @package Forge12\DoubleOptIn\Service |
| 6 |
* @since 5.8.0 |
| 7 |
*/ |
| 8 |
|
| 9 |
declare( strict_types=1 ); |
| 10 |
|
| 11 |
namespace Forge12\DoubleOptIn\Service; |
| 12 |
|
| 13 |
use Forge12\DoubleOptIn\Repository\OptInRepositoryInterface; |
| 14 |
|
| 15 |
if ( ! defined( 'ABSPATH' ) ) { |
| 16 |
exit; |
| 17 |
} |
| 18 |
|
| 19 |
/** |
| 20 |
* One implementation for every path that re-sends the confirmation mail: |
| 21 |
* the admin REST route, the legacy admin AJAX button and, from Welle 2 on, |
| 22 |
* the visitor asking for it again (addon-reminder). |
| 23 |
* |
| 24 |
* The mail is the one stored on the record (`mail_optin`), so the link in |
| 25 |
* it is the original one. Subject and sender come from the current form |
| 26 |
* settings, as before. |
| 27 |
* |
| 28 |
* Checks no capability: who may trigger a resend is the caller's business. |
| 29 |
* |
| 30 |
* @api |
| 31 |
*/ |
| 32 |
class ConfirmationMailResender { |
| 33 |
|
| 34 |
/** @var OptInRepositoryInterface */ |
| 35 |
private $repository; |
| 36 |
|
| 37 |
/** @var callable(int):array<string, mixed> */ |
| 38 |
private $formSettings; |
| 39 |
|
| 40 |
/** @var DoiMailHeaders|null */ |
| 41 |
private $headers; |
| 42 |
|
| 43 |
/** |
| 44 |
* @param OptInRepositoryInterface $repository Opt-in storage. |
| 45 |
* @param callable(int):array<string, mixed> $formSettings Form id → settings with |
| 46 |
* `subject`, `sender`, `sender_name`. |
| 47 |
* @param DoiMailHeaders|null $headers Marks the mail for `f12_doi_mail_headers`. |
| 48 |
*/ |
| 49 |
public function __construct( OptInRepositoryInterface $repository, callable $formSettings, ?DoiMailHeaders $headers = null ) { |
| 50 |
$this->repository = $repository; |
| 51 |
$this->formSettings = $formSettings; |
| 52 |
$this->headers = $headers; |
| 53 |
} |
| 54 |
|
| 55 |
public function resend( int $optInId ): ResendResult { |
| 56 |
$optIn = $optInId > 0 ? $this->repository->findById( $optInId ) : null; |
| 57 |
if ( $optIn === null ) { |
| 58 |
return ResendResult::refused( ResendResult::NOT_FOUND ); |
| 59 |
} |
| 60 |
if ( $optIn->isConfirmed() ) { |
| 61 |
return ResendResult::refused( ResendResult::CONFIRMED ); |
| 62 |
} |
| 63 |
// A withdrawn consent is not asked for again behind the person's back. |
| 64 |
if ( $optIn->isOptedOut() ) { |
| 65 |
return ResendResult::refused( ResendResult::OPTED_OUT ); |
| 66 |
} |
| 67 |
|
| 68 |
$stored = $optIn->getMailOptIn(); |
| 69 |
if ( $stored === '' ) { |
| 70 |
return ResendResult::refused( ResendResult::NO_BODY ); |
| 71 |
} |
| 72 |
|
| 73 |
$settings = (array) call_user_func( $this->formSettings, $optIn->getFormId() ); |
| 74 |
$to = $optIn->getEmail(); |
| 75 |
$body = $stored; |
| 76 |
$subject = (string) ( $settings['subject'] ?? '' ); |
| 77 |
$from = self::from( (string) ( $settings['sender'] ?? '' ), (string) ( $settings['sender_name'] ?? '' ) ); |
| 78 |
|
| 79 |
// Older writers stored a structured payload instead of the body. |
| 80 |
$payload = maybe_unserialize( $stored ); |
| 81 |
if ( is_array( $payload ) ) { |
| 82 |
$to = (string) ( $payload['to'] ?? $to ); |
| 83 |
$body = (string) ( $payload['body'] ?? '' ); |
| 84 |
$subject = (string) ( $payload['subject'] ?? $subject ); |
| 85 |
$from = (string) ( $payload['from'] ?? $from ); |
| 86 |
} |
| 87 |
|
| 88 |
if ( $body === '' ) { |
| 89 |
return ResendResult::refused( ResendResult::NO_BODY ); |
| 90 |
} |
| 91 |
if ( $to === '' ) { |
| 92 |
return ResendResult::refused( ResendResult::NO_RECIPIENT ); |
| 93 |
} |
| 94 |
|
| 95 |
$headers = array( 'Content-Type: text/html; charset=UTF-8' ); |
| 96 |
if ( $from !== '' ) { |
| 97 |
$headers[] = 'From: ' . $from; |
| 98 |
} |
| 99 |
|
| 100 |
if ( $this->headers !== null ) { |
| 101 |
$this->headers->expect( $optIn->getId(), $to, DoiMailHeaders::KIND_RESEND ); |
| 102 |
} |
| 103 |
|
| 104 |
$sent = (bool) wp_mail( |
| 105 |
$to, |
| 106 |
$subject !== '' ? $subject : __( 'Please confirm your opt-in', 'double-opt-in' ), |
| 107 |
$body, |
| 108 |
$headers |
| 109 |
); |
| 110 |
|
| 111 |
if ( $this->headers !== null ) { |
| 112 |
// A mail that never reached wp_mail's filter must not tag the next one. |
| 113 |
$this->headers->forget(); |
| 114 |
} |
| 115 |
|
| 116 |
// Record the outcome on the opt-in (OptInMailTracker). |
| 117 |
do_action( 'f12_doi_optin_mail_result', $optIn->getId(), $sent, '' ); |
| 118 |
|
| 119 |
return $sent ? ResendResult::sent() : ResendResult::refused( ResendResult::SEND_FAILED ); |
| 120 |
} |
| 121 |
|
| 122 |
private static function from( string $email, string $name ): string { |
| 123 |
if ( $email === '' ) { |
| 124 |
return ''; |
| 125 |
} |
| 126 |
return $name !== '' ? $name . ' <' . $email . '>' : $email; |
| 127 |
} |
| 128 |
} |
| 129 |
|