AdminUserSubscription.php
3 months ago
Blacklist.php
1 year ago
Comment.php
1 day ago
Form.php
1 month ago
Manage.php
1 month ago
ManageSubscriptionFormRenderer.php
3 weeks ago
Pages.php
1 week ago
Registration.php
1 day ago
SubscriptionUrlFactory.php
1 month ago
Throttling.php
3 months ago
index.php
3 years ago
Comment.php
237 lines
| 1 | <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing |
| 2 | |
| 3 | namespace MailPoet\Subscription; |
| 4 | |
| 5 | if (!defined('ABSPATH')) exit; |
| 6 | |
| 7 | |
| 8 | use MailPoet\Entities\SubscriberEntity; |
| 9 | use MailPoet\Settings\SettingsController; |
| 10 | use MailPoet\Subscribers\SubscriberActions; |
| 11 | use MailPoet\Subscribers\SubscribersRepository; |
| 12 | use MailPoet\Subscribers\TrackingConsentCapture; |
| 13 | use MailPoet\WP\Functions as WPFunctions; |
| 14 | |
| 15 | class Comment { |
| 16 | const SPAM = 'spam'; |
| 17 | const APPROVED = 1; |
| 18 | const PENDING_APPROVAL = 0; |
| 19 | |
| 20 | /** |
| 21 | * Comment meta remembering the tracking-consent choice while a comment waits |
| 22 | * for moderation. The subscribe is deferred to approval, which happens in a |
| 23 | * later request with no POST data, so the choice has to be stored with the |
| 24 | * comment rather than re-read. |
| 25 | */ |
| 26 | const TRACKING_CONSENT_META = 'mailpoet_tracking_consent'; |
| 27 | |
| 28 | /** @var SettingsController */ |
| 29 | private $settings; |
| 30 | |
| 31 | /** @var SubscriberActions */ |
| 32 | private $subscriberActions; |
| 33 | |
| 34 | /** @var TrackingConsentCapture */ |
| 35 | private $trackingConsentCapture; |
| 36 | |
| 37 | /** @var SubscribersRepository */ |
| 38 | private $subscribersRepository; |
| 39 | |
| 40 | public function __construct( |
| 41 | SettingsController $settings, |
| 42 | SubscriberActions $subscriberActions, |
| 43 | TrackingConsentCapture $trackingConsentCapture, |
| 44 | SubscribersRepository $subscribersRepository |
| 45 | ) { |
| 46 | $this->settings = $settings; |
| 47 | $this->subscriberActions = $subscriberActions; |
| 48 | $this->trackingConsentCapture = $trackingConsentCapture; |
| 49 | $this->subscribersRepository = $subscribersRepository; |
| 50 | } |
| 51 | |
| 52 | public function extendLoggedInForm($field) { |
| 53 | $field .= $this->getSubscriptionField(); |
| 54 | return $field; |
| 55 | } |
| 56 | |
| 57 | public function extendLoggedOutForm() { |
| 58 | // The method returns escaped content |
| 59 | // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 60 | echo $this->getSubscriptionField(); |
| 61 | } |
| 62 | |
| 63 | /** |
| 64 | * Returns escaped HTML for the subscription field. |
| 65 | * |
| 66 | * @return string |
| 67 | */ |
| 68 | private function getSubscriptionField(): string { |
| 69 | $label = $this->settings->get( |
| 70 | 'subscribe.on_comment.label', |
| 71 | __('Yes, please add me to your mailing list.', 'mailpoet') |
| 72 | ); |
| 73 | |
| 74 | return '<p class="comment-form-mailpoet"> |
| 75 | <label for="mailpoet_subscribe_on_comment"> |
| 76 | <input |
| 77 | type="checkbox" |
| 78 | id="mailpoet_subscribe_on_comment" |
| 79 | value="1" |
| 80 | name="mailpoet[subscribe_on_comment]" |
| 81 | /> ' . esc_html($label) . ' |
| 82 | </label> |
| 83 | </p>' . $this->getTrackingConsentField(); |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * A second, independent checkbox. Consent to open and click tracking is never |
| 88 | * inferred from the subscribe box above it, and it is only shown on sites |
| 89 | * that chose to ask. Never pre-ticked: a pre-ticked consent box is not valid |
| 90 | * consent (CJEU Planet49). |
| 91 | */ |
| 92 | private function getTrackingConsentField(): string { |
| 93 | if (!$this->trackingConsentCapture->isCaptureEnabled()) { |
| 94 | return ''; |
| 95 | } |
| 96 | $copy = $this->trackingConsentCapture->getCopy( |
| 97 | SubscriberEntity::TRACKING_CONSENT_METHOD_COMMENT |
| 98 | ); |
| 99 | |
| 100 | return '<p class="comment-form-mailpoet-tracking-consent"> |
| 101 | <label for="mailpoet_tracking_consent"> |
| 102 | <input |
| 103 | type="checkbox" |
| 104 | id="mailpoet_tracking_consent" |
| 105 | value="1" |
| 106 | name="mailpoet[tracking_consent]" |
| 107 | /> ' . esc_html($copy) . ' |
| 108 | </label> |
| 109 | </p>'; |
| 110 | } |
| 111 | |
| 112 | public function onSubmit($commentId, $commentStatus) { |
| 113 | if ($commentStatus === Comment::SPAM) return; |
| 114 | |
| 115 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- type narrowing only, value is read as bool |
| 116 | $mailpoetPost = isset($_POST['mailpoet']) && is_array($_POST['mailpoet']) ? $_POST['mailpoet'] : []; |
| 117 | |
| 118 | // Independent of the subscribe checkbox, and only for a row that already |
| 119 | // exists: never creates one. Runs whatever the moderation status, because |
| 120 | // updating an existing row's consent does not depend on the comment being |
| 121 | // approved, unlike subscribing, which does. |
| 122 | $this->applyTrackingConsentToExistingSubscriber($mailpoetPost, $commentId); |
| 123 | |
| 124 | if ( |
| 125 | isset($mailpoetPost['subscribe_on_comment']) |
| 126 | && (bool)$mailpoetPost['subscribe_on_comment'] === true |
| 127 | ) { |
| 128 | $trackingConsent = !empty($mailpoetPost['tracking_consent']); |
| 129 | if ($commentStatus === Comment::PENDING_APPROVAL) { |
| 130 | // add a comment meta to remember to subscribe the user |
| 131 | // once the comment gets approved |
| 132 | WPFunctions::get()->addCommentMeta( |
| 133 | $commentId, |
| 134 | 'mailpoet', |
| 135 | 'subscribe_on_comment', |
| 136 | true |
| 137 | ); |
| 138 | // Approval runs in a later request with no POST data, so the consent |
| 139 | // choice is stored alongside it rather than re-read then. |
| 140 | WPFunctions::get()->addCommentMeta( |
| 141 | $commentId, |
| 142 | self::TRACKING_CONSENT_META, |
| 143 | $trackingConsent ? '1' : '0', |
| 144 | true |
| 145 | ); |
| 146 | } elseif ($commentStatus === Comment::APPROVED) { |
| 147 | $this->subscribeAuthorOfComment($commentId, $trackingConsent); |
| 148 | } |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | /** |
| 153 | * Records the comment form's tracking-consent choice for a commenter who is |
| 154 | * already a subscriber, whether or not they also ticked "add me to your |
| 155 | * mailing list". Deliberately never creates a subscriber: a comment is not a |
| 156 | * signup, and someone answering a tracking question should not thereby end |
| 157 | * up on a list they did not ask to join. |
| 158 | * |
| 159 | * isNewSubscriber is always false by construction here, since this only ever |
| 160 | * reaches the apply call when findOneBy just found an existing row. That |
| 161 | * means an unticked box on an existing row is dropped by getConsentData()'s |
| 162 | * own rule, with no extra logic needed. |
| 163 | */ |
| 164 | private function applyTrackingConsentToExistingSubscriber(array $mailpoetPost, $commentId): void { |
| 165 | if (!isset($mailpoetPost['tracking_consent'])) { |
| 166 | return; |
| 167 | } |
| 168 | $comment = WPFunctions::get()->getComment($commentId); |
| 169 | $email = $comment ? $comment->comment_author_email : null; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps |
| 170 | if (empty($email)) { |
| 171 | return; |
| 172 | } |
| 173 | $subscriber = $this->subscribersRepository->findOneBy(['email' => $email]); |
| 174 | if (!$subscriber instanceof SubscriberEntity) { |
| 175 | return; |
| 176 | } |
| 177 | $method = SubscriberEntity::TRACKING_CONSENT_METHOD_COMMENT; |
| 178 | $this->trackingConsentCapture->applyToSubscriber( |
| 179 | $subscriber, |
| 180 | !empty($mailpoetPost['tracking_consent']), |
| 181 | $method, |
| 182 | $this->trackingConsentCapture->getCopy($method), |
| 183 | false |
| 184 | ); |
| 185 | $this->subscribersRepository->flush(); |
| 186 | } |
| 187 | |
| 188 | public function onStatusUpdate($commentId, $action) { |
| 189 | if ($action === 'approve') { |
| 190 | // check if the comment's author wants to subscribe |
| 191 | $doSubscribe = ( |
| 192 | WPFunctions::get()->getCommentMeta( |
| 193 | $commentId, |
| 194 | 'mailpoet', |
| 195 | true |
| 196 | ) === 'subscribe_on_comment' |
| 197 | ); |
| 198 | |
| 199 | if ($doSubscribe === true) { |
| 200 | $trackingConsent = WPFunctions::get()->getCommentMeta( |
| 201 | $commentId, |
| 202 | self::TRACKING_CONSENT_META, |
| 203 | true |
| 204 | ) === '1'; |
| 205 | $this->subscribeAuthorOfComment($commentId, $trackingConsent); |
| 206 | |
| 207 | WPFunctions::get()->deleteCommentMeta($commentId, 'mailpoet'); |
| 208 | WPFunctions::get()->deleteCommentMeta($commentId, self::TRACKING_CONSENT_META); |
| 209 | } |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | private function subscribeAuthorOfComment($commentId, bool $trackingConsent = false) { |
| 214 | $segmentIds = $this->settings->get('subscribe.on_comment.segments', []); |
| 215 | |
| 216 | if (!empty($segmentIds)) { |
| 217 | $comment = WPFunctions::get()->getComment($commentId); |
| 218 | $email = $comment->comment_author_email; // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps |
| 219 | $method = SubscriberEntity::TRACKING_CONSENT_METHOD_COMMENT; |
| 220 | $consentData = $this->trackingConsentCapture->getConsentData( |
| 221 | $trackingConsent, |
| 222 | $method, |
| 223 | $this->trackingConsentCapture->getCopy($method), |
| 224 | $this->trackingConsentCapture->isNewSubscriber($email) |
| 225 | ); |
| 226 | |
| 227 | $this->subscriberActions->subscribe( |
| 228 | array_merge([ |
| 229 | 'email' => $email, |
| 230 | 'first_name' => $comment->comment_author, // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps |
| 231 | ], $consentData), |
| 232 | $segmentIds |
| 233 | ); |
| 234 | } |
| 235 | } |
| 236 | } |
| 237 |