post_type, $group['post_types'] ?? array(), true ) ) { return false; } if ( ! empty( $group['post_ids'] ) && ! in_array( (int) $post->ID, $group['post_ids'], true ) ) { return false; } if ( ! empty( $group['page_templates'] ) && ! in_array( get_page_template_slug( $post->ID ) ?: 'default', $group['page_templates'], true ) ) { return false; } return true; } public static function can_edit( $kind, $id, $name = null ) { if ( 'user' === $kind && null !== $name && method_exists( Schema::class, 'reserved_user_name' ) && Schema::reserved_user_name( $name ) ) { return false; } $object_cap = array( 'post' => 'edit_post', 'user' => 'edit_user', 'term' => 'edit_term' ); if ( ! isset( $object_cap[ $kind ] ) || ! current_user_can( $object_cap[ $kind ], $id ) ) { return false; } return null === $name || current_user_can( 'edit_' . $kind . '_meta', $id, $name ); } public static function validate_values( $group, $submitted, $kind, $id ) { if ( ! is_array( $submitted ) || ! self::can_edit( $kind, $id ) ) { return new \WP_Error( 'cf_permission', __( 'You cannot edit these fields.', 'customfields-com' ) ); } $fields = array(); $stored = array(); $errors = new \WP_Error(); foreach ( $group['fields'] as $field ) { $name = $field['name']; if ( ! self::can_edit( $kind, $id, $name ) ) { if ( array_key_exists( $name, $submitted ) ) { $errors->add( $name, sprintf( /* translators: %s: Field label. */ __( '%s: editing is not permitted.', 'customfields-com' ), $field['label'] ) ); } unset( $submitted[$name] ); continue; } if ( ! array_key_exists( $field['type'], Fields::types() ) ) { unset( $submitted[$name] ); continue; } $fields[] = $field; if ( metadata_exists( $kind, $id, $name ) ) { $stored[$name] = get_metadata( $kind, $id, $name, true ); } } if ( $errors->has_errors() ) { return $errors; } return Conditions::resolve_values( $fields, $submitted, $stored, static function ( $field ) use ( $submitted, $stored, $kind, $id ) { $name = $field['name']; if ( ! array_key_exists( $name, $submitted ) ) { return new \WP_Error( 'missing_field', __( 'This field is missing. Reload the editor.', 'customfields-com' ) ); } if ( array_key_exists( $name, $stored ) && is_wp_error( Fields::storage_error( $stored[$name], $field ) ) ) { return new \WP_Error( 'stored_field', __( 'Existing data needs a compatible schema or an explicit migration.', 'customfields-com' ) ); } return Fields::sanitize( $submitted[$name], $field, array( 'has_stored_value' => array_key_exists( $name, $stored ), 'stored_value' => $stored[$name] ?? null, 'object_kind' => $kind, 'object_id' => $id ) ); }, $id ); } public static function render( $group, $kind, $id ) { wp_nonce_field( 'customfields_save_' . $kind . '_' . $id . '_' . $group['key'], 'customfields_nonce[' . $group['key'] . ']', false ); echo '
'; foreach ( $group['fields'] as $field ) { if ( ! self::can_edit( $kind, $id, $field['name'] ) ) { continue; } $name = 'customfields_values[' . $group['key'] . '][' . $field['name'] . ']'; $value = metadata_exists( $kind, $id, $field['name'] ) ? get_metadata( $kind, $id, $field['name'], true ) : $field['default']; $input_id = 'cf-' . substr( md5( $name ), 0, 16 ); echo '
'; Fields::render( $field, $value, $name ); if ( $field['instructions'] ) { echo '

' . esc_html( $field['instructions'] ) . '

'; } echo '
'; } echo '
'; do_action('customfields_editor_next', $group, $kind, $id); } public static function user_fields( $user ) { if ( ! self::can_edit( 'user', $user->ID ) ) { return; } foreach ( customfields_get_groups() as $group ) { if ( 'user' !== ( $group['target'] ?? '' ) ) { continue; } echo '

' . esc_html( $group['title'] ) . '

'; self::render( $group, 'user', $user->ID ); } } public static function save_user( $id ) { self::save_object( 'user', $id ); } public static function term_hooks() { foreach ( get_taxonomies( array( 'show_ui' => true ) ) as $taxonomy ) { add_action( $taxonomy . '_edit_form_fields', array( __CLASS__, 'term_fields' ), 10, 2 ); add_action( 'edited_' . $taxonomy, array( __CLASS__, 'save_term' ) ); } } public static function term_fields( $term, $taxonomy ) { if ( ! self::can_edit( 'term', $term->term_id ) ) { return; } foreach ( customfields_get_groups() as $group ) { if ( 'term' !== ( $group['target'] ?? '' ) || ! in_array( $taxonomy, $group['taxonomies'] ?? array(), true ) ) { continue; } echo '' . esc_html( $group['title'] ) . ''; self::render( $group, 'term', $term->term_id ); echo ''; } } public static function save_term( $id ) { self::save_object( 'term', $id ); } public static function save_object( $kind, $id ) { if ( ! self::can_edit( $kind, $id ) || ! isset( $_POST['customfields_values'], $_POST['customfields_nonce'] ) || ! is_array( $_POST['customfields_values'] ) || ! is_array( $_POST['customfields_nonce'] ) ) { return; } foreach ( customfields_get_groups() as $group ) { if ( ( $group['target'] ?? '' ) !== $kind ) { continue; } if ( 'term' === $kind ) { $term = get_term( $id ); if ( ! $term || is_wp_error( $term ) || ! in_array( $term->taxonomy, $group['taxonomies'] ?? array(), true ) ) { continue; } } $key = $group['key']; if ( ! isset( $_POST['customfields_nonce'][ $key ] ) || ! is_string( $_POST['customfields_nonce'][ $key ] ) ) { continue; } $nonce = sanitize_text_field( wp_unslash( $_POST['customfields_nonce'][ $key ] ) ); if ( ! wp_verify_nonce( $nonce, 'customfields_save_' . $kind . '_' . $id . '_' . $key ) ) { continue; } $complete = isset( $_POST['customfields_complete'][ $key ] ) && is_string( $_POST['customfields_complete'][ $key ] ) ? filter_var( wp_unslash( $_POST['customfields_complete'][ $key ] ), FILTER_VALIDATE_REGEXP, array( 'options' => array( 'regexp' => '/^1$/D' ) ) ) : false; if ( '1' !== $complete ) { wp_die( esc_html__( 'Incomplete fields: no values in this group were saved.', 'customfields-com' ), '', array( 'response' => 400 ) ); } // Read only this authorized group. Each value is sanitized by its registered field type. $clean = self::validate_values( $group, isset( $_POST['customfields_values'][ $key ] ) && is_array( $_POST['customfields_values'][ $key ] ) ? wp_unslash( $_POST['customfields_values'][ $key ] ) : null, $kind, $id ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- validate_values applies Fields::sanitize to every permitted field after the group nonce; text sanitation would corrupt rich and nested values. if ( is_wp_error( $clean ) ) { wp_die( esc_html( implode( ' ', $clean->get_error_messages() ) ), '', array( 'response' => 400 ) ); } if ( class_exists( ContentStore::class ) ) { $saved = ContentStore::save_group( $group, $kind, $id, $clean ); if ( is_wp_error( $saved ) ) { wp_die( esc_html( $saved->get_error_message() ), '', array( 'response' => 409 ) ); } } else { foreach ( $clean as $name => $value ) { update_metadata( $kind, $id, $name, wp_slash( $value ) ); } } } } private static function snapshot( $post_id ) { $snapshot = array(); $post = get_post( $post_id ); foreach ( customfields_get_groups() as $group ) { if ( ! self::matches( $group, $post ) ) { continue; } foreach ( $group['fields'] as $field ) { $snapshot[ $group['key'] . ':' . $field['key'] ] = array( 'name' => $field['name'], 'exists' => metadata_exists( 'post', $post_id, $field['name'] ), 'value' => get_post_meta( $post_id, $field['name'], true ) ); } } return $snapshot; } public static function snapshot_revision( $revision_id ) { $revision = get_post( $revision_id ); if ( ! $revision || ! self::can_edit( 'post', $revision->post_parent ) ) { return; } update_metadata( 'post', $revision_id, '_customfields_revision', wp_slash( self::snapshot( $revision->post_parent ) ) ); $GLOBALS['customfields_current_revision'][ $revision->post_parent ] = $revision_id; } public static function after_save( $group, $post_id, $values ) { $snapshot = self::snapshot( $post_id ); if ( isset( $GLOBALS['customfields_before_save'][ $post_id ] ) && $GLOBALS['customfields_before_save'][ $post_id ] !== $snapshot && wp_revisions_enabled( get_post( $post_id ) ) ) { $revision_id = $GLOBALS['customfields_saved_revision'][ $post_id ] ?? 0; if ( ! $revision_id ) { $revision_id = _wp_put_post_revision( get_post( $post_id, ARRAY_A ) ); } if ( $revision_id && ! is_wp_error( $revision_id ) ) { $GLOBALS['customfields_saved_revision'][ $post_id ] = $revision_id; update_metadata( 'post', $revision_id, '_customfields_revision', wp_slash( $snapshot ) ); $keep = wp_revisions_to_keep( get_post( $post_id ) ); if ( $keep > 0 ) { $revisions = array_values( wp_get_post_revisions( $post_id, array( 'order' => 'DESC', 'orderby' => 'ID' ) ) ); foreach ( array_slice( $revisions, $keep ) as $old ) { if ( ! wp_is_post_autosave( $old ) ) { wp_delete_post_revision( $old->ID ); } } } } } $draft_key = 'customfields_draft_' . get_current_user_id() . '_' . $post_id; $draft = get_transient( $draft_key ); if ( is_array( $draft ) && isset( $draft['values'][ $group['key'] ] ) && $draft['values'][ $group['key'] ] === $values ) { unset( $draft['values'][ $group['key'] ] ); if ( $draft['values'] ) { set_transient( $draft_key, $draft, DAY_IN_SECONDS ); } else { delete_transient( $draft_key ); } } } public static function start_save( $post_id ) { unset( $GLOBALS['customfields_before_save'][ $post_id ], $GLOBALS['customfields_saved_revision'][ $post_id ] ); } public static function before_save( $post_id ) { if ( isset( $GLOBALS['customfields_before_save'][ $post_id ] ) ) { return; } $snapshot = self::snapshot( $post_id ); $GLOBALS['customfields_before_save'][ $post_id ] = $snapshot; if ( ! wp_revisions_enabled( get_post( $post_id ) ) ) { return; } $revisions = wp_get_post_revisions( $post_id, array( 'posts_per_page' => 1 ) ); $latest = $revisions ? reset( $revisions ) : null; if ( ! $latest || get_metadata( 'post', $latest->ID, '_customfields_revision', true ) !== $snapshot ) { _wp_put_post_revision( get_post( $post_id, ARRAY_A ) ); } } public static function restore_revision( $post_id, $revision_id ) { if ( ! self::can_edit( 'post', $post_id ) ) { return; } $revision = get_post( $revision_id ); if ( ! $revision || (int) $revision->post_parent !== (int) $post_id || 'revision' !== $revision->post_type ) { return; } $snapshot = get_metadata( 'post', $revision_id, '_customfields_revision', true ); if ( ! is_array( $snapshot ) ) { return; } $updates = array(); $errors = array(); foreach ( customfields_get_groups() as $group ) { if ( ! self::matches( $group, get_post( $post_id ) ) ) { continue; } foreach ( $group['fields'] as $field ) { $item = $snapshot[ $group['key'] . ':' . $field['key'] ] ?? ( $snapshot[ $field['key'] ] ?? null ); if ( ! is_array( $item ) || $item['name'] !== $field['name'] ) { continue; } if ( ! self::can_edit( 'post', $post_id, $field['name'] ) || ( $item['exists'] && is_wp_error( Fields::storage_error( $item['value'], $field ) ) ) ) { $errors[] = $field['label']; continue; } $updates[ $field['name'] ] = $item; } } if ( $errors ) { set_transient( 'customfields_errors_' . get_current_user_id() . '_' . $post_id, array( __( 'Field revision was not restored because the current schema or permissions differ:', 'customfields-com' ) . ' ' . implode( ', ', $errors ) ), 120 ); return; } foreach ( customfields_get_groups() as $group ) { if ( ! self::matches( $group, get_post( $post_id ) ) ) { continue; } $group_updates = array(); foreach ( $group['fields'] as $field ) { if ( isset( $updates[ $field['name'] ] ) ) { $item = $updates[ $field['name'] ]; $group_updates[ $field['name'] ] = $item['exists'] ? $item['value'] : null; } } if ( ! $group_updates ) { continue; } if ( class_exists( ContentStore::class ) ) { $saved = ContentStore::save_group( $group, 'post', $post_id, $group_updates ); if ( is_wp_error( $saved ) ) { set_transient( 'customfields_errors_' . get_current_user_id() . '_' . $post_id, array( __( 'This field group could not be restored:', 'customfields-com' ) . ' ' . $group['title'] . ': ' . $saved->get_error_message() ), 120 ); return; } } else { foreach ( $group_updates as $name => $value ) { if ( null !== $value ) { update_post_meta( $post_id, $name, wp_slash( $value ) ); } else { delete_post_meta( $post_id, $name ); } } } } } public static function assets( $hook ) { if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) { return; } global $post; if ( ! $post || ! self::can_edit( 'post', $post->ID ) ) { return; } wp_enqueue_script( 'customfields-editor', CUSTOMFIELDS_URL . 'assets/editor.js', array( 'wp-data', 'wp-dom-ready', 'wp-api-fetch', 'wp-i18n' ), CUSTOMFIELDS_VERSION, true ); wp_set_script_translations( 'customfields-editor', 'customfields-com', CUSTOMFIELDS_PATH . 'languages' ); $draft = get_transient( 'customfields_draft_' . get_current_user_id() . '_' . $post->ID ); if ( ! is_array( $draft ) || ! isset( $draft['values'], $draft['hash'] ) || ! is_array( $draft['values'] ) || $draft['hash'] !== hash( 'sha256', Schema::encode( customfields_get_groups() ) ) ) { $draft = null; } if ( $draft && ! self::draft_accessible( $draft['values'], $post->ID ) ) { $draft = null; } wp_localize_script( 'customfields-editor', 'CustomFieldsEditor', array( 'postId' => $post->ID, 'ajax' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'customfields_draft_' . $post->ID ), 'draft' => $draft, 'saved' => __( 'Field recovery draft saved.', 'customfields-com' ), 'restore' => __( 'Restore my saved field draft', 'customfields-com' ) ) ); } public static function draft() { // The identifier is needed only to select the post-bound nonce action below. $post_id = isset( $_POST['post_id'] ) && is_string( $_POST['post_id'] ) ? filter_var( wp_unslash( $_POST['post_id'] ), FILTER_VALIDATE_REGEXP, array( 'options' => array( 'regexp' => '/^[1-9][0-9]*$/D' ) ) ) : false; $id = false === $post_id ? false : filter_var( $post_id, FILTER_VALIDATE_INT, array( 'options' => array( 'min_range' => 1 ) ) ); if ( false === $id ) { wp_send_json_error( array( 'message' => __( 'Forbidden', 'customfields-com' ) ), 403 ); } $nonce = isset( $_POST['nonce'] ) && is_string( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, 'customfields_draft_' . $id ) ) { wp_send_json_error( array( 'message' => __( 'Forbidden', 'customfields-com' ) ), 403 ); } if ( ! self::can_edit( 'post', $id ) ) { wp_send_json_error( array( 'message' => __( 'Forbidden', 'customfields-com' ) ), 403 ); } // This sanitizer returns only permitted, field-sanitized values and validated recovery context. $draft = self::sanitize_draft_json( isset( $_POST['values'] ) && is_string( $_POST['values'] ) ? wp_unslash( $_POST['values'] ) : '', $id ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitize_draft_json bounds and decodes JSON, then validates each group and sanitizes its registered field types; generic text sanitation would corrupt rich/nested values. if ( is_wp_error( $draft ) ) { wp_send_json_error( array( 'message' => $draft->get_error_message() ), 400 ); } $invalid = $draft['invalid']; unset( $draft['invalid'] ); set_transient( 'customfields_draft_' . get_current_user_id() . '_' . $id, $draft, DAY_IN_SECONDS ); wp_send_json_success( array( 'partial' => $invalid, 'message' => $invalid ? sprintf( /* translators: %s: Comma-separated invalid group names. */ __( 'Recovery draft saved for valid groups only. Correct: %s.', 'customfields-com' ), implode( ', ', $invalid ) ) : __( 'Field recovery draft saved.', 'customfields-com' ) ) ); } /** Decode the transport and sanitize every value before it can enter a recovery draft. */ private static function sanitize_draft_json( $json, $id ) { if ( ! is_string( $json ) || strlen( $json ) > Schema::MAX_BYTES ) { return new \WP_Error( 'draft_size', __( 'Draft too large', 'customfields-com' ) ); } $values = json_decode( $json, true, 24 ); if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $values ) ) { return new \WP_Error( 'draft_fields', __( 'Invalid fields', 'customfields-com' ) ); } $safe = array(); $invalid = array(); $post = get_post( $id ); foreach ( customfields_get_groups() as $group ) { if ( ! self::matches( $group, $post ) || ! isset( $values[ $group['key'] ] ) ) { continue; } $clean = self::validate_values( $group, $values[ $group['key'] ], 'post', $id ); if ( is_wp_error( $clean ) ) { $invalid[] = $group['title']; } else { $safe[ $group['key'] ] = $clean; } } if ( ! $safe ) { return new \WP_Error( 'draft_invalid', __( 'Complete valid fields to save a recovery draft.', 'customfields-com' ) ); } // Raw structured markers remain available only to the schema-aware recovery-context filter. return array( 'values' => $safe, 'contexts' => apply_filters( 'customfields_draft_contexts', array(), $safe, $values, $id ), 'hash' => hash( 'sha256', Schema::encode( customfields_get_groups() ) ), 'time' => time(), 'invalid' => $invalid, ); } public static function draft_accessible( $values, $post_id ) { if ( ! is_array( $values ) || ! self::can_edit( 'post', $post_id ) ) { return false; } $groups = array_column( customfields_get_groups(), null, 'key' ); foreach ( $values as $key => $items ) { if ( ! isset( $groups[ $key ] ) || ! self::matches( $groups[ $key ], get_post( $post_id ) ) || ! is_array( $items ) ) { return false; } $fields = array_column( $groups[ $key ]['fields'], null, 'name' ); foreach ( $items as $name => $value ) { if ( ! isset( $fields[ $name ] ) || ! self::can_edit( 'post', $post_id, $name ) ) { return false; } } } return true; } public static function publication_guard( $data, $postarr ) { if ( ! in_array( $data['post_status'] ?? '', array( 'publish', 'future' ), true ) || ! isset( $_POST['customfields_values'] ) || ! is_array( $_POST['customfields_values'] ) ) { return $data; } $id = absint( $postarr['ID'] ?? 0 ); $post = get_post( $id ); if ( ! $post || ! self::can_edit( 'post', $id ) ) { return $data; } foreach ( customfields_get_groups() as $group ) { if ( ! self::matches( $group, $post ) ) { continue; } if ( ! isset( $_POST['customfields_nonce'] ) || ! is_array( $_POST['customfields_nonce'] ) || ! isset( $_POST['customfields_nonce'][ $group['key'] ] ) || ! is_string( $_POST['customfields_nonce'][ $group['key'] ] ) ) { continue; } $nonce = sanitize_text_field( wp_unslash( $_POST['customfields_nonce'][ $group['key'] ] ) ); if ( ! wp_verify_nonce( $nonce, 'customfields_save_' . $id . '_' . $group['key'] ) ) { continue; } $key = $group['key']; $complete = isset( $_POST['customfields_complete'][ $key ] ) && is_string( $_POST['customfields_complete'][ $key ] ) ? filter_var( wp_unslash( $_POST['customfields_complete'][ $key ] ), FILTER_VALIDATE_REGEXP, array( 'options' => array( 'regexp' => '/^1$/D' ) ) ) : false; $clean = self::validate_values( $group, isset( $_POST['customfields_values'][ $key ] ) && is_array( $_POST['customfields_values'][ $key ] ) ? wp_unslash( $_POST['customfields_values'][ $key ] ) : null, 'post', $id ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- validate_values applies Fields::sanitize per registered field after this group's nonce and object permission checks. if ( '1' !== $complete || is_wp_error( $clean ) ) { $data['post_status'] = in_array( $post->post_status, array( 'publish', 'future' ), true ) ? $post->post_status : 'draft'; break; } } return $data; } public static function rest_hooks() { foreach ( get_post_types( array( 'show_in_rest' => true ) ) as $type ) { add_filter( 'rest_pre_insert_' . $type, array( __CLASS__, 'rest_validate' ), 10, 2 ); } } public static function rest_fields() { foreach ( get_post_types( array( 'show_in_rest' => true ) ) as $type ) { register_rest_field( $type, 'customfields', array( 'get_callback' => array( __CLASS__, 'rest_get' ), 'update_callback' => array( __CLASS__, 'rest_update' ), 'schema' => array( 'description' => __( 'Registered custom fields for this editor.', 'customfields-com' ), 'type' => 'object', 'context' => array( 'edit' ), 'additionalProperties' => array( 'type' => 'object', 'additionalProperties' => true ) ) ) ); register_rest_field( $type, 'customfields_contexts', array( 'get_callback' => array( __CLASS__, 'rest_contexts' ), 'schema' => array( 'description' => __( 'Opaque editing context for structured field preservation.', 'customfields-com' ), 'type' => 'object', 'readonly' => true, 'context' => array( 'edit' ), 'additionalProperties' => true ) ) ); } } public static function rest_get( $object ) { $id = $object['id']; if ( ! self::can_edit( 'post', $id ) ) { return new \stdClass(); } $values = array(); foreach ( customfields_get_groups() as $group ) { if ( ! self::matches( $group, get_post( $id ) ) ) { continue; } foreach ( $group['fields'] as $field ) { if ( self::can_edit( 'post', $id, $field['name'] ) ) { $values[ $group['key'] ][ $field['name'] ] = metadata_exists( 'post', $id, $field['name'] ) ? get_post_meta( $id, $field['name'], true ) : $field['default']; } } } return $values ?: new \stdClass(); } public static function rest_contexts( $object ) { $values = self::rest_get( $object ); if ( ! is_array( $values ) ) { return new \stdClass(); } $contexts = apply_filters( 'customfields_editor_contexts', array(), $values ); return $contexts ?: new \stdClass(); } public static function rest_validate( $prepared, $request ) { if ( is_wp_error( $prepared ) || ! $request->has_param( 'customfields' ) ) { return $prepared; } $id = (int) $request->get_param( 'id' ); $clean = self::rest_clean( $request->get_param( 'customfields' ), $id ); return is_wp_error( $clean ) ? $clean : $prepared; } private static function rest_clean( $values, $id ) { if ( ! $id || ! self::can_edit( 'post', $id ) ) { return new \WP_Error( 'cf_permission', __( 'Save a draft before editing custom fields.', 'customfields-com' ), array( 'status' => 403 ) ); } if ( ! is_array( $values ) ) { return new \WP_Error( 'cf_values', __( 'Invalid field groups.', 'customfields-com' ), array( 'status' => 400 ) ); } $clean = array(); foreach ( customfields_get_groups() as $group ) { if ( ! self::matches( $group, get_post( $id ) ) || ! array_key_exists( $group['key'], $values ) ) { continue; } $result = self::validate_values( $group, $values[ $group['key'] ], 'post', $id ); if ( is_wp_error( $result ) ) { return new \WP_Error( 'cf_values', implode( ' ', $result->get_error_messages() ), array( 'status' => 400 ) ); } $clean[ $group['key'] ] = array( 'group' => $group, 'values' => $result ); } if ( count( $clean ) !== count( $values ) ) { return new \WP_Error( 'cf_groups', __( 'Unknown or unavailable field groups.', 'customfields-com' ), array( 'status' => 400 ) ); } return $clean; } public static function rest_update( $values, $object ) { $clean = self::rest_clean( $values, $object->ID ); if ( is_wp_error( $clean ) ) { return $clean; } self::start_save( $object->ID ); self::before_save( $object->ID ); foreach ( $clean as $item ) { if ( class_exists( ContentStore::class ) ) { $saved = ContentStore::save_group( $item['group'], 'post', $object->ID, $item['values'] ); if ( is_wp_error( $saved ) ) { return new \WP_Error( 'cf_values_conflict', $saved->get_error_message(), array( 'status' => 409 ) ); } } else { foreach ( $item['values'] as $name => $value ) { update_post_meta( $object->ID, $name, wp_slash( $value ) ); } } do_action( 'customfields_group_saved', $item['group'], $object->ID, $item['values'] ); } return true; } }