Skip to content

Commit 8956932

Browse files
author
Smart Cloud Solutions Inc.
committed
Fix Flow form sync, validation, and endpoint handling
1 parent bc76c3b commit 8956932

13 files changed

Lines changed: 214 additions & 43 deletions

File tree

admin/php/admin.php

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,39 @@ public function getSettings(): FlowAdminSettings
5151
return $this->settings;
5252
}
5353

54+
private function isSupportedFormSyncPostType(string $post_type): bool
55+
{
56+
if ($post_type === '') {
57+
return false;
58+
}
59+
60+
$explicit_types = array(
61+
'wp_block',
62+
'wp_template',
63+
'wp_template_part',
64+
'smartcloud_flow_form',
65+
);
66+
67+
return in_array($post_type, $explicit_types, true)
68+
|| post_type_supports($post_type, 'editor');
69+
}
70+
71+
private function getFormSyncSourcePost(int $post_id): ?\WP_Post
72+
{
73+
if ($post_id <= 0 || wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
74+
return null;
75+
}
76+
77+
$post = get_post($post_id);
78+
if (!$post instanceof \WP_Post) {
79+
return null;
80+
}
81+
82+
return $this->isSupportedFormSyncPostType((string) $post->post_type)
83+
? $post
84+
: null;
85+
}
86+
5487
public function addMenu(): void
5588
{
5689
$page = add_submenu_page(
@@ -333,11 +366,9 @@ public function getFormSyncMeta(WP_REST_Request $request): WP_REST_Response
333366
{
334367
$post_id = (int) $request->get_param('post_id');
335368

336-
// Verify post exists and is valid type (post, page, wp_block, or smartcloud_flow_form)
337-
$post = get_post($post_id);
338-
$allowed_types = array('post', 'page', 'wp_block', 'smartcloud_flow_form');
369+
$post = $this->getFormSyncSourcePost($post_id);
339370

340-
if (!$post || !in_array($post->post_type, $allowed_types, true)) {
371+
if (!$post) {
341372
return new WP_REST_Response(
342373
array('error' => 'Invalid post ID or unsupported post type'),
343374
404
@@ -353,11 +384,9 @@ public function updateFormSyncMeta(WP_REST_Request $request): WP_REST_Response
353384
{
354385
$post_id = (int) $request->get_param('post_id');
355386

356-
// Verify post exists and is valid type (post, page, wp_block, or smartcloud_flow_form)
357-
$post = get_post($post_id);
358-
$allowed_types = array('post', 'page', 'wp_block', 'smartcloud_flow_form');
387+
$post = $this->getFormSyncSourcePost($post_id);
359388

360-
if (!$post || !in_array($post->post_type, $allowed_types, true)) {
389+
if (!$post) {
361390
return new WP_REST_Response(
362391
array('error' => 'Invalid post ID or unsupported post type'),
363392
404

blocks/dist/editor.asset.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'e7e4dac274f398676ff4');
1+
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '0e24a99871b036eb888b');

blocks/dist/editor.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

blocks/dist/form/block.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"$schema": "https://schemas.wp.org/trunk/block.json",
33
"apiVersion": 3,
44
"name": "smartcloud-flow/form",
5-
"version": "1.1.1",
5+
"version": "1.1.2",
66
"title": "Flow Form",
77
"category": "smartcloud-flow",
88
"icon": "feedback",

blocks/dist/view.asset.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<?php return array('dependencies' => array('jquery', 'react', 'react-dom', 'react-jsx-runtime', 'wp-data', 'wp-i18n'), 'version' => '18d3a3c1d901ad56c8f4');
1+
<?php return array('dependencies' => array('jquery', 'react', 'react-dom', 'react-jsx-runtime', 'wp-data', 'wp-i18n'), 'version' => '60d4f4147688ae01e55d');

blocks/dist/view.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

blocks/src/form/block.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"$schema": "https://schemas.wp.org/trunk/block.json",
33
"apiVersion": 3,
44
"name": "smartcloud-flow/form",
5-
"version": "1.1.1",
5+
"version": "1.1.2",
66
"title": "Flow Form",
77
"category": "smartcloud-flow",
88
"icon": "feedback",

blocks/src/runtime/components/FormShell.tsx

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import {
6565
AI_SUGGESTION_WATCHER_KEYS,
6666
stripConditionalSystemValues,
6767
} from "../../shared/conditional-system-watchers";
68+
import { resolveRuntimeContextString } from "../../shared/runtime-context";
6869
import { FormActionsProvider } from "../context/FormActionsContext";
6970
import type { FormReturnIntent } from "../context/FormActionsContext";
7071
import { FormAttributesProvider } from "../context/FormAttributesContext";
@@ -94,6 +95,10 @@ interface SubmissionMetaRuntime {
9495
aiSourcesUsed?: boolean;
9596
}
9697

98+
function isAbsoluteHttpUrl(value: string): boolean {
99+
return /^https?:\/\//i.test(value);
100+
}
101+
97102
function hasNamedValueField(
98103
field: FieldConfig,
99104
): field is Extract<FieldConfig, { name: string }> {
@@ -795,6 +800,17 @@ export function FormShell({
795800
}, [fieldDefaultValuesFromStore, initialValues, reducerState.values]);
796801

797802
const frontendApiBaseUrl = getFrontendApiBaseUrl();
803+
const resolvedEndpointPath = useMemo(() => {
804+
const rawValue = form.endpointPath?.trim();
805+
if (!rawValue) {
806+
return undefined;
807+
}
808+
809+
const resolvedValue = resolveRuntimeContextString(rawValue, form.wpContext)
810+
.trim();
811+
812+
return resolvedValue || undefined;
813+
}, [form.endpointPath, form.wpContext]);
798814
const fileFields = useMemo(() => collectFileFields(fields), [fields]);
799815

800816
const emitFormEvent = useCallback(
@@ -863,6 +879,28 @@ export function FormShell({
863879

864880
const dispatchFrontendRequest = useCallback(
865881
async <TResponse,>(path: string, body: unknown): Promise<TResponse> => {
882+
const normalizedPath = path.trim();
883+
884+
if (isAbsoluteHttpUrl(normalizedPath)) {
885+
const recaptchaHeaders = await getRecaptchaHeaders();
886+
const response = await fetch(normalizedPath, {
887+
method: "POST",
888+
headers: {
889+
"Content-Type": "application/json",
890+
...recaptchaHeaders,
891+
},
892+
body: JSON.stringify(body ?? {}),
893+
credentials: "omit",
894+
});
895+
896+
if (!response.ok) {
897+
const text = await response.text();
898+
throw new Error(text || `Request failed (${response.status})`);
899+
}
900+
901+
return (await response.json()) as TResponse;
902+
}
903+
866904
const backend = await resolveBackend();
867905

868906
if (backend.available) {
@@ -875,7 +913,7 @@ export function FormShell({
875913
reason: "Form submission",
876914
},
877915
"frontend",
878-
path,
916+
normalizedPath,
879917
"POST",
880918
body,
881919
{},
@@ -891,7 +929,7 @@ export function FormShell({
891929

892930
const recaptchaHeaders = await getRecaptchaHeaders();
893931

894-
const response = await fetch(`${frontendApiBaseUrl}${path}`, {
932+
const response = await fetch(`${frontendApiBaseUrl}${normalizedPath}`, {
895933
method: "POST",
896934
headers: {
897935
"Content-Type": "application/json",
@@ -1411,9 +1449,9 @@ export function FormShell({
14111449

14121450
let response: FormSubmitResponse;
14131451

1414-
if (form.endpointPath) {
1452+
if (resolvedEndpointPath) {
14151453
response = await dispatchFrontendRequest<FormSubmitResponse>(
1416-
form.endpointPath,
1454+
resolvedEndpointPath,
14171455
submitRequest,
14181456
);
14191457
} else {
@@ -1497,6 +1535,7 @@ export function FormShell({
14971535
currentLanguage,
14981536
prepareSerializableValues,
14991537
requestViewScrollReset,
1538+
resolvedEndpointPath,
15001539
resumeDraftIdInput,
15011540
resumePasswordInput,
15021541
state.aiSuggestions,

blocks/src/runtime/reducer.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,19 +149,23 @@ export function formReducer(
149149
suggestions: [],
150150
},
151151
};
152-
case "SET_VALUE":
152+
case "SET_VALUE": {
153+
const nextValues = {
154+
...state.values,
155+
[action.name]: action.value,
156+
};
157+
153158
return {
154159
...state,
155-
values: {
156-
...state.values,
157-
[action.name]: action.value,
158-
},
160+
values: nextValues,
161+
evaluationValues: nextValues,
159162
errors: {
160163
...state.errors,
161164
[action.name]: undefined,
162165
},
163166
touched: new Set([...state.touched, action.name]),
164167
};
168+
}
165169
case "SET_ERRORS":
166170
return {
167171
...state,
@@ -250,6 +254,10 @@ export function formReducer(
250254
...state.values,
251255
...action.values,
252256
},
257+
evaluationValues: {
258+
...state.evaluationValues,
259+
...action.values,
260+
},
253261
errors,
254262
touched,
255263
};
@@ -259,6 +267,7 @@ export function formReducer(
259267
...state,
260268
status: "idle",
261269
values: action.values,
270+
evaluationValues: action.values,
262271
errors: {},
263272
touched: new Set(),
264273
message: action.message,
@@ -277,6 +286,7 @@ export function formReducer(
277286
...state,
278287
status: "idle",
279288
values: action.values,
289+
evaluationValues: action.values,
280290
errors: {},
281291
touched: new Set(),
282292
fieldStates: state.fieldStates,

blocks/src/runtime/validation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,10 +245,10 @@ function validateFieldValue(
245245
value: unknown,
246246
runtimeState?: RuntimeFieldStateMap[string],
247247
): string | undefined {
248-
if (!("label" in field)) return undefined;
248+
const fieldRecord = field as unknown as Record<string, unknown>;
249249
const fieldLabel =
250-
typeof field.label === "string" && field.label.trim().length > 0
251-
? field.label
250+
typeof fieldRecord.label === "string" && fieldRecord.label.trim().length > 0
251+
? fieldRecord.label
252252
: I18n.get("Field") || "Field";
253253

254254
const isRequired = Boolean(
@@ -579,7 +579,7 @@ export function validateField(
579579
}
580580

581581
// Additional type guard to ensure field has required properties
582-
if (!("name" in field) || !("label" in field)) {
582+
if (!("name" in field)) {
583583
return undefined;
584584
}
585585

0 commit comments

Comments
 (0)