PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / trunk
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! vtrunk
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / modules / block-recovery / REST / Rebuild.php

Rebuild.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! trunk, at modules/block-recovery/REST/Rebuild.php

179 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\Modules\BlockRecovery\REST;
4
5 use Templately\API\API;
6 use Templately\Modules\BlockRecovery\AttributeSnapshot;
7 use Templately\Modules\BlockRecovery\Module;
8 use Templately\Modules\BlockRecovery\Queue;
9 use Templately\Modules\BlockRecovery\Report;
10 use WP_Error;
11 use WP_REST_Request;
12
13 /**
14 * The queue/report endpoints for the post-import block rebuild.
15 *
16 * The repair itself happens in the browser (`save()` is JS-only and third-party blocks register
17 * only inside a block editor), so PHP's part is to say WHAT to visit and to record what came
18 * back. Both routes are gated on `edit_posts` rather than the base class's connection check:
19 * this is local content maintenance and must keep working on a site whose Templately connection
20 * has since lapsed — the posts are already imported either way.
21 */
22 class Rebuild extends API {
23
24 private $endpoint = 'block-recovery';
25
26 public function permission_check( WP_REST_Request $request ) {
27 $this->request = $request;
28
29 if ( ! current_user_can( 'edit_posts' ) ) {
30 return new WP_Error(
31 'rest_forbidden',
32 __( 'Sorry, you are not allowed to repair imported blocks.', 'templately' ),
33 [ 'status' => rest_authorization_required_code() ]
34 );
35 }
36
37 return true;
38 }
39
40 public function register_routes() {
41 $this->get( $this->endpoint . '/queue', [ $this, 'get_queue' ] );
42 $this->post( $this->endpoint . '/report', [ $this, 'post_report' ] );
43 $this->post( $this->endpoint . '/snapshot', [ $this, 'post_snapshot' ] );
44 $this->post( $this->endpoint . '/restore', [ $this, 'post_restore' ] );
45 }
46
47 /**
48 * Remember a post's stored block attributes before the editor rebuilds it.
49 *
50 * Must run BEFORE the save: the attributes this protects are exactly the ones the rebuild is
51 * about to drop, and once the editor has written the post they are no longer anywhere to be
52 * read. See {@see AttributeSnapshot}.
53 */
54 public function post_snapshot() {
55 $post_id = (int) $this->get_param( 'post_id', 0, 'intval' );
56
57 if ( empty( $post_id ) ) {
58 return $this->error( 'invalid_requirements', __( 'A post id is required.', 'templately' ), $this->endpoint . '/snapshot', 400 );
59 }
60
61 if ( ! current_user_can( 'edit_post', $post_id ) ) {
62 return $this->error( 'invalid_permission', __( 'Sorry, you are not allowed to edit this post.', 'templately' ), $this->endpoint . '/snapshot', 403 );
63 }
64
65 return $this->envelope( [ 'captured' => AttributeSnapshot::capture( $post_id ) ] );
66 }
67
68 /**
69 * Put back any attribute the rebuild dropped, leaving the regenerated markup alone.
70 */
71 public function post_restore() {
72 $post_id = (int) $this->get_param( 'post_id', 0, 'intval' );
73
74 if ( empty( $post_id ) ) {
75 return $this->error( 'invalid_requirements', __( 'A post id is required.', 'templately' ), $this->endpoint . '/restore', 400 );
76 }
77
78 if ( ! current_user_can( 'edit_post', $post_id ) ) {
79 return $this->error( 'invalid_permission', __( 'Sorry, you are not allowed to edit this post.', 'templately' ), $this->endpoint . '/restore', 403 );
80 }
81
82 return $this->envelope( AttributeSnapshot::restore( $post_id ) );
83 }
84
85 /**
86 * Posts still worth visiting, plus the merge-time parity warnings as priority information.
87 */
88 public function get_queue() {
89 $queue = Queue::get();
90
91 $items = array_map( function ( $item ) {
92 return [
93 'id' => (int) $item['id'],
94 'type' => (string) $item['type'],
95 'editUrl' => get_edit_post_link( (int) $item['id'], 'raw' ),
96 ];
97 }, Queue::pending() );
98
99 return $this->envelope(
100 [
101 'enabled' => Module::is_enabled(),
102 'sessionId' => $queue['session_id'] ?? null,
103 'items' => $items,
104 'parityWarnings' => $queue['parity_warnings'] ?? [],
105 'report' => Report::get(),
106 ]
107 );
108 }
109
110 /**
111 * Record one post's outcome and take it off the queue.
112 *
113 * A post is taken off the queue whatever the outcome, failures included — re-opening an
114 * editor that already refused to boot would spend the same time for the same answer, and
115 * the failure is reported separately.
116 */
117 public function post_report() {
118 $post_id = (int) $this->get_param( 'post_id', 0, 'intval' );
119 $status = (string) $this->get_param( 'status', 'clean' );
120 $rebuilt = (int) $this->get_param( 'rebuilt', 0, 'intval' );
121 $skipped = $this->get_param( 'skipped', [], null );
122 $preserved = (int) $this->get_param( 'preserved', 0, 'intval' );
123 $restored = (int) $this->get_param( 'restored', 0, 'intval' );
124 $trigger = (string) $this->get_param( 'trigger', Report::TRIGGER_BATCH, 'sanitize_key' );
125 $duration = (int) $this->get_param( 'durationMs', 0, 'absint' );
126
127 if ( empty( $post_id ) ) {
128 return $this->error( 'invalid_requirements', __( 'A post id is required.', 'templately' ), $this->endpoint . '/report', 400 );
129 }
130
131 if ( ! current_user_can( 'edit_post', $post_id ) ) {
132 return $this->error( 'invalid_permission', __( 'Sorry, you are not allowed to edit this post.', 'templately' ), $this->endpoint . '/report', 403 );
133 }
134
135 if ( ! in_array( $status, [ 'clean', 'rebuilt', 'skipped', 'failed' ], true ) ) {
136 $status = 'clean';
137 }
138
139 Report::record( $post_id, $status, $rebuilt, $this->sanitize_skipped( $skipped ), $preserved, $restored, $trigger, $duration );
140 Queue::mark_done( $post_id );
141
142 return $this->envelope( [ 'ok' => true, 'remaining' => count( Queue::pending() ) ] );
143 }
144
145 /**
146 * The skipped list is client-supplied, so it is rebuilt field by field rather than trusted:
147 * it ends up in an option and in the UI.
148 *
149 * @param mixed $skipped
150 * @return array<int,array{block:string,droppedAttrs:array<int,string>}>
151 */
152 private function sanitize_skipped( $skipped ): array {
153 if ( ! is_array( $skipped ) ) {
154 return [];
155 }
156
157 $clean = [];
158 foreach ( $skipped as $entry ) {
159 if ( ! is_array( $entry ) || empty( $entry['block'] ) ) {
160 continue;
161 }
162
163 $attributes = [];
164 foreach ( (array) ( $entry['droppedAttrs'] ?? [] ) as $attribute ) {
165 if ( is_string( $attribute ) || is_numeric( $attribute ) ) {
166 $attributes[] = sanitize_text_field( (string) $attribute );
167 }
168 }
169
170 $clean[] = [
171 'block' => sanitize_text_field( (string) $entry['block'] ),
172 'droppedAttrs' => $attributes,
173 ];
174 }
175
176 return $clean;
177 }
178 }
179