Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- **Broken link email notification** — HTML email sent to the site admin when broken links are found, listing each affected tire with its status and failure details, plus a direct link to the admin dashboard.
- **Dashboard health indicator** — Content Health section on the main dashboard now includes a broken affiliate links indicator with a link to the Affiliate Links page for remediation.

### Fixed
- **"Check Links Now" network error** — The AJAX handler could exceed PHP's `max_execution_time` when checking many links. Added `set_time_limit(300)` and eliminated a redundant second HTTP request per link in `get_effective_url()` by reading the final URL from WordPress's transport response object.
- **Affiliate link checker missing broken CJ links** — `check_single_link()` used `wp_remote_head`, but affiliate networks like CJ (jdoqocy.com) only redirect GET requests, not HEAD. Switched to `wp_remote_get` with a 4 KB response size limit so the full redirect chain is followed.

## [1.23.0] - 2026-03-01

### Added
Expand Down
3 changes: 3 additions & 0 deletions includes/class-rtg-ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,9 @@ public function check_links() {
wp_send_json_error( 'Unauthorized.' );
}

// Allow enough time for outbound HTTP requests (up to 50 links).
set_time_limit( 300 );

$results = RTG_Link_Checker::run();

wp_send_json_success( $results );
Expand Down
48 changes: 19 additions & 29 deletions includes/class-rtg-link-checker.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,13 @@ public static function run() {
* @return array {status, reason, http_code}
*/
public static function check_single_link( $url ) {
$response = wp_remote_head( $url, array(
'timeout' => self::REQUEST_TIMEOUT,
'redirection' => 10,
'sslverify' => false,
// Use GET instead of HEAD because many affiliate networks
// (CJ, ShareASale, etc.) only redirect GET requests.
$response = wp_remote_get( $url, array(
'timeout' => self::REQUEST_TIMEOUT,
'redirection' => 10,
'sslverify' => false,
'limit_response_size' => 4096,
) );

if ( is_wp_error( $response ) ) {
Expand Down Expand Up @@ -167,39 +170,26 @@ public static function check_single_link( $url ) {
* @return string The effective URL.
*/
private static function get_effective_url( $response, $original ) {
// Check for X-Redirect-By or Location in the last response.
// WordPress's HTTP API stores the final URL after following redirects
// in the transport response object — no second request needed.
if ( isset( $response['http_response'] ) && $response['http_response'] instanceof WP_HTTP_Requests_Response ) {
$effective = $response['http_response']->get_response_object()->url;
if ( ! empty( $effective ) ) {
return $effective;
}
}

// Fallback: check Location header (for edge cases where the transport
// doesn't expose the final URL).
$location = wp_remote_retrieve_header( $response, 'location' );
if ( ! empty( $location ) ) {
// If it's a relative URL, resolve it.
if ( strpos( $location, 'http' ) !== 0 ) {
$parsed = wp_parse_url( $original );
$parsed = wp_parse_url( $original );
$location = $parsed['scheme'] . '://' . $parsed['host'] . $location;
}
return $location;
}

// If wp_remote_head followed redirects transparently, we can try
// a non-following request to see where it leads.
$probe = wp_remote_head( $original, array(
'timeout' => self::REQUEST_TIMEOUT,
'redirection' => 0,
'sslverify' => false,
) );

if ( ! is_wp_error( $probe ) ) {
$code = wp_remote_retrieve_response_code( $probe );
if ( $code >= 300 && $code < 400 ) {
$loc = wp_remote_retrieve_header( $probe, 'location' );
if ( ! empty( $loc ) ) {
if ( strpos( $loc, 'http' ) !== 0 ) {
$parsed = wp_parse_url( $original );
$loc = $parsed['scheme'] . '://' . $parsed['host'] . $loc;
}
return $loc;
}
}
}

return $original;
}

Expand Down
Loading