PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.5.0
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.5.0
5.5.0 5.4.0 5.3.2 5.3.1 5.1.6 5.1.5 trunk 2.1.5 2.11 2.12 2.13 2.15 3.0.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.51 3.0.60 3.0.61 3.0.62 3.0.70 3.0.71 3.0.72 3.1.0 All 34 releases
double-opt-in / src / EmailTemplates / EmailTemplateRestController.php

EmailTemplateRestController.php in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 5.5.0, at src/EmailTemplates/EmailTemplateRestController.php

890 lines 22.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Email Template REST Controller
4 *
5 * @package Forge12\DoubleOptIn\EmailTemplates
6 * @since 4.0.0
7 */
8
9 namespace Forge12\DoubleOptIn\EmailTemplates;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 /**
16 * Class EmailTemplateRestController
17 *
18 * REST API endpoints for email templates.
19 */
20 class EmailTemplateRestController {
21
22 /**
23 * REST API namespace.
24 */
25 const NAMESPACE = 'f12-doi/v1';
26
27 /**
28 * REST API base.
29 */
30 const BASE = 'email-templates';
31
32 /**
33 * Repository instance.
34 *
35 * @var EmailTemplateRepository
36 */
37 private EmailTemplateRepository $repository;
38
39 /**
40 * HTML Generator instance.
41 *
42 * @var EmailHtmlGenerator
43 */
44 private EmailHtmlGenerator $htmlGenerator;
45
46 /**
47 * Block Registry instance.
48 *
49 * @var BlockRegistry
50 */
51 private BlockRegistry $blockRegistry;
52
53 /**
54 * Constructor.
55 *
56 * @param EmailTemplateRepository $repository Repository instance.
57 * @param EmailHtmlGenerator $htmlGenerator HTML Generator instance.
58 */
59 public function __construct( EmailTemplateRepository $repository, EmailHtmlGenerator $htmlGenerator ) {
60 $this->repository = $repository;
61 $this->htmlGenerator = $htmlGenerator;
62 $this->blockRegistry = new BlockRegistry();
63 }
64
65 /**
66 * Initialize REST routes.
67 *
68 * @return void
69 */
70 public function init(): void {
71 add_action( 'rest_api_init', array( $this, 'registerRoutes' ) );
72 }
73
74 /**
75 * Register REST API routes.
76 *
77 * @return void
78 */
79 public function registerRoutes(): void {
80 // GET /email-templates - List all templates
81 register_rest_route(
82 self::NAMESPACE,
83 '/' . self::BASE,
84 array(
85 'methods' => \WP_REST_Server::READABLE,
86 'callback' => array( $this, 'getItems' ),
87 'permission_callback' => array( $this, 'checkPermission' ),
88 )
89 );
90
91 // GET /email-templates/{id} - Get single template
92 register_rest_route(
93 self::NAMESPACE,
94 '/' . self::BASE . '/(?P<id>[\d]+)',
95 array(
96 'methods' => \WP_REST_Server::READABLE,
97 'callback' => array( $this, 'getItem' ),
98 'permission_callback' => array( $this, 'checkPermission' ),
99 'args' => array(
100 'id' => array(
101 'validate_callback' => function ( $param ) {
102 return is_numeric( $param );
103 },
104 ),
105 ),
106 )
107 );
108
109 // POST /email-templates - Create template
110 register_rest_route(
111 self::NAMESPACE,
112 '/' . self::BASE,
113 array(
114 'methods' => \WP_REST_Server::CREATABLE,
115 'callback' => array( $this, 'createItem' ),
116 'permission_callback' => array( $this, 'checkPermission' ),
117 )
118 );
119
120 // PUT /email-templates/{id} - Update template
121 register_rest_route(
122 self::NAMESPACE,
123 '/' . self::BASE . '/(?P<id>[\d]+)',
124 array(
125 'methods' => \WP_REST_Server::EDITABLE,
126 'callback' => array( $this, 'updateItem' ),
127 'permission_callback' => array( $this, 'checkPermission' ),
128 'args' => array(
129 'id' => array(
130 'validate_callback' => function ( $param ) {
131 return is_numeric( $param );
132 },
133 ),
134 ),
135 )
136 );
137
138 // DELETE /email-templates/{id} - Delete template
139 register_rest_route(
140 self::NAMESPACE,
141 '/' . self::BASE . '/(?P<id>[\d]+)',
142 array(
143 'methods' => \WP_REST_Server::DELETABLE,
144 'callback' => array( $this, 'deleteItem' ),
145 'permission_callback' => array( $this, 'checkPermission' ),
146 'args' => array(
147 'id' => array(
148 'validate_callback' => function ( $param ) {
149 return is_numeric( $param );
150 },
151 ),
152 ),
153 )
154 );
155
156 // POST /email-templates/{id}/render - Render template HTML
157 register_rest_route(
158 self::NAMESPACE,
159 '/' . self::BASE . '/(?P<id>[\d]+)/render',
160 array(
161 'methods' => \WP_REST_Server::CREATABLE,
162 'callback' => array( $this, 'renderTemplate' ),
163 'permission_callback' => array( $this, 'checkPermission' ),
164 'args' => array(
165 'id' => array(
166 'validate_callback' => function ( $param ) {
167 return is_numeric( $param );
168 },
169 ),
170 ),
171 )
172 );
173
174 // POST /email-templates/preview - Generate live preview
175 register_rest_route(
176 self::NAMESPACE,
177 '/' . self::BASE . '/preview',
178 array(
179 'methods' => \WP_REST_Server::CREATABLE,
180 'callback' => array( $this, 'previewTemplate' ),
181 'permission_callback' => array( $this, 'checkPermission' ),
182 )
183 );
184
185 // POST /email-templates/{id}/duplicate - Duplicate template
186 register_rest_route(
187 self::NAMESPACE,
188 '/' . self::BASE . '/(?P<id>[\d]+)/duplicate',
189 array(
190 'methods' => \WP_REST_Server::CREATABLE,
191 'callback' => array( $this, 'duplicateTemplate' ),
192 'permission_callback' => array( $this, 'checkPermission' ),
193 'args' => array(
194 'id' => array(
195 'validate_callback' => function ( $param ) {
196 return is_numeric( $param );
197 },
198 ),
199 ),
200 )
201 );
202
203 // GET /email-templates/placeholders - Get available placeholders
204 register_rest_route(
205 self::NAMESPACE,
206 '/' . self::BASE . '/placeholders',
207 array(
208 'methods' => \WP_REST_Server::READABLE,
209 'callback' => array( $this, 'getPlaceholders' ),
210 'permission_callback' => array( $this, 'checkPermission' ),
211 )
212 );
213
214 // GET /email-templates/presets - Get available template presets
215 register_rest_route(
216 self::NAMESPACE,
217 '/' . self::BASE . '/presets',
218 array(
219 'methods' => \WP_REST_Server::READABLE,
220 'callback' => array( $this, 'getPresets' ),
221 'permission_callback' => array( $this, 'checkPermission' ),
222 )
223 );
224
225 // GET /email-templates/presets/{id} - Get single preset
226 register_rest_route(
227 self::NAMESPACE,
228 '/' . self::BASE . '/presets/(?P<preset_id>[a-z0-9-]+)',
229 array(
230 'methods' => \WP_REST_Server::READABLE,
231 'callback' => array( $this, 'getPreset' ),
232 'permission_callback' => array( $this, 'checkPermission' ),
233 'args' => array(
234 'preset_id' => array(
235 'validate_callback' => function ( $param ) {
236 return is_string( $param ) && preg_match( '/^[a-z0-9-]+$/', $param );
237 },
238 ),
239 ),
240 )
241 );
242
243 // POST /email-templates/{id}/send-test - Send test email
244 register_rest_route(
245 self::NAMESPACE,
246 '/' . self::BASE . '/(?P<id>[\d]+)/send-test',
247 array(
248 'methods' => \WP_REST_Server::CREATABLE,
249 'callback' => array( $this, 'sendTestEmail' ),
250 'permission_callback' => array( $this, 'checkPermission' ),
251 'args' => array(
252 'id' => array(
253 'validate_callback' => function ( $param ) {
254 return is_numeric( $param );
255 },
256 ),
257 ),
258 )
259 );
260
261 // POST /email-templates/from-preset - Create template from preset
262 register_rest_route(
263 self::NAMESPACE,
264 '/' . self::BASE . '/from-preset',
265 array(
266 'methods' => \WP_REST_Server::CREATABLE,
267 'callback' => array( $this, 'createFromPreset' ),
268 'permission_callback' => array( $this, 'checkPermission' ),
269 )
270 );
271 }
272
273 /**
274 * Check if current user has permission.
275 *
276 * @return bool
277 */
278 public function checkPermission(): bool {
279 return current_user_can( 'manage_options' );
280 }
281
282 /**
283 * Get all templates.
284 *
285 * @param \WP_REST_Request $request Request object.
286 * @return \WP_REST_Response
287 */
288 public function getItems( \WP_REST_Request $request ): \WP_REST_Response {
289 $templates = $this->repository->findAll();
290
291 return new \WP_REST_Response(
292 array(
293 'success' => true,
294 'data' => $templates,
295 ),
296 200
297 );
298 }
299
300 /**
301 * Get single template.
302 *
303 * @param \WP_REST_Request $request Request object.
304 * @return \WP_REST_Response
305 */
306 public function getItem( \WP_REST_Request $request ): \WP_REST_Response {
307 $id = (int) $request->get_param( 'id' );
308 $template = $this->repository->findById( $id );
309
310 if ( ! $template ) {
311 return new \WP_REST_Response(
312 array(
313 'success' => false,
314 'message' => __( 'Template not found.', 'double-opt-in' ),
315 ),
316 404
317 );
318 }
319
320 return new \WP_REST_Response(
321 array(
322 'success' => true,
323 'data' => $template,
324 ),
325 200
326 );
327 }
328
329 /**
330 * Create new template.
331 *
332 * @param \WP_REST_Request $request Request object.
333 * @return \WP_REST_Response
334 */
335 public function createItem( \WP_REST_Request $request ): \WP_REST_Response {
336 $data = $request->get_json_params();
337
338 // Check template limit for published templates
339 $status = sanitize_text_field( $data['status'] ?? 'draft' );
340 if ( $status === 'publish' ) {
341 $published = $this->repository->countPublished();
342 $limit = $this->blockRegistry->getTemplateLimit();
343 if ( $published >= $limit ) {
344 return new \WP_REST_Response(
345 array(
346 'success' => false,
347 'message' => __( 'You have reached the maximum number of published templates. Upgrade to Pro for unlimited templates.', 'double-opt-in' ),
348 ),
349 403
350 );
351 }
352 }
353
354 // Validate blocks for Pro gating
355 $invalidBlocks = $this->validateBlocksFromData( $data );
356 if ( ! empty( $invalidBlocks ) ) {
357 return new \WP_REST_Response(
358 array(
359 'success' => false,
360 'message' => sprintf(
361 __( 'Template contains Pro blocks that require a license: %s', 'double-opt-in' ),
362 implode( ', ', array_unique( $invalidBlocks ) )
363 ),
364 ),
365 403
366 );
367 }
368
369 $id = $this->repository->create( $data );
370
371 if ( ! $id ) {
372 return new \WP_REST_Response(
373 array(
374 'success' => false,
375 'message' => __( 'Failed to create template.', 'double-opt-in' ),
376 ),
377 500
378 );
379 }
380
381 $template = $this->repository->findById( $id );
382
383 return new \WP_REST_Response(
384 array(
385 'success' => true,
386 'data' => $template,
387 ),
388 201
389 );
390 }
391
392 /**
393 * Update template.
394 *
395 * @param \WP_REST_Request $request Request object.
396 * @return \WP_REST_Response
397 */
398 public function updateItem( \WP_REST_Request $request ): \WP_REST_Response {
399 $id = (int) $request->get_param( 'id' );
400 $data = $request->get_json_params();
401
402 // Check template limit when changing status to publish
403 $newStatus = isset( $data['status'] ) ? sanitize_text_field( $data['status'] ) : null;
404 if ( $newStatus === 'publish' ) {
405 $currentTemplate = $this->repository->findById( $id );
406 // Only check limit if the template is not already published
407 if ( $currentTemplate && $currentTemplate['status'] !== 'publish' ) {
408 $published = $this->repository->countPublished();
409 $limit = $this->blockRegistry->getTemplateLimit();
410 if ( $published >= $limit ) {
411 return new \WP_REST_Response(
412 array(
413 'success' => false,
414 'message' => __( 'You have reached the maximum number of published templates. Upgrade to Pro for unlimited templates.', 'double-opt-in' ),
415 ),
416 403
417 );
418 }
419 }
420 }
421
422 // Validate blocks for Pro gating
423 $invalidBlocks = $this->validateBlocksFromData( $data );
424 if ( ! empty( $invalidBlocks ) ) {
425 return new \WP_REST_Response(
426 array(
427 'success' => false,
428 'message' => sprintf(
429 __( 'Template contains Pro blocks that require a license: %s', 'double-opt-in' ),
430 implode( ', ', array_unique( $invalidBlocks ) )
431 ),
432 ),
433 403
434 );
435 }
436
437 $success = $this->repository->update( $id, $data );
438
439 if ( ! $success ) {
440 return new \WP_REST_Response(
441 array(
442 'success' => false,
443 'message' => __( 'Failed to update template.', 'double-opt-in' ),
444 ),
445 500
446 );
447 }
448
449 $template = $this->repository->findById( $id );
450
451 return new \WP_REST_Response(
452 array(
453 'success' => true,
454 'data' => $template,
455 ),
456 200
457 );
458 }
459
460 /**
461 * Delete template.
462 *
463 * @param \WP_REST_Request $request Request object.
464 * @return \WP_REST_Response
465 */
466 public function deleteItem( \WP_REST_Request $request ): \WP_REST_Response {
467 $id = (int) $request->get_param( 'id' );
468 $force = (bool) $request->get_param( 'force' );
469
470 $success = $this->repository->delete( $id, $force );
471
472 if ( ! $success ) {
473 return new \WP_REST_Response(
474 array(
475 'success' => false,
476 'message' => __( 'Failed to delete template.', 'double-opt-in' ),
477 ),
478 500
479 );
480 }
481
482 return new \WP_REST_Response(
483 array(
484 'success' => true,
485 'message' => __( 'Template deleted successfully.', 'double-opt-in' ),
486 ),
487 200
488 );
489 }
490
491 /**
492 * Render template HTML with placeholders.
493 *
494 * @param \WP_REST_Request $request Request object.
495 * @return \WP_REST_Response
496 */
497 public function renderTemplate( \WP_REST_Request $request ): \WP_REST_Response {
498 $id = (int) $request->get_param( 'id' );
499 $template = $this->repository->findById( $id );
500
501 if ( ! $template ) {
502 return new \WP_REST_Response(
503 array(
504 'success' => false,
505 'message' => __( 'Template not found.', 'double-opt-in' ),
506 ),
507 404
508 );
509 }
510
511 $blocks = json_decode( $template['blocks_json'], true ) ?: array();
512 $globalStyles = json_decode( $template['global_styles'], true ) ?: array();
513
514 $html = $this->htmlGenerator->generate( $blocks, $globalStyles );
515
516 return new \WP_REST_Response(
517 array(
518 'success' => true,
519 'data' => array(
520 'html' => $html,
521 ),
522 ),
523 200
524 );
525 }
526
527 /**
528 * Generate live preview.
529 *
530 * @param \WP_REST_Request $request Request object.
531 * @return \WP_REST_Response
532 */
533 public function previewTemplate( \WP_REST_Request $request ): \WP_REST_Response {
534 $data = $request->get_json_params();
535
536 $blocks = $data['blocks'] ?? array();
537 $globalStyles = $data['global_styles'] ?? array();
538
539 $html = $this->htmlGenerator->generate( $blocks, $globalStyles );
540
541 return new \WP_REST_Response(
542 array(
543 'success' => true,
544 'data' => array(
545 'html' => $html,
546 ),
547 ),
548 200
549 );
550 }
551
552 /**
553 * Duplicate template.
554 *
555 * @param \WP_REST_Request $request Request object.
556 * @return \WP_REST_Response
557 */
558 public function duplicateTemplate( \WP_REST_Request $request ): \WP_REST_Response {
559 $id = (int) $request->get_param( 'id' );
560
561 $newId = $this->repository->duplicate( $id );
562
563 if ( ! $newId ) {
564 return new \WP_REST_Response(
565 array(
566 'success' => false,
567 'message' => __( 'Failed to duplicate template.', 'double-opt-in' ),
568 ),
569 500
570 );
571 }
572
573 $template = $this->repository->findById( $newId );
574
575 return new \WP_REST_Response(
576 array(
577 'success' => true,
578 'data' => $template,
579 ),
580 201
581 );
582 }
583
584 /**
585 * Send a test email with the template.
586 *
587 * @param \WP_REST_Request $request Request object.
588 * @return \WP_REST_Response
589 */
590 public function sendTestEmail( \WP_REST_Request $request ): \WP_REST_Response {
591 // Requires Pro
592 if ( ! $this->blockRegistry->isProActive() ) {
593 return new \WP_REST_Response(
594 array(
595 'success' => false,
596 'message' => __( 'Sending test emails requires the Pro version.', 'double-opt-in' ),
597 ),
598 403
599 );
600 }
601
602 $id = (int) $request->get_param( 'id' );
603 $data = $request->get_json_params();
604 $email = sanitize_email( $data['email'] ?? '' );
605
606 if ( ! is_email( $email ) ) {
607 return new \WP_REST_Response(
608 array(
609 'success' => false,
610 'message' => __( 'Please enter a valid email address.', 'double-opt-in' ),
611 ),
612 400
613 );
614 }
615
616 $template = $this->repository->findById( $id );
617
618 if ( ! $template ) {
619 return new \WP_REST_Response(
620 array(
621 'success' => false,
622 'message' => __( 'Template not found.', 'double-opt-in' ),
623 ),
624 404
625 );
626 }
627
628 // Generate HTML from blocks
629 $blocks = json_decode( $template['blocks_json'], true ) ?: array();
630 $globalStyles = json_decode( $template['global_styles'], true ) ?: array();
631 $html = $this->htmlGenerator->generate( $blocks, $globalStyles );
632
633 // Replace placeholder tags with dummy values for test
634 $html = str_replace( '[doubleoptinlink]', '#', $html );
635 $html = str_replace( '[doubleoptoutlink]', '#', $html );
636 $html = str_replace( '[doubleoptin_form_date]', date_i18n( get_option( 'date_format' ) ), $html );
637 $html = str_replace( '[doubleoptin_form_time]', date_i18n( get_option( 'time_format' ) ), $html );
638 $html = str_replace( '[doubleoptin_form_url]', home_url(), $html );
639
640 $subject = sprintf( '[Test] %s', $template['title'] );
641 $headers = array( 'Content-Type: text/html; charset=UTF-8' );
642
643 $sent = wp_mail( $email, $subject, $html, $headers );
644
645 if ( ! $sent ) {
646 return new \WP_REST_Response(
647 array(
648 'success' => false,
649 'message' => __( 'Failed to send test email. Please check your email configuration.', 'double-opt-in' ),
650 ),
651 500
652 );
653 }
654
655 return new \WP_REST_Response(
656 array(
657 'success' => true,
658 'message' => sprintf(
659 __( 'Test email sent to %s.', 'double-opt-in' ),
660 $email
661 ),
662 ),
663 200
664 );
665 }
666
667 /**
668 * Get available placeholders.
669 *
670 * @param \WP_REST_Request $request Request object.
671 * @return \WP_REST_Response
672 */
673 public function getPlaceholders( \WP_REST_Request $request ): \WP_REST_Response {
674 $placeholders = PlaceholderMapper::getAvailablePlaceholdersForEditor();
675
676 return new \WP_REST_Response(
677 array(
678 'success' => true,
679 'data' => $placeholders,
680 ),
681 200
682 );
683 }
684
685 /**
686 * Get all available template presets.
687 *
688 * @param \WP_REST_Request $request Request object.
689 * @return \WP_REST_Response
690 */
691 public function getPresets( \WP_REST_Request $request ): \WP_REST_Response {
692 $presets = EmailTemplatePresets::getAll();
693
694 // Return only metadata, not full blocks (to keep response small)
695 $presetsMetadata = array_map(
696 function ( $preset ) {
697 return array(
698 'id' => $preset['id'],
699 'name' => $preset['name'],
700 'description' => $preset['description'],
701 'thumbnail' => $preset['thumbnail'],
702 'category' => $preset['category'],
703 );
704 },
705 $presets
706 );
707
708 return new \WP_REST_Response(
709 array(
710 'success' => true,
711 'data' => $presetsMetadata,
712 ),
713 200
714 );
715 }
716
717 /**
718 * Get a single preset by ID.
719 *
720 * @param \WP_REST_Request $request Request object.
721 * @return \WP_REST_Response
722 */
723 public function getPreset( \WP_REST_Request $request ): \WP_REST_Response {
724 $presetId = $request->get_param( 'preset_id' );
725 $preset = EmailTemplatePresets::getById( $presetId );
726
727 if ( ! $preset ) {
728 return new \WP_REST_Response(
729 array(
730 'success' => false,
731 'message' => __( 'Preset not found.', 'double-opt-in' ),
732 ),
733 404
734 );
735 }
736
737 return new \WP_REST_Response(
738 array(
739 'success' => true,
740 'data' => $preset,
741 ),
742 200
743 );
744 }
745
746 /**
747 * Create a new template from a preset.
748 *
749 * @param \WP_REST_Request $request Request object.
750 * @return \WP_REST_Response
751 */
752 public function createFromPreset( \WP_REST_Request $request ): \WP_REST_Response {
753 $data = $request->get_json_params();
754 $presetId = $data['preset_id'] ?? '';
755 $title = $data['title'] ?? '';
756
757 // Check template limit (presets are created as drafts, but check limit proactively)
758 $published = $this->repository->countPublished();
759 $limit = $this->blockRegistry->getTemplateLimit();
760 if ( $published >= $limit ) {
761 return new \WP_REST_Response(
762 array(
763 'success' => false,
764 'message' => __( 'You have reached the maximum number of published templates. Upgrade to Pro for unlimited templates.', 'double-opt-in' ),
765 ),
766 403
767 );
768 }
769
770 if ( empty( $presetId ) ) {
771 return new \WP_REST_Response(
772 array(
773 'success' => false,
774 'message' => __( 'Preset ID is required.', 'double-opt-in' ),
775 ),
776 400
777 );
778 }
779
780 $preset = EmailTemplatePresets::getById( $presetId );
781
782 if ( ! $preset ) {
783 return new \WP_REST_Response(
784 array(
785 'success' => false,
786 'message' => __( 'Preset not found.', 'double-opt-in' ),
787 ),
788 404
789 );
790 }
791
792 // Get blocks from preset
793 $blocks = $preset['blocks'] ?? array();
794 $globalStyles = $preset['globalStyles'] ?? array();
795
796 // Debug: Check if blocks exist
797 if ( empty( $blocks ) && $presetId !== 'blank' ) {
798 // Something is wrong - preset should have blocks
799 return new \WP_REST_Response(
800 array(
801 'success' => false,
802 'message' => 'Preset blocks are empty - this should not happen!',
803 '_debug' => array(
804 'preset_id' => $presetId,
805 'preset_keys' => array_keys( $preset ),
806 'blocks_type' => gettype( $preset['blocks'] ?? null ),
807 'blocks_count' => count( $blocks ),
808 'has_children' => isset( $preset['blocks'][0]['children'] ),
809 ),
810 ),
811 500
812 );
813 }
814
815 // Encode to JSON
816 $blocksJson = wp_json_encode( $blocks, JSON_UNESCAPED_UNICODE );
817 $globalStylesJson = wp_json_encode( $globalStyles, JSON_UNESCAPED_UNICODE );
818
819 $templateData = array(
820 'title' => ! empty( $title ) ? $title : $preset['name'],
821 'blocks_json' => $blocksJson,
822 'global_styles' => $globalStylesJson,
823 'status' => 'draft',
824 );
825
826 $id = $this->repository->create( $templateData );
827
828 if ( ! $id ) {
829 return new \WP_REST_Response(
830 array(
831 'success' => false,
832 'message' => __( 'Failed to create template from preset.', 'double-opt-in' ),
833 '_debug' => array_merge(
834 array(
835 'preset_id' => $presetId,
836 'blocks_count' => count( $blocks ),
837 'blocks_json_length' => strlen( $blocksJson ),
838 ),
839 $this->repository->lastCreateDebug
840 ),
841 ),
842 500
843 );
844 }
845
846 $template = $this->repository->findById( $id );
847
848 // Debug: Return additional info
849 $template['_debug'] = array_merge(
850 array(
851 'preset_id' => $presetId,
852 'blocks_count' => count( $blocks ),
853 'blocks_json_length' => strlen( $blocksJson ),
854 ),
855 $this->repository->lastCreateDebug
856 );
857
858 return new \WP_REST_Response(
859 array(
860 'success' => true,
861 'data' => $template,
862 ),
863 201
864 );
865 }
866
867 /**
868 * Validate blocks from request data for Pro gating.
869 *
870 * @param array $data The request data containing blocks_json.
871 *
872 * @return array Array of invalid block types. Empty if valid.
873 */
874 private function validateBlocksFromData( array $data ): array {
875 if ( empty( $data['blocks_json'] ) ) {
876 return array();
877 }
878
879 $blocks = is_string( $data['blocks_json'] )
880 ? json_decode( $data['blocks_json'], true )
881 : $data['blocks_json'];
882
883 if ( ! is_array( $blocks ) ) {
884 return array();
885 }
886
887 return $this->blockRegistry->validateBlocks( $blocks );
888 }
889 }
890