true ) ), array( 'attachment' ) ) ); $query = new WP_Query( array( 'post_type' => $types, 'post_status' => 'publish', 'posts_per_page' => self::MAX_POSTS, 'orderby' => 'modified', 'order' => 'DESC', 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ) ); $counts = array( 'follow' => 0, 'nofollow' => 0, 'sponsored' => 0, 'ugc' => 0, ); $links = array(); $sites = array(); $follows = array(); foreach ( $query->posts as $post ) { foreach ( self::links_in( $post->post_content, $home ) as $link ) { ++$counts[ $link['type'] ]; $sites[ $link['host'] ] = true; if ( 'follow' === $link['type'] ) { $follows[ $link['host'] ] = true; } if ( count( $links ) < self::MAX_LINKS ) { $link['post'] = $post->ID; $links[] = $link; } } } $scan = array( 'scanned_at' => time(), 'posts' => count( $query->posts ), 'counts' => $counts, 'total' => array_sum( $counts ), 'sites' => count( $sites ), 'follow_sites' => count( $follows ), 'links' => $links, ); set_transient( self::CACHE_KEY, $scan, DAY_IN_SECONDS ); return $scan; } /** * Every link in one piece of content that leaves this site. * * @param string $html Post content. * @param string $home This site's domain. * @return array[] Each with url, host and type. */ public static function links_in( $html, $home ) { $out = array(); if ( '' === trim( (string) $html ) || false === stripos( $html, 'next_tag( 'a' ) ) { $href = $tags->get_attribute( 'href' ); if ( ! is_string( $href ) || ! preg_match( '#^(https?:)?//#i', trim( $href ) ) ) { continue; // Relative, mailto:, tel:, # and empty links stay on the site. } $href = trim( $href ); $host = strtolower( (string) wp_parse_url( 0 === strpos( $href, '//' ) ? 'https:' . $href : $href, PHP_URL_HOST ) ); $host = 0 === strpos( $host, 'www.' ) ? substr( $host, 4 ) : $host; if ( '' === $host || $host === $home || self::ends_with( $host, '.' . $home ) ) { continue; } $rel = $tags->get_attribute( 'rel' ); $out[] = array( 'url' => $href, 'host' => $host, 'type' => self::type_of( is_string( $rel ) ? $rel : '' ), ); } return $out; } /** * Sponsored and UGC win over nofollow, as Google reads them that way. * * @param string $rel The rel attribute. * @return string follow, nofollow, sponsored or ugc. */ public static function type_of( $rel ) { $tokens = preg_split( '/\s+/', strtolower( trim( $rel ) ) ); if ( in_array( 'sponsored', $tokens, true ) ) { return 'sponsored'; } if ( in_array( 'ugc', $tokens, true ) ) { return 'ugc'; } if ( in_array( 'nofollow', $tokens, true ) ) { return 'nofollow'; } return 'follow'; } /** * PHP 7.4 safe ends-with. * * @param string $haystack Text. * @param string $needle Ending. * @return bool */ private static function ends_with( $haystack, $needle ) { $len = strlen( $needle ); return $len > 0 && substr( $haystack, -$len ) === $needle; } }