Validator
1 month ago
BehavioralSignals.php
1 month ago
CaptchaConstants.php
2 months ago
CaptchaFormRenderer.php
2 weeks ago
CaptchaHooks.php
2 months ago
CaptchaPhrase.php
1 year ago
CaptchaRenderer.php
4 months ago
CaptchaSession.php
1 year ago
CaptchaUrlFactory.php
4 months ago
PageRenderer.php
5 months ago
ReCaptchaHooks.php
2 months ago
ReCaptchaRenderer.php
1 year ago
ReCaptchaValidator.php
1 year ago
TurnstileHooks.php
2 months ago
TurnstileRenderer.php
2 months ago
TurnstileValidator.php
2 months ago
index.php
1 year ago
CaptchaFormRenderer.php
245 lines
| 1 | <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing |
| 2 | |
| 3 | namespace MailPoet\Captcha; |
| 4 | |
| 5 | if (!defined('ABSPATH')) exit; |
| 6 | |
| 7 | |
| 8 | use MailPoet\Config\Env; |
| 9 | use MailPoet\Entities\FormEntity; |
| 10 | use MailPoet\Form\FormsRepository; |
| 11 | use MailPoet\Form\Renderer as FormRenderer; |
| 12 | use MailPoet\Form\Util\Styles; |
| 13 | use MailPoet\Util\Url as UrlHelper; |
| 14 | use MailPoet\WP\Functions as WPFunctions; |
| 15 | |
| 16 | class CaptchaFormRenderer { |
| 17 | /** @var UrlHelper */ |
| 18 | private $urlHelper; |
| 19 | |
| 20 | /** @var CaptchaSession */ |
| 21 | private $captchaSession; |
| 22 | |
| 23 | /** @var CaptchaPhrase */ |
| 24 | private $captchaPhrase; |
| 25 | |
| 26 | /** @var CaptchaUrlFactory */ |
| 27 | private $captchaUrlFactory; |
| 28 | |
| 29 | /** @var FormRenderer */ |
| 30 | private $formRenderer; |
| 31 | |
| 32 | /** @var FormsRepository */ |
| 33 | private $formsRepository; |
| 34 | |
| 35 | /** @var Styles */ |
| 36 | private $styles; |
| 37 | |
| 38 | private $wp; |
| 39 | |
| 40 | public function __construct( |
| 41 | UrlHelper $urlHelper, |
| 42 | CaptchaSession $captchaSession, |
| 43 | CaptchaPhrase $captchaPhrase, |
| 44 | CaptchaUrlFactory $urlFactory, |
| 45 | FormsRepository $formsRepository, |
| 46 | FormRenderer $formRenderer, |
| 47 | Styles $styles, |
| 48 | WPFunctions $wp |
| 49 | ) { |
| 50 | $this->urlHelper = $urlHelper; |
| 51 | $this->captchaSession = $captchaSession; |
| 52 | $this->captchaPhrase = $captchaPhrase; |
| 53 | $this->captchaUrlFactory = $urlFactory; |
| 54 | $this->formRenderer = $formRenderer; |
| 55 | $this->formsRepository = $formsRepository; |
| 56 | $this->styles = $styles; |
| 57 | $this->wp = $wp; |
| 58 | } |
| 59 | |
| 60 | public function render(array $data) { |
| 61 | $sessionId = (isset($data['captcha_session_id']) && is_string($data['captcha_session_id'])) |
| 62 | ? $data['captcha_session_id'] |
| 63 | : null; |
| 64 | |
| 65 | if (!$sessionId) { |
| 66 | return false; |
| 67 | } |
| 68 | |
| 69 | $ref = $data['referrer_form'] ?? null; |
| 70 | if ($ref === CaptchaUrlFactory::REFERER_MP_FORM) { |
| 71 | return $this->renderFormInSubscriptionForm($sessionId); |
| 72 | } elseif ($ref === CaptchaUrlFactory::REFERER_WP_FORM) { |
| 73 | return $this->renderFormInWPRegisterForm($data, 'wp-submit'); |
| 74 | } elseif ($ref === CaptchaUrlFactory::REFERER_WC_FORM) { |
| 75 | return $this->renderFormInWPRegisterForm($data, 'register'); |
| 76 | } |
| 77 | |
| 78 | return false; |
| 79 | } |
| 80 | |
| 81 | private function renderFormInSubscriptionForm($sessionId) { |
| 82 | $captchaSessionForm = $this->captchaSession->getFormData($sessionId); |
| 83 | $showSuccessMessage = !empty($_GET['mailpoet_success']); |
| 84 | $showErrorMessage = !empty($_GET['mailpoet_error']); |
| 85 | |
| 86 | $formId = 0; |
| 87 | if (isset($captchaSessionForm['form_id']) && is_numeric($captchaSessionForm['form_id'])) { |
| 88 | $formId = (int)$captchaSessionForm['form_id']; |
| 89 | } elseif ($showSuccessMessage && is_numeric($_GET['mailpoet_success'])) { |
| 90 | $formId = (int)$_GET['mailpoet_success']; |
| 91 | } elseif ($showErrorMessage && is_numeric($_GET['mailpoet_error'])) { |
| 92 | $formId = (int)$_GET['mailpoet_error']; |
| 93 | } |
| 94 | |
| 95 | $formModel = $this->formsRepository->findOneById($formId); |
| 96 | if (!$formModel instanceof FormEntity) { |
| 97 | return false; |
| 98 | } elseif ($showSuccessMessage) { |
| 99 | // Display a success message in a no-JS flow |
| 100 | return $this->renderFormMessages($formModel, true); |
| 101 | } |
| 102 | |
| 103 | $redirectUrl = $this->urlHelper->getCurrentUrl(); |
| 104 | $hiddenFields = '<input type="hidden" name="data[form_id]" value="' . $this->wp->escAttr($formId) . '" />'; |
| 105 | $hiddenFields .= '<input type="hidden" name="data[captcha_session_id]" value="' . $this->wp->escAttr($sessionId) . '" />'; |
| 106 | $hiddenFields .= '<input type="hidden" name="api_version" value="v1" />'; |
| 107 | $hiddenFields .= '<input type="hidden" name="endpoint" value="subscribers" />'; |
| 108 | $hiddenFields .= '<input type="hidden" name="mailpoet_method" value="subscribe" />'; |
| 109 | $hiddenFields .= '<input type="hidden" name="mailpoet_redirect" value="' . $this->wp->escUrl($redirectUrl) . '" />'; |
| 110 | $hiddenFields .= '<input type="hidden" name="token" value="' . $this->wp->escAttr($this->wp->wpCreateNonce('mailpoet_token')) . '" />'; |
| 111 | |
| 112 | $actionUrl = admin_url('admin-post.php?action=mailpoet_subscription_form'); |
| 113 | |
| 114 | $submitBlocks = $formModel->getBlocksByTypes(['submit']); |
| 115 | $submitLabel = count($submitBlocks) && $submitBlocks[0]['params']['label'] |
| 116 | ? $submitBlocks[0]['params']['label'] |
| 117 | : __('Subscribe', 'mailpoet'); |
| 118 | |
| 119 | $afterSubmitElement = $this->renderFormMessages($formModel, false, $showErrorMessage); |
| 120 | |
| 121 | $styles = $this->styles->renderFormMessageStyles($formModel, '#mailpoet_captcha_form'); |
| 122 | $styles = '<style>' . $styles . '</style>'; |
| 123 | |
| 124 | return $this->renderForm($sessionId, $hiddenFields, $actionUrl, $submitLabel, $afterSubmitElement, $styles); |
| 125 | } |
| 126 | |
| 127 | private function renderFormInWPRegisterForm(array $data, string $submitLabelKey) { |
| 128 | $sessionId = $data['captcha_session_id']; |
| 129 | |
| 130 | unset($data['captcha_session_id']); |
| 131 | // The 'name' attr is required in this format for the refresh button to work |
| 132 | $hiddenFields = '<input type="hidden" name="data[captcha_session_id]" value="' . $this->wp->escAttr($sessionId) . '" />'; |
| 133 | |
| 134 | $actionUrl = $data['referrer_form_url']; |
| 135 | unset($data['referrer_form_url']); |
| 136 | |
| 137 | unset($data['referrer_form']); |
| 138 | foreach ($data as $key => $value) { |
| 139 | if (!is_scalar($value)) continue; |
| 140 | $hiddenFields .= '<input type="hidden" name="' . $this->wp->escAttr($key) . '" value="' . $this->wp->escAttr($value) . '" />'; |
| 141 | } |
| 142 | |
| 143 | $submitLabel = $data[$submitLabelKey] ?? esc_attr_e('Register'); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain |
| 144 | |
| 145 | return $this->renderForm($sessionId, $hiddenFields, $actionUrl, $submitLabel); |
| 146 | } |
| 147 | |
| 148 | private function renderForm( |
| 149 | $sessionId, |
| 150 | $hiddenFields, |
| 151 | $actionUrl, |
| 152 | $submitLabel, |
| 153 | $afterSubmitElement = null, |
| 154 | $styles = null |
| 155 | ) { |
| 156 | $this->captchaPhrase->createPhrase($sessionId); |
| 157 | |
| 158 | $fields = [ |
| 159 | [ |
| 160 | 'id' => 'captcha', |
| 161 | 'type' => 'text', |
| 162 | 'params' => [ |
| 163 | 'label' => __('Type in the characters you see in the picture above:', 'mailpoet'), |
| 164 | 'value' => '', |
| 165 | 'obfuscate' => false, |
| 166 | ], |
| 167 | ], |
| 168 | ]; |
| 169 | |
| 170 | $form = array_merge( |
| 171 | $fields, |
| 172 | [ |
| 173 | [ |
| 174 | 'id' => 'submit', |
| 175 | 'type' => 'submit', |
| 176 | 'params' => [ |
| 177 | 'label' => $submitLabel, |
| 178 | ], |
| 179 | ], |
| 180 | ], |
| 181 | ); |
| 182 | |
| 183 | if ($afterSubmitElement) { |
| 184 | // The 'mailpoet_form' class alter the form's submission behavior |
| 185 | // Refer to mailpoet/assets/js/src/public.tsx |
| 186 | $classes = 'mailpoet_form mailpoet_captcha_form'; |
| 187 | } else { |
| 188 | $classes = 'mailpoet_captcha_form'; |
| 189 | } |
| 190 | |
| 191 | $formHtml = '<form method="POST" action="' . $this->wp->escUrl($actionUrl) . '" class="' . $this->wp->escAttr($classes) . '" id="mailpoet_captcha_form" novalidate>'; |
| 192 | $formHtml .= $hiddenFields; |
| 193 | |
| 194 | $width = CaptchaRenderer::DEFAULT_WIDTH; |
| 195 | $height = CaptchaRenderer::DEFAULT_HEIGHT; |
| 196 | $captchaUrl = $this->captchaUrlFactory->getCaptchaImageUrl($sessionId); |
| 197 | $mp3CaptchaUrl = $this->captchaUrlFactory->getCaptchaAudioUrl($sessionId); |
| 198 | $reloadIcon = Env::$assetsUrl . '/img/icons/image-rotate.svg'; |
| 199 | $playIcon = Env::$assetsUrl . '/img/icons/controls-volumeon.svg'; |
| 200 | |
| 201 | $formHtml .= '<div class="mailpoet_form_hide_on_success">'; |
| 202 | $formHtml .= '<p class="mailpoet_paragraph">'; |
| 203 | $formHtml .= '<img class="mailpoet_captcha" src="' . $this->wp->escUrl($captchaUrl) . '" width="' . $this->wp->escAttr($width) . '" height="' . $this->wp->escAttr($height) . '" title="' . esc_attr__('CAPTCHA', 'mailpoet') . '" />'; |
| 204 | $formHtml .= '</p>'; |
| 205 | $formHtml .= '<button type="button" class="mailpoet_icon_button mailpoet_captcha_update" title="' . esc_attr(__('Reload CAPTCHA', 'mailpoet')) . '"><img src="' . $this->wp->escUrl($reloadIcon) . '" alt="" /></button>'; |
| 206 | $formHtml .= '<button type="button" class="mailpoet_icon_button mailpoet_captcha_audio" title="' . esc_attr(__('Play CAPTCHA', 'mailpoet')) . '"><img src="' . $this->wp->escUrl($playIcon) . '" alt="" /></button>'; |
| 207 | $formHtml .= '<audio class="mailpoet_captcha_player">'; |
| 208 | $formHtml .= '<source src="' . $this->wp->escUrl($mp3CaptchaUrl) . '" type="audio/mpeg">'; |
| 209 | $formHtml .= '</audio>'; |
| 210 | |
| 211 | $formHtml .= $this->formRenderer->renderBlocks($form, [], null, $honeypot = false); |
| 212 | $formHtml .= '</div>'; |
| 213 | |
| 214 | if ($afterSubmitElement) { |
| 215 | $formHtml .= $afterSubmitElement; |
| 216 | } |
| 217 | |
| 218 | $formHtml .= '</form>'; |
| 219 | |
| 220 | if ($styles) { |
| 221 | $formHtml .= $styles; |
| 222 | } |
| 223 | |
| 224 | return $formHtml; |
| 225 | } |
| 226 | |
| 227 | private function renderFormMessages( |
| 228 | FormEntity $formModel, |
| 229 | $showSuccessMessage = false, |
| 230 | $showErrorMessage = false |
| 231 | ) { |
| 232 | $settings = $formModel->getSettings() ?? []; |
| 233 | $errorMessage = __('The characters you entered did not match the CAPTCHA image. Please try again with this new image.', 'mailpoet'); |
| 234 | |
| 235 | $success = isset($settings['success_message']) ? (string)$settings['success_message'] : ''; |
| 236 | |
| 237 | $formHtml = '<div class="mailpoet_message" role="alert" aria-live="assertive">'; |
| 238 | $formHtml .= '<p class="mailpoet_validate_success" ' . ($showSuccessMessage ? '' : ' style="display:none;"') . '>' . $this->wp->escHtml($success) . '</p>'; |
| 239 | $formHtml .= '<p class="mailpoet_validate_error" ' . ($showErrorMessage ? '' : ' style="display:none;"') . '>' . $this->wp->escHtml($errorMessage) . '</p>'; |
| 240 | $formHtml .= '</div>'; |
| 241 | |
| 242 | return $formHtml; |
| 243 | } |
| 244 | } |
| 245 |