| Server IP : 121.121.20.254 / Your IP : 216.73.216.202 Web Server : Microsoft-IIS/10.0 System : Windows NT WEB-SERVER 10.0 build 20348 (Windows Server 2022) AMD64 User : IUSR ( 0) PHP Version : 8.3.28 Disable Function : NONE MySQL : ON | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : C:/inetpub/wwwroot/KnowledgeBase/wp-content/plugins/aioseo-link-assistant/app/Links/ |
Upload File : |
<?php
namespace AIOSEO\Plugin\Addon\LinkAssistant\Links;
use AIOSEO\Plugin\Addon\LinkAssistant\Models;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handles the extraction, parsing and storage of links for the links scan.
*
* @since 1.0.3
*/
class Data {
/**
* Indexes the links in the given post.
*
* @since 1.0.0
*
* @param int $postId The post ID.
* @param string $postContent The post content.
* @return void
*/
public function indexLinks( $postId, $postContent = null ) {
if ( empty( $postContent ) ) {
$post = aioseo()->helpers->getPost( $postId );
if ( ! is_object( $post ) ) {
return;
}
$postContent = $post->post_content;
}
$links = $this->extractLinks( $postId, $postContent );
// Delete all links first. We have to do this in order to remove old links that no longer exist.
Models\Link::deleteLinks( $postId );
if ( empty( $links ) ) {
return;
}
$this->storeLinks( $links );
}
/**
* Stores the given links to the DB.
*
* @since 1.0.3
*
* @param array $links The links.
* @return void
*/
private function storeLinks( $links ) {
$insertValues = [];
$currentDate = gmdate( 'Y-m-d H:i:s' );
foreach ( $links as $linkData ) {
$data = Models\Link::sanitizeLink( $linkData );
if ( empty( $data ) ) {
continue;
}
if ( ! Models\Link::validateLink( $data ) ) {
continue;
}
$linkedPostId = '%d';
if ( empty( $data['linked_post_id'] ) ) {
$linkedPostId = '%s';
$data['linked_post_id'] = 'null';
}
$insertValues[] = vsprintf(
"(%d, $linkedPostId, %d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '$currentDate', '$currentDate')",
$data
);
}
$implodedInsertValues = implode( ',', $insertValues );
$tableName = aioseo()->core->db->prefix . 'aioseo_links';
aioseo()->core->db->execute(
"INSERT INTO $tableName
(`post_id`, `linked_post_id`, `internal`, `external`, `affiliate`, `url`, `hostname`, `anchor`, `phrase`, `phrase_html`, `paragraph`, `paragraph_html`, `created`, `updated`)
VALUES $implodedInsertValues"
);
}
/**
* Returns the links that are in the post content.
*
* @since 1.0.0
*
* @param int $postId The post ID.
* @param string $postContent The post content.
* @return array The links.
*/
private function extractLinks( $postId, $postContent ) {
$postContent = aioseo()->helpers->decodeHtmlEntities( $postContent );
/**
* Regex pattern divided into groups:
* 0 - Full phrase with link tag.
* 2 - Start of the phrase, before the anchor.
* 4 - The URL.
* 6 - The anchor.
* 9 - The end of the phrase, after the anchor.
* 10 - The ending punctuation mark.
*/
preg_match_all(
'/(([^\r\n.?!]*)<t?a[^>]*?href=(\"|\')([^\"\']*?)(\"|\')[^>]*?>([\s\w\W]*?)<\/t?a>|<!-- wp:core-embed\/wordpress {"url":"([^"]*?)"[^}]*?"} -->|(?:>| |\s)((?:(?:http|ftp|https)\:\/\/)(?:[\w_-]+(?:(?:\.[\w_-]+)+))(?:[\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-]))(?:<| |\s))([^<>.?!\r\n]*)([.?!]?)/i', // phpcs:disable Generic.Files.LineLength.MaxExceeded
$postContent,
$matches
);
if ( empty( $matches[0] ) ) {
return [];
}
$links = [];
foreach ( $matches[0] as $k => $v ) {
if ( empty( $matches[4][ $k ] ) || empty( $matches[6][ $k ] ) ) {
continue;
}
$url = $matches[4][ $k ];
$hostname = wp_parse_url( $url, PHP_URL_HOST );
// If the URL is relative, add the hostname of the site.
if ( ! $hostname ) {
$hostname = $this->getHostname();
$url = aioseo()->helpers->makeUrlAbsolute( $url );
}
// NOTE: We need to check this here before we strip off the "www" part.
// Otherwise we will not be able to detect internal links on sites running on "www".
$isInternal = $hostname === $this->getHostname();
$hostname = aioseo()->helpers->pregReplace( '/www\./i', '', $hostname );
$anchor = wp_strip_all_tags( $matches[6][ $k ] );
// Remove trailing URL tags. The regex isn't sufficient for this.
$phrase = wp_strip_all_tags( $matches[0][ $k ] );
$phrase = trim( preg_replace( '/(.*)(<t?a[^<>].*$)/', '', $phrase ) );
// Don't continue if the anchor or phrase are empty, e.g. blank link tag.
if ( ! $anchor || ! $phrase ) {
continue;
}
$phraseHtml = aioseo()->helpers->stripIncompleteHtmlTags( $matches[0][ $k ] );
$phraseHtml = aioseo()->helpers->stripScriptTags( $phraseHtml );
$phraseHtml = aioseoLinkAssistant()->helpers->trimParagraphTags( $phraseHtml );
// For now, we'll drop list items as we can't parse them well.
if ( empty( $phraseHtml ) || preg_match( '/<li|li>|<ol|ol>|<ul|ul>/', $phraseHtml ) ) {
continue;
}
$paragraph = aioseoLinkAssistant()->main->paragraph->get( $postId, $postContent, $phrase );
$paragraphHtml = aioseoLinkAssistant()->main->paragraph->getHtml( $anchor, $paragraph, $postContent );
$isAffiliate = $this->isAffiliate( $url, $phraseHtml );
$linkData = [
'post_id' => (int) $postId,
'linked_post_id' => null,
'internal' => $isInternal,
'external' => ! $isInternal,
'affiliate' => $isAffiliate,
'url' => $url,
'hostname' => $hostname,
'anchor' => $anchor,
'phrase' => $phrase,
'phrase_html' => $phraseHtml,
'paragraph' => $paragraph,
'paragraph_html' => $paragraphHtml
];
if ( $isInternal && ! $isAffiliate ) {
$linkedPostId = url_to_postid( $linkData['url'] );
if ( $linkedPostId ) {
$linkData['linked_post_id'] = (int) $linkedPostId;
}
}
$links[] = $linkData;
}
return $links;
}
/**
* Returns the site's hostname.
*
* @since 1.0.3
*
* @return string The hostname.
*/
private function getHostname() {
static $siteUrl = null;
if ( null === $siteUrl ) {
$siteUrl = wp_parse_url( get_site_url(), PHP_URL_HOST );
}
return $siteUrl;
}
/**
* Checks whether the given link is an affiliate link.
*
* @since 1.0.0
*
* @param string $url The URL.
* @param string $phraseHtml The phrase HTML.
* @return bool Whether the link is an affiliate link.
*/
private function isAffiliate( $url, $phraseHtml ) {
static $affiliatePrefixes = null;
if ( null === $affiliatePrefixes ) {
$affiliatePrefixes = [];
$prefixes = json_decode( aioseoLinkAssistant()->options->main->affiliatePrefix );
if ( $prefixes ) {
$affiliatePrefixes = array_map( function( $prefix ) {
return $prefix->value;
}, $prefixes );
}
}
foreach ( $affiliatePrefixes as $affiliatePrefix ) {
if ( aioseo()->helpers->stringContains( $url, $affiliatePrefix ) ) {
return true;
}
}
// Check if the link is wrapped in a ThirstyAffiliates tag.
if ( preg_match( '/<ta[^>]*?href=(\"|\')([^\"\']*?)(\"|\')[^>]*?>[\s\w\W]*?<\/ta>/i', $phraseHtml ) ) {
return true;
}
return false;
}
}