<?php

/**
 * @package         Convert Forms
 * @version         5.2.4 Pro
 * 
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
*/

defined('_JEXEC') or die('Restricted access');

use Joomla\CMS\Table\Table;
use Joomla\CMS\Factory;
use Joomla\Utilities\IpHelper;
use Tassos\Framework\Helpers\Geo;
use Tassos\Framework\WebClient;

class ConvertFormsTableConversion extends Table
{
    private $app;

    /**
     *  Constructor
     *
     *  @param object Database connector object
     */
    function __construct(&$db) 
    {
    	$this->setColumnAlias('published', 'state');

        $this->app = Factory::getApplication();

        parent::__construct('#__convertforms_conversions', 'id', $db);
    }

    /**
     *  Method to perform sanity checks on the Table instance properties to ensure
     *  they are safe to store in the database.  Child classes should override this
     *  method to make sure the data they are storing in the database is safe and
     *  as expected before storage.
     * 
     *  @return  boolean  True if the instance is sane and able to be stored in the database.
     */
    public function check()
    {
        $date = Factory::getDate();
        $user = Factory::getUser();

        if ($this->id)
        {
            if (is_array($this->params))
            {
                $this->params = json_encode($this->params);
            }

            $this->modified = $date->toSql();
        }
        else
        {
            $this->created = $date->toSql();
            $this->user_id = $user->id;
            $this->visitor_id = ConvertForms\Helper::getVisitorID();

            
            // Collect environment data on frontend submissions only. Visitor tracking
            // (IP, geolocation, device, source/campaign) is a Pro-only feature, so this
            // whole block is stripped from the Free build — those columns are never stored
            // and surface as locked upgrade prompts in the admin.
            if ($this->app->isClient('site'))
            {
                $input = $this->app->input->server;

                // IP address — store nothing when the admin has opted out.
                if (ConvertForms\Helper::getComponentParams()->get('store_ip', 1))
                {
                    $this->ip = IpHelper::getIp();
                }

                // Country code via TGeoIP
                $this->country_code = Geo::getVisitorCountryCode();

                // User Agent (truncated to 512 chars).
                $this->user_agent = substr($input->getString('HTTP_USER_AGENT', ''), 0, 512);

                $this->device  = WebClient::getDeviceType();
                $this->os      = WebClient::getOS();
                $this->browser = WebClient::getBrowser()['name'];
                $this->page_title   = $this->getPageTitle();
                $this->source_url   = $this->getSourceURL();
                $this->referrer_url = $this->getReferrerURL();
            }
            
        }

        return true;
    }

    
    // The helpers below only feed the Pro-only visitor tracking captured in check().
    // Since that whole block is stripped from the Free build, these methods would be
    // dead code there, so they are stripped too.
    private function getForwardedContext()
    {
        $forward_context = $this->app->input->get('forward_context', '', 'raw');

        return is_array($forward_context) ? $forward_context : json_decode($forward_context, true);
    }

    /**
     * Returns the title of the page where the form was submitted,
     * captured in JS via document.title and forwarded in the request context.
     *
     * @return string|null
     */
    private function getPageTitle(): ?string
    {
        $forwarded = $this->getForwardedContext();

        if (empty($forwarded['page_title']))
        {
            return null;
        }

        return substr(trim($forwarded['page_title']), 0, 512) ?: null;
    }
   
    /**
     * Returns the referrer URL — the page the visitor navigated from before reaching the form page.
     * HTTP_REFERER cannot be used here because form submissions are XHR requests, which means
     * HTTP_REFERER always points to the form page itself. Instead, the value is captured in JS
     * via document.referrer and forwarded in the request context.
     *
     * @return string|null
     */
    private function getReferrerURL(): ?string
    {
        $forwarded = $this->getForwardedContext();

        if (empty($forwarded['referrer_url']))
        {
            return null;
        }

        return $this->sanitizeUrl($forwarded['referrer_url']);
    }

    /**
     * Returns the source URL — the full URL of the page where the form was submitted.
     * The value is captured in JS via window.location.href and forwarded in the request context,
     * which is more reliable than reading HTTP_REFERER from the XHR request.
     *
     * @return string|null
     */
    private function getSourceURL(): ?string
    {
        $forwarded = $this->getForwardedContext();

        if (empty($forwarded['source_url']))
        {
            return null;
        }

        return $this->sanitizeUrl($forwarded['source_url']);
    }

    /**
     * Validate and sanitize a URL. Returns null for empty or invalid URLs.
     *
     * These URLs are visitor-supplied (captured in JS and forwarded in the request),
     * so they are fully attacker-controlled. FILTER_VALIDATE_URL is permissive and
     * happily accepts HTML metacharacters (e.g. https://evil.com/"><script>...), which
     * would then sit in the database as a live XSS payload, defused only by output
     * escaping. We reject the raw < > " ' characters here so the value is safe at rest
     * too — a legitimate URL carries those percent-encoded, never literal.
     */
    private function sanitizeUrl(string $url): ?string
    {
        $url = substr(trim($url), 0, 2048);

        if ($url === '' || filter_var($url, FILTER_VALIDATE_URL) === false)
        {
            return null;
        }

        // Reject HTML/attribute-breaking characters that have no business in a literal URL.
        if (preg_match('/[<>"\']/', $url))
        {
            return null;
        }

        $scheme = strtolower(parse_url($url, PHP_URL_SCHEME));

        if (!in_array($scheme, ['http', 'https'], true))
        {
            return null;
        }

        return $url;
    }
    
}