Files
roi-apu-search/api/search-endpoint.php
FrankZamora 81d65c0f9a fix(analytics): Corregir URLs en analytics usando post_name desde BD
Problema:
- URLs en analytics mostraban dominio incorrecto (HTTP_HOST)
- URLs usaban formato /?p=ID en lugar de permalinks

Solución:
- class-search-engine.php: Agregar propiedad $prefix, incluir p.post_name
  en 5 queries fetch, agregar helpers getSiteUrlFromDb(),
  getPermalinkStructure() y buildPermalink()
- search-endpoint.php: Obtener site_url y permalink_structure desde
  wp_options, construir URLs con post_name
- click-endpoint.php: Fallback de dest usando post_name desde BD

Archivos modificados:
- includes/class-search-engine.php
- api/search-endpoint.php
- api/click-endpoint.php

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 10:59:56 -06:00

192 lines
6.1 KiB
PHP

<?php
/**
* Fast AJAX Search Endpoint with SHORTINIT bypass
*
* This endpoint loads minimal WordPress for maximum speed.
* Use this directly via plugin URL for fastest results.
*
* @package ROI_APU_Search
*/
declare(strict_types=1);
// BYPASS: Don't load full WordPress
define('SHORTINIT', true);
define('WP_USE_THEMES', false);
// Find wp-load.php (go up from plugin directory)
$wp_load = dirname(__FILE__, 5) . '/wp-load.php';
if (!file_exists($wp_load)) {
// Fallback: try alternative paths
$wp_load = dirname(__FILE__, 6) . '/wp-load.php';
if (!file_exists($wp_load)) {
http_response_code(500);
echo json_encode(['success' => false, 'data' => ['message' => 'WordPress not found']]);
exit;
}
}
require_once $wp_load;
// Set JSON headers
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-cache, must-revalidate');
// Only accept POST requests
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'data' => ['message' => 'Method not allowed']]);
exit;
}
// Verify nonce (with SHORTINIT, we need to load nonce functions manually)
// Since SHORTINIT doesn't load nonce functions, we'll implement basic validation
// sanitize_text_field() might not be available, use simple sanitization
$nonce = isset($_POST['nonce']) ? htmlspecialchars(strip_tags(trim($_POST['nonce'])), ENT_QUOTES, 'UTF-8') : '';
// Simple nonce validation using wp_hash
// In SHORTINIT mode, we have limited access to WP functions
// We'll verify based on a time-based token instead
if (empty($nonce)) {
http_response_code(403);
echo json_encode(['success' => false, 'data' => ['message' => 'Missing security token']]);
exit;
}
// For SHORTINIT, we use a simplified token validation
// The token is generated with a salt and timestamp
$token_parts = explode('|', base64_decode($nonce));
if (count($token_parts) !== 2) {
// Fallback: accept WP nonce format if available
// This allows compatibility when called from admin-ajax.php
// We'll be permissive here but log suspicious requests
error_log('ROI APU Search: Non-standard nonce format from ' . ($_SERVER['REMOTE_ADDR'] ?? 'unknown'));
}
// Get request parameters
$term = isset($_POST['term']) ? trim((string) $_POST['term']) : '';
$page = isset($_POST['page']) ? max(1, (int) $_POST['page']) : 1;
$per_page = isset($_POST['per_page']) ? max(1, min(100, (int) $_POST['per_page'])) : 10;
$categories = isset($_POST['categories']) ? trim((string) $_POST['categories']) : '';
// Validate term
$min_len = 3;
$max_len = 250;
if (mb_strlen($term, 'UTF-8') < $min_len) {
http_response_code(400);
echo json_encode([
'success' => false,
'data' => ['message' => "Minimo {$min_len} caracteres"]
]);
exit;
}
if (mb_strlen($term, 'UTF-8') > $max_len) {
$term = mb_substr($term, 0, $max_len, 'UTF-8');
}
// Load plugin classes
$plugin_dir = dirname(__FILE__, 2);
require_once $plugin_dir . '/includes/class-db-connection.php';
require_once $plugin_dir . '/includes/class-redis-cache.php';
require_once $plugin_dir . '/includes/class-search-engine.php';
require_once $plugin_dir . '/includes/class-analytics.php';
// Store raw term before any processing
$term_raw = isset($_POST['term']) ? (string) $_POST['term'] : '';
try {
// Get database connection
$db = ROI_APU_Search_DB::get_instance();
$pdo = $db->get_pdo();
$prefix = $db->get_prefix();
// Parse categories
$category_ids = [];
if (!empty($categories)) {
$parts = array_filter(array_map('trim', explode(',', $categories)));
foreach ($parts as $part) {
if (is_numeric($part)) {
$category_ids[] = (int) $part;
}
// Note: In SHORTINIT mode, we can't easily resolve slugs to IDs
// So we only accept numeric IDs in this fast endpoint
}
$category_ids = array_unique($category_ids);
}
// Execute search
$search = new ROI_APU_Search_Engine($pdo);
$offset = ($page - 1) * $per_page;
$results = $search->run($term, $per_page, $offset, $category_ids);
// In SHORTINIT mode, get_permalink() and home_url() won't work
// Obtain site_url and permalink_structure from database (not HTTP_HOST)
$stmt = $pdo->prepare(
"SELECT option_name, option_value FROM {$prefix}options
WHERE option_name IN ('home', 'permalink_structure')"
);
$stmt->execute();
$options = [];
while ($optRow = $stmt->fetch(PDO::FETCH_ASSOC)) {
$options[$optRow['option_name']] = $optRow['option_value'];
}
$site_url = isset($options['home']) ? rtrim($options['home'], '/') : '';
$permalink_structure = $options['permalink_structure'] ?? '/%postname%/';
// Build permalinks for each result using post_name
foreach ($results['rows'] as &$row) {
if (empty($row['permalink'])) {
$postName = $row['post_name'] ?? '';
// Fallback if post_name is empty
if (empty($postName)) {
$row['permalink'] = $site_url . '/?p=' . $row['ID'];
continue;
}
// Build according to permalink structure
if (strpos($permalink_structure, '%post_id%') !== false) {
$row['permalink'] = $site_url . '/' . $row['ID'] . '/';
} else {
$row['permalink'] = $site_url . '/' . $postName . '/';
}
}
}
unset($row);
// Log search for analytics
$analytics_cfg = ROI_APU_Search_Analytics::get_config();
$search_id = ROI_APU_Search_Analytics::logSearch(
$pdo,
$prefix,
$analytics_cfg,
$term_raw,
$term,
$results,
$page,
$per_page
);
// Add search_id to response for click tracking
$results['search_id'] = $search_id;
echo json_encode([
'success' => true,
'data' => $results
]);
} catch (Exception $e) {
error_log('ROI APU Search Error: ' . $e->getMessage());
http_response_code(500);
echo json_encode([
'success' => false,
'data' => ['message' => 'Error en la busqueda']
]);
}
exit;