Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions bin/create-release-zip.sh
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@ for dir in "${REQUIRED_DIRS[@]}"; do
cp -r "$dir" "$PLUGIN_DIR/"
done

# Remove development-only assets from the staged copy:
# - assets/src: uncompiled sources, never loaded at runtime
# - *.map files under assets/build: source maps for debugging only
# Note: unminified (non-.min) build files are kept on purpose — they are
# loaded at runtime when SCF_DEVELOPMENT_MODE is enabled.
# Note: assets/inc/select2/3 is kept on purpose — the legacy 'select2_version'
# setting can still select version 3 at runtime.
echo "Removing development-only asset files..."
rm -rf "$PLUGIN_DIR/assets/src"
find "$PLUGIN_DIR/assets/build" -type f -name "*.map" -delete

# Install production dependencies
echo "Installing production dependencies..."
composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist
Expand Down
68 changes: 67 additions & 1 deletion includes/acf-field-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,14 +162,80 @@ function acf_get_field_post( $id = 0 ) {

// Check $post_id and return the post when possible.
if ( $post_id ) {
return get_post( $post_id );
$post = get_post( $post_id );

// Prime sibling field lookups to avoid one query per field.
if ( $post && $post->post_parent ) {
_acf_prime_sibling_field_posts( $post );
}

return $post;
}
}

// Return false by default.
return false;
}

/**
* Primes the field key lookup cache for all sibling fields of the given field post.
*
* Resolving a field key normally runs one query per field. When a field belongs
* to a field group, all fields of that group are loaded in a single (cached)
* query instead, so subsequent sibling key lookups avoid further queries.
*
* @since SCF 6.8.9
*
* @param WP_Post $post The field post object.
* @return void
*/
function _acf_prime_sibling_field_posts( $post ) {
static $primed = array();

// Bail early if this parent was already primed during this request.
if ( isset( $primed[ $post->post_parent ] ) ) {
return;
}

/**
* Filters whether sibling field lookups are primed when a field is loaded by key or name.
*
* Priming bypasses the per-key query (and any filters applied to it), so
* plugins which filter field queries per language/context can opt out.
*
* @since SCF 6.8.9
*
* @param boolean $prime True to prime sibling field lookups. Default true.
*/
if ( ! apply_filters( 'acf/prime_field_group_fields', true ) ) {
return;
}

// Only prime when the parent is a field group.
$parent = get_post( $post->post_parent );
if ( ! $parent || 'acf-field-group' !== $parent->post_type ) {
return;
}

$primed[ $post->post_parent ] = true;

// Load all fields of the group in a single (cached) query.
$raw_fields = acf_get_raw_fields( $parent->ID );
foreach ( $raw_fields as $raw_field ) {
$field_post = get_post( $raw_field['ID'] );

// Only prime published fields; a per-key query would not match other statuses.
if ( ! $field_post || 'publish' !== $field_post->post_status ) {
continue;
}

$cache_key = acf_cache_key( "acf_get_field_post:key:{$raw_field['key']}" );
if ( false === wp_cache_get( $cache_key, 'secure-custom-fields' ) ) {
wp_cache_set( $cache_key, $field_post->ID, 'secure-custom-fields' );
}
}
}

/**
* acf_is_field_key
*
Expand Down
41 changes: 41 additions & 0 deletions includes/acf-meta-functions.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
<?php
/**
* SCF meta functions.
*
* @package wordpress/secure-custom-fields
*/

// Register store for caching option meta queries. Options are site-specific.
acf_register_store( 'option-meta' )->prop( 'multisite', true );

/**
* Returns an array of "ACF only" meta for the given post_id.
Expand Down Expand Up @@ -56,6 +64,12 @@ function acf_get_option_meta( $prefix = '' ) {
// Globals.
global $wpdb;

// Check store. Invalidated by _acf_flush_option_meta_cache() on option writes.
$store = acf_get_store( 'option-meta' );
if ( $store->has( $prefix ) ) {
return $store->get( $prefix );
}

// Vars.
$meta = array();

Expand Down Expand Up @@ -85,10 +99,37 @@ function acf_get_option_meta( $prefix = '' ) {
$meta[ substr( $row['option_name'], $len ) ][] = $row['option_value'];
}

// Cache results for repeat calls during this request.
$store->set( $prefix, $meta );

// Return results.
return $meta;
}

/**
* Flushes the cached option meta for any prefix matching the written option.
*
* Option values are written via update_option()/delete_option() from several
* code paths, so the core option actions are used to invalidate the cache.
*
* @since SCF 6.8.9
*
* @param string $option The name of the option being added, updated or deleted.
* @return void
*/
function _acf_flush_option_meta_cache( $option ) {
$store = acf_get_store( 'option-meta' );

foreach ( array_keys( $store->get_data() ) as $prefix ) {
if ( 0 === strpos( $option, "{$prefix}_" ) || 0 === strpos( $option, "_{$prefix}_" ) ) {
$store->remove( $prefix );
}
}
}
add_action( 'added_option', '_acf_flush_option_meta_cache' );
add_action( 'updated_option', '_acf_flush_option_meta_cache' );
add_action( 'deleted_option', '_acf_flush_option_meta_cache' );

/**
* Retrieves specific metadata from the database.
*
Expand Down
9 changes: 6 additions & 3 deletions includes/admin/admin-commands.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
* @since SCF 6.5.0
*/
function acf_commands_init() {
// Ensure we only load our commands where the WordPress commands API is available.
if ( ! wp_script_is( 'wp-commands', 'registered' ) ) {
// Ensure we only load our commands on screens where the command palette itself loads.
// Core enqueues 'wp-commands' in the block/site editors (and on all admin screens
// since WP 6.9 via wp_enqueue_command_palette_assets()); elsewhere there is no palette.
if ( ! wp_script_is( 'wp-commands', 'enqueued' ) ) {
return;
}

Expand All @@ -42,4 +44,5 @@ function acf_commands_init() {
}
}

add_action( 'admin_enqueue_scripts', 'acf_commands_init' );
// Priority 20 ensures this runs after core has enqueued the command palette assets (priority 10).
add_action( 'admin_enqueue_scripts', 'acf_commands_init', 20 );
31 changes: 24 additions & 7 deletions includes/admin/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ public function admin_menu() {
*/
public function admin_enqueue_scripts() {
wp_enqueue_style( 'acf-global' );

// Only load the escaped HTML notice assets when the notice will render.
if ( ! $this->should_show_escaped_html_notice() ) {
return;
}

wp_enqueue_script( 'acf-escaped-html-notice' );

wp_localize_script(
Expand Down Expand Up @@ -141,26 +147,37 @@ public function maybe_show_import_from_cptui_notice() {
* @since ACF 6.2.5
*/
public function maybe_show_escaped_html_notice() {
// Notice for when HTML has already been escaped.
if ( $this->should_show_escaped_html_notice() ) {
acf_get_view( 'escaped-html-notice', array( 'acf_escaped' => _acf_get_escaped_html_log() ) );
}
}

/**
* Checks if the escaped unsafe HTML notice should be rendered.
*
* @since SCF 6.8.9
*
* @return boolean
*/
private function should_show_escaped_html_notice() {
// Only show to editors and above.
if ( ! current_user_can( 'edit_others_posts' ) ) {
return;
return false;
}

// Allow opting-out of the notice.
if ( apply_filters( 'acf/admin/prevent_escaped_html_notice', false ) ) {
return;
return false;
}

if ( get_option( 'acf_escaped_html_notice_dismissed' ) ) {
return;
return false;
}

$escaped = _acf_get_escaped_html_log();

// Notice for when HTML has already been escaped.
if ( ! empty( $escaped ) ) {
acf_get_view( 'escaped-html-notice', array( 'acf_escaped' => $escaped ) );
}
return ! empty( $escaped );
}

/**
Expand Down
10 changes: 5 additions & 5 deletions includes/api/api-helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -1028,12 +1028,12 @@ function acf_decode_taxonomy_term( $value ) {
// allow for term_id (Used by ACF v4)
if ( is_numeric( $data['term'] ) ) {

// global
global $wpdb;

// find taxonomy
// find taxonomy (uses the term cache instead of a direct query)
if ( ! $data['taxonomy'] ) {
$data['taxonomy'] = $wpdb->get_var( $wpdb->prepare( "SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d LIMIT 1", $data['term'] ) );
$term_object = get_term( (int) $data['term'] );
if ( $term_object instanceof WP_Term ) {
$data['taxonomy'] = $term_object->taxonomy;
}
}

// find term (may have numeric slug '123')
Expand Down
26 changes: 22 additions & 4 deletions includes/assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class ACF_Assets {
*/
public function __construct() {
add_action( 'init', array( $this, 'register_scripts' ) );
add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
}

/**
Expand Down Expand Up @@ -115,7 +116,7 @@ public function register_scripts() {
'base' => 'assets/build/js/%s' . $suffix . '.js',
);
$css_path_patterns = array(
'pro' => 'assets/build/css/pro/%s.css',
'pro' => 'assets/build/css/pro/%s' . $suffix . '.css',
'base' => 'assets/build/css/%s' . $suffix . '.css',
);
$asset_path_patterns = array(
Expand Down Expand Up @@ -493,6 +494,24 @@ public function enqueue_uploader() {
do_action( 'acf/enqueue_uploader' );
}

/**
* Enqueues scripts that should only load in the block editor.
*
* The JS block bindings layer relies on block editor APIs and pulls in
* the block editor script stack, so it must not load on classic admin
* screens or front-end forms.
*
* @since SCF 6.8.9
*
* @return void
*/
public function enqueue_block_editor_assets() {
// When the datastore is enabled, the bindings layer is handled by SCF\Blocks\Bindings_Editor instead.
if ( ! acf_is_using_datastore() ) {
wp_enqueue_script( 'scf-bindings' );
}
}

/**
* Enqueues and localizes scripts.
*
Expand Down Expand Up @@ -551,9 +570,8 @@ public function enqueue_scripts() {

// @todo integrate into the above. Previously, they were simply hooked into the hook below.
wp_enqueue_script( 'acf-pro-input' );
wp_enqueue_script( 'acf-pro-ui-options-page' );
if ( ! acf_is_using_datastore() ) {
wp_enqueue_script( 'scf-bindings' );
if ( is_admin() ) {
wp_enqueue_script( 'acf-pro-ui-options-page' );
}
wp_enqueue_style( 'acf-pro-input' );

Expand Down
10 changes: 10 additions & 0 deletions includes/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,11 @@ function acf_get_empty_block_form_html( $block_name ) {
*/
function acf_parse_save_blocks( $text = '' ) {

// Bail early if no ACF block types are registered.
if ( ! acf_get_block_types() ) {
return $text;
}

// Search text for dynamic blocks and modify attrs.
return addslashes(
preg_replace_callback(
Expand Down Expand Up @@ -1910,6 +1915,11 @@ function acf_add_block_meta_values( $block, $post_id ) {
* @return void
*/
function acf_save_block_meta_values( $post_id, $post ) {
// Bail early if no ACF block types are registered.
if ( ! acf_get_block_types() ) {
return;
}

$meta_values = acf_get_block_meta_values_to_save( $post->post_content );

if ( empty( $meta_values ) ) {
Expand Down
11 changes: 10 additions & 1 deletion includes/datastore.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,17 @@
* @return boolean
*/
function acf_is_using_datastore() {
// The WordPress version cannot change during a request, so compare it once.
// The filter below is intentionally not memoized as callbacks may be added
// or removed at runtime.
static $wp_supports_datastore = null;

if ( null === $wp_supports_datastore ) {
$wp_supports_datastore = version_compare( get_bloginfo( 'version' ), '6.7', '>=' );
}

// Bail if not on WordPress 6.7+.
if ( ! version_compare( get_bloginfo( 'version' ), '6.7', '>=' ) ) {
if ( ! $wp_supports_datastore ) {
return false;
}

Expand Down
Loading
Loading