put_contents( $file, $json, FS_CHMOD_FILE ); } /** * Delete JSON file when a field group is deleted. */ public static function delete_group( $post_id ) { $config = get_post_meta( $post_id, '_metaforge_group_config', true ); if ( ! is_array( $config ) || empty( $config['group_name'] ) ) { return; } $dir = self::get_sync_dir(); $filename = sanitize_file_name( sanitize_title( $config['group_name'] ) . '.json' ); $file = trailingslashit( $dir ) . $filename; $real_dir = realpath( $dir ); $real_file = realpath( $file ); if ( $real_dir && $real_file && strpos( $real_file, $real_dir ) === 0 && file_exists( $real_file ) ) { wp_delete_file( $real_file ); } } /** * Scan directory for pending imports or updates. */ public static function get_sync_status() { $dir = self::get_sync_dir(); if ( ! file_exists( $dir ) || ! is_dir( $dir ) ) { return array(); } $files = glob( $dir . '/*.json' ); if ( empty( $files ) ) { return array(); } // Retrieve existing database groups indexed by group_key $db_posts = get_posts( array( 'post_type' => 'metaforge_group', 'posts_per_page' => -1, 'post_status' => 'any', ) ); $db_groups = array(); foreach ( $db_posts as $post ) { $c = get_post_meta( $post->ID, '_metaforge_group_config', true ); if ( is_array( $c ) ) { $key = $c['group_key'] ?? 'group_' . substr( md5( $c['group_name'] ), 0, 12 ); $meta_mod = isset( $c['modified'] ) ? intval( $c['modified'] ) : 0; $post_mod = (int) get_post_modified_time( 'U', false, $post ); $db_groups[ $key ] = array( 'id' => $post->ID, 'config' => $c, 'modified' => max( $meta_mod, $post_mod ), ); } } global $wp_filesystem; if ( empty( $wp_filesystem ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); } $pending = array(); foreach ( $files as $file ) { $content = $wp_filesystem ? $wp_filesystem->get_contents( $file ) : false; $config = json_decode( $content, true ); if ( ! is_array( $config ) || empty( $config['group_name'] ) ) { continue; } $key = $config['group_key'] ?? 'group_' . substr( md5( $config['group_name'] ), 0, 12 ); if ( ! isset( $db_groups[ $key ] ) ) { $pending[ $key ] = array( 'type' => 'new', 'json_config' => $config, 'filename' => basename( $file ), ); } else { $db_item = $db_groups[ $key ]; // Sanitize and normalize both configs so data structures match exactly $sanitized_json = MetaForge_Loader::sanitize_group_config( $config ); $sanitized_db = MetaForge_Loader::sanitize_group_config( $db_item['config'] ); $json_fields_hash = md5( wp_json_encode( $sanitized_json['fields'] ?? array() ) ); $db_fields_hash = md5( wp_json_encode( $sanitized_db['fields'] ?? array() ) ); $json_rules_hash = md5( wp_json_encode( $sanitized_json['location_rules'] ?? array() ) ); $db_rules_hash = md5( wp_json_encode( $sanitized_db['location_rules'] ?? array() ) ); $title_differs = ( $sanitized_json['group_name'] ?? '' ) !== ( $sanitized_db['group_name'] ?? '' ); $content_changed = ( $json_fields_hash !== $db_fields_hash ) || ( $json_rules_hash !== $db_rules_hash ) || $title_differs; $json_modified = isset( $config['modified'] ) ? intval( $config['modified'] ) : filemtime( $file ); $db_modified = intval( $db_item['modified'] ); // Only consider it an update if the content has actually changed and json is newer (or content is different) if ( $content_changed && ( $json_modified > ( $db_modified + 2 ) || $content_changed ) ) { $pending[ $key ] = array( 'type' => 'update', 'db_id' => $db_item['id'], 'json_config' => $config, 'filename' => basename( $file ), ); } } } return $pending; } /** * Delete a sync JSON file by group key. */ public static function delete_sync_file_by_key( $group_key ) { $pending = self::get_sync_status(); if ( ! isset( $pending[ $group_key ] ) ) { return false; } $dir = self::get_sync_dir(); $filename = $pending[ $group_key ]['filename'] ?? ''; if ( empty( $filename ) ) { return false; } $file = trailingslashit( $dir ) . $filename; $real_dir = realpath( $dir ); $real_file = realpath( $file ); if ( $real_dir && $real_file && strpos( $real_file, $real_dir ) === 0 && file_exists( $real_file ) ) { $deleted = wp_delete_file( $real_file ); if ( $deleted ) { MetaForge_Loader::clear_cache(); } return $deleted; } return false; } /** * Run synchronization on chosen groups. */ public static function sync_groups( $keys = array() ) { $pending = self::get_sync_status(); if ( empty( $pending ) ) { return 0; } $dir = self::get_sync_dir(); $synced_count = 0; foreach ( $pending as $key => $item ) { if ( ! empty( $keys ) && ! in_array( $key, $keys, true ) ) { continue; } $config = $item['json_config']; $sanitized = MetaForge_Loader::sanitize_group_config( $config ); if ( ! $sanitized ) { continue; } // Inject persistent key $sanitized['group_key'] = $key; $title = $sanitized['group_name']; $now = time(); $sanitized['modified'] = $now; // Remove legacy / previous file if the filename changed due to rename $old_filename = $item['filename'] ?? ''; $target_filename = sanitize_file_name( sanitize_title( $title ) . '.json' ); if ( ! empty( $old_filename ) && $old_filename !== $target_filename ) { $old_file = trailingslashit( $dir ) . $old_filename; $real_dir = realpath( $dir ); $real_old = realpath( $old_file ); if ( $real_dir && $real_old && strpos( $real_old, $real_dir ) === 0 && file_exists( $real_old ) ) { wp_delete_file( $real_old ); } } if ( $item['type'] === 'new' ) { $pid = wp_insert_post( array( 'post_title' => $title, 'post_type' => 'metaforge_group', 'post_status' => 'publish', 'post_modified' => current_time( 'mysql' ), 'post_modified_gmt' => current_time( 'mysql', 1 ), ) ); if ( $pid && ! is_wp_error( $pid ) ) { update_post_meta( $pid, '_metaforge_group_config', $sanitized ); self::save_group( $pid, $sanitized ); ++$synced_count; } } else { $pid = $item['db_id']; wp_update_post( array( 'ID' => $pid, 'post_title' => $title, 'post_modified' => current_time( 'mysql' ), 'post_modified_gmt' => current_time( 'mysql', 1 ), ) ); update_post_meta( $pid, '_metaforge_group_config', $sanitized ); self::save_group( $pid, $sanitized ); ++$synced_count; } } wp_cache_delete( 'metaforge_group', 'counts' ); MetaForge_Loader::clear_cache(); return $synced_count; } /** * Count pending sync items. * * @return int */ public static function count_pending_sync() { return count( self::get_sync_status() ); } }