Constants
1 day ago
Accordion.php
1 day ago
Alert.php
1 day ago
AttachmentCard.php
1 day ago
Avatar.php
1 day ago
Badge.php
1 day ago
BaseComponent.php
1 day ago
Button.php
1 day ago
ConfirmationModal.php
1 day ago
CourseFilter.php
1 day ago
DateFilter.php
1 day ago
DropdownFilter.php
1 day ago
EmptyState.php
1 day ago
FileUploader.php
1 day ago
InputField.php
1 day ago
Modal.php
1 day ago
Nav.php
1 day ago
Pagination.php
1 day ago
Popover.php
1 day ago
PreviewTrigger.php
1 day ago
Progress.php
1 day ago
SearchFilter.php
1 day ago
Sorting.php
1 day ago
StarRating.php
1 day ago
StarRatingInput.php
1 day ago
StatusSelect.php
1 day ago
SvgIcon.php
1 day ago
Table.php
1 day ago
Tabs.php
1 day ago
Tooltip.php
1 day ago
WPEditor.php
1 day ago
WPEditor.php
443 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Tutor Component: WPEditor |
| 4 | * |
| 5 | * Provides a fluent builder for rendering WordPress editor (TinyMCE/QuickTags) |
| 6 | * with tutorForm integration for validation and state management. |
| 7 | * |
| 8 | * @package Tutor\Components |
| 9 | * @author Themeum |
| 10 | * @link https://themeum.com |
| 11 | * @since 4.0.0 |
| 12 | */ |
| 13 | |
| 14 | namespace Tutor\Components; |
| 15 | |
| 16 | use Tutor\Helpers\UrlHelper; |
| 17 | |
| 18 | defined( 'ABSPATH' ) || exit; |
| 19 | |
| 20 | /** |
| 21 | * WPEditor Component Class. |
| 22 | * |
| 23 | * Example usage: |
| 24 | * ``` |
| 25 | * // Basic editor |
| 26 | * WPEditor::make() |
| 27 | * ->name( 'description' ) |
| 28 | * ->label( 'Description' ) |
| 29 | * ->content( 'Default content' ) |
| 30 | * ->render(); |
| 31 | * |
| 32 | * // Editor with validation |
| 33 | * WPEditor::make() |
| 34 | * ->name( 'answer' ) |
| 35 | * ->label( 'Your Answer' ) |
| 36 | * ->placeholder( 'Write your answer here' ) |
| 37 | * ->attr( 'x-bind', "register('answer', { required: 'Answer is required' })" ) |
| 38 | * ->render(); |
| 39 | * |
| 40 | * // Editor with custom configuration |
| 41 | * WPEditor::make() |
| 42 | * ->name( 'bio' ) |
| 43 | * ->content( $user_bio ) |
| 44 | * ->editor_config( |
| 45 | * array( |
| 46 | * 'teeny' => false, |
| 47 | * 'media_buttons' => false, |
| 48 | * 'quicktags' => false, |
| 49 | * 'editor_height' => 150, |
| 50 | * 'tinymce' => array( |
| 51 | * 'toolbar1' => 'bold,italic,underline,link,unlink,removeformat,image,bullist,codesample', |
| 52 | * 'toolbar2' => '', |
| 53 | * 'toolbar3' => '', |
| 54 | * 'plugins' => 'link,image,lists,codesample', |
| 55 | * ), |
| 56 | * ) |
| 57 | * ); |
| 58 | * ->render(); |
| 59 | * ``` |
| 60 | * |
| 61 | * @since 4.0.0 |
| 62 | */ |
| 63 | class WPEditor extends BaseComponent { |
| 64 | |
| 65 | /** |
| 66 | * Editor name attribute (also used as editor ID if ID is not set). |
| 67 | * |
| 68 | * @since 4.0.0 |
| 69 | * |
| 70 | * @var string |
| 71 | */ |
| 72 | protected $name = ''; |
| 73 | |
| 74 | /** |
| 75 | * Editor ID attribute. |
| 76 | * |
| 77 | * @since 4.0.0 |
| 78 | * |
| 79 | * @var string |
| 80 | */ |
| 81 | protected $id = ''; |
| 82 | |
| 83 | /** |
| 84 | * Editor content. |
| 85 | * |
| 86 | * @since 4.0.0 |
| 87 | * |
| 88 | * @var string |
| 89 | */ |
| 90 | protected $content = ''; |
| 91 | |
| 92 | /** |
| 93 | * Editor label text. |
| 94 | * |
| 95 | * @since 4.0.0 |
| 96 | * |
| 97 | * @var string |
| 98 | */ |
| 99 | protected $label = ''; |
| 100 | |
| 101 | /** |
| 102 | * Editor placeholder text. |
| 103 | * |
| 104 | * @since 4.0.0 |
| 105 | * |
| 106 | * @var string |
| 107 | */ |
| 108 | protected $placeholder = ''; |
| 109 | |
| 110 | /** |
| 111 | * Help text below editor. |
| 112 | * |
| 113 | * @since 4.0.0 |
| 114 | * |
| 115 | * @var string |
| 116 | */ |
| 117 | protected $help_text = ''; |
| 118 | |
| 119 | /** |
| 120 | * Error message text. |
| 121 | * |
| 122 | * @since 4.0.0 |
| 123 | * |
| 124 | * @var string |
| 125 | */ |
| 126 | protected $error = ''; |
| 127 | |
| 128 | /** |
| 129 | * Whether editor is required. |
| 130 | * |
| 131 | * @since 4.0.0 |
| 132 | * |
| 133 | * @var bool |
| 134 | */ |
| 135 | protected $required = false; |
| 136 | |
| 137 | /** |
| 138 | * WordPress editor configuration. |
| 139 | * |
| 140 | * @since 4.0.0 |
| 141 | * |
| 142 | * @var array |
| 143 | */ |
| 144 | protected $editor_config = array(); |
| 145 | |
| 146 | /** |
| 147 | * Set editor name. |
| 148 | * |
| 149 | * @since 4.0.0 |
| 150 | * |
| 151 | * @param string $name Editor name. |
| 152 | * |
| 153 | * @return $this |
| 154 | */ |
| 155 | public function name( $name ) { |
| 156 | $this->name = sanitize_key( $name ); |
| 157 | return $this; |
| 158 | } |
| 159 | |
| 160 | /** |
| 161 | * Set editor ID. |
| 162 | * |
| 163 | * @since 4.0.0 |
| 164 | * |
| 165 | * @param string $id Editor ID. |
| 166 | * |
| 167 | * @return $this |
| 168 | */ |
| 169 | public function id( $id ) { |
| 170 | $this->id = sanitize_key( $id ); |
| 171 | return $this; |
| 172 | } |
| 173 | |
| 174 | /** |
| 175 | * Set editor content. |
| 176 | * |
| 177 | * @since 4.0.0 |
| 178 | * |
| 179 | * @param string $content Editor content. |
| 180 | * |
| 181 | * @return $this |
| 182 | */ |
| 183 | public function content( $content ) { |
| 184 | $this->content = $content; |
| 185 | return $this; |
| 186 | } |
| 187 | |
| 188 | /** |
| 189 | * Set editor label. |
| 190 | * |
| 191 | * @since 4.0.0 |
| 192 | * |
| 193 | * @param string $label Label text. |
| 194 | * |
| 195 | * @return $this |
| 196 | */ |
| 197 | public function label( $label ) { |
| 198 | $this->label = $label; |
| 199 | return $this; |
| 200 | } |
| 201 | |
| 202 | /** |
| 203 | * Set editor placeholder. |
| 204 | * |
| 205 | * @since 4.0.0 |
| 206 | * |
| 207 | * @param string $placeholder Placeholder text. |
| 208 | * |
| 209 | * @return $this |
| 210 | */ |
| 211 | public function placeholder( $placeholder ) { |
| 212 | $this->placeholder = $placeholder; |
| 213 | return $this; |
| 214 | } |
| 215 | |
| 216 | /** |
| 217 | * Set help text. |
| 218 | * |
| 219 | * @since 4.0.0 |
| 220 | * |
| 221 | * @param string $text Help text. |
| 222 | * |
| 223 | * @return $this |
| 224 | */ |
| 225 | public function help_text( $text ) { |
| 226 | $this->help_text = $text; |
| 227 | return $this; |
| 228 | } |
| 229 | |
| 230 | /** |
| 231 | * Set error message. |
| 232 | * |
| 233 | * @since 4.0.0 |
| 234 | * |
| 235 | * @param string $error Error message. |
| 236 | * |
| 237 | * @return $this |
| 238 | */ |
| 239 | public function error( $error ) { |
| 240 | $this->error = $error; |
| 241 | return $this; |
| 242 | } |
| 243 | |
| 244 | /** |
| 245 | * Set required state. |
| 246 | * |
| 247 | * @since 4.0.0 |
| 248 | * |
| 249 | * @param bool $required Whether editor is required. |
| 250 | * |
| 251 | * @return $this |
| 252 | */ |
| 253 | public function required( $required = true ) { |
| 254 | $this->required = $required; |
| 255 | return $this; |
| 256 | } |
| 257 | |
| 258 | /** |
| 259 | * Set WordPress editor configuration. |
| 260 | * |
| 261 | * @since 4.0.0 |
| 262 | * |
| 263 | * @param array $config Editor configuration. |
| 264 | * |
| 265 | * @return $this |
| 266 | */ |
| 267 | public function editor_config( $config ) { |
| 268 | $this->editor_config = $config; |
| 269 | return $this; |
| 270 | } |
| 271 | |
| 272 | /** |
| 273 | * Get default editor configuration. |
| 274 | * |
| 275 | * @since 4.0.0 |
| 276 | * |
| 277 | * @return array |
| 278 | */ |
| 279 | protected function get_default_config() { |
| 280 | return array( |
| 281 | 'media_buttons' => false, |
| 282 | 'teeny' => true, |
| 283 | 'quicktags' => true, |
| 284 | 'editor_height' => 150, |
| 285 | 'textarea_name' => $this->name, |
| 286 | 'tinymce' => array( |
| 287 | 'skin' => 'light', |
| 288 | 'skin_url' => UrlHelper::asset( 'lib/tinymce/light' ), |
| 289 | 'content_css' => $this->get_content_css(), |
| 290 | ), |
| 291 | ); |
| 292 | } |
| 293 | |
| 294 | /** |
| 295 | * Get TinyMCE content styles used inside the editor iframe. |
| 296 | * |
| 297 | * Mirrors the stylesheet list used by the React WPEditor component so both |
| 298 | * entry points render editor content consistently. |
| 299 | * |
| 300 | * @since 4.0.0 |
| 301 | * |
| 302 | * @return string |
| 303 | */ |
| 304 | protected function get_content_css() { |
| 305 | return implode( |
| 306 | ',', |
| 307 | array( |
| 308 | includes_url( 'css/dashicons.min.css' ), |
| 309 | includes_url( 'js/tinymce/skins/wordpress/wp-content.css' ), |
| 310 | UrlHelper::asset( 'lib/tinymce/light/content.min.css' ), |
| 311 | ) |
| 312 | ); |
| 313 | } |
| 314 | |
| 315 | /** |
| 316 | * Attributes for the wrapper element. |
| 317 | * |
| 318 | * @since 4.0.0 |
| 319 | * |
| 320 | * @var array |
| 321 | */ |
| 322 | protected $wrapper_attr = array(); |
| 323 | |
| 324 | /** |
| 325 | * Set wrapper attribute. |
| 326 | * |
| 327 | * @since 4.0.0 |
| 328 | * |
| 329 | * @param string $key Attribute key. |
| 330 | * @param string $value Attribute value. |
| 331 | * |
| 332 | * @return $this |
| 333 | */ |
| 334 | public function wrapper_attr( $key, $value ) { |
| 335 | $this->wrapper_attr[ $key ] = $value; |
| 336 | return $this; |
| 337 | } |
| 338 | |
| 339 | /** |
| 340 | * Get the editor component as HTML string. |
| 341 | * |
| 342 | * @since 4.0.0 |
| 343 | * |
| 344 | * @return string Component HTML. |
| 345 | */ |
| 346 | public function get(): string { |
| 347 | if ( empty( $this->name ) ) { |
| 348 | return ''; |
| 349 | } |
| 350 | |
| 351 | ob_start(); |
| 352 | |
| 353 | $editor_id = ! empty( $this->id ) ? $this->id : $this->name; |
| 354 | $default_config = $this->get_default_config(); |
| 355 | $config = array_merge( $default_config, $this->editor_config ); |
| 356 | |
| 357 | if ( isset( $default_config['tinymce'] ) || isset( $this->editor_config['tinymce'] ) ) { |
| 358 | $config['tinymce'] = array_merge( |
| 359 | $default_config['tinymce'] ?? array(), |
| 360 | $this->editor_config['tinymce'] ?? array() |
| 361 | ); |
| 362 | } |
| 363 | $wrapper_class = 'tutor-wp-editor-wrapper tutor-input-field'; |
| 364 | |
| 365 | if ( isset( $this->attributes['class'] ) ) { |
| 366 | $wrapper_class .= ' ' . $this->attributes['class']; |
| 367 | } |
| 368 | if ( isset( $this->wrapper_attr['class'] ) ) { |
| 369 | $wrapper_class .= ' ' . $this->wrapper_attr['class']; |
| 370 | } |
| 371 | |
| 372 | if ( ! empty( $this->error ) ) { |
| 373 | $wrapper_class .= ' tutor-wp-editor-error tutor-input-field-error'; |
| 374 | } |
| 375 | |
| 376 | $wrapper_attrs = $this->wrapper_attr; |
| 377 | |
| 378 | $wrapper_attrs['class'] = $wrapper_class; |
| 379 | |
| 380 | ?> |
| 381 | <div |
| 382 | <?php |
| 383 | foreach ( $wrapper_attrs as $key => $value ) { |
| 384 | printf( '%s="%s"', esc_attr( $key ), esc_attr( $value ) ); |
| 385 | } |
| 386 | ?> |
| 387 | x-data="tutorWPEditor({ |
| 388 | name: '<?php echo esc_js( $this->name ); ?>', |
| 389 | editorId: '<?php echo esc_js( $editor_id ); ?>', |
| 390 | placeholder: '<?php echo esc_js( $this->placeholder ); ?>' |
| 391 | })" |
| 392 | > |
| 393 | <?php if ( ! empty( $this->label ) ) : ?> |
| 394 | <label |
| 395 | for="<?php echo esc_attr( $editor_id ); ?>" |
| 396 | class="tutor-label <?php echo $this->required ? 'tutor-label-required' : ''; ?>" |
| 397 | > |
| 398 | <?php echo esc_html( $this->label ); ?> |
| 399 | </label> |
| 400 | <?php endif; ?> |
| 401 | |
| 402 | <div class="tutor-wp-editor-container"> |
| 403 | <!-- Hidden input for form value binding --> |
| 404 | <input |
| 405 | type="hidden" |
| 406 | name="<?php echo esc_attr( $this->name ); ?>" |
| 407 | <?php $this->render_attributes(); ?> |
| 408 | /> |
| 409 | |
| 410 | <?php |
| 411 | // Render WordPress editor. |
| 412 | wp_editor( $this->content, $editor_id, $config ); |
| 413 | ?> |
| 414 | </div> |
| 415 | |
| 416 | <!-- Error Text (Alpine + PHP) --> |
| 417 | <div |
| 418 | class="tutor-error-text" |
| 419 | x-cloak |
| 420 | x-show="errors?.['<?php echo esc_js( $this->name ); ?>']" |
| 421 | x-text="errors?.['<?php echo esc_js( $this->name ); ?>']?.message" |
| 422 | role="alert" |
| 423 | aria-live="polite" |
| 424 | > |
| 425 | <?php echo esc_html( $this->error ); ?> |
| 426 | </div> |
| 427 | |
| 428 | <!-- Help Text --> |
| 429 | <?php if ( ! empty( $this->help_text ) ) : ?> |
| 430 | <div |
| 431 | class="tutor-help-text" |
| 432 | x-show="!errors?.['<?php echo esc_js( $this->name ); ?>']?.message" |
| 433 | > |
| 434 | <?php echo esc_html( $this->help_text ); ?> |
| 435 | </div> |
| 436 | <?php endif; ?> |
| 437 | </div> |
| 438 | <?php |
| 439 | |
| 440 | return ob_get_clean(); |
| 441 | } |
| 442 | } |
| 443 |