File: /var/www/html/wp-content/plugins/allspice/includes/api.php
<?php
// includes/api.php
if (!defined('ABSPATH')) exit;
if (!defined('ALLSPICE_SETTINGS_OPTION')) define('ALLSPICE_SETTINGS_OPTION', 'allspice_settings');
function allspice_opt_get(): array {
$v = get_option(ALLSPICE_SETTINGS_OPTION);
if (!is_array($v)) $v = array();
if (!isset($v['partner_id'])) $v['partner_id'] = '';
if (!isset($v['domain_id'])) $v['domain_id'] = '';
if (!isset($v['webhook_token'])) $v['webhook_token'] = '';
if (!isset($v['buttons_cache'])) $v['buttons_cache'] = '';
if (!isset($v['buttons_count'])) $v['buttons_count'] = 0;
if (!isset($v['cache_fetched_at'])) $v['cache_fetched_at'] = 0;
if (!isset($v['cache_last_error'])) $v['cache_last_error'] = '';
if (!isset($v['cache_ttl'])) $v['cache_ttl'] = 12 * HOUR_IN_SECONDS;
if (!isset($v['manual_refreshed_at'])) $v['manual_refreshed_at'] = 0;
if (!isset($v['manual_last_error'])) $v['manual_last_error'] = '';
if (!isset($v['recipes_count'])) $v['recipes_count'] = 0;
if (!isset($v['recipes_synced_at'])) $v['recipes_synced_at'] = 0;
if (!isset($v['recipes_last_error'])) $v['recipes_last_error'] = '';
if (!isset($v['theme_policies_cache'])) $v['theme_policies_cache'] = '';
if (!isset($v['theme_policies_synced_at'])) $v['theme_policies_synced_at'] = 0;
if (!isset($v['theme_policies_last_error'])) $v['theme_policies_last_error'] = '';
if (!isset($v['premium'])) $v['premium'] = true;
if (!isset($v['premium_checked_at'])) $v['premium_checked_at'] = 0;
if (!isset($v['premium_last_error'])) $v['premium_last_error'] = '';
return $v;
}
function allspice_opt_update(array $patch): bool {
$cur = allspice_opt_get();
$new = array_merge($cur, $patch);
$ok = update_option(ALLSPICE_SETTINGS_OPTION, $new, false);
wp_cache_delete(ALLSPICE_SETTINGS_OPTION, 'options');
wp_cache_delete('alloptions', 'options');
return (bool)$ok;
}
function allspice_opt_update_force(array $patch): bool {
$cur = allspice_opt_get();
$new = array_merge($cur, $patch);
delete_option(ALLSPICE_SETTINGS_OPTION);
$ok = add_option(ALLSPICE_SETTINGS_OPTION, $new, '', false);
wp_cache_delete(ALLSPICE_SETTINGS_OPTION, 'options');
wp_cache_delete('alloptions', 'options');
return (bool)$ok;
}
function allspice_webhook_base(): string {
$base = defined('ALLSPICE_WEBHOOK_BASE') ? (string)ALLSPICE_WEBHOOK_BASE : ALLSPICE_WEBHOOK_BASE_DEFAULT;
$base = rtrim($base, '/');
return apply_filters('allspice_webhook_base', $base);
}
function allspice_buttons_endpoint(): string {
$path = '/loadButtons';
return apply_filters('allspice_buttons_endpoint', allspice_webhook_base() . $path);
}
function allspice_wp_event_endpoint(): string {
$path = '/wp';
return apply_filters('allspice_wp_event_endpoint', allspice_webhook_base() . $path);
}
function allspice_buttons_cache_count_from_payload($json): int {
if (is_array($json) && isset($json['buttons']) && is_array($json['buttons'])) return count($json['buttons']);
if (is_array($json) && array_is_list($json)) return count($json);
return 0;
}
function allspice_buttons_cache_get(): array {
$s = allspice_opt_get();
$raw = (string)$s['buttons_cache'];
if ($raw === '') return array();
$json = json_decode($raw, true);
if (!is_array($json)) return array();
if (isset($json['buttons']) && is_array($json['buttons'])) return $json['buttons'];
if (isset($json[0]) && is_array($json)) return $json;
return array();
}
function allspice_refresh_buttons_cache(bool $force = false): array {
$s = allspice_opt_get();
$partner_id = trim((string)$s['partner_id']);
$domain_id = trim((string)$s['domain_id']);
$token = trim((string)$s['webhook_token']);
$ttl = (int)$s['cache_ttl'];
if ($ttl < 300) $ttl = 300;
$now = time();
if (!$force && (int)$s['cache_fetched_at'] > 0 && ($now - (int)$s['cache_fetched_at']) < $ttl && (string)$s['buttons_cache'] !== '') {
return array('ok' => true, 'cached' => true, 'count' => (int)$s['buttons_count'], 'error' => '', 'wrote' => false);
}
if ($partner_id === '' || $domain_id === '' || $token === '') {
$msg = 'Missing partner_id, domain_id, or webhook_token';
$wrote = allspice_opt_update(array('cache_last_error' => $msg));
return array('ok' => $wrote, 'cached' => false, 'count' => 0, 'error' => $msg, 'wrote' => $wrote);
}
$endpoint = allspice_buttons_endpoint();
$url = add_query_arg(array('partner_id' => $partner_id, 'domain_id' => $domain_id), $endpoint);
$headers = array(
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $token,
'X-Allspice-Partner-Id' => $partner_id,
'X-Allspice-Domain-Id' => $domain_id,
'Cache-Control' => 'no-cache',
'Pragma' => 'no-cache',
);
$resp = wp_remote_get($url, array('timeout' => 15, 'headers' => $headers));
if (is_wp_error($resp)) {
$msg = trim((string)$resp->get_error_message());
if ($msg === '') {
$codes = $resp->get_error_codes();
$msg = !empty($codes)
? ('WP_Error: ' . implode(', ', $codes))
: 'WP_Error with no message (wp_remote_get)';
}
$wrote = allspice_opt_update(array('cache_last_error' => $msg));
return array('ok' => $wrote, 'cached' => false, 'count' => 0, 'error' => $msg, 'wrote' => $wrote);
}
$code = (int)wp_remote_retrieve_response_code($resp);
$body = (string)wp_remote_retrieve_body($resp);
if ($code < 200 || $code >= 300) {
$msg = 'HTTP ' . $code . ': ' . substr($body, 0, 300);
$wrote = allspice_opt_update(array('cache_last_error' => $msg));
return array('ok' => $wrote, 'cached' => false, 'count' => 0, 'error' => $msg, 'wrote' => $wrote);
}
$json = json_decode($body, true);
if (!is_array($json)) {
$msg = 'Invalid JSON: ' . substr($body, 0, 300);
$wrote = allspice_opt_update(array('cache_last_error' => $msg));
return array('ok' => $wrote, 'cached' => false, 'count' => 0, 'error' => $msg, 'wrote' => $wrote);
}
$count = allspice_buttons_cache_count_from_payload($json);
$wrote = allspice_opt_update(array(
'buttons_cache' => wp_json_encode($json),
'buttons_count' => $count,
'cache_fetched_at' => $now,
'cache_last_error' => '',
'cache_ttl' => $ttl,
'allspice_debug_nonce' => wp_generate_uuid4(),
));
$s2 = allspice_opt_get();
// error_log('[Allspice] refresh finished. now=' . $now . ' stored_fetched_at=' . (int)$s2['cache_fetched_at']);
return array('ok' => true, 'cached' => false, 'count' => $count, 'error' => $wrote ? '' : 'Fetched OK, but option write failed (update_option returned false)', 'wrote' => $wrote);
}
function allspice_recipes_endpoint(): string {
$path = '/loadRecipesForWPSync';
return apply_filters('allspice_recipes_endpoint', allspice_webhook_base() . $path);
}
function allspice_recipes_list_from_payload($json): array {
if (is_array($json) && isset($json['recipes']) && is_array($json['recipes'])) return $json['recipes'];
if (is_array($json) && isset($json['items']) && is_array($json['items'])) return $json['items'];
if (is_array($json) && isset($json[0]) && is_array($json)) return $json;
return array();
}
function allspice_refresh_recipes_cache(bool $force = false): array {
$s = allspice_opt_get();
$partner_id = trim((string)$s['partner_id']);
$domain_id = trim((string)$s['domain_id']);
$token = trim((string)$s['webhook_token']);
$now = time();
if ($partner_id === '' || $domain_id === '' || $token === '') {
$msg = 'Missing partner_id, domain_id, or webhook_token';
allspice_opt_update(array('recipes_last_error' => $msg));
return array('ok' => false, 'count' => 0, 'error' => $msg);
}
$endpoint = allspice_recipes_endpoint();
$url = add_query_arg(array('partner_id' => $partner_id, 'domain_id' => $domain_id), $endpoint);
$headers = array(
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $token,
'X-Allspice-Partner-Id' => $partner_id,
'X-Allspice-Domain-Id' => $domain_id,
'Cache-Control' => 'no-cache',
'Pragma' => 'no-cache',
);
$resp = wp_remote_get($url, array('timeout' => 30, 'headers' => $headers));
if (is_wp_error($resp)) {
$msg = trim((string)$resp->get_error_message());
if ($msg === '') {
$codes = $resp->get_error_codes();
$msg = !empty($codes)
? ('WP_Error: ' . implode(', ', $codes))
: 'WP_Error with no message (wp_remote_get)';
}
allspice_opt_update(array('recipes_last_error' => $msg));
return array('ok' => false, 'count' => 0, 'error' => $msg);
}
$code = (int)wp_remote_retrieve_response_code($resp);
$body = (string)wp_remote_retrieve_body($resp);
if ($code < 200 || $code >= 300) {
$msg = 'HTTP ' . $code . ': ' . substr($body, 0, 300);
allspice_opt_update(array('recipes_last_error' => $msg));
return array('ok' => false, 'count' => 0, 'error' => $msg);
}
$json = json_decode($body, true);
if (!is_array($json)) {
$msg = 'Invalid JSON: ' . substr($body, 0, 300);
allspice_opt_update(array('recipes_last_error' => $msg));
return array('ok' => false, 'count' => 0, 'error' => $msg);
}
$premium = array_key_exists('premium', $json) ? (bool)$json['premium'] : true;
$items = allspice_recipes_list_from_payload($json);
if (!function_exists('allspice_upsert_recipes') || !function_exists('allspice_recipes_count')) {
$msg = 'Recipe DB helpers not loaded (allspice_upsert_recipes / allspice_recipes_count missing)';
allspice_opt_update(array('recipes_last_error' => $msg));
return array('ok' => false, 'count' => 0, 'error' => $msg);
}
if (function_exists('allspice_recipes_table_ensure_columns')) {
allspice_recipes_table_ensure_columns();
}
$written = allspice_upsert_recipes($items);
$count = allspice_recipes_count();
allspice_opt_update(array(
'recipes_count' => $count,
'recipes_synced_at' => $now,
'recipes_last_error' => '',
'premium' => $premium,
'premium_checked_at' => $now,
'premium_last_error' => '',
));
return array(
'ok' => true,
'count' => $count,
'error' => '',
'written' => $written,
'premium' => $premium,
);
}
function allspice_theme_policies_endpoint(): string {
$path = '/loadThemeAndPolicies';
return apply_filters('allspice_theme_policies_endpoint', allspice_webhook_base() . $path);
}
function allspice_theme_policies_extract($json): array {
if (!is_array($json)) return ['theme' => null, 'policy' => null, 'domain' => ''];
if (array_key_exists('theme', $json) || array_key_exists('policy', $json) || array_key_exists('domain', $json) || array_key_exists('domain_name', $json)) {
return [
'theme' => $json['theme'] ?? null,
'policy' => $json['policy'] ?? null,
'domain' => (string)($json['domain'] ?? $json['domain_name'] ?? ''),
];
}
if (isset($json['data']) && is_array($json['data']) && (isset($json['data']['theme']) || isset($json['data']['policy']) || isset($json['data']['domain']) || isset($json['data']['domain_name']))) {
return [
'theme' => $json['data']['theme'] ?? null,
'policy' => $json['data']['policy'] ?? null,
'domain' => (string)($json['data']['domain'] ?? $json['data']['domain_name'] ?? ''),
];
}
foreach ($json as $v) {
if (!is_array($v)) continue;
if (isset($v['theme']) || isset($v['policy']) || isset($v['domain']) || isset($v['domain_name'])) {
return [
'theme' => $v['theme'] ?? null,
'policy' => $v['policy'] ?? null,
'domain' => (string)($v['domain'] ?? $v['domain_name'] ?? ''),
];
}
}
return ['theme' => null, 'policy' => null, 'domain' => ''];
}
function allspice_theme_policies_cache_get(): array {
$s = allspice_opt_get();
$raw = (string)($s['theme_policies_cache'] ?? '');
if ($raw === '') return ['theme' => null, 'policy' => null, 'domain' => '', 'raw' => null];
$json = json_decode($raw, true);
if (!is_array($json)) return ['theme' => null, 'policy' => null, 'domain' => '', 'raw' => null];
$ex = allspice_theme_policies_extract($json);
return [
'theme' => $ex['theme'],
'policy' => $ex['policy'],
'domain' => (string)($ex['domain'] ?? ''),
'raw' => $json
];
}
function allspice_refresh_theme_policies_cache(bool $force = false): array {
$s = allspice_opt_get();
$partner_id = trim((string)$s['partner_id']);
$domain_id = trim((string)$s['domain_id']);
$token = trim((string)$s['webhook_token']);
$ttl = (int)($s['cache_ttl'] ?? 12 * HOUR_IN_SECONDS);
if ($ttl < 300) $ttl = 300;
$now = time();
if (
!$force &&
(int)($s['theme_policies_synced_at'] ?? 0) > 0 &&
($now - (int)($s['theme_policies_synced_at'] ?? 0)) < $ttl &&
(string)($s['theme_policies_cache'] ?? '') !== ''
) {
$cached = allspice_theme_policies_cache_get();
$theme_ok = ($cached['theme'] !== null);
$pol_ok = ($cached['policy'] !== null);
$domain_ok = (trim((string)($cached['domain'] ?? '')) !== '');
return [
'ok' => true,
'cached' => true,
'theme_ok' => $theme_ok,
'policy_ok' => $pol_ok,
'domain_ok' => $domain_ok,
'error' => '',
'wrote' => false,
];
}
if ($partner_id === '' || $domain_id === '' || $token === '') {
$msg = 'Missing partner_id, domain_id, or webhook_token';
$wrote = allspice_opt_update(['theme_policies_last_error' => $msg]);
return ['ok' => $wrote, 'cached' => false, 'theme_ok' => false, 'policy_ok' => false, 'domain_ok' => false, 'error' => $msg, 'wrote' => $wrote];
}
$endpoint = allspice_theme_policies_endpoint();
$url = add_query_arg(['partner_id' => $partner_id, 'domain_id' => $domain_id], $endpoint);
$headers = [
'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $token,
'X-Allspice-Partner-Id' => $partner_id,
'X-Allspice-Domain-Id' => $domain_id,
'Cache-Control' => 'no-cache',
'Pragma' => 'no-cache',
];
$resp = wp_remote_get($url, ['timeout' => 15, 'headers' => $headers]);
if (is_wp_error($resp)) {
$msg = trim((string)$resp->get_error_message());
if ($msg === '') {
$codes = $resp->get_error_codes();
$msg = !empty($codes) ? ('WP_Error: ' . implode(', ', $codes)) : 'WP_Error with no message (wp_remote_get)';
}
$wrote = allspice_opt_update(['theme_policies_last_error' => $msg]);
return ['ok' => $wrote, 'cached' => false, 'theme_ok' => false, 'policy_ok' => false, 'domain_ok' => false, 'error' => $msg, 'wrote' => $wrote];
}
$code = (int)wp_remote_retrieve_response_code($resp);
$body = (string)wp_remote_retrieve_body($resp);
if ($code < 200 || $code >= 300) {
$msg = 'HTTP ' . $code . ': ' . substr($body, 0, 300);
$wrote = allspice_opt_update(['theme_policies_last_error' => $msg]);
return ['ok' => $wrote, 'cached' => false, 'theme_ok' => false, 'policy_ok' => false, 'domain_ok' => false, 'error' => $msg, 'wrote' => $wrote];
}
$json = json_decode($body, true);
if (!is_array($json)) {
$msg = 'Invalid JSON: ' . substr($body, 0, 300);
$wrote = allspice_opt_update(['theme_policies_last_error' => $msg]);
return ['ok' => $wrote, 'cached' => false, 'theme_ok' => false, 'policy_ok' => false, 'domain_ok' => false, 'error' => $msg, 'wrote' => $wrote];
}
$ex = allspice_theme_policies_extract($json);
$theme_ok = ($ex['theme'] !== null);
$pol_ok = ($ex['policy'] !== null);
$domain_ok = (trim((string)($ex['domain'] ?? '')) !== '');
$wrote = allspice_opt_update([
'theme_policies_cache' => wp_json_encode($json),
'theme_policies_synced_at' => $now,
'theme_policies_last_error' => '',
]);
return [
'ok' => true,
'cached' => false,
'theme_ok' => $theme_ok,
'policy_ok' => $pol_ok,
'domain_ok' => $domain_ok,
'error' => $wrote ? '' : 'Fetched OK, but option write failed (update_option returned false)',
'wrote' => $wrote,
];
}
function allspice_theme_policy_payload(): array {
$s = allspice_opt_get();
$tp = function_exists('allspice_theme_policies_cache_get')
? allspice_theme_policies_cache_get()
: ['theme' => null, 'policy' => null, 'domain' => '', 'raw' => null];
$raw = is_array($tp['raw']) ? $tp['raw'] : [];
return [
'partner_id' => (string)$s['partner_id'],
'domain_id' => (string)$s['domain_id'],
'synced_at' => (int)($s['theme_policies_synced_at'] ?? 0),
'last_error' => (string)($s['theme_policies_last_error'] ?? ''),
'ok' => isset($raw['ok']) ? (bool)$raw['ok'] : null,
'hash' => (string)($raw['hash'] ?? ''),
'fetched_at' => (string)($raw['fetched_at'] ?? ''),
'theme_id' => (string)($raw['theme_id'] ?? ''),
'domain' => (string)($tp['domain'] ?? ''),
'theme' => $tp['theme'],
'policy' => $tp['policy'],
];
}