0) ob_end_clean(); } if (!function_exists('wp_insert_post') || !function_exists('get_option')) { iw_plain('WordPress yüklenemedi', 500); } } function iw_e($value) { return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } function iw_cut($value, $max) { return function_exists('mb_substr') ? mb_substr($value, 0, $max, 'UTF-8') : substr($value, 0, $max); } function iw_text($value, $max) { $value = trim(preg_replace('/\s+/u', ' ', strip_tags((string)$value)) ?? ''); return iw_cut($value, $max); } function iw_slug($value) { if (function_exists('sanitize_title')) { $slug = (string)sanitize_title($value); return substr($slug !== '' ? $slug : 'article', 0, 80); } $value = strtolower(trim($value)); $value = trim((string)preg_replace('/[^a-z0-9]+/', '-', $value), '-'); return substr($value !== '' ? $value : 'article', 0, 80); } function iw_valid_http_url($url) { if (!filter_var($url, FILTER_VALIDATE_URL)) return false; return in_array(strtolower((string)parse_url($url, PHP_URL_SCHEME)), ['http', 'https'], true); } function iw_config() { $value = get_option(IW_OPTION_CONFIG, []); return is_array($value) ? $value : []; } function iw_save_config($config) { update_option(IW_OPTION_CONFIG, $config, false); } function iw_setup_code() { $existing = strtoupper(trim((string)get_option(IW_OPTION_SETUP, ''))); if (preg_match('/^[A-Z0-9]{8}$/', $existing)) return $existing; $alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; $code = ''; for ($i = 0; $i < 8; $i++) { $code .= $alphabet[random_int(0, strlen($alphabet) - 1)]; } update_option(IW_OPTION_SETUP, $code, false); return $code; } function iw_set_state($state) { $allowed = ['Sipariş alındı', 'Sipariş tamamlanıyor', 'Sipariş tamamlandı']; if (!in_array($state, $allowed, true)) return; update_option(IW_OPTION_STATE, ['state'=>$state, 'updated_at'=>gmdate('c')], false); } function iw_get_state() { $row = get_option(IW_OPTION_STATE, []); $state = is_array($row) ? (string)($row['state'] ?? '') : ''; return in_array($state, ['Sipariş alındı', 'Sipariş tamamlanıyor', 'Sipariş tamamlandı'], true) ? $state : 'Sipariş alındı'; } function iw_log($event, $context = []) { error_log('[IndexWell Publisher] ' . json_encode([ 'time'=>gmdate('c'), 'event'=>$event, 'context'=>$context, ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); } function iw_verify_nonce($nonce, $timestamp) { if (!preg_match('/^[A-Za-z0-9_-]{16,128}$/', $nonce)) { iw_json(['ok'=>false, 'error'=>'Invalid nonce.'], 401); } $key = 'iw_pub_nonce_' . substr(hash('sha256', $nonce), 0, 40); if (get_transient($key) !== false) { iw_json(['ok'=>false, 'error'=>'Replay request rejected.'], 409); } set_transient($key, $timestamp, 15 * MINUTE_IN_SECONDS); } function iw_verify_signed_request($body, $config) { $secret = (string)($config['secret'] ?? ''); if (strlen($secret) < 32) iw_json(['ok'=>false, 'error'=>'Publisher is not paired.'], 503); $timestamp = (int)($_SERVER['HTTP_X_INDEXWELL_TIMESTAMP'] ?? 0); $nonce = trim((string)($_SERVER['HTTP_X_INDEXWELL_NONCE'] ?? '')); $signature = strtolower(trim((string)($_SERVER['HTTP_X_INDEXWELL_SIGNATURE'] ?? ''))); if ($timestamp < 1 || abs(time() - $timestamp) > IW_MAX_CLOCK_SKEW) { iw_json(['ok'=>false, 'error'=>'Expired request timestamp.'], 401); } if (!preg_match('/^[a-f0-9]{64}$/', $signature)) { iw_json(['ok'=>false, 'error'=>'Invalid signature.'], 401); } $expected = hash_hmac('sha256', $timestamp . "\n" . $nonce . "\n" . $body, $secret); if (!hash_equals($expected, $signature)) { iw_log('signature_rejected'); iw_json(['ok'=>false, 'error'=>'Signature verification failed.'], 401); } iw_verify_nonce($nonce, $timestamp); } function iw_wp_author_id() { if (function_exists('get_users')) { $users = get_users([ 'role'=>'administrator', 'number'=>1, 'fields'=>'ID', 'orderby'=>'ID', 'order'=>'ASC', ]); if (is_array($users) && isset($users[0])) return max(1, (int)$users[0]); } return 1; } function iw_anchor_html($anchor, $target, $linkRel) { $rel = $linkRel === 'nofollow' ? ' rel="nofollow noopener noreferrer"' : ' rel="noopener noreferrer"'; return '' . iw_e($anchor) . ''; } function iw_paragraphs($text) { $parts = preg_split('/\n\s*\n|\r\n\s*\r\n/', trim($text)) ?: []; $out = []; foreach ($parts as $part) { $part = trim($part); if ($part !== '') $out[] = '

' . nl2br(iw_e($part), false) . '

'; } return $out; } function iw_article_content($payload, $orderId) { $title = iw_text($payload['title'] ?? '', 180); $intro = trim((string)($payload['intro'] ?? '')); $anchor = iw_text($payload['anchor_text'] ?? '', 180); $target = trim((string)($payload['target_url'] ?? '')); $linkRel = strtolower(trim((string)($payload['link_rel'] ?? 'dofollow'))); if ($title === '' || $anchor === '' || !iw_valid_http_url($target)) { throw new RuntimeException('Article title, anchor or target URL is invalid.'); } if (!in_array($linkRel, ['dofollow', 'nofollow'], true)) $linkRel = 'dofollow'; $anchorLink = iw_anchor_html($anchor, $target, $linkRel); $sentences = [ 'Konu hakkında güncel ayrıntılar için %s kaynağı incelenebilir.', 'İlgili bilgi ve güncel erişim ayrıntıları %s bağlantısında yer alır.', 'Daha ayrıntılı değerlendirme için %s sayfası referans olarak kullanılabilir.', 'Güncel bilgiye ulaşmak isteyen kullanıcılar %s kaynağını kontrol edebilir.', 'Konuya ilişkin ek bilgiler %s bağlantısı üzerinden doğrulanabilir.', 'Güncel açıklamalar ve erişim bilgileri için %s sayfasına bakılabilir.', ]; $sentence = sprintf($sentences[hexdec(substr(hash('sha256', $orderId), 0, 2)) % count($sentences)], $anchorLink); $parts = iw_paragraphs($intro); if ($parts === []) { $parts[] = '

' . $sentence . '

'; } else { $insertAt = count($parts) > 1 ? 1 : count($parts); array_splice($parts, $insertAt, 0, ['

' . $sentence . '

']); } $sections = ''; foreach ((array)($payload['sections'] ?? []) as $section) { if (!is_array($section)) continue; $heading = iw_text($section['heading'] ?? '', 180); $body = trim((string)($section['body'] ?? '')); if ($heading !== '') $sections .= '

' . iw_e($heading) . '

'; foreach (iw_paragraphs($body) as $paragraph) $sections .= $paragraph; } $content = implode("\n", $parts) . "\n" . $sections; return function_exists('wp_kses_post') ? (string)wp_kses_post($content) : $content; } function iw_wp_find_existing_post($orderId) { $posts = get_posts([ 'post_type'=>'post', 'post_status'=>'any', 'posts_per_page'=>1, 'fields'=>'ids', 'meta_key'=>'_indexwell_order_id', 'meta_value'=>$orderId, 'no_found_rows'=>true, 'suppress_filters'=>true, ]); return is_array($posts) && isset($posts[0]) ? (int)$posts[0] : 0; } function iw_publish_wordpress($payload, $orderId) { $existingId = iw_wp_find_existing_post($orderId); if ($existingId > 0) { $url = (string)get_permalink($existingId); if ($url !== '') return ['post_id'=>$existingId, 'published_url'=>$url, 'duplicate'=>true]; } $title = iw_text($payload['title'] ?? '', 180); $description = iw_text($payload['meta_description'] ?? '', 300); $slug = iw_slug((string)($payload['slug'] ?? $title)) . '-' . substr(hash('sha256', $orderId), 0, 8); $anchor = iw_text($payload['anchor_text'] ?? '', 180); $target = trim((string)($payload['target_url'] ?? '')); $content = iw_article_content($payload, $orderId); if ($title === '' || $content === '') throw new RuntimeException('WordPress post content is empty.'); $postData = [ 'post_title'=>$title, 'post_name'=>$slug, 'post_content'=>$content, 'post_excerpt'=>$description, 'post_status'=>'publish', 'post_type'=>'post', 'post_author'=>iw_wp_author_id(), 'comment_status'=>'closed', 'ping_status'=>'closed', 'meta_input'=>[ '_indexwell_order_id'=>$orderId, '_indexwell_target_url'=>$target, '_indexwell_anchor_text'=>$anchor, '_yoast_wpseo_metadesc'=>$description, 'rank_math_description'=>$description, ], ]; $postId = wp_insert_post($postData, true); if (is_wp_error($postId)) { throw new RuntimeException('WordPress post could not be created: ' . $postId->get_error_message()); } $postId = (int)$postId; if ($postId < 1) throw new RuntimeException('WordPress post could not be created.'); clean_post_cache($postId); $url = (string)get_permalink($postId); if ($url === '') $url = (string)home_url('/?p=' . $postId); if (!iw_valid_http_url($url)) throw new RuntimeException('WordPress permalink could not be generated.'); return ['post_id'=>$postId, 'published_url'=>$url, 'duplicate'=>false]; } function iw_status_page($config) { $paired = strlen((string)($config['secret'] ?? '')) >= 32; iw_plain($paired ? iw_get_state() : iw_setup_code()); } try { iw_boot_wordpress(); $method = strtoupper((string)($_SERVER['REQUEST_METHOD'] ?? 'GET')); $config = iw_config(); if ($method === 'GET') iw_status_page($config); if ($method !== 'POST') iw_json(['ok'=>false, 'error'=>'Method not allowed.'], 405); $length = (int)($_SERVER['CONTENT_LENGTH'] ?? 0); if ($length > IW_MAX_BODY_BYTES) iw_json(['ok'=>false, 'error'=>'Request is too large.'], 413); $body = (string)file_get_contents('php://input'); $payload = json_decode($body, true); if (!is_array($payload)) iw_json(['ok'=>false, 'error'=>'Invalid JSON body.'], 400); $action = strtolower(trim((string)($payload['action'] ?? ''))); if ($action === 'pair') { if (strlen((string)($config['secret'] ?? '')) >= 32) { iw_json(['ok'=>false, 'error'=>'Publisher is already paired.'], 409); } $provided = strtoupper(trim((string)($payload['setup_code'] ?? ''))); if (!hash_equals(iw_setup_code(), $provided)) iw_json(['ok'=>false, 'error'=>'Setup code is incorrect.'], 401); $secret = trim((string)($payload['secret'] ?? '')); if (strlen($secret) < 48) iw_json(['ok'=>false, 'error'=>'Pairing secret is too short.'], 422); $siteName = iw_text($payload['site_name'] ?? parse_url(home_url('/'), PHP_URL_HOST), 120); $config = [ 'secret'=>$secret, 'site_name'=>$siteName, 'paired_at'=>gmdate('c'), 'version'=>IW_PUBLISHER_VERSION, 'mode'=>'wordpress', ]; iw_save_config($config); delete_option(IW_OPTION_SETUP); iw_set_state('Sipariş alındı'); iw_log('paired', ['site_name'=>$siteName]); iw_json([ 'ok'=>true, 'paired'=>true, 'version'=>IW_PUBLISHER_VERSION, 'mode'=>'wordpress', ]); } iw_verify_signed_request($body, $config); if ($action === 'health') { iw_json([ 'ok'=>true, 'status'=>'ready', 'version'=>IW_PUBLISHER_VERSION, 'mode'=>'wordpress', 'site_name'=>(string)($config['site_name'] ?? ''), 'wordpress_version'=>(string)($GLOBALS['wp_version'] ?? ''), ]); } if ($action === 'rekey') { $newSecret = trim((string)($payload['new_secret'] ?? '')); if (strlen($newSecret) < 48) iw_json(['ok'=>false, 'error'=>'New secret is too short.'], 422); $config['secret'] = $newSecret; $config['rekeyed_at'] = gmdate('c'); iw_save_config($config); iw_log('rekeyed'); iw_json(['ok'=>true, 'rekeyed'=>true]); } if ($action === 'publish') { $orderId = iw_text($payload['order_id'] ?? '', 100); if (!preg_match('/^[A-Za-z0-9._:-]{8,100}$/', $orderId)) { iw_json(['ok'=>false, 'error'=>'Invalid order ID.'], 422); } iw_set_state('Sipariş tamamlanıyor'); $result = iw_publish_wordpress($payload, $orderId); iw_set_state('Sipariş tamamlandı'); iw_log('published', ['order_id'=>$orderId, 'post_id'=>$result['post_id']]); iw_json([ 'ok'=>true, 'published_url'=>$result['published_url'], 'mode'=>'wordpress', 'post_id'=>$result['post_id'], 'duplicate'=>$result['duplicate'], 'version'=>IW_PUBLISHER_VERSION, ]); } if ($action === 'delete') { $orderId = iw_text($payload['order_id'] ?? '', 100); $postId = iw_wp_find_existing_post($orderId); if ($postId > 0) wp_delete_post($postId, true); iw_log('deleted', ['order_id'=>$orderId, 'post_id'=>$postId]); iw_json(['ok'=>true, 'deleted'=>$postId > 0]); } iw_json(['ok'=>false, 'error'=>'Unknown action.'], 400); } catch (Throwable $e) { iw_log('error', ['message'=>$e->getMessage()]); iw_json(['ok'=>false, 'error'=>$e->getMessage()], 500); }