<?php
/**
 * File: modules/canonical.php
 * Canonical Auditor REST API (implements normalization + rules + outputs)
 */

if ( ! defined('ABSPATH') ) { exit; }

// Shared API-key validation (same across all modules)
require_once plugin_dir_path(__FILE__) . '../includes/sac-utils.php';

add_action('rest_api_init', function () {
    register_rest_route('myaio/v1', '/check-non-canonical-links', [
        'methods'             => WP_REST_Server::READABLE, // GET
        'callback'            => 'myaio_sac_check_non_canonical_links',
        'permission_callback' => 'myaio_sac_rest_permission_check', // keep output behavior in callback
    ]);

    register_rest_route('myaio/v1', '/add-canonical', [
        'methods'             => WP_REST_Server::CREATABLE, // POST
        'callback'            => 'myaio_sac_add_canonical_link',
        'permission_callback' => 'myaio_sac_rest_permission_check', // keep output behavior in callback
    ]);
});

/** ===============================
 * GET /check-non-canonical-links
 * =============================== */
if (!function_exists('myaio_sac_check_non_canonical_links')) {
function myaio_sac_check_non_canonical_links($request) {

    // Keep EXACT unauthorized output
    $auth = myaio_sac_require_api_key_or_401($request);
    if ($auth !== true) return $auth;

    $format = strtolower($request->get_param('format') ? $request->get_param('format') : 'full'); // full|jsonl|csv
    $user_agent = 'CanonicalAuditor/1.0 (+'.home_url().')';

    $urls = myaio_sac_get_all_crawlable_urls();
    $results = array();
    $hash_index = array(); // content_hash => index of first seen page
    $clusters = array();

    foreach ($urls as $item) {
        $raw_url  = isset($item['url']) ? $item['url'] : null;
        if (!$raw_url) { continue; }

        $norm_url = myaio_sac_normalize_url($raw_url);

        // 1) Follow redirects to get final URL, status, headers, body
        $fetch = myaio_sac_fetch_with_redirects($raw_url, $user_agent);
        $final_url   = $fetch['final_url'];
        $http_status = intval($fetch['status']);
        $headers     = $fetch['headers'];
        $body        = $fetch['body'];
        $chain       = $fetch['chain'];

        // 2) Robots
        $robots_allowed = myaio_sac_robots_allowed($final_url, $user_agent);
        $meta = myaio_sac_parse_meta_robots($body);
        $meta_robots = $meta ? $meta : 'index,follow';

        // 3) Canonicals
        $decl_header = myaio_sac_parse_header_canonical($headers, $final_url);
        $decl_html   = myaio_sac_parse_html_canonical($body, $final_url);
        // Per rule order, header canonical is considered before HTML
        $declared_canonical = $decl_header ? $decl_header : $decl_html;

        // 4) Decision rules (first match wins)
        $is_non_canonical = false;
        $reason = null;
        $effective_canonical = null;
        $flags = array();

        // Redirected source
        if ((count($chain) > 1) || myaio_sac_is_redirect_status($http_status)) {
            $is_non_canonical = true;
            $reason = 'redirect_source';
            $last = end($chain);
            $effective_canonical = myaio_sac_normalize_url($last ? $last['url'] : $final_url);
        }

        // Noindex
        if (!$is_non_canonical && myaio_sac_has_noindex($meta_robots)) {
            if ($declared_canonical) {
                $is_non_canonical = true;
                $reason = 'noindex_points_elsewhere';
                $effective_canonical = myaio_sac_effective_canonical($declared_canonical, $flags, $user_agent);
            } else {
                $is_non_canonical = true;
                $reason = 'noindex_no_target';
                $effective_canonical = null;
            }
        }

        // Header canonical
        if (!$is_non_canonical && $decl_header) {
            $canon_effective = myaio_sac_effective_canonical($decl_header, $flags, $user_agent);
            if (myaio_sac_urls_equal($canon_effective, $final_url)) {
                $is_non_canonical = false;
                $reason = null;
                $effective_canonical = myaio_sac_normalize_url($final_url);
            } else {
                $is_non_canonical = true;
                $reason = 'declared_canonical_other';
                $effective_canonical = $canon_effective;
            }
        }

        // HTML canonical
        if (!$is_non_canonical && $decl_html) {
            $canon_effective = myaio_sac_effective_canonical($decl_html, $flags, $user_agent);
            if (myaio_sac_urls_equal($canon_effective, $final_url)) {
                $is_non_canonical = false;
                $reason = null;
                $effective_canonical = myaio_sac_normalize_url($final_url);
            } else {
                $is_non_canonical = true;
                $reason = 'declared_canonical_other';
                $effective_canonical = $canon_effective;
            }
        }

        // Duplicates (optional but strong): exact content hash
        $content_hash = $body ? md5($body) : null;
        if (!$is_non_canonical && $content_hash) {
            if (isset($hash_index[$content_hash])) {
                $W_index = $hash_index[$content_hash];
                $W_canonical = $results[$W_index]['effective_canonical'] ? $results[$W_index]['effective_canonical'] : $results[$W_index]['final_url'];
                $is_non_canonical = !myaio_sac_urls_equal($W_canonical, $final_url);
                if ($is_non_canonical) {
                    $reason = 'duplicate';
                    $effective_canonical = myaio_sac_normalize_url($W_canonical);
                }
            } else {
                $hash_index[$content_hash] = count($results);
            }
        }

        // Fallback: 200 + indexable => canonical (implicit_self)
        if ($effective_canonical === null && !$is_non_canonical) {
            if ($http_status === 200 && $robots_allowed && !myaio_sac_has_noindex($meta_robots)) {
                $effective_canonical = myaio_sac_normalize_url($final_url); // self canonical
                $reason = null;
            } else {
                $is_non_canonical = true;
                $reason = 'error_variant';
                $effective_canonical = null;
            }
        }

        // Canonical loops (A -> B and B -> A) best-effort
        if ($declared_canonical && $effective_canonical && !myaio_sac_urls_equal($declared_canonical, $final_url)) {
            $loop_check = myaio_sac_fetch_with_redirects($declared_canonical, $user_agent);
            $decl2 = myaio_sac_parse_header_canonical($loop_check['headers'], $loop_check['final_url']);
            if (!$decl2) { $decl2 = myaio_sac_parse_html_canonical($loop_check['body'], $loop_check['final_url']); }
            if ($decl2 && myaio_sac_urls_equal(myaio_sac_normalize_url($decl2), myaio_sac_normalize_url($final_url))) {
                $flags[] = 'canonical_loops';
            }
        }

        // Cross-domain warning
        if ($effective_canonical && myaio_sac_get_host($effective_canonical) !== myaio_sac_get_host($final_url)) {
            $flags[] = 'cross_domain_canonical';
        }

        // Declared canonical status flags (4xx/5xx or redirects)
        if ($declared_canonical) {
            $canon_fetch = myaio_sac_fetch_head_status($declared_canonical, $user_agent);
            if ($canon_fetch['is_redirect']) {
                $flags[] = 'canonical_redirects';
                $effective_canonical = myaio_sac_normalize_url($canon_fetch['final_url']);
            } elseif ($canon_fetch['status'] >= 400) {
                $flags[] = 'canonical_non200';
            }
        }

        // Build record
        $record = array(
            'raw_url'                   => $raw_url,
            'norm_url'                  => $norm_url,
            'final_url'                 => myaio_sac_normalize_url($final_url),
            'http_status'               => $http_status,
            'redirect_chain'            => $chain,
            'robots_allowed'            => $robots_allowed,
            'meta_robots'               => $meta_robots,
            'declared_canonical_html'   => $decl_html ? myaio_sac_normalize_url($decl_html) : null,
            'declared_canonical_header' => $decl_header ? myaio_sac_normalize_url($decl_header) : null,
            'effective_canonical'       => $effective_canonical ? myaio_sac_normalize_url($effective_canonical) : null,
            'is_non_canonical'          => (bool)$is_non_canonical,
            'non_canonical_reason'      => $is_non_canonical ? $reason : null,
            'flags'                     => array_values(array_unique($flags)),
            'content_hash'              => $content_hash,
        );

        $results[] = $record;
    }

    // Canonical clusters
    $clusters = array();
    for ($i=0; $i<count($results); $i++) {
        $r = $results[$i];
        $canon = $r['effective_canonical'] ? $r['effective_canonical'] : $r['final_url'];
        if (!$canon) { continue; }
        $canon_norm = myaio_sac_normalize_url($canon);
        if (!isset($clusters[$canon_norm])) {
            $clusters[$canon_norm] = array('canonical' => $canon_norm, 'members' => array(), 'member_count' => 0);
        }
        if (!empty($r['is_non_canonical'])) {
            $clusters[$canon_norm]['members'][] = array(
                'url'    => $r['final_url'],
                'reason' => $r['non_canonical_reason'] ? $r['non_canonical_reason'] : 'other',
            );
        }
        $clusters[$canon_norm]['member_count']++;
    }
    $clusters = array_values($clusters);

    // Formats
    if ($format === 'jsonl') {
        $jsonl = '';
        foreach ($results as $r) { $jsonl .= json_encode($r, JSON_UNESCAPED_SLASHES) . "\n"; }
        return new WP_REST_Response(['jsonl' => $jsonl], 200);
    }

    if ($format === 'csv') {
        $csv_lines = array("url,is_non_canonical,reason,effective_canonical,http_status");
        foreach ($results as $r) {
            $csv_lines[] = sprintf(
                '%s,%s,%s,%s,%d',
                myaio_sac_csv($r['final_url']),
                !empty($r['is_non_canonical']) ? 'true' : 'false',
                myaio_sac_csv($r['non_canonical_reason'] ? $r['non_canonical_reason'] : ''),
                myaio_sac_csv($r['effective_canonical'] ? $r['effective_canonical'] : ''),
                intval($r['http_status'])
            );
        }
        $csv = implode("\n", $csv_lines);
        return new WP_REST_Response(['csv' => $csv], 200);
    }

    // full (default)
    $non_canonical = array();
    foreach ($results as $r) { if (!empty($r['is_non_canonical'])) { $non_canonical[] = $r; } }

    return rest_ensure_response(array(
        'meta' => array(
            'total_checked' => count($results),
            'non_canonical_count' => count($non_canonical),
        ),
        'non_canonical_pages' => $non_canonical,
        'results' => $results,
        'canonical_clusters' => $clusters,
    ));
}}
/** ===============================
 * POST /add-canonical (output unchanged)
 * =============================== */
if (!function_exists('myaio_sac_add_canonical_link')) {
function myaio_sac_add_canonical_link($request) {

    // Keep EXACT unauthorized output
    $auth = myaio_sac_require_api_key_or_401($request);
    if ($auth !== true) return $auth;

    $url = esc_url_raw($request->get_param('url'));
    if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) {
        return new WP_REST_Response(['error' => 'Invalid URL'], 400);
    }

    $canonicals = get_option('myaio_sac_custom_canonicals', array());
    $slug = str_replace(home_url(), '', $url); // relative path as key
    $canonicals[$slug] = $url;
    update_option('myaio_sac_custom_canonicals', $canonicals);

    return rest_ensure_response(array(
        'success' => true,
        'message' => "Canonical tag will be injected for $url"
    ));
}}
/** ===============================
 * Helpers: URL list
 * =============================== */
if (!function_exists('myaio_sac_get_all_crawlable_urls')) {
function myaio_sac_get_all_crawlable_urls() {
    $urls = array();
    $types = array('post', 'page');
    foreach ($types as $type) {
        $q = new WP_Query(array(
            'post_type' => $type,
            'posts_per_page' => -1,
            'post_status' => 'publish',
            'fields' => 'ids',
            'no_found_rows' => true,
        ));
        foreach ($q->posts as $pid) {
            $url = get_permalink($pid);
            if ($url) { $urls[] = array('url' => $url); }
        }
    }
    $home = home_url('/');
    $urls[] = array('url' => $home);
    return $urls;
}}
/** ===============================
 * Helpers: Fetching & redirects
 * =============================== */
if (!function_exists('myaio_sac_fetch_with_redirects')) {
function myaio_sac_fetch_with_redirects($url, $user_agent, $max_redirects = 10) {
    $seen = array();
    $chain = array();
    $current = $url;
    $status = 0;
    $headers = array();
    $body = '';

    for ($i=0; $i<$max_redirects; $i++) {
        if (isset($seen[$current])) { break; }
        $seen[$current] = true;

        // HEAD first (no auto redirects)
        $resp = wp_remote_head($current, array(
            'redirection' => 0,
            'user-agent'  => $user_agent,
            'timeout'     => 15,
        ));
        if (is_wp_error($resp)) {
            // fallback GET
            $resp = wp_remote_get($current, array(
                'redirection' => 0,
                'user-agent'  => $user_agent,
                'timeout'     => 20,
            ));
        }
        if (is_wp_error($resp)) { $status = 0; break; }

        $status  = intval(wp_remote_retrieve_response_code($resp));
        $headers = wp_remote_retrieve_headers($resp);
        $chain[] = array('url' => $current, 'status' => $status);

        if (myaio_sac_is_redirect_status($status)) {
            $location = isset($headers['location']) ? $headers['location'] : (isset($headers['Location']) ? $headers['Location'] : null);
            if (!$location) { break; }
            $next = myaio_sac_abs_url($location, $current);
            $current = $next;
            continue;
        } else {
            // fetch body for final
            $resp_body = wp_remote_get($current, array(
                'redirection' => 0,
                'user-agent'  => $user_agent,
                'timeout'     => 25,
            ));
            if (!is_wp_error($resp_body)) {
                $body    = wp_remote_retrieve_body($resp_body);
                $headers = wp_remote_retrieve_headers($resp_body);
            }
            break;
        }
    }

    return array(
        'final_url' => $current,
        'status'    => $status,
        'headers'   => (array)$headers,
        'body'      => $body,
        'chain'     => $chain,
    );
}}
if (!function_exists('myaio_sac_fetch_head_status')) {
function myaio_sac_fetch_head_status($url, $user_agent) {
    $resp = wp_remote_head($url, array('redirection'=>10,'user-agent'=>$user_agent,'timeout'=>15));
    if (is_wp_error($resp)) { return array('status'=>0,'is_redirect'=>false,'final_url'=>$url); }
    $status = intval(wp_remote_retrieve_response_code($resp));
    $headers = wp_remote_retrieve_headers($resp);
    $final_url = $url;
    $is_redirect = myaio_sac_is_redirect_status($status);
    if ($is_redirect && isset($headers['location'])) {
        $final_url = myaio_sac_abs_url($headers['location'], $url);
    }
    return array('status'=>$status,'is_redirect'=>$is_redirect,'final_url'=>$final_url);
}}
if (!function_exists('myaio_sac_is_redirect_status')) {
function myaio_sac_is_redirect_status($code) {
    $code = intval($code);
    return in_array($code, array(301,302,303,307,308), true);
}}
/** ===============================
 * Helpers: Canonicals & meta
 * =============================== */
if (!function_exists('myaio_sac_parse_header_canonical')) {
function myaio_sac_parse_header_canonical($headers, $base_url) {
    $links = array();
    foreach (array('link','Link') as $k) {
        if (!empty($headers[$k])) {
            $val = $headers[$k];
            if (is_array($val)) { $links = array_merge($links, $val); } else { $links[] = $val; }
        }
    }
    foreach ($links as $line) {
        $parts = array_map('trim', explode(',', $line));
        foreach ($parts as $p) {
            if (preg_match('/<([^>]+)>\s*;\s*rel="?canonical"?/i', $p, $m)) {
                $abs = myaio_sac_abs_url($m[1], $base_url);
                return myaio_sac_normalize_url($abs);
            }
        }
    }
    return null;
}}
if (!function_exists('myaio_sac_parse_html_canonical')) {
function myaio_sac_parse_html_canonical($html, $base_url) {
    if (!$html) { return null; }
    if (!class_exists('DOMDocument')) { return null; }
    libxml_use_internal_errors(true);
    $dom = new DOMDocument();
    @$dom->loadHTML($html);
    $xpath = new DOMXPath($dom);

    $base_nodes = $xpath->query('//base[@href]');
    $doc_base = ($base_nodes->length ? $base_nodes->item(0)->getAttribute('href') : null);
    $context_base = $doc_base ? myaio_sac_abs_url($doc_base, $base_url) : $base_url;

    $nodes = $xpath->query('//link[translate(@rel,"CANONICAL","canonical")="canonical"][@href]');
    if ($nodes->length) {
        $href = $nodes->item(0)->getAttribute('href');
        $abs = myaio_sac_abs_url($href, $context_base);
        return myaio_sac_normalize_url($abs);
    }
    return null;
}}
if (!function_exists('myaio_sac_parse_meta_robots')) {
function myaio_sac_parse_meta_robots($html) {
    if (!$html) { return null; }
    if (!class_exists('DOMDocument')) { return null; }
    libxml_use_internal_errors(true);
    $dom = new DOMDocument();
    @$dom->loadHTML($html);
    $xpath = new DOMXPath($dom);
    $nodes = $xpath->query('//meta[translate(@name,"ROBOTS","robots")="robots"][@content]');
    if ($nodes->length) {
        $v = strtolower(trim($nodes->item(0)->getAttribute('content')));
        $v = preg_replace('/\s+/', '', $v);
        return $v;
    }
    return null;
}}
if (!function_exists('myaio_sac_has_noindex')) {
function myaio_sac_has_noindex($meta_robots) {
    if (!$meta_robots) { return false; }
    return (strpos($meta_robots, 'noindex') !== false);
}}
if (!function_exists('myaio_sac_effective_canonical')) {
function myaio_sac_effective_canonical($declared, &$flags, $user_agent) {
    $check = myaio_sac_fetch_head_status($declared, $user_agent);
    if ($check['is_redirect']) {
        $flags[] = 'canonical_redirects';
        return myaio_sac_normalize_url($check['final_url']);
    }
    if ($check['status'] >= 400) {
        $flags[] = 'canonical_non200';
    }
    return myaio_sac_normalize_url($declared);
}}
/** ===============================
 * Helpers: Robots
 * =============================== */
if (!function_exists('myaio_sac_robots_allowed')) {
function myaio_sac_robots_allowed($url, $user_agent) {
    $parsed = wp_parse_url($url);
    if (!$parsed || empty($parsed['host'])) { return true; }

    $scheme = isset($parsed['scheme']) ? $parsed['scheme'] : 'https';
    $robots_url = $scheme.'://'.$parsed['host'].'/robots.txt';
    $resp = wp_remote_get($robots_url, array('timeout'=>10, 'user-agent'=>$user_agent));
    if (is_wp_error($resp) || wp_remote_retrieve_response_code($resp) >= 400) {
        return true;
    }
    $rules = wp_remote_retrieve_body($resp);
    $path = isset($parsed['path']) ? $parsed['path'] : '/';
    return myaio_sac_robots_txt_allows($rules, $user_agent, $path);
}}
if (!function_exists('myaio_sac_robots_txt_allows')) {
function myaio_sac_robots_txt_allows($robots_txt, $ua, $path) {
    $ua = strtolower($ua);
    $lines = preg_split('/\R+/', $robots_txt);
    $blocks = array();
    $current = array('ua'=>array(), 'disallow'=>array(), 'allow'=>array());

    foreach ($lines as $line) {
        $line = trim(preg_replace('/#.*/', '', $line));
        if ($line === '') { continue; }
        if (stripos($line, 'user-agent:') === 0) {
            if (!empty($current['ua'])) { $blocks[] = $current; }
            $current = array('ua'=>array(), 'disallow'=>array(), 'allow'=>array());
            $current['ua'][] = trim(substr($line, 11));
        } elseif (stripos($line, 'disallow:') === 0) {
            $current['disallow'][] = trim(substr($line, 9));
        } elseif (stripos($line, 'allow:') === 0) {
            $current['allow'][] = trim(substr($line, 6));
        }
    }
    if (!empty($current['ua'])) { $blocks[] = $current; }

    $ua_l = strtolower($ua);
    $chosen = null;
    foreach ($blocks as $b) {
        foreach ($b['ua'] as $u) {
            $u = strtolower(trim($u));
            if ($u !== '*' && $u !== '' && strpos($ua_l, $u) !== false) { $chosen = $b; break 2; }
        }
    }
    if (!$chosen) {
        foreach ($blocks as $b) {
            foreach ($b['ua'] as $u) {
                if (trim($u) === '*') { $chosen = $b; break 2; }
            }
        }
    }
    if (!$chosen) { return true; }

    $p = ($path === '') ? '/' : $path;
    $best_len = -1; $decision = true;
    foreach ($chosen['disallow'] as $rule) {
        $rule = ($rule === '') ? '/' : $rule;
        if (myaio_sac_path_matches($p, $rule)) {
            $len = strlen($rule);
            if ($len > $best_len) { $best_len = $len; $decision = false; }
        }
    }
    foreach ($chosen['allow'] as $rule) {
        $rule = ($rule === '') ? '/' : $rule;
        if (myaio_sac_path_matches($p, $rule)) {
            $len = strlen($rule);
            if ($len >= $best_len) { $best_len = $len; $decision = true; }
        }
    }
    return $decision;
}}
if (!function_exists('myaio_sac_path_matches')) {
function myaio_sac_path_matches($path, $rule) {
    if ($rule === '/') { return true; }
    return strpos($path, $rule) === 0;
}}
/** ===============================
 * Helpers: URL normalization & utils
 * =============================== */
if (!function_exists('myaio_sac_normalize_url')) {
function myaio_sac_normalize_url($url) {
    if (!$url) { return $url; }
    $p = wp_parse_url($url);
    if (!$p || empty($p['host'])) { return $url; }

    $scheme = isset($p['scheme']) ? strtolower($p['scheme']) : 'https';
    $host   = strtolower($p['host']);

    if ($scheme === 'http') { $scheme = 'https'; }

    if (isset($p['port'])) {
        if (($scheme === 'http' && intval($p['port']) === 80) || ($scheme === 'https' && intval($p['port']) === 443)) {
            unset($p['port']);
        }
    }

    $path = isset($p['path']) ? myaio_sac_collapse_path($p['path']) : '/';
    if ($path !== '/' && substr($path, -1) === '/') { $path = rtrim($path, '/'); }

    $query = '';
    if (!empty($p['query'])) {
        parse_str($p['query'], $q);
        $q = myaio_sac_strip_tracking_params($q);
        ksort($q);
        $query = http_build_query($q, '', '&', PHP_QUERY_RFC3986);
    }

    $norm = $scheme . '://' . $host;
    if (!empty($p['port'])) { $norm .= ':' . $p['port']; }
    $norm .= $path;
    if ($query !== '') { $norm .= '?' . $query; }
    if (!empty($p['fragment'])) { $norm .= '#' . $p['fragment']; }
    return $norm;
}}
if (!function_exists('myaio_sac_collapse_path')) {
function myaio_sac_collapse_path($path) {
    $parts = explode('/', $path);
    $stack = array();
    foreach ($parts as $part) {
        if ($part === '' || $part === '.') { continue; }
        if ($part === '..') { array_pop($stack); continue; }
        $stack[] = $part;
    }
    return '/' . implode('/', $stack);
}}
if (!function_exists('myaio_sac_strip_tracking_params')) {
function myaio_sac_strip_tracking_params($q) {
    $out = array();
    foreach ($q as $k => $v) {
        $kl = strtolower($k);
        if (preg_match('/^(utm_|gclid|fbclid|mc_eid|mc_cid|_hsenc|_hsmi|si)$/', $kl)) {
            continue;
        }
        $out[$k] = $v;
    }
    return $out;
}}
if (!function_exists('myaio_sac_abs_url')) {
function myaio_sac_abs_url($maybe_relative, $base) {
    if (preg_match('#^[a-z][a-z0-9+.-]*://#i', $maybe_relative)) { return $maybe_relative; }
    if (strpos($maybe_relative, '//') === 0) {
        $b = wp_parse_url($base);
        $scheme = isset($b['scheme']) ? $b['scheme'] : 'https';
        return $scheme . ':' . $maybe_relative;
    }
    $b = wp_parse_url($base);
    if (!$b) { return $maybe_relative; }
    $scheme = isset($b['scheme']) ? $b['scheme'] : 'https';
    $host   = isset($b['host']) ? $b['host'] : '';
    $port   = isset($b['port']) ? ':'.$b['port'] : '';
    $base_path = isset($b['path']) ? $b['path'] : '/';
    if ($maybe_relative && substr($maybe_relative, 0, 1) === '/') {
        $path = myaio_sac_collapse_path($maybe_relative);
    } else {
        $pos = strrpos($base_path, '/');
        $dir = ($pos !== false) ? substr($base_path, 0, $pos+1) : '/';
        $path = myaio_sac_collapse_path($dir.$maybe_relative);
    }
    return $scheme . '://' . $host . $port . $path;
}}
if (!function_exists('myaio_sac_urls_equal')) {
function myaio_sac_urls_equal($a, $b) {
    return myaio_sac_normalize_url($a) === myaio_sac_normalize_url($b);
}}
if (!function_exists('myaio_sac_get_host')) {
function myaio_sac_get_host($url) {
    $p = wp_parse_url($url);
    return isset($p['host']) ? $p['host'] : '';
}}
if (!function_exists('myaio_sac_csv')) {
function myaio_sac_csv($val) {
    $val = (string)$val;
    if (strpos($val, ',') !== false || strpos($val, '"') !== false) {
        $val = '"' . str_replace('"', '""', $val) . '"';
    }
    return $val;
}}
