plugin updates

This commit is contained in:
Tony Volpe
2024-10-29 13:49:07 -04:00
parent 66268c4512
commit 9000316050
41 changed files with 916 additions and 570 deletions

View File

@@ -0,0 +1,495 @@
<?php
/**
* The ACF Update Class, responsible for talking to the connect API server and injecting PRO's update data into WordPress.
*
* @package ACF
*/
namespace ACF;
use WP_Error;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'Updater' ) ) {
/**
* class for handling API services.
*/
class Updater {
/**
* The Updater version
*
* @var string
*/
public $version = '3.0';
/**
* The array of registered plugins
*
* @var array
*/
public $plugins = array();
/**
* Counts the number of plugin update checks
*
* @var integer
*/
public $checked = 0;
/**
* Sets up the class functionality.
*
* @since 5.0.0
*/
public function __construct() {
// disable showing PRO updates if show updates is hidden.
if ( acf_is_pro() && ! acf_pro_is_updates_page_visible() ) {
return;
}
// append update information to transient.
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'modify_plugins_transient' ), 10, 1 );
// modify plugin data visible in the 'View details' popup.
add_filter( 'plugins_api', array( $this, 'modify_plugin_details' ), 10, 3 );
}
/**
* Registeres a plugin for updates.
*
* @since 5.5.10
*
* @param array $plugin The plugin array.
* @return void
*/
public function add_plugin( $plugin ) {
// validate.
$plugin = wp_parse_args(
$plugin,
array(
'id' => '',
'key' => '',
'slug' => '',
'basename' => '',
'version' => '',
)
);
// Check if is_plugin_active() function exists. This is required on the front end of the
// site, since it is in a file that is normally only loaded in the admin.
if ( ! function_exists( 'is_plugin_active' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
// add if is active plugin (not included in theme).
if ( is_plugin_active( $plugin['basename'] ) ) {
$this->plugins[ $plugin['basename'] ] = $plugin;
}
}
/**
* Returns a registered plugin for the give key and value.
*
* @since 5.7.2
*
* @param string $key The array key to compare.
* @param string $value The value to compare against.
* @return array|false
*/
public function get_plugin_by( $key = '', $value = null ) {
foreach ( $this->plugins as $plugin ) {
if ( $plugin[ $key ] === $value ) {
return $plugin;
}
}
return false;
}
/**
* Makes a request to the ACF connect server.
*
* @since 5.5.10
*
* @param string $endpoint The API endpoint.
* @param array $body The body to post.
* @return (array|string|WP_Error)
*/
public function request( $endpoint = '', $body = null ) {
$site_url = acf_get_home_url();
if ( empty( $site_url ) || ! is_string( $site_url ) ) {
$site_url = '';
}
$headers = array(
'X-ACF-Version' => ACF_VERSION,
'X-ACF-URL' => $site_url,
);
$url = "https://connect.advancedcustomfields.com/$endpoint";
// Staging environment.
if ( defined( 'ACF_DEV_API' ) && ACF_DEV_API ) {
$url = trailingslashit( ACF_DEV_API ) . $endpoint;
acf_log( $url, $body );
}
// Determine URL.
if ( acf_is_pro() ) {
$license_key = acf_pro_get_license_key();
if ( empty( $license_key ) || ! is_string( $license_key ) ) {
$license_key = '';
}
$headers['X-ACF-License'] = $license_key;
$headers['X-ACF-Plugin'] = 'pro';
} else {
$headers['X-ACF-Plugin'] = 'acf';
}
// Make request.
$raw_response = wp_remote_post(
$url,
array(
'timeout' => 20,
'body' => $body,
'headers' => $headers,
)
);
// Handle response error.
if ( is_wp_error( $raw_response ) ) {
return $raw_response;
// Handle http error.
} elseif ( wp_remote_retrieve_response_code( $raw_response ) !== 200 ) {
return new WP_Error( 'server_error', wp_remote_retrieve_response_message( $raw_response ) );
}
// Decode JSON response.
$json = json_decode( wp_remote_retrieve_body( $raw_response ), true );
// Allow non json value.
if ( $json === null ) {
return wp_remote_retrieve_body( $raw_response );
}
return $json;
}
/**
* Returns update information for the given plugin id.
*
* @since 5.5.10
*
* @param string $id The plugin id such as 'pro'.
* @param boolean $force_check Bypasses cached result. Defaults to false.
* @return array|WP_Error
*/
public function get_plugin_info( $id = '', $force_check = false ) {
$transient_name = 'acf_plugin_info_' . $id;
// check cache but allow for $force_check override.
if ( ! $force_check ) {
$transient = get_transient( $transient_name );
if ( $transient !== false ) {
return $transient;
}
}
$response = $this->request( 'v2/plugins/get-info?p=' . $id );
// convert string (misc error) to WP_Error object.
if ( is_string( $response ) ) {
$response = new WP_Error( 'server_error', esc_html( $response ) );
}
// allow json to include expiration but force minimum and max for safety.
$expiration = $this->get_expiration( $response, DAY_IN_SECONDS );
// update transient.
set_transient( $transient_name, $response, $expiration );
return $response;
}
/**
* Returns specific data from the 'update-check' response.
*
* @since 5.7.2
*
* @param string $basename The plugin basename.
* @param boolean $force_check Bypasses cached result. Defaults to false.
* @return array|false
*/
public function get_plugin_update( $basename = '', $force_check = false ) {
// get updates.
$updates = $this->get_plugin_updates( $force_check );
// check for and return update.
if ( is_array( $updates ) && isset( $updates['plugins'][ $basename ] ) ) {
return $updates['plugins'][ $basename ];
}
return false;
}
/**
* Checks if an update is available, but can't be updated to.
*
* @since 6.2.1
*
* @param string $basename The plugin basename.
* @param boolean $force_check Bypasses cached result. Defaults to false.
* @return array|false
*/
public function get_no_update( $basename = '', $force_check = false ) {
// get updates.
$updates = $this->get_plugin_updates( $force_check );
// check for and return update.
if ( is_array( $updates ) && isset( $updates['no_update'][ $basename ] ) ) {
return $updates['no_update'][ $basename ];
}
return false;
}
/**
* Checks for plugin updates.
*
* @since 5.6.9
* @since 5.7.2 Added 'checked' comparison
*
* @param boolean $force_check Bypasses cached result. Defaults to false.
* @return array|WP_Error.
*/
public function get_plugin_updates( $force_check = false ) {
$transient_name = 'acf_plugin_updates';
// Don't call our site if no plugins have registered updates.
if ( empty( $this->plugins ) ) {
return array();
}
// Construct array of 'checked' plugins.
// Sort by key to avoid detecting change due to "include order".
$checked = array();
foreach ( $this->plugins as $basename => $plugin ) {
$checked[ $basename ] = $plugin['version'];
}
ksort( $checked );
// $force_check prevents transient lookup.
if ( ! $force_check ) {
$transient = get_transient( $transient_name );
// If cached response was found, compare $transient['checked'] against $checked and ignore if they don't match (plugins/versions have changed).
if ( is_array( $transient ) ) {
$transient_checked = isset( $transient['checked'] ) ? $transient['checked'] : array();
if ( wp_json_encode( $checked ) !== wp_json_encode( $transient_checked ) ) {
$transient = false;
}
}
if ( $transient !== false ) {
return $transient;
}
}
$post = array(
'plugins' => wp_json_encode( $this->plugins ),
'wp' => wp_json_encode(
array(
'wp_name' => get_bloginfo( 'name' ),
'wp_url' => acf_get_home_url(),
'wp_version' => get_bloginfo( 'version' ),
'wp_language' => get_bloginfo( 'language' ),
'wp_timezone' => get_option( 'timezone_string' ),
'wp_multisite' => (int) is_multisite(),
'php_version' => PHP_VERSION,
)
),
'acf' => wp_json_encode(
array(
'acf_version' => get_option( 'acf_version' ),
'acf_pro' => acf_is_pro(),
'block_count' => function_exists( 'acf_pro_get_registered_block_count' ) ? acf_pro_get_registered_block_count() : 0,
)
),
);
// Check update from connect.
$response = $this->request( 'v2/plugins/update-check', $post );
// Append checked reference.
if ( is_array( $response ) ) {
$response['checked'] = $checked;
if ( isset( $response['license_status'] ) && function_exists( 'acf_pro_update_license_status' ) ) {
acf_pro_update_license_status( $response['license_status'] );
unset( $response['license_status'] );
}
}
// Allow json to include expiration but force minimum and max for safety.
$expiration = $this->get_expiration( $response );
// Update transient and return.
set_transient( $transient_name, $response, $expiration );
return $response;
}
/**
* This function safely gets the expiration value from a response.
*
* @since 5.6.9
*
* @param mixed $response The response from the server. Default false.
* @param integer $min The minimum expiration limit. Default 3 hours.
* @param integer $max The maximum expiration limit. Default 7 days.
* @return integer
*/
public function get_expiration( $response = false, $min = 10800, $max = 604800 ) {
$expiration = 0;
// Check possible error conditions.
if ( is_wp_error( $response ) || is_string( $response ) ) {
return 15 * MINUTE_IN_SECONDS;
}
// Use the server requested expiration if present.
if ( is_array( $response ) && isset( $response['expiration'] ) ) {
$expiration = (int) $response['expiration'];
}
// Use the minimum if neither check matches, or ensure the server expiration isn't lower than our minimum.
if ( $expiration < $min ) {
return $min;
}
// Ensure the server expiration isn't higher than our max.
if ( $expiration > $max ) {
return $max;
}
return $expiration;
}
/**
* Deletes transients and allows a fresh lookup.
*
* @since 5.5.10
*/
public function refresh_plugins_transient() {
delete_site_transient( 'update_plugins' );
delete_transient( 'acf_plugin_updates' );
}
/**
* Called when WP updates the 'update_plugins' site transient. Used to inject ACF plugin update info.
*
* @since 5.0.0
*
* @param object $transient The current transient value.
* @return object $transient The modified transient value.
*/
public function modify_plugins_transient( $transient ) {
// Bail early if no response (error).
if ( ! isset( $transient->response ) ) {
return $transient;
}
// Ensure no_update is set for back compat.
if ( ! isset( $transient->no_update ) ) {
$transient->no_update = array();
}
// Force-check (only once).
$force_check = ( $this->checked == 0 ) ? ! empty( $_GET['force-check'] ) : false; // phpcs:ignore -- False positive, value not used.
// Fetch updates (this filter is called multiple times during a single page load).
$updates = $this->get_plugin_updates( $force_check );
// Append ACF pro plugins.
if ( is_array( $updates ) ) {
if ( ! empty( $updates['plugins'] ) ) {
foreach ( $updates['plugins'] as $basename => $update ) {
$transient->response[ $basename ] = (object) $update;
}
}
if ( ! empty( $updates['no_update'] ) ) {
foreach ( $updates['no_update'] as $basename => $update ) {
$transient->no_update[ $basename ] = (object) $update;
}
}
}
++$this->checked;
return $transient;
}
/**
* Returns the plugin data visible in the 'View details' popup
*
* @since 5.0.0
*
* @param object $result The current result of plugin data.
* @param string $action The action being performed.
* @param object $args Data about the plugin being retried.
* @return $result
*/
public function modify_plugin_details( $result, $action = null, $args = null ) {
$plugin = false;
// Only for 'plugin_information' action.
if ( $action !== 'plugin_information' ) {
return $result;
}
// Find plugin via slug.
$plugin = $this->get_plugin_by( 'slug', $args->slug );
if ( ! $plugin ) {
return $result;
}
// Get data from connect or cache.
$response = $this->get_plugin_info( $plugin['id'] );
// Bail early if no response.
if ( ! is_array( $response ) ) {
return $result;
}
// Remove tags (different context).
unset( $response['tags'] );
// Convert to object.
$response = (object) $response;
$sections = array(
'description' => '',
'installation' => '',
'changelog' => '',
'upgrade_notice' => '',
);
foreach ( $sections as $k => $v ) {
$sections[ $k ] = $response->$k;
}
$response->sections = $sections;
return $response;
}
}
}

View File

@@ -0,0 +1,2 @@
<?php
// There are many ways to WordPress.

View File

@@ -1,17 +0,0 @@
<?php
namespace ACF\Upgrades;
/**
* Initialize the checking for plugin updates for ACF non-PRO.
*/
function check_for_acf_upgrades() {
$properties = array(
// This must match the key in "https://wpe-plugin-updates.wpengine.com/plugins.json".
'plugin_slug' => 'advanced-custom-fields',
'plugin_basename' => ACF_BASENAME,
);
new \ACF\Upgrades\PluginUpdater( $properties );
}
add_action( 'admin_init', __NAMESPACE__ . '\check_for_acf_upgrades' );

View File

@@ -312,6 +312,15 @@ if ( ! class_exists( 'ACF_Admin_Post_Type' ) ) :
$_POST['acf_post_type']['ID'] = $post_id;
$_POST['acf_post_type']['title'] = isset( $_POST['acf_post_type']['labels']['name'] ) ? $_POST['acf_post_type']['labels']['name'] : '';
if ( ! acf_get_setting( 'enable_meta_box_cb_edit' ) ) {
$_POST['acf_post_type']['register_meta_box_cb'] = '';
$existing_post = acf_maybe_unserialize( $post->post_content );
if ( ! empty( $existing_post['register_meta_box_cb'] ) ) {
$_POST['acf_post_type']['register_meta_box_cb'] = $existing_post['register_meta_box_cb'];
}
}
// Save the post type.
acf_update_internal_post_type( $_POST['acf_post_type'], $this->post_type ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Validated in verify_save_post
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

View File

@@ -314,6 +314,29 @@ if ( ! class_exists( 'ACF_Admin_Taxonomy' ) ) :
$_POST['acf_taxonomy']['ID'] = $post_id;
$_POST['acf_taxonomy']['title'] = isset( $_POST['acf_taxonomy']['labels']['name'] ) ? $_POST['acf_taxonomy']['labels']['name'] : '';
if ( ! acf_get_setting( 'enable_meta_box_cb_edit' ) ) {
$_POST['acf_taxonomy']['meta_box_cb'] = '';
$_POST['acf_taxonomy']['meta_box_sanitize_cb'] = '';
if ( ! empty( $_POST['acf_taxonomy']['meta_box'] ) && 'custom' === $_POST['acf_taxonomy']['meta_box'] ) {
$_POST['acf_taxonomy']['meta_box'] = 'default';
}
$existing_post = acf_maybe_unserialize( $post->post_content );
if ( ! empty( $existing_post['meta_box'] ) ) {
$_POST['acf_taxonomy']['meta_box'] = $existing_post['meta_box'];
}
if ( ! empty( $existing_post['meta_box_cb'] ) ) {
$_POST['acf_taxonomy']['meta_box_cb'] = $existing_post['meta_box_cb'];
}
if ( ! empty( $existing_post['meta_box_sanitize_cb'] ) ) {
$_POST['acf_taxonomy']['meta_box_sanitize_cb'] = $existing_post['meta_box_sanitize_cb'];
}
}
// Save the taxonomy.
acf_update_internal_post_type( $_POST['acf_taxonomy'], $this->post_type ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Validated in verify_save_post
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

View File

@@ -319,6 +319,7 @@ if ( isset( $field['conditional_logic'] ) && is_array( $field['conditional_logic
?>
<div class="acf-field-settings-footer">
<a class="button close-field edit-field" title="<?php esc_attr_e( 'Close Field', 'acf' ); ?>" href="#"><?php esc_html_e( 'Close Field', 'acf' ); ?></a>
<a class="acf-btn acf-btn-secondary close-add-field" title="<?php esc_attr_e( 'Close and Add Field', 'acf' ); ?>" href="#"><?php esc_html_e( 'Close and Add Field', 'acf' ); ?></a>
</div>
</div>
</div>

View File

@@ -98,7 +98,7 @@ if ( $is_subfield ) {
<ul class="acf-hl acf-tfoot">
<li class="acf-fr">
<a href="#" class="acf-btn acf-btn-secondary add-field"><i class="acf-icon acf-icon-plus"></i><?php esc_html_e( 'Add Field', 'acf' ); ?></a>
<a href="#" class="acf-btn acf-btn-sm add-field"><i class="acf-icon acf-icon-plus"></i><?php esc_html_e( 'Add Field', 'acf' ); ?></a>
</li>
</ul>

View File

@@ -838,24 +838,39 @@ foreach ( acf_get_combined_post_type_settings_tabs() as $tab_key => $tab_label )
'field'
);
acf_render_field_wrap(
array(
'type' => 'text',
'name' => 'register_meta_box_cb',
'key' => 'register_meta_box_cb',
'prefix' => 'acf_post_type',
'value' => $acf_post_type['register_meta_box_cb'],
'label' => __( 'Custom Meta Box Callback', 'acf' ),
'instructions' => __( 'A PHP function name to be called when setting up the meta boxes for the edit screen. For security, this callback will be executed in a special context without access to any superglobals like $_POST or $_GET.', 'acf' ),
'conditions' => array(
'field' => 'show_ui',
'operator' => '==',
'value' => '1',
$acf_enable_meta_box_cb_edit = acf_get_setting( 'enable_meta_box_cb_edit' );
$acf_meta_box_cb_instructions = __( 'A PHP function name to be called when setting up the meta boxes for the edit screen. For security, this callback will be executed in a special context without access to any superglobals like $_POST or $_GET.', 'acf' );
// Only show if user is allowed to update, or if it already has a value.
if ( $acf_enable_meta_box_cb_edit || ! empty( $acf_post_type['register_meta_box_cb'] ) ) {
if ( ! $acf_enable_meta_box_cb_edit ) {
if ( is_multisite() ) {
$acf_meta_box_cb_instructions .= ' ' . __( 'By default only super admin users can edit this setting.', 'acf' );
} else {
$acf_meta_box_cb_instructions .= ' ' . __( 'By default only admin users can edit this setting.', 'acf' );
}
}
acf_render_field_wrap(
array(
'type' => 'text',
'name' => 'register_meta_box_cb',
'key' => 'register_meta_box_cb',
'prefix' => 'acf_post_type',
'value' => $acf_post_type['register_meta_box_cb'],
'label' => __( 'Custom Meta Box Callback', 'acf' ),
'instructions' => $acf_meta_box_cb_instructions,
'readonly' => ! $acf_enable_meta_box_cb_edit,
'conditions' => array(
'field' => 'show_ui',
'operator' => '==',
'value' => '1',
),
),
),
'div',
'field'
);
'div',
'field'
);
}
acf_render_field_wrap(
array(

View File

@@ -745,6 +745,16 @@ foreach ( acf_get_combined_taxonomy_settings_tabs() as $tab_key => $tab_label )
$acf_tags_meta_box_text = __( 'Tags Meta Box', 'acf' );
$acf_categories_meta_box_text = __( 'Categories Meta Box', 'acf' );
$acf_default_meta_box_text = empty( $acf_taxonomy['hierarchical'] ) ? $acf_tags_meta_box_text : $acf_categories_meta_box_text;
$acf_enable_meta_box_cb_edit = acf_get_setting( 'enable_meta_box_cb_edit' );
$acf_meta_box_choices = array(
'default' => $acf_default_meta_box_text,
'custom' => __( 'Custom Meta Box', 'acf' ),
'disabled' => __( 'No Meta Box', 'acf' ),
);
if ( ! $acf_enable_meta_box_cb_edit && 'custom' !== $acf_taxonomy['meta_box'] ) {
unset( $acf_meta_box_choices['custom'] );
}
acf_render_field_wrap(
array(
@@ -757,11 +767,7 @@ foreach ( acf_get_combined_taxonomy_settings_tabs() as $tab_key => $tab_label )
'label' => __( 'Meta Box', 'acf' ),
'instructions' => __( 'Controls the meta box on the content editor screen. By default, the Categories meta box is shown for hierarchical taxonomies, and the Tags meta box is shown for non-hierarchical taxonomies.', 'acf' ),
'hide_search' => true,
'choices' => array(
'default' => $acf_default_meta_box_text,
'custom' => __( 'Custom Meta Box', 'acf' ),
'disabled' => __( 'No Meta Box', 'acf' ),
),
'choices' => $acf_meta_box_choices,
'data' => array(
'tags_meta_box' => __( 'Tags Meta Box', 'acf' ),
'categories_meta_box' => __( 'Categories Meta Box', 'acf' ),
@@ -794,54 +800,68 @@ foreach ( acf_get_combined_taxonomy_settings_tabs() as $tab_key => $tab_label )
)
);
acf_render_field_wrap(
array(
'type' => 'text',
'name' => 'meta_box_cb',
'key' => 'meta_box_cb',
'prefix' => 'acf_taxonomy',
'value' => $acf_taxonomy['meta_box_cb'],
'label' => __( 'Register Meta Box Callback', 'acf' ),
'instructions' => __( 'A PHP function name to be called to handle the content of a meta box on your taxonomy. For security, this callback will be executed in a special context without access to any superglobals like $_POST or $_GET.', 'acf' ),
'conditions' => array(
'field' => 'meta_box',
'operator' => '==',
'value' => 'custom',
),
),
'div',
'field'
);
if ( $acf_enable_meta_box_cb_edit || 'custom' === $acf_taxonomy['meta_box'] ) {
$acf_meta_box_cb_instructions = __( 'A PHP function name to be called to handle the content of a meta box on your taxonomy. For security, this callback will be executed in a special context without access to any superglobals like $_POST or $_GET.', 'acf' );
acf_render_field_wrap(
array(
'type' => 'text',
'name' => 'meta_box_sanitize_cb',
'key' => 'meta_box_sanitize_cb',
'prefix' => 'acf_taxonomy',
'value' => $acf_taxonomy['meta_box_sanitize_cb'],
'label' => __( 'Meta Box Sanitization Callback', 'acf' ),
'instructions' => __( 'A PHP function name to be called for sanitizing taxonomy data saved from a meta box.', 'acf' ),
'conditions' => array(
'field' => 'meta_box',
'operator' => '==',
'value' => 'custom',
),
),
'div',
'field'
);
if ( ! $acf_enable_meta_box_cb_edit ) {
if ( is_multisite() ) {
$acf_meta_box_cb_instructions .= ' ' . __( 'By default only super admin users can edit this setting.', 'acf' );
} else {
$acf_meta_box_cb_instructions .= ' ' . __( 'By default only admin users can edit this setting.', 'acf' );
}
}
acf_render_field_wrap(
array(
'type' => 'seperator',
'conditions' => array(
'field' => 'meta_box',
'operator' => '==',
'value' => 'custom',
acf_render_field_wrap(
array(
'type' => 'text',
'name' => 'meta_box_cb',
'key' => 'meta_box_cb',
'prefix' => 'acf_taxonomy',
'value' => $acf_taxonomy['meta_box_cb'],
'label' => __( 'Register Meta Box Callback', 'acf' ),
'instructions' => $acf_meta_box_cb_instructions,
'readonly' => ! $acf_enable_meta_box_cb_edit,
'conditions' => array(
'field' => 'meta_box',
'operator' => '==',
'value' => 'custom',
),
),
)
);
'div',
'field'
);
acf_render_field_wrap(
array(
'type' => 'text',
'name' => 'meta_box_sanitize_cb',
'key' => 'meta_box_sanitize_cb',
'prefix' => 'acf_taxonomy',
'value' => $acf_taxonomy['meta_box_sanitize_cb'],
'label' => __( 'Meta Box Sanitization Callback', 'acf' ),
'instructions' => __( 'A PHP function name to be called for sanitizing taxonomy data saved from a meta box.', 'acf' ),
'readonly' => ! $acf_enable_meta_box_cb_edit,
'conditions' => array(
'field' => 'meta_box',
'operator' => '==',
'value' => 'custom',
),
),
'div',
'field'
);
acf_render_field_wrap(
array(
'type' => 'seperator',
'conditions' => array(
'field' => 'meta_box',
'operator' => '==',
'value' => 'custom',
),
)
);
}
acf_render_field_wrap(
array(

View File

@@ -24,8 +24,9 @@ if ( ! class_exists( 'ACF_Ajax_Query_Users' ) ) :
return new WP_Error( 'acf_invalid_args', __( 'Invalid request args.', 'acf' ), array( 'status' => 404 ) );
}
$nonce = $request['nonce'];
$action = $request['field_key'];
$nonce = $request['nonce'];
$action = $request['field_key'];
$field_action = true;
if ( isset( $request['conditional_logic'] ) && true === (bool) $request['conditional_logic'] ) {
if ( ! acf_current_user_can_admin() ) {
@@ -33,11 +34,12 @@ if ( ! class_exists( 'ACF_Ajax_Query_Users' ) ) :
}
// Use the standard ACF admin nonce.
$nonce = '';
$action = '';
$nonce = '';
$action = '';
$field_action = false;
}
if ( ! acf_verify_ajax( $nonce, $action ) ) {
if ( ! acf_verify_ajax( $nonce, $action, $field_action ) ) {
return new WP_Error( 'acf_invalid_nonce', __( 'Invalid nonce.', 'acf' ), array( 'status' => 404 ) );
}

View File

@@ -687,16 +687,32 @@ function acf_verify_nonce( $value ) {
*
* @since 5.2.3
*
* @param string $nonce The nonce to check.
* @param string $action The action of the nonce.
* @param string $nonce The nonce to check.
* @param string $action The action of the nonce.
* @param boolean $action_is_field If the action is a field, modify the action to match validate the field type.
* @return boolean
*/
function acf_verify_ajax( $nonce = '', $action = '' ) {
function acf_verify_ajax( $nonce = '', $action = '', $action_is_field = false ) {
// Bail early if we don't have a nonce to check.
if ( empty( $nonce ) && empty( $_REQUEST['nonce'] ) ) {
return false;
}
// Build the action if we're trying to validate a specific field nonce.
if ( $action_is_field ) {
if ( ! acf_is_field_key( $action ) ) {
return false;
}
$field = acf_get_field( $action );
if ( empty( $field['type'] ) ) {
return false;
}
$action = 'acf_field_' . $field['type'] . '_' . $action;
}
$nonce_to_check = ! empty( $nonce ) ? $nonce : $_REQUEST['nonce']; // phpcs:ignore WordPress.Security -- We're verifying a nonce here.
$nonce_action = ! empty( $action ) ? $action : 'acf_nonce';
@@ -3974,3 +3990,20 @@ function acf_is_multisite_main_site() {
}
return false;
}
/**
* Allow filterable permissions metabox callbacks.
*
* @since 6.3.10
*
* @param boolean $enable_meta_box_cb_edit Can the current user edit metabox callbacks.
* @return boolean
*/
function acf_settings_enable_meta_box_cb_edit( $enable_meta_box_cb_edit ): bool {
if ( ! is_super_admin() ) {
return false;
}
return (bool) $enable_meta_box_cb_edit;
}
add_filter( 'acf/settings/enable_meta_box_cb_edit', 'acf_settings_enable_meta_box_cb_edit', 1 );

View File

@@ -1,251 +0,0 @@
<?php
/**
* The PluginUpdater class which can be used to pull plugin updates from a new location.
* @package advanced-custom-fields
*/
namespace ACF\Upgrades;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use stdClass;
/**
* The PluginUpdater class which can be used to pull plugin updates from a new location.
*/
class PluginUpdater {
/**
* The URL where the api is located.
* @var ApiUrl
*/
private $api_url;
/**
* The amount of time to wait before checking for new updates.
* @var CacheTime
*/
private $cache_time;
/**
* These properties are passed in when instantiating to identify the plugin and it's update location.
* @var Properties
*/
private $properties;
/**
* Get the class constructed.
*
* @param Properties $properties These properties are passed in when instantiating to identify the plugin and it's update location.
*/
public function __construct( $properties ) {
if (
// This must match the key in "https://wpe-plugin-updates.wpengine.com/plugins.json".
empty( $properties['plugin_slug'] ) ||
// This must be the result of calling plugin_basename( __FILE__ ); in the main plugin root file.
empty( $properties['plugin_basename'] )
) {
// If any of the values we require were not passed, throw a fatal.
error_log( 'WPE Secure Plugin Updater received a malformed request.' );
return;
}
$this->api_url = 'https://wpe-plugin-updates.wpengine.com/';
$this->cache_time = time() + HOUR_IN_SECONDS * 5;
$this->properties = $this->get_full_plugin_properties( $properties, $this->api_url );
if ( ! $this->properties ) {
return;
}
$this->register();
}
/**
* Get the full plugin properties, including the directory name, version, basename, and add a transient name.
*
* @param Properties $properties These properties are passed in when instantiating to identify the plugin and it's update location.
* @param ApiUrl $api_url The URL where the api is located.
*/
public function get_full_plugin_properties( $properties, $api_url ) {
$plugins = \get_plugins();
// Scan through all plugins installed and find the one which matches this one in question.
foreach ( $plugins as $plugin_basename => $plugin_data ) {
// Match using the passed-in plugin's basename.
if ( $plugin_basename === $properties['plugin_basename'] ) {
// Add the values we need to the properties.
$properties['plugin_dirname'] = dirname( $plugin_basename );
$properties['plugin_version'] = $plugin_data['Version'];
$properties['plugin_update_transient_name'] = 'wpesu-plugin-' . sanitize_title( $properties['plugin_dirname'] );
$properties['plugin_update_transient_exp_name'] = 'wpesu-plugin-' . sanitize_title( $properties['plugin_dirname'] ) . '-expiry';
$properties['plugin_manifest_url'] = trailingslashit( $api_url ) . trailingslashit( $properties['plugin_slug'] ) . 'info.json';
return $properties;
}
}
// No matching plugin was found installed.
return null;
}
/**
* Register hooks.
*
* @return void
*/
public function register() {
add_filter( 'plugins_api', array( $this, 'filter_plugin_update_info' ), 20, 3 );
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'filter_plugin_update_transient' ) );
}
/**
* Filter the plugin update transient to take over update notifications.
*
* @param object $transient The site_transient_update_plugins transient.
*
* @handles site_transient_update_plugins
* @return object
*/
public function filter_plugin_update_transient( $transient ) {
// No update object exists. Return early.
if ( empty( $transient ) ) {
return $transient;
}
$result = $this->fetch_plugin_info();
if ( false === $result ) {
return $transient;
}
$res = $this->parse_plugin_info( $result );
if ( version_compare( $this->properties['plugin_version'], $result->version, '<' ) ) {
$transient->response[ $res->plugin ] = $res;
$transient->checked[ $res->plugin ] = $result->version;
} else {
$transient->no_update[ $res->plugin ] = $res;
}
return $transient;
}
/**
* Filters the plugin update information.
*
* @param object $res The response to be modified for the plugin in question.
* @param string $action The action in question.
* @param object $args The arguments for the plugin in question.
*
* @handles plugins_api
* @return object
*/
public function filter_plugin_update_info( $res, $action, $args ) {
// Do nothing if this is not about getting plugin information.
if ( 'plugin_information' !== $action ) {
return $res;
}
// Do nothing if it is not our plugin.
if ( $this->properties['plugin_dirname'] !== $args->slug ) {
return $res;
}
$result = $this->fetch_plugin_info();
// Do nothing if we don't get the correct response from the server.
if ( false === $result ) {
return $res;
}
return $this->parse_plugin_info( $result );
}
/**
* Fetches the plugin update object from the WP Product Info API.
*
* @return object|false
*/
private function fetch_plugin_info() {
// Fetch cache first.
$expiry = get_option( $this->properties['plugin_update_transient_exp_name'], 0 );
$response = get_option( $this->properties['plugin_update_transient_name'] );
if ( empty( $expiry ) || time() > $expiry || empty( $response ) ) {
$response = wp_remote_get(
$this->properties['plugin_manifest_url'],
array(
'timeout' => 10,
'headers' => array(
'Accept' => 'application/json',
),
)
);
if (
is_wp_error( $response ) ||
200 !== wp_remote_retrieve_response_code( $response ) ||
empty( wp_remote_retrieve_body( $response ) )
) {
return false;
}
$response = wp_remote_retrieve_body( $response );
// Cache the response.
update_option( $this->properties['plugin_update_transient_exp_name'], $this->cache_time, false );
update_option( $this->properties['plugin_update_transient_name'], $response, false );
}
$decoded_response = json_decode( $response );
if ( json_last_error() !== JSON_ERROR_NONE ) {
return false;
}
return $decoded_response;
}
/**
* Parses the product info response into an object that WordPress would be able to understand.
*
* @param object $response The response object.
*
* @return stdClass
*/
private function parse_plugin_info( $response ) {
global $wp_version;
$res = new stdClass();
$res->name = $response->name;
$res->slug = $response->slug;
$res->version = $response->version;
$res->requires = $response->requires;
$res->download_link = $response->download_link;
$res->trunk = $response->download_link;
$res->new_version = $response->version;
$res->plugin = $this->properties['plugin_basename'];
$res->package = $response->download_link;
// Plugin information modal and core update table use a strict version comparison, which is weird.
// If we're genuinely not compatible with the point release, use our WP tested up to version.
// otherwise use exact same version as WP to avoid false positive.
$res->tested = 1 === version_compare( substr( $wp_version, 0, 3 ), $response->tested )
? $response->tested
: $wp_version;
$res->sections = array(
'description' => $response->sections->description,
'changelog' => $response->sections->changelog,
);
return $res;
}
}

View File

@@ -110,7 +110,7 @@ if ( ! class_exists( 'acf_field_oembed' ) ) :
)
);
if ( ! acf_verify_ajax( $args['nonce'], $args['field_key'] ) ) {
if ( ! acf_verify_ajax( $args['nonce'], $args['field_key'], true ) ) {
die();
}
@@ -169,7 +169,7 @@ if ( ! class_exists( 'acf_field_oembed' ) ) :
public function render_field( $field ) {
$atts = array(
'class' => 'acf-oembed',
'data-nonce' => wp_create_nonce( $field['key'] ),
'data-nonce' => wp_create_nonce( 'acf_field_' . $this->name . '_' . $field['key'] ),
);
if ( $field['value'] ) {

View File

@@ -81,7 +81,7 @@ if ( ! class_exists( 'acf_field_page_link' ) ) :
$key = '';
}
if ( ! acf_verify_ajax( $nonce, $key ) ) {
if ( ! acf_verify_ajax( $nonce, $key, ! $conditional_logic ) ) {
die();
}
@@ -392,7 +392,7 @@ if ( ! class_exists( 'acf_field_page_link' ) ) :
$field['ui'] = 1;
$field['ajax'] = 1;
$field['choices'] = array();
$field['nonce'] = wp_create_nonce( $field['key'] );
$field['nonce'] = wp_create_nonce( 'acf_field_' . $this->name . '_' . $field['key'] );
// populate choices if value exists
if ( ! empty( $field['value'] ) ) {

View File

@@ -76,7 +76,7 @@ if ( ! class_exists( 'acf_field_post_object' ) ) :
$key = '';
}
if ( ! acf_verify_ajax( $nonce, $key ) ) {
if ( ! acf_verify_ajax( $nonce, $key, ! $conditional_logic ) ) {
die();
}
@@ -314,7 +314,7 @@ if ( ! class_exists( 'acf_field_post_object' ) ) :
$field['type'] = 'select';
$field['ui'] = 1;
$field['ajax'] = 1;
$field['nonce'] = wp_create_nonce( $field['key'] );
$field['nonce'] = wp_create_nonce( 'acf_field_' . $this->name . '_' . $field['key'] );
$field['choices'] = array();
// load posts

View File

@@ -102,7 +102,7 @@ if ( ! class_exists( 'acf_field_relationship' ) ) :
$key = '';
}
if ( ! acf_verify_ajax( $nonce, $key ) ) {
if ( ! acf_verify_ajax( $nonce, $key, ! $conditional_logic ) ) {
die();
}
@@ -417,7 +417,7 @@ if ( ! class_exists( 'acf_field_relationship' ) ) :
'data-paged' => 1,
'data-post_type' => '',
'data-taxonomy' => '',
'data-nonce' => wp_create_nonce( $field['key'] ),
'data-nonce' => wp_create_nonce( 'acf_field_' . $this->name . '_' . $field['key'] ),
);
?>

View File

@@ -115,13 +115,19 @@ if ( ! class_exists( 'acf_field_select' ) ) :
$nonce = acf_request_arg( 'nonce', '' );
$key = acf_request_arg( 'field_key', '' );
$is_field_key = acf_is_field_key( $key );
// Back-compat for field settings.
if ( ! acf_is_field_key( $key ) ) {
if ( ! $is_field_key ) {
if ( ! acf_current_user_can_admin() ) {
die();
}
$nonce = '';
$key = '';
}
if ( ! acf_verify_ajax( $nonce, $key ) ) {
if ( ! acf_verify_ajax( $nonce, $key, $is_field_key ) ) {
die();
}
@@ -286,7 +292,7 @@ if ( ! class_exists( 'acf_field_select' ) ) :
$select['data-nonce'] = $field['nonce'];
}
if ( $field['ajax'] && empty( $field['nonce'] ) && acf_is_field_key( $field['key'] ) ) {
$select['data-nonce'] = wp_create_nonce( $field['key'] );
$select['data-nonce'] = wp_create_nonce( 'acf_field_' . $this->name . '_' . $field['key'] );
}
if ( ! empty( $field['hide_search'] ) ) {
$select['data-minimum-results-for-search'] = '-1';

View File

@@ -70,7 +70,7 @@ if ( ! class_exists( 'acf_field_taxonomy' ) ) :
$key = '';
}
if ( ! acf_verify_ajax( $nonce, $key ) ) {
if ( ! acf_verify_ajax( $nonce, $key, ! $conditional_logic ) ) {
die();
}
@@ -470,6 +470,8 @@ if ( ! class_exists( 'acf_field_taxonomy' ) ) :
// force value to array
$field['value'] = acf_get_array( $field['value'] );
$nonce = wp_create_nonce( 'acf_field_' . $this->name . '_' . $field['key'] );
// vars
$div = array(
'class' => 'acf-taxonomy-field',
@@ -477,7 +479,7 @@ if ( ! class_exists( 'acf_field_taxonomy' ) ) :
'data-ftype' => $field['field_type'],
'data-taxonomy' => $field['taxonomy'],
'data-allow_null' => $field['allow_null'],
'data-nonce' => wp_create_nonce( $field['key'] ),
'data-nonce' => $nonce,
);
// get taxonomy
$taxonomy = get_taxonomy( $field['taxonomy'] );
@@ -499,11 +501,11 @@ if ( ! class_exists( 'acf_field_taxonomy' ) ) :
if ( $field['field_type'] == 'select' ) {
$field['multiple'] = 0;
$this->render_field_select( $field );
$this->render_field_select( $field, $nonce );
} elseif ( $field['field_type'] == 'multi_select' ) {
$field['multiple'] = 1;
$this->render_field_select( $field );
$this->render_field_select( $field, $nonce );
} elseif ( $field['field_type'] == 'radio' ) {
$this->render_field_checkbox( $field );
} elseif ( $field['field_type'] == 'checkbox' ) {
@@ -524,12 +526,13 @@ if ( ! class_exists( 'acf_field_taxonomy' ) ) :
*
* @param $field - an array holding all the field's data
*/
function render_field_select( $field ) {
function render_field_select( $field, $nonce ) {
// Change Field into a select
$field['type'] = 'select';
$field['ui'] = 1;
$field['ajax'] = 1;
$field['nonce'] = $nonce;
$field['choices'] = array();
// value
@@ -766,7 +769,7 @@ if ( ! class_exists( 'acf_field_taxonomy' ) ) :
)
);
if ( ! acf_verify_ajax( $args['nonce'], $args['field_key'] ) ) {
if ( ! acf_verify_ajax( $args['nonce'], $args['field_key'], true ) ) {
die();
}

View File

@@ -164,7 +164,7 @@ if ( ! class_exists( 'ACF_Field_User' ) ) :
$field['ui'] = 1;
$field['ajax'] = 1;
$field['choices'] = array();
$field['nonce'] = wp_create_nonce( $field['key'] );
$field['nonce'] = wp_create_nonce( 'acf_field_' . $this->name . '_' . $field['key'] );
// Populate choices.
if ( $field['value'] ) {
@@ -404,7 +404,7 @@ if ( ! class_exists( 'ACF_Field_User' ) ) :
$nonce = acf_request_arg( 'nonce', '' );
$key = acf_request_arg( 'field_key', '' );
if ( ! acf_verify_ajax( $nonce, $key ) ) {
if ( ! acf_verify_ajax( $nonce, $key, true ) ) {
$query->send( new WP_Error( 'acf_invalid_request', __( 'Invalid request.', 'acf' ), array( 'status' => 404 ) ) );
}
}

View File

@@ -691,6 +691,12 @@ if ( ! class_exists( 'ACF_Post_Type' ) ) {
// Validate and prepare the post for export.
$post = $this->validate_post( $post );
$args = $this->get_post_type_args( $post, false );
// Restore original metabox callback.
if ( ! empty( $args['register_meta_box_cb'] ) && ! empty( $post['register_meta_box_cb'] ) ) {
$args['register_meta_box_cb'] = (string) $post['register_meta_box_cb'];
}
$code = var_export( $args, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions -- Used for PHP export.
if ( ! $code ) {
@@ -767,6 +773,30 @@ if ( ! class_exists( 'ACF_Post_Type' ) ) {
return $post;
}
/**
* Prepares an ACF post type for import.
*
* @since 6.3.10
*
* @param array $post The ACF post array.
* @return array
*/
public function prepare_post_for_import( $post ) {
if ( ! acf_get_setting( 'enable_meta_box_cb_edit' ) && ! empty( $post['register_meta_box_cb'] ) ) {
$post['register_meta_box_cb'] = '';
if ( ! empty( $post['ID'] ) ) {
$existing_post = $this->get_post( $post['ID'] );
if ( is_array( $existing_post ) ) {
$post['register_meta_box_cb'] = ! empty( $existing_post['register_meta_box_cb'] ) ? (string) $existing_post['register_meta_box_cb'] : '';
}
}
}
return parent::prepare_post_for_import( $post );
}
/**
* Imports a post type from CPTUI.
*

View File

@@ -577,7 +577,13 @@ if ( ! class_exists( 'ACF_Taxonomy' ) ) {
$objects = (array) $post['object_type'];
$objects = var_export( $objects, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions -- Used for PHP export.
$args = $this->get_taxonomy_args( $post, false );
$args = var_export( $args, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions -- Used for PHP export.
// Restore original metabox callback.
if ( ! empty( $args['meta_box_cb'] ) && ! empty( $post['meta_box_cb'] ) ) {
$args['meta_box_cb'] = $post['meta_box_cb'];
}
$args = var_export( $args, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions -- Used for PHP export.
if ( ! $args ) {
return $return;
@@ -654,6 +660,37 @@ if ( ! class_exists( 'ACF_Taxonomy' ) ) {
return $post;
}
/**
* Prepares an ACF taxonomy for import.
*
* @since 6.3.10
*
* @param array $post The ACF post array.
* @return array
*/
public function prepare_post_for_import( $post ) {
if ( ! acf_get_setting( 'enable_meta_box_cb_edit' ) && ( ! empty( $post['meta_box_cb'] ) || ! empty( $post['meta_box_sanitize_cb'] ) ) ) {
$post['meta_box_cb'] = '';
$post['meta_box_sanitize_cb'] = '';
if ( ! empty( $post['meta_box'] ) && 'custom' === $post['meta_box'] ) {
$post['meta_box'] = 'default';
}
if ( ! empty( $post['ID'] ) ) {
$existing_post = $this->get_post( $post['ID'] );
if ( is_array( $existing_post ) ) {
$post['meta_box'] = ! empty( $existing_post['meta_box'] ) ? (string) $existing_post['meta_box'] : '';
$post['meta_box_cb'] = ! empty( $existing_post['meta_box_cb'] ) ? (string) $existing_post['meta_box_cb'] : '';
$post['meta_box_sanitize_cb'] = ! empty( $existing_post['meta_box_sanitize_cb'] ) ? (string) $existing_post['meta_box_sanitize_cb'] : '';
}
}
}
return parent::prepare_post_for_import( $post );
}
/**
* Imports a taxonomy from CPTUI.
*