get_charset_collate(); // The two CREATE TABLE statements below are the only place in the plugin // that still interpolates a table name instead of using prepare()'s %i. // dbDelta() parses the literal SQL text — it extracts the table name // with its own regex and diffs the column list — so a prepared // statement, or a backtick-quoted identifier from %i, would defeat it. // Both names are built from $wpdb->prefix and a literal; no input // reaches them. $sql = "CREATE TABLE {$wpdb->prefix}fpca_posts ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, post_id bigint(20) unsigned NOT NULL, freshness_score tinyint(3) unsigned DEFAULT NULL, sub_scores text, scored_at datetime DEFAULT NULL, ai_reasoning text, assigned_to bigint(20) unsigned DEFAULT NULL, assigned_at datetime DEFAULT NULL, status varchar(32) DEFAULT 'unreviewed', notes text, gsc_clicks int(11) DEFAULT NULL, gsc_impressions int(11) DEFAULT NULL, gsc_position float DEFAULT NULL, gsc_ctr float DEFAULT NULL, gsc_synced_at datetime DEFAULT NULL, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY post_id (post_id), KEY status (status), KEY freshness_score (freshness_score) ) $charset;"; $sql_assignments = "CREATE TABLE {$wpdb->prefix}fpca_assignments ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, post_id bigint(20) unsigned NOT NULL, user_id bigint(20) unsigned NOT NULL, assigned_by bigint(20) unsigned NOT NULL, assigned_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY unique_post_user (post_id, user_id) ) $charset;"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( $sql ); dbDelta( $sql_assignments ); } private static function set_default_options(): void { $defaults = [ 'fpca_db_version' => FPCA_VERSION, 'fpca_gsc_connected' => false, 'fpca_gsc_property' => '', 'fpca_gsc_tokens' => '', 'fpca_gsc_properties_list' => '', 'fpca_decay_threshold' => 180, 'fpca_tracked_post_types' => [ 'post', 'page' ], 'fpca_weight_age' => 25, 'fpca_weight_refs' => 40, 'fpca_weight_depth' => 35, 'fpca_flag_threshold' => 50, 'fpca_overdue_days' => 7, 'fpca_auto_rescore' => false, 'fpca_acf_autodetect' => 1, 'fpca_extra_fields' => '', 'fpca_scoring_ack' => 0, ]; foreach ( $defaults as $key => $value ) { // Use a sentinel rather than `false === get_option()`, which is also // true for options legitimately storing the value false. if ( self::MISSING !== get_option( $key, self::MISSING ) ) { continue; } $autoload = ! in_array( $key, self::NO_AUTOLOAD_OPTIONS, true ); add_option( $key, $value, '', $autoload ); } } }