403Webshell
Server IP : 66.29.153.156  /  Your IP : 216.73.216.226
Web Server : LiteSpeed
System : Linux premium322.web-hosting.com 4.18.0-553.50.1.lve.el8.x86_64 #1 SMP Thu Apr 17 19:10:24 UTC 2025 x86_64
User : lastyfjz ( 1521)
PHP Version : 8.1.34
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /home/lastyfjz/./werepairmobile.com/wp-content/plugins/broken-link-checker-seo/app/Main/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/lastyfjz/./werepairmobile.com/wp-content/plugins/broken-link-checker-seo/app/Main/Main.php
<?php
namespace AIOSEO\BrokenLinkChecker\Main;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use AIOSEO\BrokenLinkChecker\Highlighter;
use AIOSEO\BrokenLinkChecker\Links;
use AIOSEO\BrokenLinkChecker\LinkStatus;
use AIOSEO\BrokenLinkChecker\Models;

/**
 * Main class where core features are handled/registered.
 *
 * @since 1.0.0
 */
class Main {
	/**
	 * Paragraph class.
	 *
	 * @since 1.0.0
	 *
	 * @var Paragraph
	 */
	public $paragraph = null;

	/**
	 * Links class.
	 *
	 * @since 1.0.0
	 *
	 * @var Links\Links
	 */
	public $links = null;

	/**
	 * LinkStatus class.
	 *
	 * @since 1.0.0
	 *
	 * @var LinkStatus\LinkStatus
	 */
	public $linkStatus = null;

	/**
	 * LocalScan class.
	 *
	 * @since 1.3.0
	 *
	 * @var LinkStatus\LocalScan
	 */
	public $localScan = null;

	/**
	 * The action name for the recurring orphan-row cleanup scan.
	 *
	 * @since 1.3.0
	 *
	 * @var string
	 */
	private $cleanupActionName = 'aioseo_blc_links_cleanup';

	/**
	 * Class constructor.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {
		if (
			! aioseoBrokenLinkChecker()->core->db->tableExists( 'aioseo_blc_links' ) ||
			! aioseoBrokenLinkChecker()->core->db->tableExists( 'aioseo_blc_link_status' ) ||
			! aioseoBrokenLinkChecker()->core->db->tableExists( 'aioseo_blc_cache' )
		) {
			// Don't call updateDbSchema() here — it will be called by runUpdates()
			// on the init hook later in this same request.
			// Calling it here AND in runUpdates() causes duplicate dbDelta() calls
			// which triggers "table already exists" errors.

			// Don't return here; otherwise the Setup Wizard won't show on the first activation, but on the second.
		}

		new Activate();

		$this->paragraph  = new Paragraph();
		$this->links      = new Links\Links();
		$this->linkStatus = new LinkStatus\LinkStatus();
		$this->localScan  = new LinkStatus\LocalScan();

		add_filter( 'the_content', [ $this, 'filterLinks' ], 999 ); // High prio to make sure other plugins get a chance to render their content, parse their blocks, etc..

		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueStandaloneApp' ] );
		add_action( 'admin_footer', [ $this, 'adminFooter' ] );

		// Real-time orphan cleanup. Hooks fire in admin, REST, CLI, and cron contexts.
		add_action( 'delete_post', [ $this, 'removeOrphanedData' ] );
		add_action( 'wp_trash_post', [ $this, 'removeOrphanedData' ] );

		// Recurring sweeper for orphan rows that pre-date the real-time hooks.
		add_action( 'admin_init', [ $this, 'scheduleCleanupScan' ] );
		add_action( $this->cleanupActionName, [ $this, 'runCleanupScan' ] );
	}

	/**
	 * Removes Broken Link Checker data for a deleted/trashed post.
	 *
	 * On permanent delete, {@see \AIOSEO\BrokenLinkChecker\Links\Links::deletePostLinks()} already
	 * removes the link rows (and prunes any link statuses they leave orphaned), so here we only
	 * drop the post's scan-metadata row. On trash that handler does not run, so we delete the
	 * links too — but keep link statuses, since a trashed post may be restored.
	 *
	 * @since 1.3.0
	 *
	 * @param  int  $postId The post ID.
	 * @return void
	 */
	public function removeOrphanedData( $postId ) {
		if ( ! doing_action( 'delete_post' ) ) {
			Models\Link::deleteLinks( $postId );
		}

		Models\Post::deleteByPostId( $postId );
	}

	/**
	 * Schedules the recurring orphan-row cleanup scan.
	 *
	 * @since 1.3.0
	 *
	 * @return void
	 */
	public function scheduleCleanupScan() {
		aioseoBrokenLinkChecker()->actionScheduler->scheduleRecurrent( $this->cleanupActionName, 0, DAY_IN_SECONDS );
	}

	/**
	 * Sweeps Broken Link Checker rows whose post is gone, trashed, or private, plus
	 * link-status rows no longer referenced by any link.
	 *
	 * Runs in batches of 10,000 until every table is clean or 30 seconds elapse.
	 *
	 * @since 1.3.0
	 *
	 * @return void
	 */
	public function runCleanupScan() {
		$lockKey = 'as_blc_links_cleanup_running';
		if ( aioseoBrokenLinkChecker()->core->cache->get( $lockKey ) ) {
			return;
		}

		// Timeout in case the action fails mid-execution.
		aioseoBrokenLinkChecker()->core->cache->update( $lockKey, true, 5 * MINUTE_IN_SECONDS );

		$deadline = microtime( true ) + 30;
		$this->sweepOrphanRows( 'aioseo_blc_links', $deadline, [ 'trash', 'private' ] );
		$this->sweepOrphanRows( 'aioseo_blc_posts', $deadline, [ 'trash', 'private' ] );
		$this->sweepOrphanLinkStatuses( $deadline );

		aioseoBrokenLinkChecker()->core->cache->delete( $lockKey );
	}

	/**
	 * Deletes rows from the given table whose `post_id` is gone or has an excluded status.
	 *
	 * Uses a single-table DELETE with `NOT EXISTS` so MySQL accepts `LIMIT` (which it
	 * rejects on multi-table DELETE statements).
	 *
	 * @since 1.3.0
	 *
	 * @param  string   $tableName        The unprefixed table name to sweep (must have a `post_id` column).
	 * @param  float    $deadline         Unix timestamp (with microseconds) at which to stop.
	 * @param  string[] $excludeStatuses  Post statuses to treat as orphan-equivalent.
	 * @return void
	 */
	private function sweepOrphanRows( $tableName, $deadline, $excludeStatuses = [ 'trash' ] ) {
		$batchSize  = 10000;
		$prefix     = aioseoBrokenLinkChecker()->core->db->prefix;
		$table      = $prefix . $tableName;
		$wpPosts    = $prefix . 'posts';
		$statusList = implode( ',', array_map( function( $status ) {
			return "'" . esc_sql( $status ) . "'";
		}, $excludeStatuses ) );

		while ( microtime( true ) < $deadline ) {
			aioseoBrokenLinkChecker()->core->db->execute(
				"DELETE FROM $table
				WHERE NOT EXISTS (
					SELECT 1 FROM $wpPosts AS p
					WHERE p.ID = $table.post_id
						AND p.post_status NOT IN ($statusList)
				)
				LIMIT $batchSize"
			);

			if ( (int) aioseoBrokenLinkChecker()->core->db->rowsAffected() < $batchSize ) {
				return;
			}
		}
	}

	/**
	 * Deletes link-status rows no longer referenced by any link.
	 *
	 * Uses the same single-table `DELETE … NOT EXISTS … LIMIT N` shape as sweepOrphanRows().
	 * Skips rows created within the last hour so an in-progress scan — which inserts a status
	 * row shortly before linking it (see {@see \AIOSEO\BrokenLinkChecker\Links\Data::indexLinks()})
	 * — is never raced.
	 *
	 * @since 1.3.0
	 *
	 * @param  float $deadline Unix timestamp (with microseconds) at which to stop.
	 * @return void
	 */
	private function sweepOrphanLinkStatuses( $deadline ) {
		$batchSize   = 10000;
		$prefix      = aioseoBrokenLinkChecker()->core->db->prefix;
		$statusTable = $prefix . 'aioseo_blc_link_status';
		$linksTable  = $prefix . 'aioseo_blc_links';
		$cutoff      = esc_sql( aioseoBrokenLinkChecker()->helpers->timeToMysql( time() - HOUR_IN_SECONDS ) );

		while ( microtime( true ) < $deadline ) {
			aioseoBrokenLinkChecker()->core->db->execute(
				"DELETE FROM $statusTable
				WHERE created < '$cutoff'
					AND NOT EXISTS (
						SELECT 1 FROM $linksTable
						WHERE $linksTable.blc_link_status_id = $statusTable.id
					)
				LIMIT $batchSize"
			);

			if ( (int) aioseoBrokenLinkChecker()->core->db->rowsAffected() < $batchSize ) {
				return;
			}
		}
	}

	/**
	 * Enqueues the standalone app for admin menu styles.
	 *
	 * @since 1.2.6
	 *
	 * @return void
	 */
	public function enqueueStandaloneApp() {
		aioseoBrokenLinkChecker()->core->assets->load( 'src/vue/standalone/app/main.js', [], [], 'aioseoBrokenLinkCheckerApp' );
	}

	/**
	 * Enqueue the footer div to let Vue attach.
	 *
	 * @since 1.2.6
	 *
	 * @return void
	 */
	public function adminFooter() {
		echo '<div id="aioseo-blc-admin"></div>';
	}

	/**
	 * Filters links in the post content.
	 *
	 * @since 1.0.0
	 *
	 * @param  string $postContent The post content.
	 * @return string              The post content.
	 */
	public function filterLinks( $postContent ) {
		if ( aioseoBrokenLinkChecker()->options->general->linkTweaks->nofollowBroken ) {
			$postContent = $this->nofollowBrokenLinks( $postContent );
		}

		return $postContent;
	}

	/**
	 * Adds rel="nofollow" to links in the post content that we know are broken.
	 *
	 * Routes to the HTML API path (WP 6.2+) or falls back to the regex path.
	 *
	 * @since   1.0.0
	 * @version 1.3.0 Refactored into junction between HtmlApi and Regex methods.
	 *
	 * @param  string $postContent The post content.
	 * @return string              The post content.
	 */
	private function nofollowBrokenLinks( $postContent ) {
		if ( version_compare( get_bloginfo( 'version' ), '6.2', '>=' ) ) {
			return $this->nofollowBrokenLinksHtmlApi( $postContent );
		}

		return $this->nofollowBrokenLinksRegex( $postContent );
	}

	/**
	 * Adds rel="nofollow" to broken links using WP_HTML_Tag_Processor.
	 *
	 * Two-pass approach: first collects all hrefs and batch-loads broken
	 * link statuses in a single DB query, then applies nofollow attributes.
	 *
	 * @since 1.3.0
	 *
	 * @param  string $postContent The post content.
	 * @return string              The post content.
	 */
	private function nofollowBrokenLinksHtmlApi( $postContent ) {
		$processor = new \WP_HTML_Tag_Processor( $postContent );
		$siteUrl   = rtrim( get_site_url(), '/' );

		// First pass: collect all unique hrefs.
		$allHrefs = [];
		while ( $processor->next_tag( 'a' ) ) {
			$href = $processor->get_attribute( 'href' );
			if ( is_string( $href ) && '' !== $href ) {
				$allHrefs[ $href ] = true;
			}
		}

		if ( empty( $allHrefs ) ) {
			return $postContent;
		}

		// Batch-load broken URL hashes in a single query.
		$brokenHashes = $this->loadBrokenUrlHashes( array_keys( $allHrefs ), $siteUrl );

		if ( empty( $brokenHashes ) ) {
			return $postContent;
		}

		// Second pass: apply nofollow to broken links.
		$processor = new \WP_HTML_Tag_Processor( $postContent );

		while ( $processor->next_tag( 'a' ) ) {
			$href = $processor->get_attribute( 'href' );
			if ( ! is_string( $href ) || '' === $href || ! $this->isHrefBroken( $href, $siteUrl, $brokenHashes ) ) {
				continue;
			}

			$rel = $processor->get_attribute( 'rel' );
			if ( is_string( $rel ) && '' !== $rel ) {
				$relParts = array_filter( preg_split( '/\s+/', $rel ) ?: [] );
				if ( in_array( 'nofollow', $relParts, true ) ) {
					continue;
				}
				$relParts[] = 'nofollow';
				$newRel = implode( ' ', $relParts );
			} else {
				$newRel = 'nofollow';
			}

			$processor->set_attribute( 'rel', $newRel );
		}

		return $processor->get_updated_html();
	}

	/**
	 * Batch-loads broken URL hashes for the given hrefs.
	 *
	 * Generates all URL variants (exact, trailing-slash, absolute for relative hrefs)
	 * and queries the link_status table in a single query.
	 *
	 * @since 1.3.0
	 *
	 * @param  array  $hrefs   The unique href values.
	 * @param  string $siteUrl The site URL (no trailing slash).
	 * @return array           Map of broken URL hashes (hash => true).
	 */
	private function loadBrokenUrlHashes( $hrefs, $siteUrl ) {
		$hashes = [];
		foreach ( $hrefs as $href ) {
			foreach ( $this->getUrlVariants( $href, $siteUrl ) as $variant ) {
				$hashes[] = sha1( $variant );
			}
		}

		$hashes = array_unique( $hashes );

		if ( empty( $hashes ) ) {
			return [];
		}

		$results = aioseoBrokenLinkChecker()->core->db->start( 'aioseo_blc_link_status' )
			->select( 'url_hash' )
			->whereIn( 'url_hash', $hashes )
			->where( 'broken', 1 )
			->where( 'needs_additional_scan', 0 )
			->where( 'dismissed', 0 )
			->run()
			->result();

		$brokenHashes = [];
		foreach ( $results as $row ) {
			$brokenHashes[ $row->url_hash ] = true;
		}

		return $brokenHashes;
	}

	/**
	 * Checks if an href has a broken link status using pre-loaded hashes.
	 *
	 * @since 1.3.0
	 *
	 * @param  string $href         The href attribute value.
	 * @param  string $siteUrl      The site URL (no trailing slash).
	 * @param  array  $brokenHashes Map of broken URL hashes.
	 * @return bool                 Whether the href is broken.
	 */
	private function isHrefBroken( $href, $siteUrl, $brokenHashes ) {
		foreach ( $this->getUrlVariants( $href, $siteUrl ) as $variant ) {
			if ( isset( $brokenHashes[ sha1( $variant ) ] ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Returns all URL variants for matching against stored URL hashes.
	 *
	 * Generates trailing-slash, scheme-agnostic (http <-> https), and
	 * relative-to-absolute variants of the given href.
	 *
	 * @since 1.3.0
	 *
	 * @param  string $href    The href attribute value.
	 * @param  string $siteUrl The site URL (no trailing slash).
	 * @return array           The URL variants (including the original).
	 */
	private function getUrlVariants( $href, $siteUrl ) {
		// Strip URL fragment — stored URLs have fragments removed during extraction.
		$fragmentPos = strpos( $href, '#' );
		if ( false !== $fragmentPos ) {
			$href = substr( $href, 0, $fragmentPos );
			if ( '' === $href ) {
				return [];
			}
		}

		$variants = [ $href ];

		// Trailing-slash variant.
		$variants[] = '/' === substr( $href, -1 ) ? rtrim( $href, '/' ) : $href . '/';

		// Scheme-agnostic variants (http: <-> https:).
		$schemeless = preg_replace( '#^https?:#i', '', $href ) ?? $href;
		if ( $schemeless !== $href ) {
			$altScheme  = 0 === strpos( $href, 'https:' ) ? 'http:' . $schemeless : 'https:' . $schemeless;
			$variants[] = $altScheme;
			$variants[] = '/' === substr( $altScheme, -1 ) ? rtrim( $altScheme, '/' ) : $altScheme . '/';
		}

		// Protocol-relative URLs (//example.com/path): add both scheme variants.
		if ( 0 === strpos( $href, '//' ) && $schemeless === $href ) {
			$httpsVariant = 'https:' . $href;
			$httpVariant  = 'http:' . $href;
			$variants[]   = $httpsVariant;
			$variants[]   = $httpVariant;
			$variants[]   = '/' === substr( $httpsVariant, -1 ) ? rtrim( $httpsVariant, '/' ) : $httpsVariant . '/';
			$variants[]   = '/' === substr( $httpVariant, -1 ) ? rtrim( $httpVariant, '/' ) : $httpVariant . '/';
		}

		// If the href is relative, add absolute variants.
		if ( ! wp_parse_url( $href, PHP_URL_HOST ) ) {
			$absoluteUrl = trim( sanitize_url( $siteUrl . '/' . ltrim( $href, '/' ) ) );
			$variants[]  = $absoluteUrl;
			$variants[]  = '/' === substr( $absoluteUrl, -1 ) ? rtrim( $absoluteUrl, '/' ) : $absoluteUrl . '/';
		}

		// RFC 3986 §3.2.2: host is case-insensitive. The href in content and the stored URL can
		// independently have different host casing, so check both forms.
		$lowercasedHref = preg_replace_callback( '#^(https?:)?//[^/]+#i', static function ( $m ) {
			return strtolower( $m[0] );
		}, $href ) ?? $href;

		if ( $lowercasedHref !== $href ) {
			$variants[] = $lowercasedHref;
			$variants[] = '/' === substr( $lowercasedHref, -1 ) ? rtrim( $lowercasedHref, '/' ) : $lowercasedHref . '/';
		}

		// Normalize each variant through sanitize_url() + trim() to match the
		// storage pipeline (Data.php applies buildUrl, sanitize_url, trim before hashing).
		$normalized = [];
		foreach ( $variants as $variant ) {
			$normalized[] = trim( sanitize_url( $variant ) );
		}

		return array_unique( $normalized );
	}

	/**
	 * Regex-based nofollow injection for WP < 6.2 (without WP_HTML_Tag_Processor).
	 *
	 * @since   1.0.0
	 * @version 1.3.0 Renamed from nofollowBrokenLinks().
	 *
	 * @param  string $postContent The post content.
	 * @return string              The post content.
	 */
	private function nofollowBrokenLinksRegex( $postContent ) {
		// First, capture all link tags.
		preg_match_all( '/<a.*href="(.*?").*>(.*?)<\/a>/i', (string) $postContent, $linkTags );

		if ( empty( $linkTags[0] ) ) {
			return $postContent;
		}

		foreach ( $linkTags[0] as $linkTag ) {
			preg_match( '/href="(.*?)"/i', (string) $linkTag, $url );
			if ( empty( $url[1] ) ) {
				continue;
			}

			// Now check if we've indexed the link. If so, check if it's broken and act accordingly.
			$linkStatus = Models\LinkStatus::getByUrl( $url[1] );
			if ( ! $linkStatus->exists() || ! $linkStatus->broken || $linkStatus->needs_additional_scan ) {
				continue;
			}

			preg_match( '/rel="(.*?)"/i', (string) $linkTag, $relAttributes );
			if ( ! empty( $relAttributes[0] ) ) {
				$relAttributes = explode( ' ', $relAttributes[1] );
				if ( ! in_array( 'nofollow', $relAttributes, true ) ) {
					$relAttributes[] = 'nofollow';
				}
				$relAttributes = implode( ' ', $relAttributes );
			} else {
				$relAttributes = 'nofollow';
			}

			$newLinkTag = $this->insertAttribute( $linkTag, 'rel', $relAttributes );

			$oldLinkTag = aioseoBrokenLinkChecker()->helpers->escapeRegex( $linkTag );
			$newLinkTag = aioseoBrokenLinkChecker()->helpers->escapeRegexReplacement( $newLinkTag );

			$postContent = preg_replace( "/{$oldLinkTag}/i", $newLinkTag, (string) $postContent ) ?? $postContent;
		}

		return $postContent;
	}

	/**
	 * Inserts a given value for a given image attribute.
	 * Used by the regex nofollow method for WP < 6.2.
	 *
	 * @since 1.0.0
	 *
	 * @param  string $linkTag       The HTML tag.
	 * @param  string $attributeName The attribute name.
	 * @param  string $value         The attribute value.
	 * @return string                The modified tag.
	 */
	private function insertAttribute( $linkTag, $attributeName, $value ) {
		if ( empty( $value ) ) {
			return $linkTag;
		}

		$value   = esc_attr( $value );
		$linkTag = preg_replace( $this->attributeRegex( $attributeName, true ), '${1}' . $value . '${2}', (string) $linkTag, 1, $count ) ?? $linkTag;

		// Attribute does not exist. Let's append it at the beginning of the tag.
		if ( ! $count ) {
			$linkTag = str_replace( '<a ', '<a ' . $this->attributeToHtml( $attributeName, $value ) . ' ', (string) $linkTag );
		}

		return $linkTag;
	}

	/**
	 * Returns a regex string to match an attribute.
	 * Used by the regex nofollow method for WP < 6.2.
	 *
	 * @since 1.0.0
	 *
	 * @param  string $attributeName      The attribute name.
	 * @param  bool   $groupReplaceValue  Regex groupings without the value.
	 * @return string                     The regex string.
	 */
	private function attributeRegex( $attributeName, $groupReplaceValue = false ) {
		$regex = $groupReplaceValue ? "/(\s%s=['\"]).*?(['\"])/" : "/\s%s=['\"](.*?)['\"]/";

		return sprintf( $regex, trim( $attributeName ) );
	}

	/**
	 * Returns an attribute as HTML.
	 * Used by the regex nofollow method for WP < 6.2.
	 *
	 * @since 1.0.0
	 *
	 * @param  string $attributeName The attribute name.
	 * @param  string $value         The attribute value.
	 * @return string                The HTML formatted attribute.
	 */
	private function attributeToHtml( $attributeName, $value ) {
		return sprintf( '%s="%s"', $attributeName, esc_attr( $value ) );
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit