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_Stats {
/**
* Counts plugin-inserted links (data-il="1") pointing to a pilar URL.
*/
public static function count_real_links_for_pilar( $pilar ) {
$detail = self::get_link_detail_for_pilar( $pilar );
return array_sum( array_column( $detail, 'count' ) );
}
/**
* Returns detail of all posts linking to this pilar via plugin links.
* Only counts — manual editor links are excluded.
*/
public static function get_link_detail_for_pilar( $pilar ) {
global $wpdb;
if ( empty( $pilar->url ) ) return array();
$url = esc_url_raw( $pilar->url );
$url_trail = trailingslashit( $url );
$like_il = '%' . $wpdb->esc_like( 'data-il="1"' ) . '%';
$like1 = '%' . $wpdb->esc_like( 'href="' . $url . '"' ) . '%';
$like2 = '%' . $wpdb->esc_like( 'href="' . $url_trail . '"' ) . '%';
$posts = $wpdb->get_results( $wpdb->prepare(
"SELECT ID, post_title, post_type, post_content
FROM {$wpdb->posts}
WHERE post_status = 'publish'
AND post_type NOT IN ('revision','attachment','nav_menu_item')
AND post_content LIKE %s
AND ( post_content LIKE %s OR post_content LIKE %s )
ORDER BY post_title ASC",
$like_il, $like1, $like2
) );
$detail = array();
foreach ( $posts as $post ) {
// Build a pattern that matches tags containing BOTH data-il="1"
// AND href pointing to this URL, in any attribute order.
// Using lookaheads ensures each tag is matched only once.
$url_alts = implode( '|', array_unique( array_map(
function( $u ) { return preg_quote( $u, '/' ); },
array( $url, $url_trail )
) ) );
$pattern = '/]*\bdata-il=["\']1["\'])(?=[^>]*\bhref=["\'](?:' . $url_alts . ')["\'])[^>]*>(.*?)<\/a>/is';
$count = preg_match_all( $pattern, $post->post_content, $m );
if ( ! $count ) continue;
$anchors = array();
foreach ( $m[1] as $html ) {
$clean = trim( wp_strip_all_tags( $html ) );
if ( $clean ) $anchors[] = $clean;
}
if ( $count === 0 ) continue;
$detail[] = array(
'post_id' => $post->ID,
'title' => $post->post_title,
'url' => get_permalink( $post->ID ),
'type' => $post->post_type,
'count' => $count,
'anchors' => array_values( array_unique( $anchors ) ),
);
}
usort( $detail, function( $a, $b ) { return $b['count'] - $a['count']; } );
return $detail;
}
/**
* Counts REAL links in post content for a specific rule.
* Matches by BOTH destination URL AND the rule's keywords (anchor text).
*/
public static function count_links_by_rule( $rule_id ) {
global $wpdb;
$reglas = new FLINK_Reglas();
$regla = $reglas->get( (int) $rule_id );
if ( ! $regla ) return 0;
// Get destination URL
$dest_url = '';
if ( ! empty( $regla->destino_valor ) ) {
$dest_url = $regla->destino_valor;
} elseif ( ! empty( $regla->pilar_id ) ) {
$pilar = ( new FLINK_Pilares() )->get( (int) $regla->pilar_id );
if ( $pilar ) $dest_url = $pilar->url;
}
if ( empty( $dest_url ) ) return 0;
// Get all keywords for this rule
$keywords = array( $regla->nombre );
if ( ! empty( $regla->descripcion ) ) {
$syns = array_filter( array_map( 'trim', explode( "\n", $regla->descripcion ) ) );
$keywords = array_merge( $keywords, $syns );
}
$keywords = array_unique( array_filter( $keywords ) );
if ( empty( $keywords ) ) return 0;
// Find posts with plugin links pointing to this URL (classic). Merge
// with Elementor builder pages, whose links live in _elementor_data and
// whose post_content may hold stale, non-rendered links.
$like_il = '%' . $wpdb->esc_like( 'data-il="1"' ) . '%';
$url_esc = esc_url_raw( $dest_url );
$like_url = '%' . $wpdb->esc_like( $url_esc ) . '%';
$posts = $wpdb->get_results( $wpdb->prepare(
"SELECT ID, post_content FROM {$wpdb->posts}
WHERE post_status IN ('publish','draft','pending')
AND post_content LIKE %s
AND post_content LIKE %s",
$like_il, $like_url
) );
$by_id = array();
foreach ( $posts as $p ) { $by_id[ (int) $p->ID ] = $p; }
$builder = $wpdb->get_results(
"SELECT p.ID, p.post_content
FROM {$wpdb->postmeta} pm
INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = '_elementor_edit_mode'
AND pm.meta_value = 'builder'
AND p.post_status IN ('publish','draft','pending')"
);
foreach ( $builder as $b ) { $by_id[ (int) $b->ID ] = $b; }
$total = 0;
$url_alts = array_unique( array( $url_esc, trailingslashit( $url_esc ) ) );
foreach ( $by_id as $post ) {
// Count against the rendered source.
if ( get_post_meta( $post->ID, '_elementor_edit_mode', true ) === 'builder'
&& class_exists( 'FLINK_Link_Stats' ) ) {
$haystack = FLINK_Link_Stats::extract_elementor_html( $post->ID );
} else {
$haystack = $post->post_content;
}
foreach ( $keywords as $kw ) {
$kw_esc = preg_quote( trim( $kw ), '/' );
// Single pattern matching both URL forms at once to avoid double-counting.
$alts_re = implode( '|', array_unique( array_map( function($u){ return preg_quote($u, '/'); }, $url_alts ) ) );
$pat = '/]*\bdata-il=["\']1["\'])(?=[^>]*\bhref=["\'](?:' . $alts_re . ')["\'])[^>]*>\s*' . $kw_esc . '\s*<\/a>/iu';
$total += preg_match_all( $pat, $haystack );
}
}
return $total;
}
}