HEX
Server: Apache/2.4.68 (Debian)
System: Linux as-cs-widget-demo-us-central1 6.1.0-44-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.164-1 (2026-03-09) x86_64
User: root (0)
PHP: 8.2.32
Disabled: NONE
Upload Files
File: //proc/self/cwd/wp-content/plugins/allspice/includes/db_recipes.php
<?php
// includes/db_recipes.php
if (!defined('ABSPATH')) exit;

function allspice_db_cache_group(): string {
    return 'allspice_db';
}

function allspice_recipes_table_suffix(): string {
    return 'allspice_recipes';
}

function allspice_recipes_table_prefix(): string {
    global $wpdb;
    $prefix = (is_object($wpdb) && isset($wpdb->prefix)) ? (string) $wpdb->prefix : 'wp_';
    if (!preg_match('/^[A-Za-z0-9_]+$/', $prefix)) $prefix = 'wp_';
    return $prefix;
}

function allspice_recipes_table_name_raw(): string {
    return allspice_recipes_table_prefix() . allspice_recipes_table_suffix();
}

function allspice_recipes_table_ident(): string {
    $raw = allspice_recipes_table_name_raw();
    if (!preg_match('/^[A-Za-z0-9_]+$/', $raw)) $raw = 'wp_' . allspice_recipes_table_suffix();
    return '`' . $raw . '`';
}

function allspice_recipes_table_exists(): bool {
    static $exists = null;
    if ($exists !== null) return $exists;
    $cache_key = 'recipes_table_exists';
    $cached = wp_cache_get($cache_key, allspice_db_cache_group());
    if ($cached !== false) {
        $exists = (bool) $cached;
        return $exists;
    }
    global $wpdb;
    if (!is_object($wpdb)) {
        $exists = false;
        return $exists;
    }
    $raw = allspice_recipes_table_name_raw();
    if (!preg_match('/^[A-Za-z0-9_]+$/', $raw)) {
        $exists = false;
        wp_cache_set($cache_key, 0, allspice_db_cache_group(), 5 * MINUTE_IN_SECONDS);
        return $exists;
    }
    $pattern = $wpdb->esc_like($raw);
    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Table existence check (cached via object cache).
    $found = $wpdb->get_var(
        $wpdb->prepare('SHOW TABLES LIKE %s', $pattern)
    );
    $exists = (strcasecmp((string)$found, (string)$raw) === 0);
    wp_cache_set($cache_key, $exists ? 1 : 0, allspice_db_cache_group(), HOUR_IN_SECONDS);
    return $exists;
}

function allspice_create_recipes_table(): void {
    global $wpdb;
    if (!is_object($wpdb)) return;
    $raw = allspice_recipes_table_name_raw();
    if (!preg_match('/^[A-Za-z0-9_]+allspice_recipes$/', $raw)) return;
    $table = allspice_recipes_table_ident(); // backticked
    $charset_collate = $wpdb->get_charset_collate();
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    $sql = "CREATE TABLE {$table} (
        id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
        url_key VARCHAR(255) NOT NULL,
        url TEXT NULL,
        recipe_id VARCHAR(64) NULL,
        wp_post_id VARCHAR(64) NULL,
        status VARCHAR(32) NOT NULL DEFAULT 'recipe',
        import_status VARCHAR(32) NOT NULL DEFAULT 'imported',
        last_checked_gmt DATETIME NULL,
        synced_at_gmt DATETIME NULL,
    
        ingredients_json LONGTEXT NULL,
        time_text VARCHAR(64) NULL,
        recipe_title TEXT NULL,
        meta_description TEXT NULL,
        meta_description_valid TINYINT(1) NULL,
        takeaways_json LONGTEXT NULL,
        faqs_json LONGTEXT NULL,
        servings INT NULL,
        calories INT NULL,
    
        PRIMARY KEY (id),
        UNIQUE KEY url_key (url_key),
        KEY wp_post_id (wp_post_id),
        KEY last_checked_gmt (last_checked_gmt),
        KEY status_import (status, import_status),
        KEY recipe_id (recipe_id)
    ) {$charset_collate};";
    dbDelta($sql);
    wp_cache_delete('recipes_table_exists', allspice_db_cache_group());
}

function allspice_recipes_cache_key(string $prefix, string $url_key): string {
    return $prefix . ':' . md5($url_key);
}

function allspice_recipes_has_url_key(string $url_key): bool {
    if (!allspice_recipes_table_exists()) return false;
    $url_key = trim($url_key);
    if ($url_key === '') return false;
    $cache_key = allspice_recipes_cache_key('has', $url_key);
    $cached = wp_cache_get($cache_key, allspice_db_cache_group());
    if ($cached !== false) return (bool)$cached;
    global $wpdb;
    if (!is_object($wpdb)) return false;
    $table = allspice_recipes_table_ident();
    // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table query (cached via object cache).
    // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table identifier cannot be parameterized; table name is fixed suffix + $wpdb->prefix.
    // phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is prepared; table ident is validated/allowlisted.
    $exists = (bool) $wpdb->get_var(
        $wpdb->prepare(
            "SELECT 1 FROM {$table} WHERE url_key = %s AND status = %s AND import_status = %s LIMIT 1",
            $url_key,
            'recipe',
            'imported'
        )
    );
    // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
    // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    // phpcs:enable PluginCheck.Security.DirectDB.UnescapedDBParameter
    wp_cache_set($cache_key, $exists ? 1 : 0, allspice_db_cache_group(), 10 * MINUTE_IN_SECONDS);
    return $exists;
}

function allspice_recipes_get_id_by_url_key(string $url_key): ?string {
    if (!allspice_recipes_table_exists()) return null;
    $url_key = trim($url_key);
    if ($url_key === '') return null;
    $cache_key = allspice_recipes_cache_key('rid', $url_key);
    $cached = wp_cache_get($cache_key, allspice_db_cache_group());
    if ($cached !== false) {
        $rid = is_string($cached) ? trim($cached) : '';
        return $rid === '' ? null : $rid;
    }
    global $wpdb;
    if (!is_object($wpdb)) return null;
    $table = allspice_recipes_table_ident();
    // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table query (cached via object cache).
    // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table identifier cannot be parameterized; table name is fixed suffix + $wpdb->prefix.
    // phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is prepared; table ident is validated/allowlisted.
    $rid = $wpdb->get_var(
        $wpdb->prepare(
            "SELECT recipe_id FROM {$table} WHERE url_key = %s AND status = %s AND import_status = %s LIMIT 1",
            $url_key,
            'recipe',
            'imported'
        )
    );
    // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
    // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    // phpcs:enable PluginCheck.Security.DirectDB.UnescapedDBParameter
    $rid = is_string($rid) ? trim($rid) : '';
    wp_cache_set($cache_key, $rid, allspice_db_cache_group(), 10 * MINUTE_IN_SECONDS);
    return $rid === '' ? null : $rid;
}

function allspice_recipes_cache_flush(): void {
    wp_cache_delete('recipes_table_exists', allspice_db_cache_group());
}

function allspice_recipes_table_schema_cache_key(): string {
    $raw = allspice_recipes_table_name_raw();
    return 'recipes:schema_ok:' . md5($raw);
}

function allspice_recipes_table_ensure_columns(): void {
    if (!allspice_recipes_table_exists()) return;
    $group = allspice_db_cache_group();
    $cache_key = allspice_recipes_table_schema_cache_key();
    $cached = wp_cache_get($cache_key, $group);
    if ($cached === '1') return;
    global $wpdb;
    if (!is_object($wpdb)) return;
    $table = allspice_recipes_table_ident();
    // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    $cols = $wpdb->get_col("SHOW COLUMNS FROM {$table}", 0);
    // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    if (!is_array($cols)) $cols = [];
    $have = array_fill_keys($cols, true);
    $adds = [];
    if (empty($have['ingredients_json'])) $adds[] = "ADD COLUMN ingredients_json LONGTEXT NULL";
    if (empty($have['time_text'])) $adds[] = "ADD COLUMN time_text VARCHAR(64) NULL";
    if (empty($have['recipe_title'])) $adds[] = "ADD COLUMN recipe_title TEXT NULL";
    if (empty($have['meta_description'])) $adds[] = "ADD COLUMN meta_description TEXT NULL";
    if (empty($have['meta_description_valid'])) $adds[] = "ADD COLUMN meta_description_valid TINYINT(1) NULL";
    if (empty($have['takeaways_json'])) $adds[] = "ADD COLUMN takeaways_json LONGTEXT NULL";
    if (empty($have['faqs_json'])) $adds[] = "ADD COLUMN faqs_json LONGTEXT NULL";
    if (empty($have['servings'])) $adds[] = "ADD COLUMN servings INT NULL";
    if (empty($have['calories'])) $adds[] = "ADD COLUMN calories INT NULL";
//    if ($adds) {
//        $alter = "ALTER TABLE {$table} " . implode(', ', $adds);
//        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
//        $wpdb->query($alter);
//        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
//    }
//    wp_cache_set($cache_key, '1', $group, 6 * HOUR_IN_SECONDS);
    $ok = true;
    if ($adds) {
        $alter = "ALTER TABLE {$table} " . implode(', ', $adds);
        $res = $wpdb->query($alter);
        if ($res === false || !empty($wpdb->last_error)) {
            $ok = false;
            if (allspice_is_debug()) {
                allspice_console_log('[Allspice][DB] ensure_columns_failed', [
                    'alter' => $alter,
                    'last_error' => (string)$wpdb->last_error,
                ]);
            }
        }
    }
    if ($ok) {
        wp_cache_set($cache_key, '1', $group, 6 * HOUR_IN_SECONDS);
    } else {
        wp_cache_delete($cache_key, $group);
    }
}

function allspice_recipes_get_overview_by_url_key(string $url_key): ?array {
    if (!allspice_recipes_table_exists()) return null;
    $url_key = trim($url_key);
    if ($url_key === '') return null;
    if (function_exists('allspice_recipes_table_ensure_columns')) {
        allspice_recipes_table_ensure_columns();
    }
    $cache_key = allspice_recipes_cache_key('ov', $url_key);
    $cached = wp_cache_get($cache_key, allspice_db_cache_group());
    if ($cached !== false && is_array($cached)) return $cached;
    global $wpdb;
    if (!is_object($wpdb)) return null;
    $table = allspice_recipes_table_ident();
    // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
    // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    // phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter
    $row = $wpdb->get_row(
        $wpdb->prepare(
            "SELECT recipe_id, url, wp_post_id, ingredients_json, time_text, recipe_title, meta_description, meta_description_valid, takeaways_json, faqs_json, servings, calories
            FROM {$table}
            WHERE url_key = %s AND status = %s AND import_status = %s
            LIMIT 1",
            $url_key,
            'recipe',
            'imported'
        ),
        ARRAY_A
    );
    // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery
    // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
    // phpcs:enable PluginCheck.Security.DirectDB.UnescapedDBParameter
    if (!$row || !is_array($row)) return null;
    $out = [
        'recipe_id' => null,
        'url' => null,
        'wp_post_id' => null,
        'ingredients' => [],
        'time' => null,
        'recipe_title' => null,
        'meta_description' => null,
        'meta_description_valid' => null,
        'takeaways' => [],
        'faqs' => [],
        'servings' => null,
        'calories' => null,
    ];
    $rid = isset($row['recipe_id']) ? trim((string)$row['recipe_id']) : '';
    $out['recipe_id'] = ($rid !== '') ? $rid : null;
    $u = isset($row['url']) ? trim((string)$row['url']) : '';
    $out['url'] = ($u !== '') ? $u : null;
    $wpid = isset($row['wp_post_id']) ? trim((string)$row['wp_post_id']) : '';
    $out['wp_post_id'] = ($wpid !== '') ? $wpid : null;
    if (!empty($row['ingredients_json'])) {
        $decoded = json_decode((string)$row['ingredients_json'], true);
        if (is_array($decoded)) $out['ingredients'] = $decoded;
    }
    $time = isset($row['time_text']) ? trim((string)$row['time_text']) : '';
    $out['time'] = ($time !== '') ? $time : null;
    $ti = isset($row['recipe_title']) ? trim((string)$row['recipe_title']) : '';
    $out['recipe_title'] = ($ti !== '') ? $ti : null;
    $md = isset($row['meta_description']) ? trim((string)$row['meta_description']) : '';
    $out['meta_description'] = ($md !== '') ? $md : null;
    $out['meta_description_valid'] = (isset($row['meta_description_valid']) && $row['meta_description_valid'] !== null) ? (bool)intval($row['meta_description_valid']) : null;
    if (!empty($row['takeaways_json'])) {
        $decoded = json_decode((string)$row['takeaways_json'], true);
        if (is_array($decoded)) $out['takeaways'] = $decoded;
    }
    if (!empty($row['faqs_json'])) {
        $decoded = json_decode((string)$row['faqs_json'], true);
        if (is_array($decoded)) $out['faqs'] = $decoded;
    }
    $out['servings'] = (isset($row['servings']) && $row['servings'] !== null) ? (int)$row['servings'] : null;
    $out['calories'] = (isset($row['calories']) && $row['calories'] !== null) ? (int)$row['calories'] : null;
    wp_cache_set($cache_key, $out, allspice_db_cache_group(), 10 * MINUTE_IN_SECONDS);
    return $out;
}