- Agregar KPIs con tendencias vs período anterior (↑↓% comparativo) - Implementar secciones de recomendaciones: Contenido a Crear, CTR 0%, Quick Wins, Contenido Estrella, Contenido en Decadencia - Convertir listados a tablas con columnas separadas para mejor legibilidad - Agregar botones Editar + Ver en todas las tablas de posts - Ocultar secciones vacías dinámicamente (Búsquedas Sin Resultados) - Relajar criterios Quick Wins: pos 2-15, CTR ≥2%, búsquedas ≥2 - Incluir distribución de clicks por posición con barras de progreso - Agregar exportación a Markdown para análisis con IA Archivos nuevos: - admin/class-analytics-dashboard.php (UI del dashboard) - admin/class-metrics-repository.php (queries de métricas) - admin/assets/dashboard.css (estilos Bootstrap 5) - admin/assets/dashboard.js (interactividad y export) - sql/create-indices.sql (índices para optimización) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
283 lines
8.6 KiB
PHP
283 lines
8.6 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: ROI APU Search
|
|
* Plugin URI: https://analisisdepreciosunitarios.com
|
|
* Description: Motor de busqueda ultra-rapido para Analisis de Precios Unitarios con PDO persistente y scoring hibrido.
|
|
* Version: 1.3.0
|
|
* Author: ROI Theme
|
|
* Author URI: https://analisisdepreciosunitarios.com
|
|
* Text Domain: roi-apu-search
|
|
* Domain Path: /languages
|
|
* Requires at least: 6.0
|
|
* Requires PHP: 8.0
|
|
*
|
|
* @package ROI_APU_Search
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
// Prevent direct access
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
// Plugin constants
|
|
define('ROI_APU_SEARCH_VERSION', '1.3.0');
|
|
define('ROI_APU_SEARCH_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
|
define('ROI_APU_SEARCH_PLUGIN_URL', plugin_dir_url(__FILE__));
|
|
define('ROI_APU_SEARCH_PLUGIN_BASENAME', plugin_basename(__FILE__));
|
|
|
|
/**
|
|
* Main plugin class
|
|
*/
|
|
final class ROI_APU_Search_Plugin
|
|
{
|
|
private static ?self $instance = null;
|
|
|
|
/**
|
|
* Get singleton instance
|
|
*/
|
|
public static function get_instance(): self
|
|
{
|
|
if (self::$instance === null) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
private function __construct()
|
|
{
|
|
$this->load_dependencies();
|
|
$this->init_hooks();
|
|
}
|
|
|
|
/**
|
|
* Load required files
|
|
*/
|
|
private function load_dependencies(): void
|
|
{
|
|
require_once ROI_APU_SEARCH_PLUGIN_DIR . 'includes/class-db-connection.php';
|
|
require_once ROI_APU_SEARCH_PLUGIN_DIR . 'includes/class-redis-cache.php';
|
|
require_once ROI_APU_SEARCH_PLUGIN_DIR . 'includes/class-search-engine.php';
|
|
require_once ROI_APU_SEARCH_PLUGIN_DIR . 'includes/class-shortcode.php';
|
|
require_once ROI_APU_SEARCH_PLUGIN_DIR . 'includes/class-analytics.php';
|
|
|
|
// Admin dashboard
|
|
if (is_admin()) {
|
|
require_once ROI_APU_SEARCH_PLUGIN_DIR . 'admin/class-metrics-repository.php';
|
|
require_once ROI_APU_SEARCH_PLUGIN_DIR . 'admin/class-analytics-dashboard.php';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize WordPress hooks
|
|
*/
|
|
private function init_hooks(): void
|
|
{
|
|
// Register shortcode
|
|
add_action('init', [$this, 'register_shortcode']);
|
|
|
|
// Enqueue assets
|
|
add_action('wp_enqueue_scripts', [$this, 'enqueue_assets']);
|
|
|
|
// AJAX handlers (for non-SHORTINIT fallback)
|
|
add_action('wp_ajax_roi_apu_search', [$this, 'handle_ajax_search']);
|
|
add_action('wp_ajax_nopriv_roi_apu_search', [$this, 'handle_ajax_search']);
|
|
|
|
// Initialize admin dashboard
|
|
if (is_admin()) {
|
|
ROI_APU_Analytics_Dashboard::get_instance()->init();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register the shortcode
|
|
*/
|
|
public function register_shortcode(): void
|
|
{
|
|
$shortcode = new ROI_APU_Search_Shortcode();
|
|
add_shortcode('roi_apu_search', [$shortcode, 'render']);
|
|
}
|
|
|
|
/**
|
|
* Enqueue frontend assets
|
|
*/
|
|
public function enqueue_assets(): void
|
|
{
|
|
// Only load if shortcode is used
|
|
global $post;
|
|
if (!$post || !has_shortcode($post->post_content, 'roi_apu_search')) {
|
|
return;
|
|
}
|
|
|
|
wp_enqueue_style(
|
|
'roi-apu-search',
|
|
ROI_APU_SEARCH_PLUGIN_URL . 'assets/css/search-ui.css',
|
|
[],
|
|
ROI_APU_SEARCH_VERSION
|
|
);
|
|
|
|
wp_enqueue_script(
|
|
'roi-apu-search',
|
|
ROI_APU_SEARCH_PLUGIN_URL . 'assets/js/search-handler.js',
|
|
[],
|
|
ROI_APU_SEARCH_VERSION,
|
|
true
|
|
);
|
|
|
|
// Obtener configuracion de AdSense del tema (si existe)
|
|
// Verifica que la funcion exista para evitar errores si se usa otro tema
|
|
$adsConfig = function_exists('roi_get_adsense_search_config')
|
|
? roi_get_adsense_search_config()
|
|
: ['enabled' => false];
|
|
|
|
// Localize script with AJAX URL, nonce, click tracking URL, and ads config
|
|
wp_localize_script('roi-apu-search', 'roiApuSearch', [
|
|
'ajaxUrl' => admin_url('admin-ajax.php'),
|
|
'apiUrl' => ROI_APU_SEARCH_PLUGIN_URL . 'api/search-endpoint.php',
|
|
'clickUrl' => ROI_APU_SEARCH_PLUGIN_URL . 'api/click-endpoint.php',
|
|
'nonce' => wp_create_nonce('roi_apu_search'),
|
|
'ads' => $adsConfig,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Handle AJAX search request (fallback if SHORTINIT doesn't work)
|
|
*/
|
|
public function handle_ajax_search(): void
|
|
{
|
|
// Verify nonce
|
|
if (!check_ajax_referer('roi_apu_search', 'nonce', false)) {
|
|
wp_send_json_error(['message' => 'Invalid security token'], 403);
|
|
}
|
|
|
|
// Store raw term before sanitization
|
|
$term_raw = isset($_POST['term']) ? (string) wp_unslash($_POST['term']) : '';
|
|
$term = sanitize_text_field($term_raw);
|
|
$page = isset($_POST['page']) ? absint($_POST['page']) : 1;
|
|
$per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 10;
|
|
$categories = isset($_POST['categories']) ? sanitize_text_field(wp_unslash($_POST['categories'])) : '';
|
|
|
|
// Validate term
|
|
$min_len = 3;
|
|
$max_len = 250;
|
|
|
|
if (mb_strlen($term, 'UTF-8') < $min_len) {
|
|
wp_send_json_error(['message' => "Minimo {$min_len} caracteres"], 400);
|
|
}
|
|
|
|
if (mb_strlen($term, 'UTF-8') > $max_len) {
|
|
$term = mb_substr($term, 0, $max_len, 'UTF-8');
|
|
}
|
|
|
|
// Execute search
|
|
try {
|
|
$db = ROI_APU_Search_DB::get_instance();
|
|
$pdo = $db->get_pdo();
|
|
$prefix = $db->get_prefix();
|
|
|
|
$search = new ROI_APU_Search_Engine($pdo);
|
|
|
|
// Parse categories
|
|
$category_ids = [];
|
|
if (!empty($categories)) {
|
|
$category_ids = $this->parse_categories($categories);
|
|
}
|
|
|
|
$offset = ($page - 1) * $per_page;
|
|
$results = $search->run($term, $per_page, $offset, $category_ids);
|
|
|
|
// Build permalinks for each result (search engine returns empty permalink)
|
|
$site_url = rtrim(home_url(), '/');
|
|
$permalink_structure = get_option('permalink_structure', '/%postname%/');
|
|
|
|
foreach ($results['rows'] as &$row) {
|
|
if (empty($row['permalink'])) {
|
|
$postName = $row['post_name'] ?? '';
|
|
|
|
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;
|
|
|
|
wp_send_json_success($results);
|
|
} catch (\Exception $e) {
|
|
wp_send_json_error(['message' => 'Error en la busqueda'], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse categories string to array of IDs
|
|
*/
|
|
private function parse_categories(string $categories): array
|
|
{
|
|
$parts = array_filter(array_map('trim', explode(',', $categories)));
|
|
$ids = [];
|
|
|
|
foreach ($parts as $part) {
|
|
if (is_numeric($part)) {
|
|
$ids[] = (int) $part;
|
|
} else {
|
|
// Try to get category by slug
|
|
$term = get_term_by('slug', $part, 'category');
|
|
if ($term) {
|
|
$ids[] = (int) $term->term_id;
|
|
}
|
|
}
|
|
}
|
|
|
|
return array_unique($ids);
|
|
}
|
|
}
|
|
|
|
// Initialize plugin
|
|
add_action('plugins_loaded', function () {
|
|
ROI_APU_Search_Plugin::get_instance();
|
|
});
|
|
|
|
// Activation hook
|
|
register_activation_hook(__FILE__, function () {
|
|
// Check PHP version
|
|
if (version_compare(PHP_VERSION, '8.0', '<')) {
|
|
deactivate_plugins(plugin_basename(__FILE__));
|
|
wp_die('ROI APU Search requiere PHP 8.0 o superior.');
|
|
}
|
|
|
|
// Flush rewrite rules
|
|
flush_rewrite_rules();
|
|
});
|
|
|
|
// Deactivation hook
|
|
register_deactivation_hook(__FILE__, function () {
|
|
flush_rewrite_rules();
|
|
});
|