120) { $error = new \WP_Error(); $error->errors['cf_transaction_busy'] = array('Another CustomFields.com operation is already in progress. Finish it before starting another transaction.'); return $error; } try { $token = bin2hex(random_bytes(32)); } catch (\Throwable $exception) { $error = new \WP_Error(); $error->errors['cf_transaction_random'] = array('A protected operation could not be started. Retry after checking the server.'); return $error; } self::$transaction_owner = array('token' => $token, 'owner' => $owner); return $token; } /** Only the exact successful claimant may release ownership, including on errors. */ public static function leave_transaction($token) { if (!is_string($token) || self::$transaction_owner === null || !hash_equals(self::$transaction_owner['token'], $token)) { return false; } self::$transaction_owner = null; return true; } /** Contain interrupted rollback before the owner can release its transaction token. */ public static function rollback_transaction() { global $wpdb; $rolled_back = false; try { $rolled_back = false !== $wpdb->query('ROLLBACK'); } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Database transaction/advisory-lock operation; WordPress has no equivalent API and lock results must never be cached. catch (\Throwable $exception) { /* Third-party query details must not escape cleanup. */ } if (!$rolled_back) { // Closing the connection discards its uncommitted writes. Do not reconnect here. try { $wpdb->close(); } catch (\Throwable $exception) { /* Keep the original operation's controlled result. */ } error_log('CustomFields.com storage: rollback was interrupted; the database connection was closed.'); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Operational failure report with a fixed message and no private data; retaining it is necessary when rollback or post-commit cleanup fails. } return $rolled_back; } public static function boot() { add_filter('customfields_validate_group_schema',array(__CLASS__,'schema'),30,2); add_action('customfields_group_editor_settings',array(__CLASS__,'settings'),30); add_action('customfields_metadata_transaction_written',array(__CLASS__,'transaction_written'),10,5); add_action('admin_menu',array(__CLASS__,'menu'),26); foreach(array('post','user','term') as $kind){ add_filter('get_'.$kind.'_metadata',static function($check,$id,$name,$single)use($kind){return self::read_filter($check,$kind,$id,$name,$single);},20,4); add_filter('update_'.$kind.'_metadata',static function($check,$id,$name,$value,$previous)use($kind){return self::update_filter($check,$kind,$id,$name,$value,$previous);},20,5); add_filter('add_'.$kind.'_metadata',static function($check,$id,$name,$value,$unique)use($kind){return self::update_filter($check,$kind,$id,$name,$value,'',true);},20,5); add_filter('delete_'.$kind.'_metadata',static function($check,$id,$name,$value,$all)use($kind){return self::delete_filter($check,$kind,$id,$name,$value,$all);},20,5); } } public static function table(){global $wpdb;return $wpdb->prefix.'customfields_values';} public static function installed(){global $wpdb;return $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s',$wpdb->esc_like(self::table())))===self::table();} // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Inspect/provision the actual database schema; cached application metadata cannot establish storage-engine or column guarantees. public static function install(){ if(!current_user_can('manage_options'))return new \WP_Error('cf_storage_permission',__('Only administrators can provision storage.','customfields-com')); $transaction_token=self::enter_transaction(__METHOD__);if($transaction_token instanceof \WP_Error){return $transaction_token;} try{ global $wpdb;$table=self::table(); $sql="CREATE TABLE IF NOT EXISTS `$table` (generation varchar(32) NOT NULL, object_kind varchar(8) NOT NULL, object_id bigint unsigned NOT NULL, field_name varchar(64) NOT NULL, field_value longtext NOT NULL, PRIMARY KEY (generation,object_kind,object_id,field_name)) ENGINE=InnoDB ".$wpdb->get_charset_collate(); return $wpdb->query($sql)===false?new \WP_Error('cf_storage_install',__('The custom values table could not be created.','customfields-com')):true; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared -- Read authoritative workflow/metadata state for validation and conflict detection; cached values can miss concurrent edits or duplicate rows. sql is the fixed CREATE TABLE definition using the WordPress-prefixed table name and wpdb::get_charset_collate; no request fragments enter the schema statement. }finally{self::leave_transaction($transaction_token);} } public static function schema($group,$raw){ if(is_wp_error($group))return $group; $storage=$raw['storage']??'metadata'; if(!in_array($storage,array('metadata','custom_table'),true))return new \WP_Error('cf_storage_schema',__('Choose native metadata or custom table storage.','customfields-com')); $group['storage']=$storage;$group['storage_generation']=''; if($storage==='custom_table'){ if(!in_array($group['target'],array('post','user','term'),true))return new \WP_Error('cf_storage_target',__('Custom table storage supports post, user and term groups.','customfields-com')); if(!self::installed())return new \WP_Error('cf_storage_missing',__('Provision the table under CustomFields.com → Storage before opting in.','customfields-com')); $generation=$raw['storage_generation']??''; if($generation!==''&&(!is_string($generation)||!preg_match('/^[a-f0-9]{32}$/D',$generation)))return new \WP_Error('cf_storage_generation',__('The storage generation is invalid.','customfields-com')); // A new opt-in always starts from the current native mirror, never dormant table rows. $previous=null;foreach(get_option('customfields_groups',array()) as $item){if(($item['key']??'')===$group['key']){$previous=$item;break;}} if (($previous['storage']??'metadata') === 'custom_table' && !empty($previous['storage_generation'])) { // An active generation belongs to this registry entry, not to an imported file. $generation = $previous['storage_generation']; } else { // Validation can run twice before a schema write. Retain only identifiers // issued against this exact pre-write state during the current request. $binding = hash('sha256', wp_json_encode(array($group['key'], $previous))); if ($generation === '' || (self::$issued_generations[$generation] ?? null) !== $binding) { $generation = str_replace('-', '', wp_generate_uuid4()); self::$issued_generations[$generation] = $binding; } } $group['storage_generation']=$generation; } return $group; } public static function settings($group){ echo '
'; } private static function resolve($kind,$id,$name){ if(!is_string($name)||$name===''||$name[0]==='_')return null; if($kind==='user' && method_exists(Schema::class,'reserved_user_name') && Schema::reserved_user_name($name))return null; foreach(customfields_get_groups() as $group){ if (($group['target']??'post') !== $kind) { continue; } // Resolve names before object capabilities: user construction itself reads usermeta. foreach($group['fields'] as $field){ if($field['name']===$name && Relationships::group_matches($group,$kind,$id))return array('group'=>$group,'field'=>$field); } }return null; } private static function table_row($group,$kind,$id,$name,$lock=false){ global $wpdb;$table=self::table(); $row=$wpdb->get_row($wpdb->prepare("SELECT field_value FROM %i WHERE generation=%s AND object_kind=%s AND object_id=%d AND field_name=%s".($lock?' FOR UPDATE':''),$table,$group['storage_generation'],$kind,$id,$name),ARRAY_A); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared -- Read or lock authoritative rows for the transaction; an object-cache result cannot provide database locking or conflict detection. Query concatenation adds only the fixed FOR UPDATE suffix (or fixed snapshot-size column expressions); data values remain bound by prepare. if($wpdb->last_error)throw new \RuntimeException(__('Custom table storage could not be read.','customfields-com')); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception transport, not an HTML output sink; public handlers escape the resulting error at rendering time. return $row; } public static function read_filter($check,$kind,$id,$name,$single){ if($check!==null||self::$inside)return $check; $found=self::resolve($kind,$id,$name); if(!$found||($found['group']['storage']??'metadata')!=='custom_table')return $check; try{$row=self::table_row($found['group'],$kind,$id,$name);}catch(\Throwable $error){return $check;} // Both metadata_exists() and get_metadata() accept this wrapper, including false/empty values. return $row?array(maybe_unserialize($row['field_value'])):$check; } private static function meta_info($kind){global $wpdb;return array('table'=>$kind==='user'?$wpdb->usermeta:($kind==='term'?$wpdb->termmeta:$wpdb->postmeta),'object'=>$kind.'_id','primary'=>$kind==='user'?'umeta_id':'meta_id');} public static function locked_value($kind,$id,$name){ global $wpdb;$info=self::meta_info($kind); $rows=$wpdb->get_results($wpdb->prepare("SELECT %i,meta_value FROM %i WHERE %i=%d AND meta_key=%s ORDER BY %i FOR UPDATE", $info['primary'], $info['table'], $info['object'], $id, $name, $info['primary']),ARRAY_A); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Read or lock authoritative rows for the transaction; an object-cache result cannot provide database locking or conflict detection. if($wpdb->last_error||count($rows)>1)throw new \RuntimeException(__('The value is ambiguous or could not be locked. Nothing was changed.','customfields-com')); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception transport, not an HTML output sink; public handlers escape the resulting error at rendering time. $native=array('exists'=>(bool)$rows,'value'=>$rows?maybe_unserialize($rows[0]['meta_value']):null,'rows'=>$rows); $found=self::resolve($kind,$id,$name); if($found&&($found['group']['storage']??'metadata')==='custom_table'){ $row=self::table_row($found['group'],$kind,$id,$name,true); if($row){if(!$rows||$row['field_value']!==$rows[0]['meta_value'])throw new \RuntimeException(__('The table and compatibility mirror disagree. Restore consistency before editing.','customfields-com'));$native['value']=maybe_unserialize($row['field_value']);} // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception transport, not an HTML output sink; public handlers escape the resulting error at rendering time. } return $native; } /** Write native metadata and table within an existing transaction, preserving literal serialized text. */ public static function write_locked($group,$kind,$id,$name,$value){ global $wpdb;$info=self::meta_info($kind);$before=self::locked_value($kind,$id,$name); if($value===null){$ok=$wpdb->query($wpdb->prepare("DELETE FROM %i WHERE %i=%d AND meta_key=%s", $info['table'], $info['object'], $id, $name));} // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Atomic workflow write with explicit formats/prepared values; surrounding transaction performs cache invalidation after commit. else{ $raw=maybe_serialize($value); $ok=$before['exists']?$wpdb->query($wpdb->prepare("UPDATE %i SET meta_value=%s WHERE %i=%d AND meta_key=%s", $info['table'], $raw, $info['object'], $id, $name)):$wpdb->insert($info['table'],array($info['object']=>$id,'meta_key'=>$name,'meta_value'=>$raw),array('%d','%s','%s')); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Atomic workflow write with explicit formats/prepared values; surrounding transaction performs cache invalidation after commit. Metadata column name or presentation value, not an unbounded WP_Query meta scan. } if($ok===false)throw new \RuntimeException(__('Metadata could not be saved. Nothing was changed.','customfields-com')); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception transport, not an HTML output sink; public handlers escape the resulting error at rendering time. self::mirror($group,$kind,$id,$name,$value); self::$touched[$kind.':'.$id]=array($kind,$id); } private static function mirror($group,$kind,$id,$name,$value){ if(($group['storage']??'metadata')!=='custom_table')return; global $wpdb;$table=self::table(); if($value===null){$ok=$wpdb->query($wpdb->prepare("DELETE FROM %i WHERE generation=%s AND object_kind=%s AND object_id=%d AND field_name=%s", $table, $group['storage_generation'], $kind, $id, $name));} // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Atomic workflow write with explicit formats/prepared values; surrounding transaction performs cache invalidation after commit. else{$ok=$wpdb->query($wpdb->prepare("INSERT INTO %i (generation,object_kind,object_id,field_name,field_value) VALUES (%s,%s,%d,%s,%s) ON DUPLICATE KEY UPDATE field_value=VALUES(field_value)", $table, $group['storage_generation'], $kind, $id, $name, maybe_serialize($value)));} // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Atomic workflow write with explicit formats/prepared values; surrounding transaction performs cache invalidation after commit. if($ok===false)throw new \RuntimeException(__('The custom table could not be saved. Nothing was changed.','customfields-com')); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception transport, not an HTML output sink; public handlers escape the resulting error at rendering time. } public static function flush(){foreach(self::$touched as $object){wp_cache_delete($object[1],$object[0].'_meta');}self::$touched=array();wp_cache_delete('customfields_groups','options');wp_cache_delete('alloptions','options');wp_cache_delete('notoptions','options');} private static function transactional(){ global $wpdb;$tables=array($wpdb->options,$wpdb->postmeta,$wpdb->usermeta,$wpdb->termmeta);if(self::installed())$tables[]=self::table(); foreach($tables as $table){$engine=$wpdb->get_var($wpdb->prepare('SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=%s',$table));if(strtoupper((string)$engine)!=='INNODB')throw new \RuntimeException(__('Atomic content changes require InnoDB metadata, options and custom tables.','customfields-com'));} // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Inspect/provision the actual database schema; cached application metadata cannot establish storage-engine or column guarantees. Internal exception transport, not an HTML output sink; public handlers escape the resulting error at rendering time. } public static function save_group($group,$kind,$id,$updates){ if(!is_array($updates)||!Relationships::group_matches($group,$kind,$id)||!Editor::can_edit($kind,$id))return new \WP_Error('cf_storage_permission',__('You cannot save this group on this object.','customfields-com')); $known = array_column($group['fields'], null, 'name'); foreach ($updates as $name => $value) { if (!isset($known[$name]) || !Editor::can_edit($kind,$id,$name)) { return new \WP_Error('cf_storage_permission', __('Unknown or protected field.', 'customfields-com')); } } $atomic = ($group['storage'] ?? 'metadata') === 'custom_table'; foreach ($group['fields'] as $field) { if ($field['type'] === 'relationship' && array_key_exists($field['name'], $updates)) { $atomic = true; } } // Existing ordinary groups keep WordPress metadata hooks and storage-engine compatibility. if (!$atomic) { foreach ($updates as $name => $value) { if (!Editor::can_edit($kind, $id, $name)) { return new \WP_Error('cf_storage_permission', __('You cannot edit this field.', 'customfields-com')); } if ($value === null) { delete_metadata($kind, $id, $name); } else { update_metadata($kind, $id, $name, wp_slash($value)); } // Native metadata APIs return false for both unchanged values and failures. Verify the actual rows. global $wpdb; wp_cache_delete($id, $kind . '_meta'); $rows = $wpdb->get_col($wpdb->prepare('SELECT meta_value FROM %i WHERE %i=%d AND meta_key=%s', _get_meta_table($kind), $kind . '_id', $id, $name)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Read back authoritative persisted rows after native metadata hooks; cached or filtered metadata cannot distinguish unchanged success from a failed write. $verified = !$wpdb->last_error && ($value === null ? !$rows : (bool) $rows); if ($value !== null) { foreach ($rows as $row) { if ((string) $row !== (string) maybe_serialize($value)) { $verified = false; break; } } } if (!$verified) { return new \WP_Error('cf_storage_save', __('A field value could not be saved and verified. Earlier fields may have saved. Reload this editor and check the saved values before retrying.', 'customfields-com')); } } return true; } $transaction_token=self::enter_transaction(__METHOD__);if($transaction_token instanceof \WP_Error){return $transaction_token;} global $wpdb;$begun=false; try{ self::transactional(); if($wpdb->query('START TRANSACTION')===false)throw new \RuntimeException(__('The content transaction could not start.','customfields-com'));$begun=true; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Database transaction/advisory-lock operation; WordPress has no equivalent API and lock results must never be cached. if($wpdb->query($wpdb->prepare("SELECT option_value FROM {$wpdb->options} WHERE option_name=%s FOR UPDATE",'customfields_groups'))===false)throw new \RuntimeException(__('The schema could not be locked.','customfields-com')); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Read or lock authoritative rows for the transaction; an object-cache result cannot provide database locking or conflict detection. wp_cache_delete('customfields_groups','options');wp_cache_delete('alloptions','options');$current=null; foreach(customfields_get_groups() as $item){if($item['key']===$group['key']){$current=$item;break;}} if(!$current||wp_json_encode($current)!==wp_json_encode($group))throw new \RuntimeException(__('The schema changed during editing. Reload before saving.','customfields-com')); $before=array();$fields=array_column($group['fields'],null,'name'); foreach($updates as $name=>$value){ if(!isset($fields[$name])||!Editor::can_edit($kind,$id,$name))throw new \RuntimeException(__('You cannot edit one of these fields.','customfields-com')); $row=self::locked_value($kind,$id,$name);$before[$name]=$row['exists']?$row['value']:array(); if($row['exists'] && is_wp_error(Fields::storage_error($row['value'],$fields[$name])))throw new \RuntimeException(__('Existing data needs a compatible schema or explicit migration.','customfields-com')); if($value!==null&&is_wp_error(Fields::storage_error($value,$fields[$name])))throw new \RuntimeException(__('A value is incompatible with its field schema.','customfields-com')); } Relationships::synchronize($group,$kind,$id,array_map(static function($value){return $value===null?array():$value;},$updates),$before); foreach($updates as $name=>$value)self::write_locked($group,$kind,$id,$name,$value); if($wpdb->query('COMMIT')===false)throw new \RuntimeException(__('The content transaction could not commit.','customfields-com')); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Database transaction/advisory-lock operation; WordPress has no equivalent API and lock results must never be cached. $begun=false;return true; }catch(\Throwable $error){if($begun)self::rollback_transaction();return new \WP_Error('cf_storage_save',$error->getMessage());} finally{ try{self::flush();}catch(\Throwable $cleanup){error_log('CustomFields.com storage: cache cleanup failed. Inspect saved content before retrying.');} // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Operational failure report with a fixed message and no private data; retaining it is necessary when rollback or post-commit cleanup fails. finally{self::leave_transaction($transaction_token);} } } /** Direct SQL callers must invoke before COMMIT and call flush() after COMMIT/ROLLBACK. */ public static function transaction_written($group,$kind,$id,$updates,$before_rows){ $managed = ($group['storage'] ?? 'metadata') === 'custom_table'; foreach ($group['fields'] as $field) { if ($field['type'] === 'relationship' && array_key_exists($field['name'], $updates)) { $managed = true; } } // Other transaction owners retain their own storage-engine requirements. if (!$managed) { return; } self::transactional(); $before=array();foreach($updates as $name=>$value){ $rows=$before_rows[$name]??array(); if(count($rows)>1)throw new \RuntimeException(__('Duplicate metadata prevents a safe relationship save.','customfields-com')); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception transport, not an HTML output sink; public handlers escape the resulting error at rendering time. if (($group['storage']??'metadata')==='custom_table') { $table_row=self::table_row($group,$kind,$id,$name,true); if ($table_row && (!$rows || $table_row['field_value']!==$rows[0]['meta_value'])) { throw new \RuntimeException(__('The table and metadata mirror changed independently. Nothing was saved.','customfields-com')); } // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception transport, not an HTML output sink; public handlers escape the resulting error at rendering time. } $before[$name]=$rows?maybe_unserialize($rows[0]['meta_value']):array();self::$touched[$kind.':'.$id]=array($kind,$id); } Relationships::synchronize($group,$kind,$id,array_map(static function($value){return $value===null?array():$value;},$updates),$before); foreach($updates as $name=>$value)self::mirror($group,$kind,$id,$name,$value); } public static function update_filter($check,$kind,$id,$name,$value,$previous='',$add=false){ if($check!==null||self::$inside)return $check;$found=self::resolve($kind,$id,$name); if(!$found||(($found['group']['storage']??'metadata')!=='custom_table'&&$found['field']['type']!=='relationship'))return $check; $existing=get_metadata($kind,$id,$name,true); if(($add&&metadata_exists($kind,$id,$name))||($previous!==''&&$existing!==$previous))return false; $clean=Fields::sanitize($value,$found['field'],array('stored_value'=>$existing,'has_stored_value'=>metadata_exists($kind,$id,$name),'object_kind'=>$kind,'object_id'=>$id)); if(is_wp_error($clean))return false; return !is_wp_error(self::save_group($found['group'],$kind,$id,array($name=>$clean))); } public static function delete_filter($check,$kind,$id,$name,$value,$all){ if($check!==null||self::$inside)return $check; if($all){foreach(customfields_get_groups() as $group){if(($group['target']??'post')!==$kind)continue;foreach($group['fields'] as $field){if($field['name']===$name&&(($group['storage']??'metadata')==='custom_table'||$field['type']==='relationship'))return false;}}return $check;} $found=self::resolve($kind,$id,$name);if(!$found||(($found['group']['storage']??'metadata')!=='custom_table'&&$found['field']['type']!=='relationship'))return $check; if(!empty($found['field']['required'])||($value!==''&&get_metadata($kind,$id,$name,true)!==$value))return false; $result=self::save_group($found['group'],$kind,$id,array($name=>null));return !is_wp_error($result); } public static function menu(){add_submenu_page('customfields',__('Storage','customfields-com'),__('Storage','customfields-com'),'manage_options','customfields-storage',array(__CLASS__,'page'));} public static function backfill($group_key,$after=0){ if(!current_user_can('manage_options'))return new \WP_Error('cf_storage_permission',__('Only administrators can backfill values.','customfields-com')); $group=null;foreach(customfields_get_groups() as $candidate){if($candidate['key']===$group_key)$group=$candidate;} if(!$group||($group['storage']??'')!=='custom_table')return new \WP_Error('cf_storage_group',__('Choose a group using custom table storage.','customfields-com')); global $wpdb;$info=self::meta_info($group['target']);$fields=array_column($group['fields'],null,'name');$names=array_keys($fields); if(!$names)return array('cursor'=>$after,'count'=>0,'more'=>false); $placeholders=implode(',',array_fill(0,count($names),'%s')); $rows=$wpdb->get_results($wpdb->prepare("SELECT %i AS row_id,%i AS object_id,meta_key FROM %i WHERE %i>%d AND meta_key IN ($placeholders) ORDER BY %i LIMIT 100",array_merge(array($info['primary'],$info['object'],$info['table'],$info['primary'],$after),$names,array($info['primary']))),ARRAY_A); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Read authoritative workflow/metadata state for validation and conflict detection; cached values can miss concurrent edits or duplicate rows. Dynamic list contains only generated %s placeholders; the corresponding values and %i identifiers are passed in order to prepare via array_merge. if($wpdb->last_error)return new \WP_Error('cf_storage_read',__('The source metadata could not be read.','customfields-com')); $count=0;$cursor=$after; foreach($rows as $row){$cursor=(int)$row['row_id'];if(!Relationships::group_matches($group,$group['target'],(int)$row['object_id']))continue; $value=get_metadata($group['target'],$row['object_id'],$row['meta_key'],true); $result=self::save_group($group,$group['target'],(int)$row['object_id'],array($row['meta_key']=>$value));if(is_wp_error($result))return $result;$count++; } return array('cursor'=>$cursor,'count'=>$count,'more'=>count($rows)===100); } public static function page(){ if(!current_user_can('manage_options'))wp_die(esc_html__('Not permitted.','customfields-com')); $result=null; if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Exact HTTP method comparison; never stored or rendered. $nonce = isset($_POST['_wpnonce']) && is_string($_POST['_wpnonce']) ? sanitize_text_field(wp_unslash($_POST['_wpnonce'])) : ''; if (!wp_verify_nonce($nonce, 'customfields_storage')) { wp_die(esc_html__('Your session expired. Reload the storage page.', 'customfields-com'), '', array('response' => 403)); } if (isset($_POST['install'])) { $result = self::install(); } else { $cursor = isset($_POST['cursor']) && is_string($_POST['cursor']) ? sanitize_text_field(wp_unslash($_POST['cursor'])) : ''; if ($cursor === '' || $cursor !== wp_unslash($_POST['cursor']) || !preg_match('/^(0|[1-9][0-9]*)$/D', $cursor) || (string) (int) $cursor !== $cursor) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Second read only compares original bytes to the sanitized cursor; the typed sanitized value is the only value used. $result = new \WP_Error('cf_storage_cursor', __('Choose a valid nonnegative metadata row ID.', 'customfields-com')); } else { $group_key = isset($_POST['group']) && is_string($_POST['group']) ? sanitize_text_field(wp_unslash($_POST['group'])) : ''; $result = self::backfill($group_key, (int) $cursor); } } } echo ''.esc_html__('Opt individual post, user or term groups into the dedicated values table. A native metadata mirror remains mandatory for WordPress queries and revisions. Switching the group back to metadata uses this up-to-date mirror; no content is deleted. Direct SQL writes bypass this contract and are unsupported.','customfields-com').'
'; if(is_wp_error($result))echo ''.esc_html($result->get_error_message()).'
'.esc_html(is_array($result)?sprintf(/* translators: 1: Copied value count; 2: cursor position; 3: continuation message. */ __('Copied %1$d values. Cursor: %2$d. %3$s','customfields-com'),$result['count'],$result['cursor'],$result['more']?__('Continue from this cursor.','customfields-com'):__('This pass is complete.','customfields-com')):__('Table provisioned. Choose custom table storage in a field group.','customfields-com')).'