@wp-typia/project-tools 0.13.4 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/scaffold-onboarding.js +8 -4
- package/dist/runtime/scaffold.d.ts +1 -0
- package/dist/runtime/scaffold.js +3 -0
- package/dist/runtime/schema-core.d.ts +1 -0
- package/dist/runtime/schema-core.js +188 -8
- package/package.json +1 -1
- package/templates/_shared/compound/persistence/scripts/block-config.ts.mustache +16 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api-types.ts.mustache +10 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api.ts.mustache +51 -38
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/data.ts.mustache +76 -33
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/interactivity.ts.mustache +206 -41
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/render.php.mustache +37 -43
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/transport.ts.mustache +254 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/types.ts.mustache +13 -8
- package/templates/_shared/compound/persistence-auth/{{slugKebabCase}}.php.mustache +139 -0
- package/templates/_shared/compound/persistence-public/{{slugKebabCase}}.php.mustache +159 -0
- package/templates/_shared/persistence/auth/{{slugKebabCase}}.php.mustache +139 -0
- package/templates/_shared/persistence/core/scripts/sync-rest-contracts.ts.mustache +16 -0
- package/templates/_shared/persistence/core/src/api-types.ts.mustache +10 -0
- package/templates/_shared/persistence/core/src/api.ts.mustache +51 -38
- package/templates/_shared/persistence/core/src/data.ts.mustache +76 -33
- package/templates/_shared/persistence/core/src/interactivity.ts.mustache +206 -43
- package/templates/_shared/persistence/core/src/transport.ts.mustache +254 -0
- package/templates/_shared/persistence/public/{{slugKebabCase}}.php.mustache +159 -0
- package/templates/_shared/rest-helpers/public/inc/rest-public.php.mustache +1 -1
- package/templates/_shared/workspace/persistence-auth/server.php.mustache +139 -0
- package/templates/_shared/workspace/persistence-public/inc/rest-public.php.mustache +1 -1
- package/templates/_shared/workspace/persistence-public/server.php.mustache +159 -0
- package/templates/persistence/src/edit.tsx.mustache +1 -1
- package/templates/persistence/src/render.php.mustache +37 -43
- package/templates/persistence/src/types.ts.mustache +13 -9
package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/transport.ts.mustache
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type EndpointCallOptions,
|
|
3
|
+
resolveRestRouteUrl,
|
|
4
|
+
} from '@wp-typia/rest';
|
|
5
|
+
|
|
6
|
+
export type PersistenceTransportTarget = 'editor' | 'frontend';
|
|
7
|
+
export type PersistenceTransportOperation = 'read' | 'write';
|
|
8
|
+
|
|
9
|
+
export interface PersistenceTransportOptions {
|
|
10
|
+
restNonce?: string;
|
|
11
|
+
transportTarget?: PersistenceTransportTarget;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface EndpointWithPath {
|
|
15
|
+
path: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type PersistenceTransportResolver = (
|
|
19
|
+
endpoint: EndpointWithPath,
|
|
20
|
+
request?: unknown,
|
|
21
|
+
options?: PersistenceTransportOptions
|
|
22
|
+
) => EndpointCallOptions;
|
|
23
|
+
|
|
24
|
+
interface PersistenceTransportTargetResolvers {
|
|
25
|
+
read: PersistenceTransportResolver;
|
|
26
|
+
write: PersistenceTransportResolver;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Replace any of these base URLs to route a given target through a contract-compatible proxy or BFF.
|
|
30
|
+
const EDITOR_READ_BASE_URL: string | undefined = undefined;
|
|
31
|
+
const EDITOR_WRITE_BASE_URL: string | undefined = undefined;
|
|
32
|
+
const FRONTEND_READ_BASE_URL: string | undefined = undefined;
|
|
33
|
+
const FRONTEND_WRITE_BASE_URL: string | undefined = undefined;
|
|
34
|
+
|
|
35
|
+
function resolveEndpointUrl(
|
|
36
|
+
endpointPath: string,
|
|
37
|
+
baseUrl?: string
|
|
38
|
+
): string {
|
|
39
|
+
if ( typeof baseUrl === 'string' && baseUrl.trim().length > 0 ) {
|
|
40
|
+
const origin =
|
|
41
|
+
typeof window !== 'undefined' ? window.location.origin : 'http://localhost';
|
|
42
|
+
const resolvedBaseUrl = new URL( baseUrl.trim(), origin );
|
|
43
|
+
const [ pathWithQuery, hash = '' ] = endpointPath.split( '#', 2 );
|
|
44
|
+
const [ rawPath, rawQuery = '' ] = pathWithQuery.split( '?', 2 );
|
|
45
|
+
const normalizedEndpointPath = rawPath.replace( /^\/+/, '' );
|
|
46
|
+
|
|
47
|
+
if ( resolvedBaseUrl.searchParams.has( 'rest_route' ) ) {
|
|
48
|
+
const restRouteBase = resolvedBaseUrl.searchParams.get( 'rest_route' ) ?? '/';
|
|
49
|
+
const normalizedRestRouteBase = restRouteBase
|
|
50
|
+
.replace( /^\/+/, '' )
|
|
51
|
+
.replace( /\/+$/, '' );
|
|
52
|
+
const nextRestRoute = normalizedRestRouteBase.length > 0
|
|
53
|
+
? `/${ normalizedRestRouteBase }/${ normalizedEndpointPath }`
|
|
54
|
+
: `/${ normalizedEndpointPath }`;
|
|
55
|
+
resolvedBaseUrl.searchParams.set( 'rest_route', nextRestRoute );
|
|
56
|
+
} else {
|
|
57
|
+
const basePath = resolvedBaseUrl.pathname.endsWith( '/' )
|
|
58
|
+
? resolvedBaseUrl.pathname
|
|
59
|
+
: `${ resolvedBaseUrl.pathname }/`;
|
|
60
|
+
resolvedBaseUrl.pathname = `${ basePath }${ normalizedEndpointPath }`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for ( const [ key, value ] of new URLSearchParams( rawQuery ) ) {
|
|
64
|
+
resolvedBaseUrl.searchParams.append( key, value );
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if ( hash ) {
|
|
68
|
+
resolvedBaseUrl.hash = hash;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return resolvedBaseUrl.toString();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return resolveRestRouteUrl( endpointPath );
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isPlainObject(
|
|
78
|
+
value: unknown
|
|
79
|
+
): value is Record< string, unknown > {
|
|
80
|
+
if ( Object.prototype.toString.call( value ) !== '[object Object]' ) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const prototype = Object.getPrototypeOf( value );
|
|
85
|
+
return prototype === null || prototype === Object.prototype;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function encodeRequestQuery( request: unknown ): string {
|
|
89
|
+
if ( request === undefined || request === null ) {
|
|
90
|
+
return '';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if ( request instanceof URLSearchParams ) {
|
|
94
|
+
return request.toString();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if ( ! isPlainObject( request ) ) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
'Persistence transport read requests must be plain objects or URLSearchParams.'
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const params = new URLSearchParams();
|
|
104
|
+
|
|
105
|
+
for ( const [ key, value ] of Object.entries( request ) ) {
|
|
106
|
+
if ( value === undefined || value === null ) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if ( Array.isArray( value ) ) {
|
|
111
|
+
for ( const item of value ) {
|
|
112
|
+
params.append( key, String( item ) );
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
params.set( key, String( value ) );
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return params.toString();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function joinUrlWithQuery(
|
|
124
|
+
url: string,
|
|
125
|
+
query: string
|
|
126
|
+
): string {
|
|
127
|
+
if ( ! query ) {
|
|
128
|
+
return url;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const nextUrl = new URL(
|
|
132
|
+
url,
|
|
133
|
+
typeof window !== 'undefined' ? window.location.origin : 'http://localhost'
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
for ( const [ key, value ] of new URLSearchParams( query ) ) {
|
|
137
|
+
nextUrl.searchParams.append( key, value );
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return nextUrl.toString();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function resolveRestNonce( fallback?: string ): string | undefined {
|
|
144
|
+
if ( typeof fallback === 'string' && fallback.length > 0 ) {
|
|
145
|
+
return fallback;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if ( typeof window === 'undefined' ) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const wpApiSettings = ( window as typeof window & {
|
|
153
|
+
wpApiSettings?: { nonce?: string };
|
|
154
|
+
} ).wpApiSettings;
|
|
155
|
+
|
|
156
|
+
return typeof wpApiSettings?.nonce === 'string' && wpApiSettings.nonce.length > 0
|
|
157
|
+
? wpApiSettings.nonce
|
|
158
|
+
: undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function buildCallOptions( {
|
|
162
|
+
baseUrl,
|
|
163
|
+
endpoint,
|
|
164
|
+
includeRestNonce,
|
|
165
|
+
operation,
|
|
166
|
+
options,
|
|
167
|
+
request,
|
|
168
|
+
}: {
|
|
169
|
+
baseUrl?: string;
|
|
170
|
+
endpoint: EndpointWithPath;
|
|
171
|
+
includeRestNonce: boolean;
|
|
172
|
+
operation: PersistenceTransportOperation;
|
|
173
|
+
options?: PersistenceTransportOptions;
|
|
174
|
+
request?: unknown;
|
|
175
|
+
} ): EndpointCallOptions {
|
|
176
|
+
const nonce = includeRestNonce ? resolveRestNonce( options?.restNonce ) : undefined;
|
|
177
|
+
const requestOptions: EndpointCallOptions['requestOptions'] = {};
|
|
178
|
+
|
|
179
|
+
if ( nonce ) {
|
|
180
|
+
requestOptions.headers = {
|
|
181
|
+
'X-WP-Nonce': nonce,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if ( typeof baseUrl === 'string' && baseUrl.trim().length > 0 ) {
|
|
186
|
+
const endpointUrl = resolveEndpointUrl( endpoint.path, baseUrl );
|
|
187
|
+
requestOptions.url =
|
|
188
|
+
operation === 'read'
|
|
189
|
+
? joinUrlWithQuery( endpointUrl, encodeRequestQuery( request ) )
|
|
190
|
+
: endpointUrl;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return Object.keys( requestOptions ).length > 0
|
|
194
|
+
? {
|
|
195
|
+
requestOptions,
|
|
196
|
+
}
|
|
197
|
+
: {};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const persistenceTransportTargets: Record<
|
|
201
|
+
PersistenceTransportTarget,
|
|
202
|
+
PersistenceTransportTargetResolvers
|
|
203
|
+
> = {
|
|
204
|
+
editor: {
|
|
205
|
+
read: ( endpoint, request, options ) =>
|
|
206
|
+
buildCallOptions( {
|
|
207
|
+
baseUrl: EDITOR_READ_BASE_URL,
|
|
208
|
+
endpoint,
|
|
209
|
+
includeRestNonce: {{isAuthenticatedPersistencePolicy}},
|
|
210
|
+
operation: 'read',
|
|
211
|
+
options,
|
|
212
|
+
request,
|
|
213
|
+
} ),
|
|
214
|
+
write: ( endpoint, request, options ) =>
|
|
215
|
+
buildCallOptions( {
|
|
216
|
+
baseUrl: EDITOR_WRITE_BASE_URL,
|
|
217
|
+
endpoint,
|
|
218
|
+
includeRestNonce: {{isAuthenticatedPersistencePolicy}},
|
|
219
|
+
operation: 'write',
|
|
220
|
+
options,
|
|
221
|
+
request,
|
|
222
|
+
} ),
|
|
223
|
+
},
|
|
224
|
+
frontend: {
|
|
225
|
+
read: ( endpoint, request, options ) =>
|
|
226
|
+
buildCallOptions( {
|
|
227
|
+
baseUrl: FRONTEND_READ_BASE_URL,
|
|
228
|
+
endpoint,
|
|
229
|
+
includeRestNonce: false,
|
|
230
|
+
operation: 'read',
|
|
231
|
+
options,
|
|
232
|
+
request,
|
|
233
|
+
} ),
|
|
234
|
+
write: ( endpoint, request, options ) =>
|
|
235
|
+
buildCallOptions( {
|
|
236
|
+
baseUrl: FRONTEND_WRITE_BASE_URL,
|
|
237
|
+
endpoint,
|
|
238
|
+
includeRestNonce: {{isAuthenticatedPersistencePolicy}},
|
|
239
|
+
operation: 'write',
|
|
240
|
+
options,
|
|
241
|
+
request,
|
|
242
|
+
} ),
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
export function resolveTransportCallOptions(
|
|
247
|
+
target: PersistenceTransportTarget,
|
|
248
|
+
operation: PersistenceTransportOperation,
|
|
249
|
+
endpoint: EndpointWithPath,
|
|
250
|
+
request?: unknown,
|
|
251
|
+
options?: PersistenceTransportOptions
|
|
252
|
+
): EndpointCallOptions {
|
|
253
|
+
return persistenceTransportTargets[ target ][ operation ]( endpoint, request, options );
|
|
254
|
+
}
|
package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/types.ts.mustache
CHANGED
|
@@ -32,25 +32,30 @@ export interface {{pascalCase}}Attributes {
|
|
|
32
32
|
|
|
33
33
|
export interface {{pascalCase}}Context {
|
|
34
34
|
buttonLabel: string;
|
|
35
|
+
bootstrapReady: boolean;
|
|
35
36
|
canWrite: boolean;
|
|
36
37
|
count: number;
|
|
38
|
+
error: string;
|
|
39
|
+
isBootstrapping: boolean;
|
|
40
|
+
isLoading: boolean;
|
|
41
|
+
isSaving: boolean;
|
|
37
42
|
persistencePolicy: 'authenticated' | 'public';
|
|
38
43
|
postId: number;
|
|
39
|
-
publicWriteExpiresAt?: number;
|
|
40
|
-
publicWriteToken?: string;
|
|
41
44
|
resourceKey: string;
|
|
42
|
-
restNonce?: string;
|
|
43
45
|
showCount: boolean;
|
|
44
46
|
storage: 'post-meta' | 'custom-table';
|
|
47
|
+
client?: {{pascalCase}}ClientState;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
export interface {{pascalCase}}State {
|
|
48
|
-
canWrite: boolean;
|
|
49
|
-
count: number;
|
|
50
|
-
error?: string;
|
|
51
51
|
isHydrated: boolean;
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface {{pascalCase}}ClientState {
|
|
55
|
+
bootstrapError: string;
|
|
56
|
+
writeExpiry: number;
|
|
57
|
+
writeNonce: string;
|
|
58
|
+
writeToken: string;
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
export type {{pascalCase}}ValidationResult = ValidationResult< {{pascalCase}}Attributes >;
|
|
@@ -192,6 +192,105 @@ function {{phpPrefix}}_build_state_response( $post_id, $resource_key, $count ) {
|
|
|
192
192
|
);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
function {{phpPrefix}}_block_tree_has_resource_key( $blocks, $block_name, $resource_key ) {
|
|
196
|
+
if ( ! is_array( $blocks ) ) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
foreach ( $blocks as $block ) {
|
|
201
|
+
if ( ! is_array( $block ) ) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if ( (string) ( $block['blockName'] ?? '' ) === $block_name ) {
|
|
206
|
+
$attributes = isset( $block['attrs'] ) && is_array( $block['attrs'] ) ? $block['attrs'] : array();
|
|
207
|
+
$candidate_resource_key = array_key_exists( 'resourceKey', $attributes )
|
|
208
|
+
? (string) $attributes['resourceKey']
|
|
209
|
+
: 'primary';
|
|
210
|
+
if ( (string) $resource_key === $candidate_resource_key ) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (
|
|
216
|
+
isset( $block['innerBlocks'] ) &&
|
|
217
|
+
{{phpPrefix}}_block_tree_has_resource_key( $block['innerBlocks'], $block_name, $resource_key )
|
|
218
|
+
) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function {{phpPrefix}}_get_rendered_block_instance_key( $post_id, $block_name, $resource_key ) {
|
|
227
|
+
return 'wpt_pri_' . md5( implode( '|', array( (string) $block_name, (int) $post_id, (string) $resource_key ) ) );
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function {{phpPrefix}}_record_rendered_block_instance( $post_id, $block_name, $resource_key ) {
|
|
231
|
+
if ( $post_id <= 0 || '' === (string) $resource_key || '' === (string) $block_name ) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
set_transient(
|
|
236
|
+
{{phpPrefix}}_get_rendered_block_instance_key( $post_id, $block_name, $resource_key ),
|
|
237
|
+
1,
|
|
238
|
+
5 * MINUTE_IN_SECONDS
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function {{phpPrefix}}_has_rendered_block_instance( $post_id, $resource_key ) {
|
|
243
|
+
if ( $post_id <= 0 || '' === (string) $resource_key ) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (
|
|
248
|
+
false !== get_transient(
|
|
249
|
+
{{phpPrefix}}_get_rendered_block_instance_key(
|
|
250
|
+
$post_id,
|
|
251
|
+
'{{namespace}}/{{slugKebabCase}}',
|
|
252
|
+
$resource_key
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
) {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
$post = get_post( $post_id );
|
|
260
|
+
if ( ! ( $post instanceof WP_Post ) ) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return {{phpPrefix}}_block_tree_has_resource_key(
|
|
265
|
+
parse_blocks( (string) $post->post_content ),
|
|
266
|
+
'{{namespace}}/{{slugKebabCase}}',
|
|
267
|
+
(string) $resource_key
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function {{phpPrefix}}_build_bootstrap_response( $post_id, $resource_key ) {
|
|
272
|
+
$post = get_post( $post_id );
|
|
273
|
+
$can_read_post =
|
|
274
|
+
$post instanceof WP_Post &&
|
|
275
|
+
(
|
|
276
|
+
is_post_publicly_viewable( $post ) ||
|
|
277
|
+
current_user_can( 'read_post', $post->ID )
|
|
278
|
+
);
|
|
279
|
+
$can_write = $post_id > 0 &&
|
|
280
|
+
is_user_logged_in() &&
|
|
281
|
+
$can_read_post &&
|
|
282
|
+
{{phpPrefix}}_has_rendered_block_instance( (int) $post_id, (string) $resource_key );
|
|
283
|
+
$response = array(
|
|
284
|
+
'canWrite' => $can_write,
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
if ( $can_write ) {
|
|
288
|
+
$response['restNonce'] = wp_create_nonce( 'wp_rest' );
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return $response;
|
|
292
|
+
}
|
|
293
|
+
|
|
195
294
|
// Route handlers are the main product-level extension point for parent-block request/response shaping.
|
|
196
295
|
function {{phpPrefix}}_handle_get_state( WP_REST_Request $request ) {
|
|
197
296
|
$payload = {{phpPrefix}}_validate_and_sanitize_request(
|
|
@@ -218,6 +317,34 @@ function {{phpPrefix}}_handle_get_state( WP_REST_Request $request ) {
|
|
|
218
317
|
);
|
|
219
318
|
}
|
|
220
319
|
|
|
320
|
+
function {{phpPrefix}}_handle_get_bootstrap( WP_REST_Request $request ) {
|
|
321
|
+
$payload = {{phpPrefix}}_validate_and_sanitize_request(
|
|
322
|
+
array(
|
|
323
|
+
'postId' => $request->get_param( 'postId' ),
|
|
324
|
+
'resourceKey' => $request->get_param( 'resourceKey' ),
|
|
325
|
+
),
|
|
326
|
+
{{phpPrefix}}_get_rest_build_dir(),
|
|
327
|
+
'bootstrap-query',
|
|
328
|
+
'query'
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
if ( is_wp_error( $payload ) ) {
|
|
332
|
+
return $payload;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
$response = rest_ensure_response(
|
|
336
|
+
{{phpPrefix}}_build_bootstrap_response(
|
|
337
|
+
(int) $payload['postId'],
|
|
338
|
+
(string) $payload['resourceKey']
|
|
339
|
+
)
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
$response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0, s-maxage=0' );
|
|
343
|
+
$response->header( 'Pragma', 'no-cache' );
|
|
344
|
+
$response->header( 'Vary', 'Cookie' );
|
|
345
|
+
return $response;
|
|
346
|
+
}
|
|
347
|
+
|
|
221
348
|
function {{phpPrefix}}_handle_write_state( WP_REST_Request $request ) {
|
|
222
349
|
$payload = {{phpPrefix}}_validate_and_sanitize_request(
|
|
223
350
|
$request->get_json_params(),
|
|
@@ -267,6 +394,18 @@ function {{phpPrefix}}_register_routes() {
|
|
|
267
394
|
),
|
|
268
395
|
)
|
|
269
396
|
);
|
|
397
|
+
|
|
398
|
+
register_rest_route(
|
|
399
|
+
'{{namespace}}/v1',
|
|
400
|
+
'/{{slugKebabCase}}/bootstrap',
|
|
401
|
+
array(
|
|
402
|
+
array(
|
|
403
|
+
'methods' => WP_REST_Server::READABLE,
|
|
404
|
+
'callback' => '{{phpPrefix}}_handle_get_bootstrap',
|
|
405
|
+
'permission_callback' => '__return_true',
|
|
406
|
+
),
|
|
407
|
+
)
|
|
408
|
+
);
|
|
270
409
|
}
|
|
271
410
|
|
|
272
411
|
function {{phpPrefix}}_register_blocks() {
|
|
@@ -195,6 +195,126 @@ function {{phpPrefix}}_build_state_response( $post_id, $resource_key, $count ) {
|
|
|
195
195
|
);
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
function {{phpPrefix}}_block_tree_has_resource_key( $blocks, $block_name, $resource_key ) {
|
|
199
|
+
if ( ! is_array( $blocks ) ) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
foreach ( $blocks as $block ) {
|
|
204
|
+
if ( ! is_array( $block ) ) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if ( (string) ( $block['blockName'] ?? '' ) === $block_name ) {
|
|
209
|
+
$attributes = isset( $block['attrs'] ) && is_array( $block['attrs'] ) ? $block['attrs'] : array();
|
|
210
|
+
$candidate_resource_key = array_key_exists( 'resourceKey', $attributes )
|
|
211
|
+
? (string) $attributes['resourceKey']
|
|
212
|
+
: 'primary';
|
|
213
|
+
if ( (string) $resource_key === $candidate_resource_key ) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (
|
|
219
|
+
isset( $block['innerBlocks'] ) &&
|
|
220
|
+
{{phpPrefix}}_block_tree_has_resource_key( $block['innerBlocks'], $block_name, $resource_key )
|
|
221
|
+
) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function {{phpPrefix}}_get_rendered_block_instance_key( $post_id, $block_name, $resource_key ) {
|
|
230
|
+
return 'wpt_pri_' . md5( implode( '|', array( (string) $block_name, (int) $post_id, (string) $resource_key ) ) );
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function {{phpPrefix}}_record_rendered_block_instance( $post_id, $block_name, $resource_key ) {
|
|
234
|
+
if ( $post_id <= 0 || '' === (string) $resource_key || '' === (string) $block_name ) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
set_transient(
|
|
239
|
+
{{phpPrefix}}_get_rendered_block_instance_key( $post_id, $block_name, $resource_key ),
|
|
240
|
+
1,
|
|
241
|
+
5 * MINUTE_IN_SECONDS
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function {{phpPrefix}}_has_rendered_block_instance( $post_id, $resource_key ) {
|
|
246
|
+
if ( $post_id <= 0 || '' === (string) $resource_key ) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (
|
|
251
|
+
false !== get_transient(
|
|
252
|
+
{{phpPrefix}}_get_rendered_block_instance_key(
|
|
253
|
+
$post_id,
|
|
254
|
+
'{{namespace}}/{{slugKebabCase}}',
|
|
255
|
+
$resource_key
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
$post = get_post( $post_id );
|
|
263
|
+
if ( ! ( $post instanceof WP_Post ) ) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if ( ! is_post_publicly_viewable( $post ) ) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return {{phpPrefix}}_block_tree_has_resource_key(
|
|
272
|
+
parse_blocks( (string) $post->post_content ),
|
|
273
|
+
'{{namespace}}/{{slugKebabCase}}',
|
|
274
|
+
(string) $resource_key
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function {{phpPrefix}}_build_bootstrap_response( $post_id, $resource_key ) {
|
|
279
|
+
$response = array(
|
|
280
|
+
'canWrite' => false,
|
|
281
|
+
);
|
|
282
|
+
$post = get_post( $post_id );
|
|
283
|
+
|
|
284
|
+
if (
|
|
285
|
+
$post_id <= 0 ||
|
|
286
|
+
! ( $post instanceof WP_Post ) ||
|
|
287
|
+
! is_post_publicly_viewable( $post ) ||
|
|
288
|
+
! function_exists( '{{phpPrefix}}_create_public_write_token' ) ||
|
|
289
|
+
! {{phpPrefix}}_has_rendered_block_instance( (int) $post_id, (string) $resource_key )
|
|
290
|
+
) {
|
|
291
|
+
return $response;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
$public_write = {{phpPrefix}}_create_public_write_token( (int) $post_id, (string) $resource_key );
|
|
295
|
+
if ( ! is_array( $public_write ) ) {
|
|
296
|
+
return $response;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
$token = isset( $public_write['token'] ) ? (string) $public_write['token'] : '';
|
|
300
|
+
$expires_at = isset( $public_write['expiresAt'] ) ? (int) $public_write['expiresAt'] : 0;
|
|
301
|
+
$is_expired = $expires_at > 0 && $expires_at <= time();
|
|
302
|
+
|
|
303
|
+
if ( '' !== $token && $expires_at > 0 && ! $is_expired ) {
|
|
304
|
+
$response['publicWriteToken'] = $token;
|
|
305
|
+
$response['canWrite'] = true;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if ( $expires_at > 0 ) {
|
|
309
|
+
$response['publicWriteExpiresAt'] = $expires_at;
|
|
310
|
+
if ( $is_expired ) {
|
|
311
|
+
$response['canWrite'] = false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return $response;
|
|
316
|
+
}
|
|
317
|
+
|
|
198
318
|
// Route handlers are the main product-level extension point for parent-block request/response shaping.
|
|
199
319
|
function {{phpPrefix}}_handle_get_state( WP_REST_Request $request ) {
|
|
200
320
|
$payload = {{phpPrefix}}_validate_and_sanitize_request(
|
|
@@ -221,6 +341,33 @@ function {{phpPrefix}}_handle_get_state( WP_REST_Request $request ) {
|
|
|
221
341
|
);
|
|
222
342
|
}
|
|
223
343
|
|
|
344
|
+
function {{phpPrefix}}_handle_get_bootstrap( WP_REST_Request $request ) {
|
|
345
|
+
$payload = {{phpPrefix}}_validate_and_sanitize_request(
|
|
346
|
+
array(
|
|
347
|
+
'postId' => $request->get_param( 'postId' ),
|
|
348
|
+
'resourceKey' => $request->get_param( 'resourceKey' ),
|
|
349
|
+
),
|
|
350
|
+
{{phpPrefix}}_get_rest_build_dir(),
|
|
351
|
+
'bootstrap-query',
|
|
352
|
+
'query'
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
if ( is_wp_error( $payload ) ) {
|
|
356
|
+
return $payload;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
$response = rest_ensure_response(
|
|
360
|
+
{{phpPrefix}}_build_bootstrap_response(
|
|
361
|
+
(int) $payload['postId'],
|
|
362
|
+
(string) $payload['resourceKey']
|
|
363
|
+
)
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
$response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0, s-maxage=0' );
|
|
367
|
+
$response->header( 'Pragma', 'no-cache' );
|
|
368
|
+
return $response;
|
|
369
|
+
}
|
|
370
|
+
|
|
224
371
|
function {{phpPrefix}}_handle_write_state( WP_REST_Request $request ) {
|
|
225
372
|
$payload = {{phpPrefix}}_validate_and_sanitize_request(
|
|
226
373
|
$request->get_json_params(),
|
|
@@ -285,6 +432,18 @@ function {{phpPrefix}}_register_routes() {
|
|
|
285
432
|
),
|
|
286
433
|
)
|
|
287
434
|
);
|
|
435
|
+
|
|
436
|
+
register_rest_route(
|
|
437
|
+
'{{namespace}}/v1',
|
|
438
|
+
'/{{slugKebabCase}}/bootstrap',
|
|
439
|
+
array(
|
|
440
|
+
array(
|
|
441
|
+
'methods' => WP_REST_Server::READABLE,
|
|
442
|
+
'callback' => '{{phpPrefix}}_handle_get_bootstrap',
|
|
443
|
+
'permission_callback' => '__return_true',
|
|
444
|
+
),
|
|
445
|
+
)
|
|
446
|
+
);
|
|
288
447
|
}
|
|
289
448
|
|
|
290
449
|
function {{phpPrefix}}_register_blocks() {
|