HEX
Server: nginx/1.27.0
System: Linux 20d173156d2c 6.8.0-88-generic #89-Ubuntu SMP PREEMPT_DYNAMIC Sat Oct 11 01:02:46 UTC 2025 x86_64
User: www-data (33)
PHP: 8.1.29
Disabled: NONE
Upload Files
File: /var/www/html/wp-content/plugins/content-egg/application/components/LinkHandler.php
<?php

namespace ContentEgg\application\components;

defined('\ABSPATH') || exit;

use ContentEgg\application\helpers\TextHelper;



/**
 * LinkHandler class file
 *
 * @author keywordrush.com <support@keywordrush.com>
 * @link https://www.keywordrush.com
 * @copyright Copyright &copy; 2025 keywordrush.com
 */
class LinkHandler
{

    private static $instance = null;

    public static function getInstance()
    {
        if (self::$instance == null)
        {
            self::$instance = new self;
        }

        return self::$instance;
    }

    /**
     * Deeplink & more...
     */
    public static function createAffUrl($url, $deeplink, $item = array(), $subid = '')
    {
        // custom filter
        $filtered = \apply_filters('cegg_create_affiliate_link', $url, $deeplink);
        if ($filtered != $url)
        {
            return $url;
        }

        // profitshare fix. return if url already created
        if (!empty($item['url']) && strstr($item['url'], '/l.profitshare.ro/'))
        {
            return $item['url'];
        }
        // lomadee fix. return if url already created
        if (!empty($item['url']) && strstr($item['url'], '/redir.lomadee.com/') && !strstr($item['url'], 'https://redir.lomadee.com/v2/deeplink?url='))
        {
            return $item['url'];
        }
        // coupang fix. return if url already created
        if (!empty($item['url']) && (strstr($item['url'], 'https://coupa.ng/') || strstr($item['url'], 'https://link.coupang.com')))
        {
            return $item['url'];
        }

        $deeplink = self::getMultiDeeplink($deeplink, $url);

        if (!$deeplink)
        {
            $result = $url;
        }
        elseif (substr(trim($deeplink), 0, 7) == '[regex]')
        {
            // regex preg_replace
            $result = self::getRegexReplace($url, $deeplink);
        }
        elseif (substr(trim($deeplink), 0, 13) == '[profitshare]')
        {
            // ProfitShare link creator
            $result = self::getProfitshareLink($url, $deeplink, $item);
        }
        elseif (substr(trim($deeplink), 0, 9) == '[lomadee]')
        {
            // Lomadee link creator
            $result = self::getLomadeeLink($url, $deeplink, $item);
        }
        elseif (substr(trim($deeplink), 0, 13) == '[trovaprezzi]')
        {
            // Trovaprezzi link creator
            $result = self::getTrovaprezziLink($url, $deeplink, $item);
        }
        elseif (substr(trim($deeplink), 0, 9) == '[coupang]')
        {
            // Coupang link creator
            $result = self::getCoupangLink($url, $deeplink, $item);
        }
        elseif (strstr($deeplink, '{{') && strstr($deeplink, '}}'))
        {
            // template deeplink
            $result = self::getUrlTemplate($url, $deeplink, $item);
        }
        elseif (!preg_match('/^https?:\/\//i', $deeplink))
        {
            // url with tail
            $result = self::getUrlWithTail($url, $deeplink);
        }
        elseif (!strstr($deeplink, '%PRODUCT.'))
        {
            $result = $deeplink . urlencode($url);
        }
        else
        {
            $result = $deeplink;
        }

        if ($subid)
        {
            $result = self::getUrlWithTail($result, $subid);
        }

        $result = self::replaceProductTags($result, $item);

        return $result;
    }

    public static function getUrlWithTail($url, $tail)
    {
        // replace params in URL
        parse_str($tail, $vars);
        if (count($vars) == 1 && strstr($tail, '='))
        {
            return \add_query_arg($vars, $url);
        }

        $tail = preg_replace('/^[?&]/', '', $tail);

        $query = parse_url($url, PHP_URL_QUERY);
        if ($query)
        {
            $url .= '&';
        }
        else
        {
            $url .= '?';
        }

        parse_str($tail, $tail_array);
        $url .= http_build_query($tail_array);

        return $url;
    }

    /**
     * Build URL by replacing template placeholders.
     *
     * @param string $url
     * @param string $template
     * @param array  $item
     * @return string
     */
    public static function getUrlTemplate($url, $template, $item = array())
    {
        // --- Base (existing) placeholders - keep exact behavior for BC ---
        $template = str_replace('{{url}}', $url, $template);
        $template = str_replace('{{url_encoded}}', urlencode($url), $template);
        $template = str_replace('{{url_base64}}', base64_encode($url), $template);

        global $post;

        // Resolve post_id & item id from $item or current post
        $post_id = 0;
        if (!empty($item) && isset($item['post_id']))
        {
            $post_id = (int) $item['post_id'];
        }
        elseif (!empty($post) && isset($post->ID))
        {
            $post_id = (int) $post->ID;
        }

        if ($post_id)
        {
            $template = str_replace('{{post_id}}', urlencode((string) $post_id), $template);
        }

        if (!empty($item) && !empty($item['unique_id']))
        {
            $template = str_replace('{{item_unique_id}}', urlencode((string) $item['unique_id']), $template);
        }

        if (!empty($post))
        {
            $author_id    = (int) $post->post_author;
            $user         = \get_user_by('ID', $author_id);
            $author_login = $user ? (string) $user->data->user_login : '';
            // Keep legacy placeholders URL-encoded (BC)
            $template = str_replace('{{author_id}}', urlencode((string) $author_id), $template);
            $template = str_replace('{{author_login}}', urlencode($author_login), $template);
        }

        // ==========================================================
        // New placeholders (+ automatic _encoded, _base64, _slug, _subid)
        // ==========================================================
        $addVariants = static function (array &$map, string $key, $value): void
        {
            $val = (string) ($value ?? '');
            $map['{{' . $key . '}}']          = $val;
            $map['{{' . $key . '_encoded}}']  = $val === '' ? '' : urlencode($val);
            $map['{{' . $key . '_base64}}']   = $val === '' ? '' : base64_encode($val);

            // Slug only for non-URLs (BC with prior behavior)
            if ($val !== '' && strpos($val, '://') === false)
            {
                $map['{{' . $key . '_slug}}'] = sanitize_title($val);
            }

            // NEW: SubID-safe variants (common-denominator length & encoding)
            $defaultMax = (int) apply_filters('cegg_subid_default_max_len', 100);
            $map['{{' . $key . '_subid}}']    = self::makeSubIdSafe($val, $defaultMax);
            $map['{{' . $key . '_subid64}}']  = self::makeSubIdSafe($val, 64);
            $map['{{' . $key . '_subid32}}']  = self::makeSubIdSafe($val, 32);
        };

        $repl = [];

        // --- Item / Product fields (from $item example) ---
        if (is_array($item))
        {
            if (isset($item['title']))         $addVariants($repl, 'item_title', (string) $item['title']);
            if (!empty($item['manufacturer'])) $addVariants($repl, 'item_brand', (string) $item['manufacturer']);
            if (!empty($item['sku']))          $addVariants($repl, 'item_sku',  (string) $item['sku']);
            if (!empty($item['ean']))          $addVariants($repl, 'item_ean',  (string) $item['ean']);
            if (!empty($item['upc']))          $addVariants($repl, 'item_upc',  (string) $item['upc']);
            if (!empty($item['isbn']))         $addVariants($repl, 'item_isbn', (string) $item['isbn']);

            if (isset($item['price']))         $repl['{{item_price}}']    = (string) $item['price'];
            if (isset($item['currencyCode']))  $repl['{{item_currency}}'] = (string) $item['currencyCode'];

            if (!empty($item['domain']))       $addVariants($repl, 'item_domain', (string) $item['domain']);
            if (!empty($item['module_id']))    $addVariants($repl, 'item_module_id', (string) $item['module_id']);
            if (!empty($item['group']))        $addVariants($repl, 'item_group', (string) $item['group']);
        }

        // --- Page / Post context ---
        $post_obj   = $post_id ? get_post($post_id) : (is_object($post) ? $post : null);
        $permalink  = ($post_obj && !is_wp_error($post_obj)) ? get_permalink($post_obj) : '';
        $post_title = ($post_obj && !is_wp_error($post_obj)) ? (string) $post_obj->post_title : '';
        $post_slug  = ($post_obj && !is_wp_error($post_obj)) ? (string) $post_obj->post_name  : '';
        $post_type  = ($post_obj && !is_wp_error($post_obj)) ? (string) $post_obj->post_type  : '';

        if ($post_title !== '') $addVariants($repl, 'post_title', $post_title);
        if ($post_slug  !== '') $addVariants($repl, 'post_slug',  $post_slug);
        if ($post_type  !== '') $addVariants($repl, 'post_type',  $post_type);
        if ($permalink  !== '')
        {
            $addVariants($repl, 'post_url',          $permalink);
            $addVariants($repl, 'post_url_relative', wp_make_link_relative($permalink));
        }

        if ($post_obj && isset($post_obj->post_author))
        {
            $display = get_the_author_meta('display_name', (int) $post_obj->post_author);
            if (!empty($display)) $addVariants($repl, 'post_author', (string) $display);
        }

        if ($post_obj)
        {
            $post_date = get_the_date('Y-m-d', $post_obj);
            if ($post_date) $repl['{{post_date}}'] = $post_date;
        }

        // --- Site / global ---
        $repl['{{site_name}}']   = (string) get_bloginfo('name');
        $home_host               = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
        $repl['{{site_domain}}'] = $home_host ?: '';
        $repl['{{site_locale}}'] = (string) get_locale();

        // --- Time / utility ---
        $now_ts                 = (int) current_time('timestamp'); // local
        $repl['{{date}}']       = gmdate('Y-m-d', $now_ts + (int) get_option('gmt_offset') * HOUR_IN_SECONDS);
        $repl['{{timestamp}}']  = (string) $now_ts;
        if (function_exists('wp_generate_uuid4'))
        {
            $repl['{{uuid}}'] = wp_generate_uuid4();
        }

        $uid_for_hash          = is_array($item) && !empty($item['unique_id']) ? (string) $item['unique_id'] : '';
        $hash_source           = $post_id . ':' . $uid_for_hash;
        $repl['{{post_hash}}'] = substr(sha1($hash_source), 0, 8);

        // Auto-variants for common text placeholders (already added via $addVariants)
        $autoVariantKeys = [
            'site_name',
            'site_domain',
            'post_title',
            'post_slug',
            'post_type',
            'post_author',
            'item_title',
            'item_brand',
            'item_sku',
            'item_ean',
            'item_upc',
            'item_isbn',
            'item_domain',
            'item_module_id',
            'item_group',
            'post_url',
            'post_url_relative',
        ];
        foreach ($autoVariantKeys as $k)
        {
            if (isset($repl['{{' . $k . '}}']))
            {
                $addVariants($repl, $k, $repl['{{' . $k . '}}']);
            }
        }

        if (!empty($repl))
        {
            $template = strtr($template, $repl);
        }

        return $template;
    }

    /**
     * Produce a SubID-safe ASCII token (alnum + _ . -), collapse separators,
     * trim, and cap to $maxLen. If truncated, append a short hash for stability.
     * Defaults: 100 chars (common denominator across networks).
     *
     * @param string $value
     * @param int    $maxLen
     * @return string
     */
    private static function makeSubIdSafe(string $value, int $maxLen = 100): string
    {
        if ($maxLen <= 0) return '';

        // Strip tags/entities; attempt transliteration to ASCII
        $s = wp_strip_all_tags($value);
        $s = html_entity_decode($s, ENT_QUOTES, 'UTF-8');
        if (function_exists('iconv'))
        {
            $t = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
            if ($t !== false) $s = $t;
        }

        // Keep [A-Za-z0-9_.-], collapse others to '-'
        $s = preg_replace('/[^A-Za-z0-9_.-]+/', '-', (string) $s);
        $s = preg_replace('/-+/', '-', $s);
        $s = trim($s, '-._');

        if ($s === '') $s = 'na';

        // Cap length, append short hash if truncated
        if (strlen($s) > $maxLen)
        {
            $hash = substr(md5($value), 0, 6);
            $keep = max(0, $maxLen - (strlen($hash) + 1));
            $s = ($keep > 0 ? substr($s, 0, $keep) . '-' : '') . $hash;
        }

        return $s;
    }

    public static function getRegexReplace($url, $regex)
    {
        $regex = trim($regex);

        $parts = explode('][', $regex);
        if (count($parts) != 3)
        {
            return $url;
        }

        $pattern = $parts[1];
        $replacement = substr($parts[2], 0, -1);

        // null character allows a premature regex end and "/../e" injection
        if (strpos($pattern, chr(0)) !== false || !trim($pattern))
        {
            return $url;
        }

        if ($result = @preg_replace($pattern, $replacement, $url))
        {
            return $result;
        }
        else
        {
            return $url;
        }
    }

    public static function getProfitshareLink($url, $regex, $item = array())
    {
        $regex = trim($regex);
        $parts = explode('][', $regex);
        if (count($parts) != 3)
        {
            return $url;
        }

        $api_user = $parts[1];
        $api_key = rtrim($parts[2], ']');

        $api_url = 'http://api.profitshare.ro/affiliate-links/?';
        $query_string = '';

        $spider = curl_init();
        curl_setopt($spider, CURLOPT_HEADER, false);
        curl_setopt($spider, CURLOPT_URL, $api_url . $query_string);
        curl_setopt($spider, CURLOPT_CONNECTTIMEOUT, 60);
        curl_setopt($spider, CURLOPT_TIMEOUT, 30);
        curl_setopt($spider, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($spider, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
        curl_setopt($spider, CURLOPT_USERAGENT, 'Content Egg WP Plugin (https://www.keywordrush.com/contentegg)');

        $data = array();
        $name = 'CE:' . TextHelper::getHostName($url);
        if (!empty($item['title']))
        {
            $name .= ' ' . $item['title'];
        }
        $data[] = array(
            'name' => $name,
            'url' => $url
        );

        curl_setopt($spider, CURLOPT_POST, true);
        curl_setopt($spider, CURLOPT_POSTFIELDS, http_build_query($data));

        $profitshare_login = array('api_user' => $api_user, 'api_key' => $api_key,);
        $date = gmdate('D, d M Y H:i:s T', time());
        $signature_string = 'POSTaffiliate-links/?' . $query_string . '/' . $profitshare_login['api_user'] . $date;
        $auth = hash_hmac('sha1', $signature_string, $profitshare_login['api_key']);

        $extra_headers = array(
            "Date: {$date}",
            "X-PS-Client: {$profitshare_login['api_user']}",
            "X-PS-Accept: json",
            "X-PS-Auth: {$auth}"
        );

        curl_setopt($spider, CURLOPT_HTTPHEADER, $extra_headers);

        $output = curl_exec($spider);
        if (!$output)
        {
            return $url;
        }

        $result = json_decode($output, true);

        if (!$result)
        {
            return $url;
        }
        if (isset($result['result'][0]['ps_url']))
        {
            return $result['result'][0]['ps_url'];
        }
        else
        {
            return $url;
        }
    }

    public static function getLomadeeLink($url, $regex, $item = array())
    {
        $regex = trim($regex);
        $parts = explode('][', $regex);
        if (count($parts) != 2)
        {
            return $url;
        }

        $sourceId = rtrim($parts[1], ']');
        $api_url = 'https://api.lomadee.com/v2/15071999399311f734bd1/deeplink/_create?sourceId=' . urlencode($sourceId) . '&url=' . urlencode($url);

        $response = \wp_remote_get($api_url);
        if (\is_wp_error($response))
        {
            return $url;
        }
        $response_code = (int) \wp_remote_retrieve_response_code($response);
        if ($response_code != 200)
        {
            return $url;
        }
        $output = \wp_remote_retrieve_body($response);
        $result = json_decode($output, true);
        if (!$result)
        {
            return $url;
        }
        if (isset($result['deeplinks'][0]['deeplink']))
        {
            return $result['deeplinks'][0]['deeplink'];
        }
        else
        {
            return $url;
        }
    }

    public static function getTrovaprezziLink($url, $regex, $item = array())
    {
        /**
         * Note: tracking links include a token in order to ensure that offers are updated as much as possible.
         * This token expires in 12 hours!  Therefore   you need to set your script to update your feed at least
         * once each 11 hours  , in order to guarantee the correct click tracking!
         */
        if (strstr($item['url'], 'splash?impression') && time() - $item['last_update'] < 111 * 3600)
        {
            return $item['url'];
        }

        $regex = trim($regex);
        $parts = explode('][', $regex);
        if (count($parts) != 2)
        {
            return $url;
        }

        /*
          $path = parse_url($url, PHP_URL_PATH);
          $path = trim($path, "/");
          $path = preg_replace('/\.aspx$/', '', $path);
          $path = explode('/', $path);
          $path = end($path);
          $path = explode('-', $path);
          $path = end($path);
          $keyword = $path;
         *
         */

        $keyword = $item['title'];
        $keyword = strtolower($keyword);
        $keyword = str_replace(' ', '_', $keyword);

        $partnerId = rtrim($parts[1], ']');
        $api_url = 'https://quickshop.shoppydoo.it/' . urlencode($partnerId) . '/' . urlencode($keyword) . '.aspx?format=json&sort=price';

        $response = \wp_remote_get($api_url);
        if (\is_wp_error($response))
        {
            return $url;
        }
        $response_code = (int) \wp_remote_retrieve_response_code($response);
        if ($response_code != 200)
        {
            return $url;
        }
        $output = \wp_remote_retrieve_body($response);
        $result = json_decode($output, true);
        if (!$result)
        {
            return $url;
        }
        if (isset($result['offers'][0]['url']))
        {
            return $result['offers'][0]['url'];
        }
        else
        {
            return $url;
        }
    }

    public static function getCoupangLink($url, $regex, $item = array())
    {
        $regex = trim($regex);
        $parts = explode('][', $regex);
        if (count($parts) != 3)
        {
            return $url;
        }

        $ACCESS_KEY = $parts[1];
        $SECRET_KEY = rtrim($parts[2], ']');

        //date_default_timezone_set("GMT+0");

        $datetime = date("ymd") . 'T' . date("His") . 'Z';
        $method = "POST";
        $path = "/v2/providers/affiliate_open_api/apis/openapi/v1/deeplink";
        $message = $datetime . $method . str_replace("?", "", $path);
        $algorithm = "HmacSHA256";

        $signature = hash_hmac('sha256', $message, $SECRET_KEY);

        $authorization = "CEA algorithm=HmacSHA256, access-key=" . $ACCESS_KEY . ", signed-date=" . $datetime . ", signature=" . $signature;

        $rurl = 'https://api-gateway.coupang.com' . $path;

        $strjson = '{"coupangUrls": ["' . $url . '"]}';

        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $rurl);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($curl, CURLOPT_HTTPHEADER, array(
            "Content-Type:  application/json;charset=UTF-8",
            "Authorization:" . $authorization
        ));
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $strjson);
        $output = curl_exec($curl);

        if (!$output)
        {
            return $url;
        }

        $result = json_decode($output, true);
        if (!$result)
        {
            return $url;
        }
        if (isset($result['data'][0]['shortenUrl']))
        {
            return $result['data'][0]['shortenUrl'];
        }
        else
        {
            return $url;
        }
    }

    public static function getMultiDeeplink($deeplink, $url)
    {
        if (!strstr($deeplink, ';') || strstr($deeplink, 'ad.doubleclick'))
        {
            return $deeplink;
        }

        $url_host = TextHelper::urlHost($url);
        $deeplink_array = str_getcsv($deeplink, ';');
        $default = '';
        foreach ($deeplink_array as $da)
        {
            $parts = explode(':', $da, 2);

            // default deeplink
            if (count($parts) == 1)
            {
                $default = trim($da);
            }
            elseif (count($parts) == 2)
            {
                if (!$default)
                {
                    $default = trim($parts[1]);
                }

                $host = $parts[0];
                $host = preg_replace('/^https?:\/\//', '', $host);
                $host = preg_replace('/^www\./', '', $host);

                if ($host == $url_host)
                {
                    return trim($parts[1]);
                }
            }
        }

        return $default;
    }

    static public function replaceProductTags($template, array $item)
    {
        if (!$item)
            return $template;

        if (!stristr($template, '%PRODUCT.'))
            return $template;

        if (!preg_match_all('/(%PRODUCT\.[a-zA-Z0-9_\.\,\(\)]+%)/', $template, $matches))
            return $template;

        $replace = array();
        foreach ($matches[1] as $pattern)
        {
            $replace[$pattern] = '';
            $pattern_parts = explode('.', $pattern);
            $var_name = $pattern_parts[1];
            $var_name = rtrim($var_name, '%');
            $var_name = \sanitize_text_field($var_name);

            if (strtoupper($var_name) == 'EXTRA' && isset($pattern_parts[2]) && is_scalar($item[$var_name]))
            {
                $extra_var = rtrim($pattern_parts[2], '%');
                $extra_var = \sanitize_text_field($extra_var);

                if (isset($item['extra'][$extra_var]))
                    $replace[$pattern] = urlencode($item['extra'][$extra_var]);
            }
            elseif (isset($item[$var_name]) && is_scalar($item[$var_name]))
                $replace[$pattern] = urlencode($item[$var_name]);
        }

        return str_ireplace(array_keys($replace), array_values($replace), $template);
    }
}