| 1 |
<?php |
| 2 |
/** |
| 3 |
* Form Field Base Class. |
| 4 |
* |
| 5 |
* This file defines the base class for form field markup in the SureDonation package. |
| 6 |
* |
| 7 |
* @package SureDonation |
| 8 |
* @since 0.0.1 |
| 9 |
*/ |
| 10 |
|
| 11 |
namespace SureDonation\Inc\Fields; |
| 12 |
|
| 13 |
use SureDonation\Inc\Helper; |
| 14 |
|
| 15 |
if ( ! defined( 'ABSPATH' ) ) { |
| 16 |
exit; // Exit if accessed directly. |
| 17 |
} |
| 18 |
|
| 19 |
/** |
| 20 |
* Field Base Class |
| 21 |
* |
| 22 |
* Defines the base class for form field markup generation. |
| 23 |
* |
| 24 |
* @since 0.0.1 |
| 25 |
*/ |
| 26 |
class Base { |
| 27 |
/** |
| 28 |
* Stores the attributes of the block. |
| 29 |
* |
| 30 |
* @var array<string, mixed> $attributes Block attributes. |
| 31 |
* @since 0.0.1 |
| 32 |
*/ |
| 33 |
protected $attributes = []; |
| 34 |
|
| 35 |
/** |
| 36 |
* Flag indicating if the field is required. |
| 37 |
* |
| 38 |
* @var bool |
| 39 |
* @since 0.0.1 |
| 40 |
*/ |
| 41 |
protected $required = false; |
| 42 |
|
| 43 |
/** |
| 44 |
* Width of the field (percentage). |
| 45 |
* |
| 46 |
* @var string |
| 47 |
* @since 0.0.1 |
| 48 |
*/ |
| 49 |
protected $field_width = ''; |
| 50 |
|
| 51 |
/** |
| 52 |
* Stores the label for the field. |
| 53 |
* |
| 54 |
* @var string $label Label used for the input field. |
| 55 |
* @since 0.0.1 |
| 56 |
*/ |
| 57 |
protected $label = ''; |
| 58 |
|
| 59 |
/** |
| 60 |
* Stores the help text. |
| 61 |
* |
| 62 |
* @var string $help |
| 63 |
* @since 0.0.1 |
| 64 |
*/ |
| 65 |
protected $help = ''; |
| 66 |
|
| 67 |
/** |
| 68 |
* Validation error message for the field. |
| 69 |
* |
| 70 |
* @var string $error_msg Input field validation error message. |
| 71 |
* @since 0.0.1 |
| 72 |
*/ |
| 73 |
protected $error_msg = ''; |
| 74 |
|
| 75 |
/** |
| 76 |
* Unique identifier for the block. |
| 77 |
* |
| 78 |
* @var string $block_id Unique identifier representing the block. |
| 79 |
* @since 0.0.1 |
| 80 |
*/ |
| 81 |
protected $block_id = ''; |
| 82 |
|
| 83 |
/** |
| 84 |
* Stores the ID of the form. |
| 85 |
* |
| 86 |
* @var string $form_id Form ID. |
| 87 |
* @since 0.0.1 |
| 88 |
*/ |
| 89 |
protected $form_id = ''; |
| 90 |
|
| 91 |
/** |
| 92 |
* Stores the block slug. |
| 93 |
* |
| 94 |
* @var string |
| 95 |
* @since 0.0.1 |
| 96 |
*/ |
| 97 |
protected $block_slug = ''; |
| 98 |
|
| 99 |
/** |
| 100 |
* Stores the slug type (e.g., 'input', 'email'). |
| 101 |
* |
| 102 |
* @var string $slug slug value. |
| 103 |
* @since 0.0.1 |
| 104 |
*/ |
| 105 |
protected $slug = ''; |
| 106 |
|
| 107 |
/** |
| 108 |
* Data-required attribute value. |
| 109 |
* |
| 110 |
* @var string $data_require_attr Value of the data-required attribute. |
| 111 |
* @since 0.0.1 |
| 112 |
*/ |
| 113 |
protected $data_require_attr = 'false'; |
| 114 |
|
| 115 |
/** |
| 116 |
* CSS class for block width. |
| 117 |
* |
| 118 |
* @var string $block_width The CSS class for block width. |
| 119 |
* @since 0.0.1 |
| 120 |
*/ |
| 121 |
protected $block_width = ''; |
| 122 |
|
| 123 |
/** |
| 124 |
* Stores custom class names. |
| 125 |
* |
| 126 |
* @var string $class_name The value of the class name attribute. |
| 127 |
* @since 0.0.1 |
| 128 |
*/ |
| 129 |
protected $class_name = ''; |
| 130 |
|
| 131 |
/** |
| 132 |
* Stores the placeholder text. |
| 133 |
* |
| 134 |
* @var string $placeholder HTML field placeholder. |
| 135 |
* @since 0.0.1 |
| 136 |
*/ |
| 137 |
protected $placeholder = ''; |
| 138 |
|
| 139 |
/** |
| 140 |
* Stores the HTML placeholder attribute. |
| 141 |
* |
| 142 |
* @var string $placeholder_attr HTML field placeholder attribute. |
| 143 |
* @since 0.0.1 |
| 144 |
*/ |
| 145 |
protected $placeholder_attr = ''; |
| 146 |
|
| 147 |
/** |
| 148 |
* Default value for the field. |
| 149 |
* |
| 150 |
* @var string |
| 151 |
* @since 0.0.1 |
| 152 |
*/ |
| 153 |
protected $default = ''; |
| 154 |
|
| 155 |
/** |
| 156 |
* HTML attribute string for the default value. |
| 157 |
* |
| 158 |
* @var string |
| 159 |
* @since 0.0.1 |
| 160 |
*/ |
| 161 |
protected $default_value_attr = ''; |
| 162 |
|
| 163 |
/** |
| 164 |
* Unique slug combining slug and block ID. |
| 165 |
* |
| 166 |
* @var string |
| 167 |
* @since 0.0.1 |
| 168 |
*/ |
| 169 |
protected $unique_slug = ''; |
| 170 |
|
| 171 |
/** |
| 172 |
* Stores the field name for form submission. |
| 173 |
* |
| 174 |
* @var string $field_name HTML field name. |
| 175 |
* @since 0.0.1 |
| 176 |
*/ |
| 177 |
protected $field_name = ''; |
| 178 |
|
| 179 |
/** |
| 180 |
* Options for select/checkbox/radio fields. |
| 181 |
* |
| 182 |
* @var array<mixed> |
| 183 |
* @since 0.0.1 |
| 184 |
*/ |
| 185 |
protected $options = []; |
| 186 |
|
| 187 |
/** |
| 188 |
* Checked state for the field. |
| 189 |
* |
| 190 |
* @var string |
| 191 |
* @since 0.0.1 |
| 192 |
*/ |
| 193 |
protected $checked = ''; |
| 194 |
|
| 195 |
/** |
| 196 |
* HTML attribute string for the checked state. |
| 197 |
* |
| 198 |
* @var string |
| 199 |
* @since 0.0.1 |
| 200 |
*/ |
| 201 |
protected $checked_attr = ''; |
| 202 |
|
| 203 |
/** |
| 204 |
* Stores the help text markup. |
| 205 |
* |
| 206 |
* @var string |
| 207 |
* @since 0.0.1 |
| 208 |
*/ |
| 209 |
protected $help_markup = ''; |
| 210 |
|
| 211 |
/** |
| 212 |
* Stores the error message markup. |
| 213 |
* |
| 214 |
* @var string |
| 215 |
* @since 0.0.1 |
| 216 |
*/ |
| 217 |
protected $error_msg_markup = ''; |
| 218 |
|
| 219 |
/** |
| 220 |
* Stores the HTML label markup. |
| 221 |
* |
| 222 |
* @var string |
| 223 |
* @since 0.0.1 |
| 224 |
*/ |
| 225 |
protected $label_markup = ''; |
| 226 |
|
| 227 |
/** |
| 228 |
* Render the field markup. |
| 229 |
* |
| 230 |
* @return string |
| 231 |
* @since 0.0.1 |
| 232 |
*/ |
| 233 |
public function markup() { |
| 234 |
return ''; |
| 235 |
} |
| 236 |
|
| 237 |
/** |
| 238 |
* Get CSS classes for the field wrapper. |
| 239 |
* |
| 240 |
* @param array<string> $extra_classes Extra classes to be added. |
| 241 |
* @since 0.0.1 |
| 242 |
* @return string |
| 243 |
*/ |
| 244 |
public function get_field_classes( $extra_classes = [] ) { |
| 245 |
$common_classes = [ |
| 246 |
'sd-block-single', |
| 247 |
'sd-block', |
| 248 |
"sd-{$this->slug}-block", |
| 249 |
"sd-{$this->slug}-{$this->block_id}-block", |
| 250 |
$this->block_width, |
| 251 |
$this->class_name, |
| 252 |
]; |
| 253 |
|
| 254 |
if ( $this->block_slug ) { |
| 255 |
$common_classes[] = "sd-slug-{$this->block_slug}"; |
| 256 |
} |
| 257 |
|
| 258 |
if ( ! empty( $extra_classes ) && is_array( $extra_classes ) ) { |
| 259 |
$common_classes = array_merge( $common_classes, $extra_classes ); |
| 260 |
} |
| 261 |
|
| 262 |
return Helper::join_strings( $common_classes ); |
| 263 |
} |
| 264 |
|
| 265 |
/** |
| 266 |
* Setter for the properties of class based on block attributes. |
| 267 |
* |
| 268 |
* @param array<string, mixed> $attributes Block attributes. |
| 269 |
* @since 0.0.1 |
| 270 |
* @return void |
| 271 |
*/ |
| 272 |
protected function set_properties( $attributes ) { |
| 273 |
$this->attributes = $attributes; |
| 274 |
$this->required = ! empty( $attributes['required'] ); |
| 275 |
$this->field_width = isset( $attributes['fieldWidth'] ) ? Helper::get_string_value( $attributes['fieldWidth'] ) : ''; |
| 276 |
$this->label = isset( $attributes['label'] ) ? Helper::get_string_value( $attributes['label'] ) : ''; |
| 277 |
$this->help = isset( $attributes['help'] ) ? Helper::get_string_value( $attributes['help'] ) : ''; |
| 278 |
$this->error_msg = isset( $attributes['errorMsg'] ) ? Helper::get_string_value( $attributes['errorMsg'] ) : ''; |
| 279 |
$this->block_id = isset( $attributes['block_id'] ) ? Helper::get_string_value( $attributes['block_id'] ) : ''; |
| 280 |
$this->form_id = isset( $attributes['formId'] ) ? Helper::get_string_value( $attributes['formId'] ) : ''; |
| 281 |
$this->block_slug = isset( $attributes['slug'] ) ? Helper::get_string_value( $attributes['slug'] ) : ''; |
| 282 |
$this->placeholder = isset( $attributes['placeholder'] ) ? Helper::get_string_value( $attributes['placeholder'] ) : ''; |
| 283 |
$this->default = isset( $attributes['defaultValue'] ) ? Helper::get_string_value( $attributes['defaultValue'] ) : ''; |
| 284 |
$this->checked = isset( $attributes['checked'] ) ? Helper::get_string_value( $attributes['checked'] ) : ''; |
| 285 |
$this->options = isset( $attributes['options'] ) && is_array( $attributes['options'] ) ? $attributes['options'] : []; |
| 286 |
$this->class_name = isset( $attributes['className'] ) && is_string( $attributes['className'] ) ? ' ' . $attributes['className'] : ''; |
| 287 |
$this->data_require_attr = $this->required ? 'true' : 'false'; |
| 288 |
$this->block_width = $this->field_width ? ' sd-block-width-' . str_replace( '.', '-', $this->field_width ) : ''; |
| 289 |
$this->placeholder_attr = '' !== $this->placeholder ? ' placeholder="' . esc_attr( $this->placeholder ) . '" ' : ''; |
| 290 |
$this->default_value_attr = '' !== $this->default ? ' value="' . esc_attr( $this->default ) . '" ' : ''; |
| 291 |
$this->checked_attr = $this->checked ? 'checked' : ''; |
| 292 |
} |
| 293 |
|
| 294 |
/** |
| 295 |
* Set the unique slug for the field. |
| 296 |
* |
| 297 |
* @since 0.0.1 |
| 298 |
* @return void |
| 299 |
*/ |
| 300 |
protected function set_unique_slug() { |
| 301 |
$this->unique_slug = 'sd-' . $this->slug . '-' . $this->block_id; |
| 302 |
$this->field_name = $this->unique_slug; |
| 303 |
} |
| 304 |
|
| 305 |
/** |
| 306 |
* Set markup properties (label, help, error). |
| 307 |
* |
| 308 |
* @since 0.0.1 |
| 309 |
* @return void |
| 310 |
*/ |
| 311 |
protected function set_markup_properties() { |
| 312 |
$this->label_markup = $this->generate_label_markup(); |
| 313 |
$this->help_markup = $this->generate_help_markup(); |
| 314 |
$this->error_msg_markup = $this->generate_error_markup(); |
| 315 |
} |
| 316 |
|
| 317 |
/** |
| 318 |
* Generate label markup. |
| 319 |
* |
| 320 |
* @since 0.0.1 |
| 321 |
* @return string |
| 322 |
*/ |
| 323 |
protected function generate_label_markup() { |
| 324 |
if ( empty( $this->label ) ) { |
| 325 |
return ''; |
| 326 |
} |
| 327 |
|
| 328 |
$required_mark = $this->required ? '<span class="sd-required" aria-hidden="true">*</span>' : ''; |
| 329 |
|
| 330 |
return sprintf( |
| 331 |
'<label for="%s" class="sd-label">%s%s</label>', |
| 332 |
esc_attr( $this->unique_slug ), |
| 333 |
esc_html( $this->label ), |
| 334 |
$required_mark |
| 335 |
); |
| 336 |
} |
| 337 |
|
| 338 |
/** |
| 339 |
* Generate help text markup. |
| 340 |
* |
| 341 |
* @since 0.0.1 |
| 342 |
* @return string |
| 343 |
*/ |
| 344 |
protected function generate_help_markup() { |
| 345 |
if ( empty( $this->help ) ) { |
| 346 |
return ''; |
| 347 |
} |
| 348 |
|
| 349 |
return sprintf( |
| 350 |
'<p id="sd-help-%s" class="sd-help">%s</p>', |
| 351 |
esc_attr( $this->block_id ), |
| 352 |
esc_html( $this->help ) |
| 353 |
); |
| 354 |
} |
| 355 |
|
| 356 |
/** |
| 357 |
* Generate error message markup. |
| 358 |
* |
| 359 |
* @since 0.0.1 |
| 360 |
* @return string |
| 361 |
*/ |
| 362 |
protected function generate_error_markup() { |
| 363 |
$error_text = $this->error_msg ? $this->error_msg : $this->default_required_message(); |
| 364 |
|
| 365 |
return sprintf( |
| 366 |
'<p id="sd-error-%s" class="sd-error" role="alert" style="display: none;">%s</p>', |
| 367 |
esc_attr( $this->block_id ), |
| 368 |
esc_html( $error_text ) |
| 369 |
); |
| 370 |
} |
| 371 |
|
| 372 |
/** |
| 373 |
* Resolve the default required-field message for this field type. |
| 374 |
* |
| 375 |
* Mirrors the server-side resolution so the pre-rendered message matches |
| 376 |
* what the server would return: the admin-configured global default (Global |
| 377 |
* Settings → Form Validation) for the field type, falling back to a generic |
| 378 |
* message for field types without a configurable default. |
| 379 |
* |
| 380 |
* @since 1.1.0 |
| 381 |
* @return string |
| 382 |
*/ |
| 383 |
protected function default_required_message() { |
| 384 |
// Derive the key via the same helper the server uses, so the key is |
| 385 |
// normalized identically (e.g. hyphenated field types) and the editor / |
| 386 |
// markup / server never resolve different keys for the same field. |
| 387 |
$message = \SureDonation\Inc\Field_Validation::get_validation_message( |
| 388 |
\SureDonation\Inc\Field_Validation::required_message_key( 'suredonation/' . $this->slug ) |
| 389 |
); |
| 390 |
|
| 391 |
return '' !== $message ? $message : __( 'This field is required.', 'suredonation' ); |
| 392 |
} |
| 393 |
|
| 394 |
/** |
| 395 |
* Get aria-describedby attribute value. |
| 396 |
* |
| 397 |
* @since 0.0.1 |
| 398 |
* @return string |
| 399 |
*/ |
| 400 |
protected function get_aria_describedby() { |
| 401 |
$describedby = []; |
| 402 |
|
| 403 |
if ( ! empty( $this->help ) ) { |
| 404 |
$describedby[] = 'sd-help-' . $this->block_id; |
| 405 |
} |
| 406 |
|
| 407 |
$describedby[] = 'sd-error-' . $this->block_id; |
| 408 |
|
| 409 |
return implode( ' ', $describedby ); |
| 410 |
} |
| 411 |
|
| 412 |
/** |
| 413 |
* Darken a hex color by a percentage. |
| 414 |
* |
| 415 |
* @param string $hex Hex color code. |
| 416 |
* @param int $percent Percentage to darken (0-100). |
| 417 |
* @return string Darkened hex color. |
| 418 |
* @since 0.0.1 |
| 419 |
*/ |
| 420 |
protected function darken_color( $hex, $percent = 15 ) { |
| 421 |
$hex = ltrim( $hex, '#' ); |
| 422 |
|
| 423 |
if ( strlen( $hex ) === 3 ) { |
| 424 |
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; |
| 425 |
} |
| 426 |
|
| 427 |
$r = hexdec( substr( $hex, 0, 2 ) ); |
| 428 |
$g = hexdec( substr( $hex, 2, 2 ) ); |
| 429 |
$b = hexdec( substr( $hex, 4, 2 ) ); |
| 430 |
|
| 431 |
$r = max( 0, $r - ( $r * $percent / 100 ) ); |
| 432 |
$g = max( 0, $g - ( $g * $percent / 100 ) ); |
| 433 |
$b = max( 0, $b - ( $b * $percent / 100 ) ); |
| 434 |
|
| 435 |
return sprintf( '#%02x%02x%02x', $r, $g, $b ); |
| 436 |
} |
| 437 |
|
| 438 |
/** |
| 439 |
* Lighten a hex color by a percentage. |
| 440 |
* |
| 441 |
* @param string $hex Hex color code. |
| 442 |
* @param int $percent Percentage to lighten (0-100). |
| 443 |
* @return string Lightened hex color. |
| 444 |
* @since 0.0.1 |
| 445 |
*/ |
| 446 |
protected function lighten_color( $hex, $percent = 90 ) { |
| 447 |
$hex = ltrim( $hex, '#' ); |
| 448 |
|
| 449 |
if ( strlen( $hex ) === 3 ) { |
| 450 |
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; |
| 451 |
} |
| 452 |
|
| 453 |
$r = hexdec( substr( $hex, 0, 2 ) ); |
| 454 |
$g = hexdec( substr( $hex, 2, 2 ) ); |
| 455 |
$b = hexdec( substr( $hex, 4, 2 ) ); |
| 456 |
|
| 457 |
$r = min( 255, $r + ( ( 255 - $r ) * $percent / 100 ) ); |
| 458 |
$g = min( 255, $g + ( ( 255 - $g ) * $percent / 100 ) ); |
| 459 |
$b = min( 255, $b + ( ( 255 - $b ) * $percent / 100 ) ); |
| 460 |
|
| 461 |
return sprintf( '#%02x%02x%02x', $r, $g, $b ); |
| 462 |
} |
| 463 |
|
| 464 |
/** |
| 465 |
* Get the checked/unchecked SVG icon for a radio or checkbox option. |
| 466 |
* |
| 467 |
* Shared by every field that renders the option-pill markup (the donation |
| 468 |
* amount choices and the payment-type chooser), so the two cannot drift apart. |
| 469 |
* |
| 470 |
* @param string $type Icon type (circle-checked, circle-unchecked, square-checked, square-unchecked). |
| 471 |
* @param string $classes CSS class. |
| 472 |
* @return string SVG markup. |
| 473 |
* @since 1.5.1 |
| 474 |
*/ |
| 475 |
protected function get_svg_icon( $type, $classes = '' ) { |
| 476 |
$class_attr = $classes ? ' class="' . esc_attr( $classes ) . '"' : ''; |
| 477 |
|
| 478 |
switch ( $type ) { |
| 479 |
case 'circle-checked': |
| 480 |
return '<svg' . $class_attr . ' width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> |
| 481 |
<path d="M15.1663 7.38674V8.00007C15.1655 9.43769 14.7 10.8365 13.8392 11.988C12.9785 13.1394 11.7685 13.9817 10.3899 14.3893C9.0113 14.797 7.53785 14.748 6.18932 14.2498C4.8408 13.7516 3.68944 12.8308 2.90698 11.6248C2.12452 10.4188 1.75287 8.99211 1.84746 7.55761C1.94205 6.12312 2.49781 4.75762 3.43186 3.66479C4.36591 2.57195 5.6282 1.81033 7.03047 1.4935C8.43274 1.17668 9.89985 1.32163 11.213 1.90674" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/> |
| 482 |
<path d="M15.1667 2.6665L8.5 9.33984L6.5 7.33984" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/> |
| 483 |
</svg>'; |
| 484 |
case 'circle-unchecked': |
| 485 |
return '<svg' . $class_attr . ' width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> |
| 486 |
<path d="M7.99967 14.6668C11.6816 14.6668 14.6663 11.6821 14.6663 8.00016C14.6663 4.31826 11.6816 1.3335 7.99967 1.3335C4.31778 1.3335 1.33301 4.31826 1.33301 8.00016C1.33301 11.6821 4.31778 14.6668 7.99967 14.6668Z" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/> |
| 487 |
</svg>'; |
| 488 |
case 'square-checked': |
| 489 |
return '<svg' . $class_attr . ' width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> |
| 490 |
<path d="M6.5 7.33366L8.5 9.33366L15.1667 2.66699" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/> |
| 491 |
<path d="M14.5 8V12.6667C14.5 13.0203 14.3595 13.3594 14.1095 13.6095C13.8594 13.8595 13.5203 14 13.1667 14H3.83333C3.47971 14 3.14057 13.8595 2.89052 13.6095C2.64048 13.3594 2.5 13.0203 2.5 12.6667V3.33333C2.5 2.97971 2.64048 2.64057 2.89052 2.39052C3.14057 2.14048 3.47971 2 3.83333 2H11.1667" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/> |
| 492 |
</svg>'; |
| 493 |
case 'square-unchecked': |
| 494 |
return '<svg' . $class_attr . ' width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> |
| 495 |
<path d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/> |
| 496 |
</svg>'; |
| 497 |
default: |
| 498 |
return ''; |
| 499 |
} |
| 500 |
} |
| 501 |
} |
| 502 |
|