Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions system/handlers/dbmigrations/WebflowStepOrphanedRecordsFix.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @feature webflow
*/
component {

property name="sqlRunner" inject="SqlRunner";

private void function run() {
var dsn = getPresideObject( "webflow_configuration" ).getDsn();

// Step 1: Update orphaned steps to point to correct configuration
// These are steps where the step's site doesn't match the configuration's site
sqlRunner.runSql(
dsn = dsn
, sql = "
UPDATE webflow_configuration_step wcs
INNER JOIN webflow_configuration wc_current ON wc_current.id = wcs.webflow
INNER JOIN webflow_configuration wc_correct ON wc_correct.webflow_id = wc_current.webflow_id
AND wc_correct.site = wcs.site
AND wc_correct.is_singleton = 1
AND wc_correct.instance_ref IS NULL
SET wcs.webflow = wc_correct.id
WHERE wc_current.site != wcs.site
AND wc_current.is_singleton = 1
AND wc_current.instance_ref IS NULL
"
);

// Step 2: Delete duplicate steps (keep ones pointing to correct config)
// This handles cases where both old orphaned and new correct steps exist
sqlRunner.runSql(
dsn = dsn
, sql = "
DELETE wcs_old FROM webflow_configuration_step wcs_old
INNER JOIN webflow_configuration_step wcs_new ON wcs_new.step_id = wcs_old.step_id
AND wcs_new.site = wcs_old.site
AND wcs_new.id != wcs_old.id
INNER JOIN webflow_configuration wc_old ON wc_old.id = wcs_old.webflow
INNER JOIN webflow_configuration wc_new ON wc_new.id = wcs_new.webflow
WHERE wc_old.webflow_id = wc_new.webflow_id
AND wc_old.site != wcs_old.site
AND wc_new.site = wcs_new.site
"
);
}

}
10 changes: 8 additions & 2 deletions system/services/webflow/WebflowConfigurationService.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -779,16 +779,22 @@ component {
}

private struct function _getExistingSingletonFlowForStartupCheck( required string webflowId ) {
var currentSiteId = $getRequestContext().getSiteId();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think we need to consider the case where the application isn't had the sites feature enabled


if ( !StructKeyExists( request, "_webflowSingletonsStartupCache" ) ) {
request._webflowSingletonsStartupCache = {};
}

if ( !StructKeyExists( request._webflowSingletonsStartupCache, currentSiteId ) ) {
request._webflowSingletonsStartupCache[ currentSiteId ] = {};

var rawflows = $getPresideObject( "webflow_configuration" ).selectData( filter="instance_ref is null" );
for( var f in rawFlows ) {
request._webflowSingletonsStartupCache[ f.webflow_id ] = f;
request._webflowSingletonsStartupCache[ currentSiteId ][ f.webflow_id ] = f;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Site-aware cache populates with all sites' data

The fix creates a site-specific cache bucket at request._webflowSingletonsStartupCache[ currentSiteId ], but the selectData query has no site filter - it fetches configurations from all sites. When multiple sites have configurations with the same webflow_id, the loop overwrites entries and the wrong site's configuration ends up cached. The query needs a site filter or the loop needs to check f.site == currentSiteId before storing.

Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explicit site check isn't needed because Preside's site tenancy handles filtering automatically.
The webflow_configuration object is site-tenanted (@tenant site). When selectData() is called without bypassTenants, Preside filters by the current site context set by event.setSite() in the interceptor (line 126 of PresideCfFlowInterceptors.cfc).
So the query at line 791 only returns records for the current site, and all records in rawFlows are already for currentSiteId.
The fix is making the cache site-aware by using currentSiteId as a key in the cache structure (lines 788-796). This prevents cross-site data reuse—the original bug—without needing an explicit site filter in the loop.
If tenancy weren't working, we'd see broader issues across the system, not just this method.

}
}

return request._webflowSingletonsStartupCache[ arguments.webflowId ] ?: {};
return request._webflowSingletonsStartupCache[ currentSiteId ][ arguments.webflowId ] ?: {};
}

private struct function _getExistingNonSingletonFlowForStartupCheck( required string webflowId, required string instanceRef, string siteId="" ) {
Expand Down