| @@ -1636,9 +1636,9 @@ | ||
| 1636 | 1636 | $optimization['suggestions'][] = 'Add a site logo for better branding and professional appearance'; |
| 1637 | 1637 | $optimization['score'] -= 20; |
| 1638 | 1638 | } else { |
| 1639 | 1639 | // Validate logo URL and dimensions |
| 1640 | - if (!filter_var($logo_url, FILTER_VALIDATE_URL)) { | |
| 1640 | + if (!\ThinkRank\Core\Url_Validator::is_http_url($logo_url)) { | |
| 1641 | 1641 | $optimization['warnings'][] = 'Logo URL format is invalid'; |
| 1642 | 1642 | $optimization['score'] -= 15; |
| 1643 | 1643 | } |
| 1644 | 1644 | } |
| @@ -1657,9 +1657,9 @@ | ||
| 1657 | 1657 | $optimization['score'] -= 10; |
| 1658 | 1658 | } |
| 1659 | 1659 | |
| 1660 | 1660 | // Additional logo analysis for local images |
| 1661 | - if (!empty($logo_url) && filter_var($logo_url, FILTER_VALIDATE_URL)) { | |
| 1661 | + if (!empty($logo_url) && \ThinkRank\Core\Url_Validator::is_http_url($logo_url)) { | |
| 1662 | 1662 | $attachment_id = Attachment_Lookup::id_from_url($logo_url); |
| 1663 | 1663 | if ($attachment_id) { |
| 1664 | 1664 | $image_meta = wp_get_attachment_metadata($attachment_id); |
| 1665 | 1665 | // The configured file's own size — a logo picked at a generated |
| @@ -2145,12 +2145,37 @@ | ||
| 2145 | 2145 | $validation['suggestions'][] = 'Consider making site description longer (120-160 characters)'; |
| 2146 | 2146 | } |
| 2147 | 2147 | } |
| 2148 | 2148 | |
| 2149 | - // Validate logo URL | |
| 2150 | - if (isset($settings['logo_url']) && !empty($settings['logo_url'])) { | |
| 2151 | - if (!filter_var($settings['logo_url'], FILTER_VALIDATE_URL)) { | |
| 2152 | - $validation['errors'][] = 'Logo URL must be a valid URL'; | |
| 2149 | + // Image and link URLs. These are written into src and href | |
| 2150 | + // attributes (the schema logo, the admin previews, the hero section), | |
| 2151 | + // so only web URLs are accepted. is_valid() takes any scheme, and | |
| 2152 | + // "javascript://%0Aalert(1)" passed it and was stored as | |
| 2153 | + // "javascript://alert(1)" once sanitize_text_field() dropped the %0A. | |
| 2154 | + // The logo and default social image must be absolute: they are | |
| 2155 | + // published in schema and Open Graph, which require it. The others | |
| 2156 | + // may also be a path on this site. | |
| 2157 | + $url_fields = [ | |
| 2158 | + 'logo_url' => ['Logo URL', false], | |
| 2159 | + 'default_social_image' => ['Default social image URL', false], | |
| 2160 | + 'favicon_url' => ['Favicon URL', true], | |
| 2161 | + 'apple_touch_icon_url' => ['Apple touch icon URL', true], | |
| 2162 | + 'hero_background_image' => ['Hero background image URL', true], | |
| 2163 | + 'hero_cta_url' => ['Call-to-action URL', true], | |
| 2164 | + ]; | |
| 2165 | + foreach ($url_fields as $key => [$label, $allow_path]) { | |
| 2166 | + if (!isset($settings[$key]) || '' === $settings[$key] || null === $settings[$key]) { | |
| 2167 | + continue; | |
| 2168 | + } | |
| 2169 | + | |
| 2170 | + $ok = $allow_path | |
| 2171 | + ? \ThinkRank\Core\Url_Validator::is_http_url_or_path($settings[$key]) | |
| 2172 | + : \ThinkRank\Core\Url_Validator::is_http_url($settings[$key]); | |
| 2173 | + | |
| 2174 | + if (!$ok) { | |
| 2175 | + $validation['errors'][] = $allow_path | |
| 2176 | + ? sprintf('%s must be an http or https URL, or a path starting with /', $label) | |
| 2177 | + : sprintf('%s must be an http or https URL', $label); | |
| 2153 | 2178 | $validation['valid'] = false; |
| 2154 | 2179 | } |
| 2155 | 2180 | } |
| 2156 | 2181 | |
| @@ -2588,9 +2613,9 @@ | ||
| 2588 | 2613 | } |
| 2589 | 2614 | |
| 2590 | 2615 | // CTA URL validation |
| 2591 | 2616 | if (!empty($settings['hero_cta_url'])) { |
| 2592 | - if (filter_var($settings['hero_cta_url'], FILTER_VALIDATE_URL) || strpos($settings['hero_cta_url'], '/') === 0) { | |
| 2617 | + if (\ThinkRank\Core\Url_Validator::is_http_url_or_path($settings['hero_cta_url'])) { | |
| 2593 | 2618 | $field_details[] = [ |
| 2594 | 2619 | 'field' => 'hero_cta_url', |
| 2595 | 2620 | 'label' => 'Call-to-action URL is properly configured.', |
| 2596 | 2621 | 'status' => 'valid', |
| @@ -2631,9 +2656,9 @@ | ||
| 2631 | 2656 | } |
| 2632 | 2657 | |
| 2633 | 2658 | // Site Logo validation (from Site Assets section) |
| 2634 | 2659 | if (!empty($settings['logo_url'])) { |
| 2635 | - if (filter_var($settings['logo_url'], FILTER_VALIDATE_URL)) { | |
| 2660 | + if (\ThinkRank\Core\Url_Validator::is_http_url($settings['logo_url'])) { | |
| 2636 | 2661 | $field_details[] = [ |
| 2637 | 2662 | 'field' => 'logo_url', |
| 2638 | 2663 | 'label' => 'Site logo is properly configured.', |
| 2639 | 2664 | 'status' => 'valid', |
| @@ -4102,9 +4127,12 @@ | ||
| 4102 | 4127 | $validation['warnings'][] = "Path '{$value}' should start with '/'"; |
| 4103 | 4128 | } |
| 4104 | 4129 | break; |
| 4105 | 4130 | case 'sitemap': |
| 4106 | - if (!filter_var($value, FILTER_VALIDATE_URL)) { | |
| 4131 | + // Url_Validator, not the raw PHP filter: on a site with an | |
| 4132 | + // internationalised domain the site's own sitemap URL is | |
| 4133 | + // non-ASCII and the raw filter refused it. | |
| 4134 | + if (!\ThinkRank\Core\Url_Validator::is_valid($value)) { | |
| 4107 | 4135 | $validation['errors'][] = "Invalid sitemap URL: {$value}"; |
| 4108 | 4136 | $validation['valid'] = false; |
| 4109 | 4137 | } |
| 4110 | 4138 | break; |
| @@ -4503,10 +4531,10 @@ | ||
| 4503 | 4531 | return $optimization; |
| 4504 | 4532 | } |
| 4505 | 4533 | |
| 4506 | 4534 | // Validate URL |
| 4507 | - if (!filter_var($value, FILTER_VALIDATE_URL)) { | |
| 4508 | - $optimization['validation']['errors'][] = "{$element} must be a valid URL"; | |
| 4535 | + if (!\ThinkRank\Core\Url_Validator::is_http_url($value)) { | |
| 4536 | + $optimization['validation']['errors'][] = "{$element} must be an http or https URL"; | |
| 4509 | 4537 | $optimization['validation']['valid'] = false; |
| 4510 | 4538 | return $optimization; |
| 4511 | 4539 | } |
| 4512 | 4540 | |