BlockRendererHelper.php
3 weeks ago
Checkbox.php
1 year ago
Close.php
2 weeks ago
Column.php
2 months ago
Columns.php
2 months ago
Date.php
1 year ago
Divider.php
3 years ago
Heading.php
2 months ago
Html.php
1 year ago
Image.php
10 months ago
Paragraph.php
3 weeks ago
Radio.php
1 year ago
Segment.php
2 months ago
Select.php
2 months ago
Submit.php
1 year ago
Text.php
1 year ago
Textarea.php
1 year ago
index.php
3 years ago
BlockRendererHelper.php
310 lines
| 1 | <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing |
| 2 | |
| 3 | namespace MailPoet\Form\Block; |
| 4 | |
| 5 | if (!defined('ABSPATH')) exit; |
| 6 | |
| 7 | |
| 8 | use MailPoet\Form\Util\FieldNameObfuscator; |
| 9 | use MailPoet\Services\Validator; |
| 10 | use MailPoet\WP\Functions as WPFunctions; |
| 11 | |
| 12 | /** |
| 13 | * This class still covers several responsibilities and could be further refactored |
| 14 | * @package MailPoet\Form\Block |
| 15 | */ |
| 16 | class BlockRendererHelper { |
| 17 | |
| 18 | /** @var FieldNameObfuscator */ |
| 19 | private $fieldNameObfuscator; |
| 20 | |
| 21 | /** @var WPFunctions */ |
| 22 | protected $wp; |
| 23 | |
| 24 | public function __construct( |
| 25 | FieldNameObfuscator $fieldNameObfuscator, |
| 26 | WPFunctions $wp |
| 27 | ) { |
| 28 | $this->fieldNameObfuscator = $fieldNameObfuscator; |
| 29 | $this->wp = $wp; |
| 30 | } |
| 31 | |
| 32 | public function getInputValidation(array $block, array $extraRules = [], ?int $formId = null): string { |
| 33 | $rules = [ |
| 34 | 'errors-container' => '.' . $this->getErrorsContainerClass($block, $formId), |
| 35 | ]; |
| 36 | $blockId = $this->wp->escAttr($block['id']); |
| 37 | |
| 38 | if ($blockId === 'email') { |
| 39 | $rules['required'] = true; |
| 40 | $rules['minlength'] = Validator::EMAIL_MIN_LENGTH; |
| 41 | $rules['maxlength'] = Validator::EMAIL_MAX_LENGTH; |
| 42 | $rules['type-message'] = __('This value should be a valid email.', 'mailpoet'); |
| 43 | } |
| 44 | |
| 45 | if (($blockId === 'first_name') || ($blockId === 'last_name')) { |
| 46 | $errorMessages = [ |
| 47 | __('Please specify a valid name.', 'mailpoet'), |
| 48 | __('Addresses in names are not permitted, please add your name instead.', 'mailpoet'), |
| 49 | ]; |
| 50 | $rules['names'] = '[' . implode(',', array_map(function (string $errorMessage): string { |
| 51 | return $this->wp->escAttr('"' . $errorMessage . '"'); |
| 52 | }, $errorMessages)) . ']'; |
| 53 | } |
| 54 | |
| 55 | // Segments should be required only when form ID is not empty. That allows save form on subscription management site when any segment is not checked. |
| 56 | if ($blockId === 'segments' && $formId) { |
| 57 | $rules['required'] = true; |
| 58 | $rules['mincheck'] = 1; |
| 59 | $rules['group'] = $blockId; |
| 60 | $rules['required-message'] = __('Please select a list.', 'mailpoet'); |
| 61 | } |
| 62 | |
| 63 | if (static::getFieldIsRequired($block)) { |
| 64 | $rules['required'] = true; |
| 65 | $rules['required-message'] = __('This field is required.', 'mailpoet'); |
| 66 | } |
| 67 | |
| 68 | if (!empty($block['params']['validate'])) { |
| 69 | if ($block['params']['validate'] === 'phone') { |
| 70 | $rules['pattern'] = "^[\d\+\-\.\(\)\/\s]*$"; |
| 71 | $rules['error-message'] = __('Please specify a valid phone number.', 'mailpoet'); |
| 72 | } else { |
| 73 | $rules['type'] = $this->wp->escAttr($block['params']['validate']); |
| 74 | $rules['error-message'] = $this->translateValidationErrorMessage($block['params']['validate']); |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | if (in_array($block['type'], ['radio', 'checkbox', 'date'])) { |
| 79 | $rules['group'] = 'custom_field_' . $blockId; |
| 80 | } |
| 81 | |
| 82 | $rules = array_merge($rules, $extraRules); |
| 83 | |
| 84 | if (empty($rules)) { |
| 85 | return ''; |
| 86 | } |
| 87 | |
| 88 | $validation = []; |
| 89 | $rules = array_unique($rules); |
| 90 | foreach ($rules as $rule => $value) { |
| 91 | if (is_bool($value)) { |
| 92 | $value = ($value) ? 'true' : 'false'; |
| 93 | } |
| 94 | // We need to use single quotes because we need to pass array of strings as a parameter for custom validation |
| 95 | if ($rule === 'names') { |
| 96 | $validation[] = 'data-parsley-' . $rule . '=\'' . $this->wp->wpKsesPost($value) . '\''; // The value has been escaped above. |
| 97 | } else { |
| 98 | $validation[] = 'data-parsley-' . $this->wp->escAttr($rule) . '="' . $this->wp->escAttr($this->wp->wpKsesPost($value)) . '"'; |
| 99 | if ($rule === 'required') { |
| 100 | $validation[] = 'required aria-required="true"'; |
| 101 | } |
| 102 | } |
| 103 | } |
| 104 | return join(' ', $validation); |
| 105 | } |
| 106 | |
| 107 | public function renderLabel(array $block, array $formSettings): string { |
| 108 | $html = ''; |
| 109 | $forId = ''; |
| 110 | |
| 111 | if ( |
| 112 | isset($block['params']['hide_label']) |
| 113 | && $block['params']['hide_label'] |
| 114 | ) { |
| 115 | return $html; |
| 116 | } |
| 117 | |
| 118 | // If the label is displayed within the field, |
| 119 | // we'll use aria-label instead of a label element |
| 120 | if ( |
| 121 | isset($block['params']['label_within']) |
| 122 | && $block['params']['label_within'] |
| 123 | ) { |
| 124 | return $html; |
| 125 | } |
| 126 | |
| 127 | $automationId = null; |
| 128 | if (in_array($block['id'], ['email', 'last_name', 'first_name'], true)) { |
| 129 | $automationId = 'data-automation-id="form_' . $block['id'] . '_label" '; |
| 130 | } |
| 131 | |
| 132 | if (!empty($block['params']['input_id']) && is_scalar($block['params']['input_id'])) { |
| 133 | $forId = 'for="' . $this->wp->escAttr((string)$block['params']['input_id']) . '" '; |
| 134 | } elseif (isset($formSettings['id'])) { |
| 135 | $forId = 'for="' . $this->wp->escAttr('form_' . (string)$block['id'] . '_' . (string)$formSettings['id']) . '" '; |
| 136 | } |
| 137 | |
| 138 | if ( |
| 139 | isset($block['params']['label']) |
| 140 | && strlen(trim($block['params']['label'])) > 0 |
| 141 | ) { |
| 142 | $labelClass = 'class="mailpoet_' . $block['type'] . '_label" '; |
| 143 | |
| 144 | $html .= '<label ' |
| 145 | . $forId |
| 146 | . $labelClass |
| 147 | . $this->renderFontStyle($formSettings, $block['styles'] ?? []) |
| 148 | . ($automationId ? " $automationId" : '') |
| 149 | . '>'; |
| 150 | $html .= static::getFieldLabel($block); |
| 151 | |
| 152 | if (static::getFieldIsRequired($block)) { |
| 153 | $html .= ' <span class="mailpoet_required" aria-hidden="true">*</span>'; |
| 154 | } |
| 155 | |
| 156 | $html .= '</label>'; |
| 157 | } |
| 158 | return $html; |
| 159 | } |
| 160 | |
| 161 | public function renderLegend(array $block, array $formSettings): string { |
| 162 | $html = ''; |
| 163 | |
| 164 | if ( |
| 165 | isset($block['params']['hide_label']) |
| 166 | && $block['params']['hide_label'] |
| 167 | ) { |
| 168 | return $html; |
| 169 | } |
| 170 | |
| 171 | if ( |
| 172 | isset($block['params']['label']) |
| 173 | && strlen(trim($block['params']['label'])) > 0 |
| 174 | ) { |
| 175 | // Use _label suffix for backward compatibility |
| 176 | $labelClass = 'class="mailpoet_' . $block['type'] . '_label" '; |
| 177 | $html .= '<legend ' |
| 178 | . $labelClass |
| 179 | . $this->renderFontStyle($formSettings, $block['styles'] ?? []) |
| 180 | . '>'; |
| 181 | $html .= static::getFieldLabel($block); |
| 182 | |
| 183 | if (static::getFieldIsRequired($block)) { |
| 184 | $html .= ' <span class="mailpoet_required" aria-hidden="true">*</span>'; |
| 185 | } |
| 186 | |
| 187 | $html .= '</legend>'; |
| 188 | } |
| 189 | return $html; |
| 190 | } |
| 191 | |
| 192 | public function renderFontStyle(array $formSettings, array $styles = []) { |
| 193 | $rules = []; |
| 194 | if (isset($formSettings['fontSize'])) { |
| 195 | $rules[] = 'font-size: ' . $formSettings['fontSize'] . (is_numeric($formSettings['fontSize']) ? "px;" : ";"); |
| 196 | $rules[] = 'line-height: 1.2;'; |
| 197 | } |
| 198 | if (isset($styles['bold']) && $styles['bold']) { |
| 199 | $rules[] = 'font-weight: bold;'; |
| 200 | } |
| 201 | return $rules ? 'style="' . $this->wp->escAttr(implode("", $rules)) . '"' : ''; |
| 202 | } |
| 203 | |
| 204 | public function renderInputPlaceholder(array $block): string { |
| 205 | $html = ''; |
| 206 | // if the label is displayed as a placeholder, |
| 207 | if ( |
| 208 | isset($block['params']['label_within']) |
| 209 | && $block['params']['label_within'] |
| 210 | ) { |
| 211 | $label = $this->wp->escAttr(static::getFieldLabel($block)); |
| 212 | if (static::getFieldIsRequired($block)) { |
| 213 | $label .= ' *'; |
| 214 | } |
| 215 | // Some screen readers don't read placeholders, so we need to add aria-label |
| 216 | // but to prevent reading it twice, they need to be the same (including *) |
| 217 | $html .= ' placeholder="' . $label . '"'; |
| 218 | $html .= ' aria-label="' . $label . '" '; |
| 219 | } |
| 220 | return $html; |
| 221 | } |
| 222 | |
| 223 | // return field name depending on block data |
| 224 | public function getFieldName(array $block = []): string { |
| 225 | $blockId = $this->wp->escAttr($block['id']); |
| 226 | if ((int)$blockId > 0) { |
| 227 | return 'cf_' . $blockId; |
| 228 | } elseif (isset($block['params']['obfuscate']) && !$block['params']['obfuscate']) { |
| 229 | return $blockId; |
| 230 | } else { |
| 231 | return $this->fieldNameObfuscator->obfuscate($block['id']);//obfuscate field name for spambots |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | public function getFieldLabel(array $block = []): string { |
| 236 | return (isset($block['params']['label']) |
| 237 | && strlen(trim($block['params']['label'])) > 0) |
| 238 | ? $this->wp->escHtml(trim($block['params']['label'])) : ''; |
| 239 | } |
| 240 | |
| 241 | public function getFieldValue($block = []) { |
| 242 | return (isset($block['params']['value']) |
| 243 | && strlen(trim($block['params']['value'])) > 0) |
| 244 | ? $this->wp->escAttr(trim($block['params']['value'])) : ''; |
| 245 | } |
| 246 | |
| 247 | public function getFieldIsRequired($block = []): bool { |
| 248 | return (isset($block['params']['required']) |
| 249 | && strlen(trim($block['params']['required'])) > 0) |
| 250 | ? !empty($block['params']['required']) : false; |
| 251 | } |
| 252 | |
| 253 | public function getInputModifiers(array $block = []): string { |
| 254 | $modifiers = []; |
| 255 | |
| 256 | if (isset($block['params']['readonly']) && $block['params']['readonly']) { |
| 257 | $modifiers[] = 'readonly'; |
| 258 | } |
| 259 | |
| 260 | if (isset($block['params']['disabled']) && $block['params']['disabled']) { |
| 261 | $modifiers[] = 'disabled'; |
| 262 | } |
| 263 | return join(' ', $modifiers); |
| 264 | } |
| 265 | |
| 266 | public function escapeShortCodes(?string $value): ?string { |
| 267 | if ($value === null) { |
| 268 | return null; |
| 269 | } |
| 270 | return preg_replace_callback('/' . $this->wp->getShortcodeRegex() . '/s', function ($matches) { |
| 271 | return str_replace(['[', ']'], ['[', ']'], $matches[0]); |
| 272 | }, $value); |
| 273 | } |
| 274 | |
| 275 | public function renderErrorsContainer(array $block = [], ?int $formId = null): string { |
| 276 | $errorContainerClass = $this->getErrorsContainerClass($block, $formId); |
| 277 | return '<span class="' . $errorContainerClass . '"></span>'; |
| 278 | } |
| 279 | |
| 280 | private function getErrorsContainerClass(array $block = [], ?int $formId = null): string { |
| 281 | $validationId = $block['validation_id'] ?? null; |
| 282 | if (!$validationId) { |
| 283 | $validationId = $this->wp->escAttr($block['id']); |
| 284 | if ($formId) { |
| 285 | $validationId .= '_' . $formId; |
| 286 | } |
| 287 | } |
| 288 | return 'mailpoet_error_' . $validationId; |
| 289 | } |
| 290 | |
| 291 | private function translateValidationErrorMessage(string $validate): string { |
| 292 | switch ($validate) { |
| 293 | case 'email': |
| 294 | return __('This value should be a valid email.', 'mailpoet'); |
| 295 | case 'url': |
| 296 | return __('This value should be a valid url.', 'mailpoet'); |
| 297 | case 'number': |
| 298 | return __('This value should be a valid number.', 'mailpoet'); |
| 299 | case 'integer': |
| 300 | return __('This value should be a valid integer.', 'mailpoet'); |
| 301 | case 'digits': |
| 302 | return __('This value should be digits.', 'mailpoet'); |
| 303 | case 'alphanum': |
| 304 | return __('This value should be alphanumeric.', 'mailpoet'); |
| 305 | default: |
| 306 | return __('This value seems to be invalid.', 'mailpoet'); |
| 307 | } |
| 308 | } |
| 309 | } |
| 310 |