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>
192 lines
6.1 KiB
PHP
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;
|