function gemini_log($data) {
$dir = WP_CONTENT_DIR . '/secure/logs';
$log_file = $dir . '/gemini-error.log';
// Ensure directory exists
if (!is_dir($dir)) {
wp_mkdir_p($dir);
}
$entry = "[" . date('Y-m-d H:i:s') . "]
";
$entry .= print_r($data, true) . "
";
// Try writing to file
$ok = @file_put_contents($log_file, $entry, FILE_APPEND);
// Fallback to PHP error_log if file write fails
if ($ok === false) {
$last = error_get_last();
error_log("GEMINI_LOG_WRITE_FAILED: " . print_r([
'log_file' => $log_file,
'is_dir' => is_dir($dir),
'dir_writable' => is_writable($dir),
'file_exists' => file_exists($log_file),
'file_writable' => file_exists($log_file) ? is_writable($log_file) : null,
'last_error' => $last,
'data' => $data,
], true));
}
}
// Debug flag shared between handlers and http_api_debug hook
$GLOBALS['gemini_debug_mode'] = $GLOBALS['gemini_debug_mode'] ?? false;
function gemini_is_debug_mode() {
// JSON requests may set the global flag explicitly inside handlers
if (!empty($GLOBALS['gemini_debug_mode'])) {
return true;
}
// Fallback for classic form/query param
return (isset($_REQUEST['debug']) && ($_REQUEST['debug'] === '1' || $_REQUEST['debug'] === 1));
}
// Low-level HTTP debug (logs request/response) - logs only when debug mode is enabled
add_action('http_api_debug', function ($response, $context, $class, $args, $url) {
if (!gemini_is_debug_mode()) {
return;
}
$safe_args = [
'method' => $args['method'] ?? null,
'timeout' => $args['timeout'] ?? null,
'headers' => $args['headers'] ?? null,
];
// Avoid logging huge bodies in debug hook
if (isset($args['body'])) {
$body = is_string($args['body']) ? $args['body'] : wp_json_encode($args['body']);
$safe_args['body_len'] = is_string($body) ? strlen($body) : null;
}
$resp_payload = null;
if (is_wp_error($response)) {
$resp_payload = ['wp_error' => $response->get_error_message()];
} else {
$resp_payload = [
'code' => wp_remote_retrieve_response_code($response),
'headers' => wp_remote_retrieve_headers($response),
'body' => wp_remote_retrieve_body($response),
];
}
gemini_log([
'type' => 'http_api_debug',
'context' => $context,
'url' => $url,
'args' => $safe_args,
'response' => $resp_payload,
]);
}, 10, 5);
function my_get_gemini_access_token() {
$json_path = WP_CONTENT_DIR . '/secure/gemini-service-account.json';
if (!file_exists($json_path)) {
gemini_log(['type' => 'token_error', 'message' => 'Service account JSON not found', 'json_path' => $json_path]);
return false;
}
$sa = json_decode(file_get_contents($json_path), true);
if (!$sa || empty($sa['client_email']) || empty($sa['private_key'])) {
gemini_log(['type' => 'token_error', 'message' => 'Invalid service account JSON (missing client_email/private_key)', 'json_path' => $json_path]);
return false;
}
$now = time();
$jwt_header = rtrim(strtr(base64_encode(json_encode([
'alg' => 'RS256',
'typ' => 'JWT'
])), '+/', '-_'), '=');
$jwt_claim = rtrim(strtr(base64_encode(json_encode([
'iss' => $sa['client_email'],
'scope' => 'https://www.googleapis.com/auth/generative-language',
'aud' => 'https://oauth2.googleapis.com/token',
'iat' => $now,
'exp' => $now + 3600,
])), '+/', '-_'), '=');
$jwt_unsigned = $jwt_header . '.' . $jwt_claim;
$signature = '';
$signed_ok = openssl_sign($jwt_unsigned, $signature, $sa['private_key'], 'sha256');
if (!$signed_ok) {
gemini_log(['type' => 'token_error', 'message' => 'openssl_sign failed while creating JWT']);
return false;
}
$jwt_signature = rtrim(strtr(base64_encode($signature), '+/', '-_'), '=');
$jwt = $jwt_unsigned . '.' . $jwt_signature;
$response = wp_remote_post('https://oauth2.googleapis.com/token', [
'headers' => [
'Content-Type' => 'application/x-www-form-urlencoded',
],
'body' => [
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion' => $jwt,
],
'timeout' => 20,
]);
if (is_wp_error($response)) {
gemini_log(['type' => 'token_wp_error', 'error' => $response->get_error_message()]);
return false;
}
$code = wp_remote_retrieve_response_code($response);
$raw = wp_remote_retrieve_body($response);
$body = json_decode($raw, true);
if ($code !== 200) {
gemini_log([
'type' => 'token_http_error',
'http_code' => $code,
'raw_body' => $raw,
'parsed' => $body,
]);
return false;
}
$token = $body['access_token'] ?? false;
if (!$token) {
gemini_log([
'type' => 'token_error',
'message' => 'access_token missing in token response',
'raw_body' => $raw,
'parsed' => $body,
]);
return false;
}
// Optional: log token acquisition only in debug mode (avoid noise)
if (gemini_is_debug_mode()) {
gemini_log(['type' => 'token_ok', 'http_code' => $code]);
}
return $token;
}
add_action('wp_ajax_send_to_gemini', 'handle_send_to_gemini');
add_action('wp_ajax_nopriv_send_to_gemini', 'handle_send_to_gemini');
function handle_send_to_gemini() {
// ورودی هم با JSON و هم با فرم ساپورت شود
$userData = null;
$plansJson = null;
$debug_mode = false;
// اگر JSON باشد
$content_type = strtolower($_SERVER['CONTENT_TYPE'] ?? '');
if (strpos($content_type, 'application/json') !== false) {
$raw = file_get_contents('php://input');
$json = json_decode($raw, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($json)) {
if (isset($json['userData'])) {
$userData = sanitize_textarea_field($json['userData']);
}
if (isset($json['plansJson'])) {
$plansJson = is_string($json['plansJson']) ? $json['plansJson'] : json_encode($json['plansJson'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
if (isset($json['debug'])) {
$debug_mode = ($json['debug'] === '1' || $json['debug'] === 1 || $json['debug'] === true);
}
}
}
// اگر فرم POST کلاسیک باشد
if (!$userData && isset($_POST['userData'])) {
$userData = sanitize_textarea_field($_POST['userData']);
}
if (!$plansJson && isset($_POST['plansJson'])) {
$plansJson = wp_unslash($_POST['plansJson']);
}
if (!$debug_mode && isset($_POST['debug'])) {
$debug_mode = ($_POST['debug'] === '1');
}
$GLOBALS['gemini_debug_mode'] = $debug_mode;
// Read optional extra fields (name, phone, website, formId)
$extraFields = array();
if (isset($_POST['extraFields'])) {
$ef = json_decode(wp_unslash($_POST['extraFields']), true);
if (is_array($ef)) {
$extraFields = array(
'name' => isset($ef['name']) ? sanitize_text_field($ef['name']) : '',
'phone' => isset($ef['phone']) ? sanitize_text_field($ef['phone']) : '',
'website' => isset($ef['website']) ? esc_url_raw($ef['website']) : '',
'formId' => isset($ef['formId']) ? sanitize_text_field($ef['formId']) : '',
);
}
}
if (!$userData) {
wp_send_json_error(['message' => 'دادهای دریافت نشد']);
}
if (!$plansJson) {
wp_send_json_error(['message' => 'لیست پلنها ارسال نشد']);
}
// دیکد JSON پلنها
$plansArrayRaw = json_decode($plansJson, true);
if (!is_array($plansArrayRaw)) {
wp_send_json_error(['message' => 'فرمت JSON پلنها معتبر نیست.']);
}
// نرمالسازی کلیدها برای پرامپت (کوچک نگه داشتن ورودی)
$plansArray = [];
foreach ($plansArrayRaw as $p) {
$plansArray[] = [
'planName' => $p['name'] ?? '',
'planType' => $p['type'] ?? '',
'location' => $p['location'] ?? '',
'datacenter' => $p['datacenter'] ?? '',
'cpu' => $p['cpu'] ?? '',
'ram' => $p['ram'] ?? '',
'storage' => $p['storage'] ?? '',
'bandwidth' => $p['traffic'] ?? '',
'disk' => $p['storage'] ?? '',
'price' => $p['price'] ?? '',
'pricePeriod'=> $p['price_period'] ?? '',
'priceFormatted' => isset($p['price']) ? number_format((int) $p['price'], 0, '.', ',') : '',
'buyLink' => $p['link'] ?? '',
];
}
// در صورت خیلی زیاد بودن، یک سقف منطقی بگذاریم
if (count($plansArray) > 200) {
$plansArray = array_slice($plansArray, 0, 200);
}
$plansJsonMin = wp_json_encode($plansArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
// کلید و مدل
// $api_key = 'AIzaSyCuMe-s-NxY3cFLHTX3IgW5MhzJLndpQaU';
// $url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=' . $api_key;
// پیشفرض خالی
$flexBackupMsg = '';
// پرامپت — فقط HTML خروجی
$prompt = "
نقش شما «مشاور زیرساخت» است. بر اساس ورودی کاربر و لیست پلنها (به صورت JSON)، دقیقا یک پلن (VPS یا Dedicated) را انتخاب کنید.
شرط: اگر کاربر گیمر نیست و ماهانه بالای 20000 کاربر دارد و خودش حرفهای یا متخصص است و مانیتورینگ دائم دارد، از بین پلنهای سرور اختصاصی گزینه مناسب را پیشنهاد بده در غیر اینصورت از بین پلن های سرور مجازی یکی را انتخاب کن.
اگر چند گزینه مشابه دیدی، فقط بهترین را انتخاب کن و یک مورد برگردان.
هیچ بلاک کد یا backtick ارسال نکن. فقط HTML خالص قابل رندر بده.
- در خروجی، متن {{flexBackupMsg}} را بدون تغییر قرار بده. این متن را تو نباید پر کنی.
هر جا unlimited هستش به جاش بنویس نامحدود
[HTML قالب خروجی]
پیشنهاد هوشمند ما برای شما
با توجه به نیازمندی های شما و تحلیل پاسخ های شما توسط هوش مصنوعی ایران سرور، پلن زیر مناسب شما می باشد و تمام نیازمندی های شما را برطرف میکند
{{planName}}
نوع پلن: {{planType}}
موقعیت: {{location}}
پردازنده: {{cpu}}
رم: {{ram}} گیگ
فضا: {{storage}}
ترافیک ماهانه: {{bandwidth}}
{{priceFormatted}} تومان /ماهانه
{{flexBackupMsg}}
چتر آبی ایرانسرور: پشتیبانی کامل و همیشهدردسترس
همیشه یک تیم فنی حرفهای کنار شماست؛ از مانیتورینگ ۲۴ ساعته و پاسخ فوری زیر ۳۰ دقیقه، تا رفع فوری مشکلات امنیتی و فنی، مدیریت بکاپها، بهروزرسانیها و بهینهسازی سرور.
پیشنهاد ویژه بک آپ: شما میتوانید دو نسخه بک آپ کامل از سرور خود را به صورت رایگان روی سرورهای دیگر ما، بدون محدودیت حجمی، ذخیره کنید
';
}
// 3) جایگذاری: اگر placeholder هست، همانجا؛ وگرنه قبل از دکمه خرید
if (strpos($reply, '{{flexBackupMsg}}') !== false) {
$reply = str_replace('{{flexBackupMsg}}', $flexBackupMsg, $reply);
} else if ($flexBackupMsg) {
$reply = str_replace('
', $flexBackupMsg . '
', $reply);
}
$response_data = ['reply' => $reply];
if ($debug_mode) {
$response_data['debug'] = [
'model' => 'gemini-2.0-flash',
'prompt_length' => mb_strlen($prompt, 'UTF-8'),
'user_data_length' => mb_strlen($userData, 'UTF-8'),
'plans_count' => count($plansArray),
'plans_json_length' => mb_strlen($plansJsonMin, 'UTF-8'),
'plans_sample_3' => array_slice($plansArray, 0, 3),
'api_response_raw' => $body,
'payload_sent' => json_decode($payload, true),
];
}
// ✅ ارسال موفق به گوگل شیت با وضعیت موفق
send_to_gsheet_with_status($userData, $selectedPlanName, 'موفق ✓', $extraFields, $reply);
wp_send_json_success($response_data);
}
// ==========================================
// ✅ تابع کمکی جدید: ارسال به Google Sheet با وضعیت
// ============
// ==========================================
// ✅ تابع کمکی جدید: ارسال به Google Sheet با وضعیت
// ==========================================
function send_to_gsheet_with_status($userData, $planName, $status, $extraFields = [], $aiResponse = '') {
// $webhook_url = 'https://script.google.com/macros/s/AKfycbywyfWnfcqIrys8It_c18H6lMGeQnjdpv3LLGP7qSrsUWGHntmUGtEoN0YZX2QBgn6Y/exec';
$webhook_url = 'https://script.google.com/macros/s/AKfycbzVSwIkLziKpSaPFWV0KWHXg2lZd0BpO7Yx2T9RDRjzHIhR4dpaYaNGnM_XEiyRb1wXCA/exec';
// متن خلاصه پاسخ AI
$reply_text = '';
if ($aiResponse) {
$reply_text = html_entity_decode(wp_strip_all_tags($aiResponse));
if (function_exists('mb_substr')) {
$reply_text = mb_substr($reply_text, 0, 45000, 'UTF-8');
} else {
$reply_text = substr($reply_text, 0, 45000);
}
}
$gs_payload = array(
'name' => $extraFields['name'] ?? '',
'phone' => $extraFields['phone'] ?? '',
'website' => $extraFields['website'] ?? '',
'userData' => $userData,
'selectedPlanName' => $planName,
'aiResponseText' => $reply_text,
'geminiStatus' => $status, // ✅ فیلد جدید
'timestamp' => current_time('Y-m-d H:i:s'), // ✅ زمان دقیق
);
$args = array(
'timeout' => 12,
'headers' => array('Content-Type' => 'application/json; charset=utf-8'),
'body' => wp_json_encode($gs_payload),
);
wp_remote_post($webhook_url, $args);
}
add_action('wp_ajax_send_to_gemini_v2', 'is_handle_send_to_gemini_v2');
add_action('wp_ajax_nopriv_send_to_gemini_v2', 'is_handle_send_to_gemini_v2');
function is_handle_send_to_gemini_v2() {
// ورودیها
$userData = isset($_POST['userData']) ? sanitize_textarea_field(wp_unslash($_POST['userData'])) : '';
$userDataJson = isset($_POST['userDataJson']) ? json_decode(wp_unslash($_POST['userDataJson']), true) : array();
$plansJson = isset($_POST['plansJson']) ? wp_unslash($_POST['plansJson']) : '';
$hostingJson = isset($_POST['hostingJson']) ? wp_unslash($_POST['hostingJson']) : '';
$formType = isset($_POST['formType']) ? sanitize_text_field(wp_unslash($_POST['formType'])) : ''; // A یا B
if ($userData === '') {
wp_send_json_error(['message' => 'دادههای کاربر دریافت نشد.']);
}
// نرمالسازی: پلنها (اختیاری ولی توصیهشده)
$normalize_unlimited = function($v){ return is_string($v) && preg_match('/unlimited/i',$v) ? 'نامحدود' : $v; };
$plansArray = [];
if ($plansJson) {
$raw = json_decode($plansJson, true);
if (is_array($raw)) {
foreach ($raw as $p) {
$plansArray[] = [
'planName' => $p['plan_name'] ?? $p['name'] ?? '',
'planType' => $p['type'] ?? '', // اگر بود
'location' => $p['location'] ?? '', // موقعیت مکانی
'storage' => $p['storage'] ?? '', // حجم هارد
'bandwidth' => $normalize_unlimited($p['bandwidth'] ?? $p['traffic'] ?? ''), // ترافیک
'controlPanel' => $p['control_panel'] ?? '', // کنترل پنل
'dns' => $p['dns'] ?? '',
'support' => $p['support'] ?? '',
'warranty' => $p['warranty'] ?? '', // اگر در دادهات این کلید را داشته باشی
'features' => (isset($p['features']) && is_array($p['features'])) ? array_values($p['features']) : [],
'buyLink' => $p['buyLink'] ?? $p['link'] ?? $p['url'] ?? '',
];
}
}
}
// هاستینگ با similar_to (اختیاری)
$hostingMerged = [];
if ($hostingJson) {
$h = json_decode($hostingJson, true);
if (is_array($h)) {
foreach ($h as $catKey => $cat) {
$url = isset($cat['url']) ? esc_url_raw($cat['url']) : '';
$plans = isset($cat['plans']) && is_array($cat['plans']) ? $cat['plans'] : [];
foreach ($plans as $pl) {
$plansArray[] = [
'planName' => $pl['plan_name'] ?? $pl['name'] ?? '',
'planType' => $catKey, // wordpress_hosting / woocommerce_hosting / ...
'location' => $pl['location'] ?? '',
'storage' => $pl['storage'] ?? '',
'bandwidth' => $normalize_unlimited($pl['bandwidth'] ?? $pl['traffic'] ?? ''),
'controlPanel' => $pl['control_panel'] ?? '',
'dns' => $pl['dns'] ?? '',
'support' => $pl['support'] ?? '',
'warranty' => $pl['warranty'] ?? '',
'features' => (isset($pl['features']) && is_array($pl['features'])) ? array_values($pl['features']) : [],
'buyLink' => $pl['buyLink'] ?? $url,
];
}
}
}
}
// محدودیت منطقی
if (count($plansArray) > 400) $plansArray = array_slice($plansArray, 0, 400);
$plansJsonMin = wp_json_encode($plansArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
// پرامپت جدید (برای دو فرم A/B)
// A = دارای سایت: تاکید بر پلتفرم فعلی، مشکلات، ترافیک
// B = بدون سایت: تاکید بر نوع پروژه، ترجیحات مدیریت، اولویتها
$prompt_header = "نقش شما «مشاور خرید هاست و سرور» است. از بین لیست پلنهای ارائهشده (VPS/Dedicated + دستههای هاست)، دقیقا یک پلن مناسب را انتخاب کن و فقط HTML خالص (بدون بکتیک) برگردان.";
$prompt_rules = implode("\n", [
"- فقط از دادههای JSON استفاده کن و چیزی اختراع نکن.",
"- اگر هرجا Unlimited دیدی آن را به «نامحدود» تبدیل کن.",
"- خروجی باید دقیقا همان HTML دادهشده را برگرداند و فقط جای {{...}} ها را با مقادیر پر کند.",
"- اگر هر یک از فیلدهای زیر خالی بود، به جای مقدار آن «—» بگذار:",
" {{planName}}, {{storage}}, {{bandwidth}}, {{controlPanel}}, {{dns}}, {{support}}, {{location}}, {{warranty}}, {{featuresList}}",
"- برای «سایر ویژگیها»: اگر آرایه features موجود بود، {{featuresList}} را با
...
بساز؛ وگرنه برای {{featuresList}} «—» قرار بده.",
"- برای «نام پلن» از planName استفاده کن (نه planType).",
"- برای «حجم هارد» از storage، برای «ترافیک» از bandwidth، برای «کنترل پنل» از controlPanel، برای «DNS» از dns، برای «پشتیبانی» از support، برای «موقعیت مکانی» از location، برای «گارانتی استفاده از سرویس» از warranty استفاده کن.",
"- مقدار {{buyLink}} را دقیقا از کلید buyLink همان پلن انتخابی بگذار."
]);
$prompt_html = '
پیشنهاد هوشمند ما برای شما
بر اساس پاسخهای شما، این پلن بیشترین انطباق را با نیازهایتان دارد:
ایران سرور، با همکاری همیار آکادمی، از تمام کاربرانی که در حال آموزش در دوره های وبمستران هوشمند هستند، حمایت میکنه. این حمایت، به صورت ارائه رایگان خدمات میزبانیه. کافیه فرم انتهای همین صفحه رو تکمیل کنی تا کد تخفیف ۱۰۰٪ برات ایمیل بشه.
با هم میسازیم پویشی برای اطلاعرسانی و فرهنگسازیه. هر کار گروهی، هر مشارکت سازنده، هر مسئولیت اجتماعی که به حیات و رشد اکوسیستم کسبوکار کشور کمک میکنه، در باهم میسازیم اطلاع رسانی میشه. یکی از محورهای این پویش اطلاع رسانی حمایتهای کسبوکارهای بزرگ از استارتاپهاست.