| @@ -959,32 +959,8 @@ | ||
| 959 | 959 | } |
| 960 | 960 | |
| 961 | 961 | return $normalized; |
| 962 | 962 | } |
| 963 | - /** | |
| 964 | - * The URL to check and fetch for a competitor URL the user entered, or | |
| 965 | - * null when it is not a valid URL. | |
| 966 | - * | |
| 967 | - * A competitor page on an internationalised domain, or with a Bengali or | |
| 968 | - * Arabic slug, is a valid URL, so the syntax check is Url_Validator's. | |
| 969 | - * What comes back is the ASCII form (punycode host, percent-encoded path), | |
| 970 | - * and both the SSRF guard and the fetch use it: the guard cannot resolve | |
| 971 | - * a Unicode host name, and it must check exactly the URL that is then | |
| 972 | - * requested. | |
| 973 | - * | |
| 974 | - * @since 2.14.2 | |
| 975 | - * | |
| 976 | - * @param string $url Trimmed URL as entered. | |
| 977 | - * @return string|null ASCII URL, or null when invalid. | |
| 978 | - */ | |
| 979 | - private function competitor_fetch_url(string $url): ?string { | |
| 980 | - if ('' === $url || !\ThinkRank\Core\Url_Validator::is_valid($url)) { | |
| 981 | - return null; | |
| 982 | - } | |
| 983 | - | |
| 984 | - return \ThinkRank\Core\Url_Validator::to_ascii($url); | |
| 985 | - } | |
| 986 | - | |
| 987 | 963 | private function analyze_competitor_urls(array $urls): string { |
| 988 | 964 | $analysis_results = []; |
| 989 | 965 | $failed_urls = []; |
| 990 | 966 | |
| @@ -992,10 +968,9 @@ | ||
| 992 | 968 | $urls = array_slice($urls, 0, 3); |
| 993 | 969 | |
| 994 | 970 | foreach ($urls as $url) { |
| 995 | 971 | $url = trim($url); |
| 996 | - $fetch_url = $this->competitor_fetch_url($url); | |
| 997 | - if (null === $fetch_url) { | |
| 972 | + if (empty($url) || !filter_var($url, FILTER_VALIDATE_URL)) { | |
| 998 | 973 | $failed_urls[] = $url . " (invalid URL)"; |
| 999 | 974 | continue; |
| 1000 | 975 | } |
| 1001 | 976 | |
| @@ -1001,14 +976,14 @@ | ||
| 1001 | 976 | |
| 1002 | 977 | // SSRF guard: only fetch public http/https hosts. Blocks loopback, |
| 1003 | 978 | // link-local (cloud metadata), private and reserved ranges before any |
| 1004 | 979 | // request is made. |
| 1005 | - if (!$this->is_safe_public_url($fetch_url)) { | |
| 980 | + if (!$this->is_safe_public_url($url)) { | |
| 1006 | 981 | $failed_urls[] = $url . " (blocked: non-public host)"; |
| 1007 | 982 | continue; |
| 1008 | 983 | } |
| 1009 | 984 | |
| 1010 | - $content_data = $this->scrape_competitor_content($fetch_url); | |
| 985 | + $content_data = $this->scrape_competitor_content($url); | |
| 1011 | 986 | if ($content_data) { |
| 1012 | 987 | $analysis_results[] = $this->format_competitor_analysis($url, $content_data); |
| 1013 | 988 | } else { |
| 1014 | 989 | $failed_urls[] = $url . " (failed to scrape)"; |
| @@ -1254,9 +1229,9 @@ | ||
| 1254 | 1229 | // Add heading structure |
| 1255 | 1230 | if (!empty($content_data['headings'])) { |
| 1256 | 1231 | $analysis .= "\nCONTENT STRUCTURE:\n"; |
| 1257 | 1232 | foreach ($content_data['headings'] as $level => $headings) { |
| 1258 | - $analysis .= "- " . strtoupper($level) . " (" . count($headings) . "): " . implode(', ', array_slice($headings, 0, 3)); | |
| 1233 | + $analysis .= "- " . strtoupper($level) . " ({count}): " . implode(', ', array_slice($headings, 0, 3)); | |
| 1259 | 1234 | if (count($headings) > 3) { |
| 1260 | 1235 | $analysis .= "... (+" . (count($headings) - 3) . " more)"; |
| 1261 | 1236 | } |
| 1262 | 1237 | $analysis .= "\n"; |