prefix . 'il_pilares') whose names and column lists // are constants under plugin control. Generic WordPress checks // for direct DB queries, caching and prepared-SQL interpolation // don't apply meaningfully here. See docs/LICENSING.md for the // full architecture rationale. class FLINK_Reglas { private $table; public function __construct() { global $wpdb; $this->table = $wpdb->prefix . 'il_reglas'; } public function get_all( $args = array() ) { global $wpdb; $defaults = array( 'orderby' => 'prioridad', 'order' => 'DESC', 'activo' => null, ); $args = wp_parse_args( $args, $defaults ); $where = ''; if ( ! is_null( $args['activo'] ) ) { $where = $wpdb->prepare( 'WHERE activo = %d', (int) $args['activo'] ); } $orderby = sanitize_sql_orderby( $args['orderby'] . ' ' . $args['order'] ); return $wpdb->get_results( "SELECT * FROM {$this->table} {$where} ORDER BY {$orderby}" ); } public function get( $id ) { global $wpdb; return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->table} WHERE id = %d", $id ) ); } public function get_active_ordered() { global $wpdb; return $wpdb->get_results( "SELECT * FROM {$this->table} WHERE activo = 1 ORDER BY prioridad DESC" ); } /** * Get active rules that apply to a post in a specific language. * Rules with idioma = '' apply to all languages. * Rules with idioma = 'es' apply only to Spanish posts. */ public function get_active_for_post( $post_id ) { global $wpdb; $all_active = $this->get_active_ordered(); // Detect post language from our meta or Polylang $post_lang = get_post_meta( $post_id, '_flink_idioma', true ); if ( empty( $post_lang ) && function_exists( 'pll_get_post_language' ) ) { $post_lang = pll_get_post_language( $post_id ); } // If no language detected, apply all rules (backwards compatible) if ( empty( $post_lang ) ) return $all_active; return array_filter( $all_active, function( $regla ) use ( $post_lang ) { // Empty idioma = applies to all languages return empty( $regla->idioma ) || $regla->idioma === $post_lang; } ); } public function insert( $data ) { global $wpdb; $data = $this->sanitize( $data ); $result = $wpdb->insert( $this->table, $data ); return $result ? $wpdb->insert_id : false; } public function update( $id, $data ) { global $wpdb; $data = $this->sanitize( $data ); return $wpdb->update( $this->table, $data, array( 'id' => (int) $id ) ); } public function delete( $id ) { global $wpdb; return $wpdb->delete( $this->table, array( 'id' => (int) $id ) ); } public function count_active() { global $wpdb; return (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$this->table} WHERE activo = 1" ); } private function sanitize( $data ) { $clean = array(); if ( isset( $data['nombre'] ) ) $clean['nombre'] = sanitize_text_field( $data['nombre'] ); if ( isset( $data['descripcion'] ) ) $clean['descripcion'] = sanitize_textarea_field( $data['descripcion'] ); if ( isset( $data['origen_tipo'] ) ) $clean['origen_tipo'] = sanitize_key( $data['origen_tipo'] ); if ( isset( $data['origen_valor'] ) ) $clean['origen_valor'] = sanitize_text_field( $data['origen_valor'] ); if ( isset( $data['destino_tipo'] ) ) $clean['destino_tipo'] = sanitize_key( $data['destino_tipo'] ); if ( isset( $data['destino_valor'] ) ) $clean['destino_valor'] = sanitize_text_field( $data['destino_valor'] ); if ( isset( $data['pilar_id'] ) ) $clean['pilar_id'] = absint( $data['pilar_id'] ) ?: null; if ( isset( $data['anchor_text'] ) ) $clean['anchor_text'] = sanitize_text_field( $data['anchor_text'] ); if ( isset( $data['activo'] ) ) $clean['activo'] = (int) (bool) $data['activo']; if ( isset( $data['max_enlaces'] ) ) $clean['max_enlaces'] = max( 1, min( 20, (int) $data['max_enlaces'] ) ); if ( isset( $data['prioridad'] ) ) { $clean['prioridad'] = max( 1, min( 100, (int) $data['prioridad'] ) ); } if ( isset( $data['idioma'] ) ) { $clean['idioma'] = sanitize_key( $data['idioma'] ); } if ( isset( $data['lang_variantes'] ) ) { // Store as JSON — validate it's a proper array first $variantes = $data['lang_variantes']; if ( is_string( $variantes ) ) { $variantes = json_decode( $variantes, true ); } if ( is_array( $variantes ) ) { // Sanitize each variant $clean_variantes = array(); foreach ( $variantes as $lang => $v ) { $lang = sanitize_key( $lang ); if ( empty( $lang ) ) continue; $clean_variantes[ $lang ] = array( 'keyword' => sanitize_text_field( $v['keyword'] ?? '' ), 'sinonimos' => sanitize_textarea_field( $v['sinonimos'] ?? '' ), 'pilar_id' => absint( $v['pilar_id'] ?? 0 ) ?: null, 'url' => esc_url_raw( $v['url'] ?? '' ), ); } $clean['lang_variantes'] = wp_json_encode( $clean_variantes ); } } return $clean; } /** * Get decoded lang_variantes for a rule. * Returns array [ 'en' => ['keyword'=>..., 'sinonimos'=>..., 'pilar_id'=>..., 'url'=>...], ... ] */ public function get_variantes( $regla ) { if ( empty( $regla->lang_variantes ) ) return array(); $v = json_decode( $regla->lang_variantes, true ); return is_array( $v ) ? $v : array(); } }