PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.6.2
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.6.2
5.6.2 5.6.3 5.6.1 5.6.0 5.5.0 5.4.0 5.3.2 5.3.1 5.1.6 5.1.5 trunk 2.1.5 2.11 2.12 2.13 2.15 3.0.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.51 3.0.60 3.0.61 3.0.62 All 38 releases
double-opt-in / src / FollowUp / FollowUpResult.php

FollowUpResult.php in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 5.6.2, at src/FollowUp/FollowUpResult.php

188 lines 5.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Outcome of one follow-up action.
4 *
5 * @package Forge12\DoubleOptIn\FollowUp
6 * @since 5.6.0
7 */
8
9 declare( strict_types=1 );
10
11 namespace Forge12\DoubleOptIn\FollowUp;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Immutable result an adapter reports for one action.
19 *
20 * Carries only structural diagnostics — a machine error code, an HTTP
21 * status, a response class and an entry reference. Never form values,
22 * recipients, tokens or third-party error text: those can contain
23 * personal data and this object ends up in the audit log.
24 */
25 final class FollowUpResult {
26
27 /**
28 * Response classes for HTTP-based adapters.
29 */
30 public const RESPONSE_JSON_SUCCESS = 'json_success';
31 public const RESPONSE_JSON_ERROR = 'json_error';
32 public const RESPONSE_INVALID_JSON = 'invalid_json';
33 public const RESPONSE_EMPTY = 'empty';
34 public const RESPONSE_NONE = '';
35
36 /** @var string */
37 private $status;
38
39 /** @var string */
40 private $errorCode;
41
42 /** @var bool */
43 private $retryable;
44
45 /** @var int */
46 private $httpStatus;
47
48 /** @var string */
49 private $responseKind;
50
51 /** @var string */
52 private $entryRef;
53
54 /** @var string */
55 private $evidence;
56
57 private function __construct( string $status, string $errorCode, bool $retryable, int $httpStatus, string $responseKind, string $entryRef, string $evidence ) {
58 $this->status = $status;
59 $this->errorCode = self::normaliseCode( $errorCode );
60 $this->retryable = $retryable;
61 $this->httpStatus = $httpStatus;
62 $this->responseKind = $responseKind;
63 $this->entryRef = substr( $entryRef, 0, 100 );
64 $this->evidence = self::normaliseCode( $evidence );
65 }
66
67 /**
68 * The action demonstrably completed.
69 *
70 * @param string $evidence What the success is based on, e.g.
71 * `wp_mail_true`, `entry_id`, `no_exception`.
72 * @param string $entryRef ID of a record the action created, if any.
73 */
74 public static function succeeded( string $evidence, string $entryRef = '' ): self {
75 return new self( FollowUpStatus::SUCCEEDED, '', false, 0, self::RESPONSE_NONE, $entryRef, $evidence );
76 }
77
78 /**
79 * The action was deliberately not executed.
80 */
81 public static function skipped( string $reason ): self {
82 return new self( FollowUpStatus::SKIPPED, $reason, false, 0, self::RESPONSE_NONE, '', '' );
83 }
84
85 /**
86 * The action did not run and running it again is safe.
87 */
88 public static function failedRetryable( string $errorCode ): self {
89 return new self( FollowUpStatus::FAILED_RETRYABLE, $errorCode, true, 0, self::RESPONSE_NONE, '', '' );
90 }
91
92 /**
93 * The action failed; retrying without fixing the cause is pointless.
94 */
95 public static function failedPermanent( string $errorCode, string $entryRef = '' ): self {
96 return new self( FollowUpStatus::FAILED_PERMANENT, $errorCode, false, 0, self::RESPONSE_NONE, $entryRef, '' );
97 }
98
99 /**
100 * The action may or may not have run.
101 */
102 public static function unknown( string $errorCode ): self {
103 return new self( FollowUpStatus::UNKNOWN, $errorCode, false, 0, self::RESPONSE_NONE, '', '' );
104 }
105
106 /**
107 * Copy with transport diagnostics attached.
108 */
109 public function withTransport( int $httpStatus, string $responseKind ): self {
110 $copy = clone $this;
111 $copy->httpStatus = $httpStatus;
112 $copy->responseKind = $responseKind;
113 return $copy;
114 }
115
116 /**
117 * Copy with a different status, keeping diagnostics — used when a
118 * retryable failure has exhausted its retries.
119 */
120 public function withStatus( string $status ): self {
121 $copy = clone $this;
122 $copy->status = $status;
123 $copy->retryable = $status === FollowUpStatus::FAILED_RETRYABLE;
124 return $copy;
125 }
126
127 public function getStatus(): string {
128 return $this->status;
129 }
130
131 public function getErrorCode(): string {
132 return $this->errorCode;
133 }
134
135 public function isRetryable(): bool {
136 return $this->retryable;
137 }
138
139 public function getHttpStatus(): int {
140 return $this->httpStatus;
141 }
142
143 public function getResponseKind(): string {
144 return $this->responseKind;
145 }
146
147 public function getEntryRef(): string {
148 return $this->entryRef;
149 }
150
151 public function getEvidence(): string {
152 return $this->evidence;
153 }
154
155 /**
156 * @return array<string, mixed>
157 */
158 public function toArray(): array {
159 return array(
160 'status' => $this->status,
161 'error_code' => $this->errorCode,
162 'retryable' => $this->retryable,
163 'http_status' => $this->httpStatus,
164 'response_kind' => $this->responseKind,
165 'entry_ref' => $this->entryRef,
166 'evidence' => $this->evidence,
167 );
168 }
169
170 /**
171 * Error codes are machine identifiers: [a-z0-9_:.-], at most 64
172 * characters. Anything else is replaced as a whole rather than
173 * "cleaned" — replacing single characters would turn
174 * `max@example.com` into a still readable `max_example.com`. A code
175 * is never the place for a message.
176 */
177 private static function normaliseCode( string $code ): string {
178 if ( $code === '' ) {
179 return '';
180 }
181 $code = strtolower( $code );
182 if ( ! preg_match( '/^[a-z0-9_:.\-]{1,64}$/', $code ) ) {
183 return 'unclassified_error';
184 }
185 return $code;
186 }
187 }
188