<?php

namespace Sibs2;

defined('ABSPATH') || exit;

/**
 * File: sibsBrowser.php
 * Author: Michel Melo (http://michelmelo.pt/).
 *
 * Copyright (C) 2023 Michel Melo  (eu@michelmelo.pt)
 *
 * Typical Usage:
 *
 *   $browser = new Sibs2\sibsBrowser();
 *   if( $browser->getBrowser() == Sibs2\sibsBrowser::BROWSER_SAFARI ) {
 *      echo 'You have Safari';
 *   }
 */
class sibsBrowser
{
    private $_agent        = '';
    private $_browser_name = '';
    private $_version      = '';
    private $_platform     = '';
    private $_os           = '';
    private $_is_mobile    = false;
    private $_is_robot     = false;

    const BROWSER_UNKNOWN = 'unknown';
    const VERSION_UNKNOWN = 'unknown';

    const BROWSER_OPERA        = 'Opera';
    const BROWSER_OPERA_MINI   = 'Opera Mini';
    const BROWSER_WEBTV        = 'WebTV';
    const BROWSER_IE           = 'Internet Explorer';
    const BROWSER_POCKET_IE    = 'Pocket Internet Explorer';
    const BROWSER_KONQUEROR    = 'Konqueror';
    const BROWSER_ICAB         = 'iCab';
    const BROWSER_OMNIWEB      = 'OmniWeb';
    const BROWSER_FIREBIRD     = 'Firebird';
    const BROWSER_FIREFOX      = 'Firefox';
    const BROWSER_ICEWEASEL    = 'Iceweasel';
    const BROWSER_SHIRETOKO    = 'Shiretoko';
    const BROWSER_MOZILLA      = 'Mozilla';
    const BROWSER_AMAYA        = 'Amaya';
    const BROWSER_LYNX         = 'Lynx';
    const BROWSER_SAFARI       = 'Safari';
    const BROWSER_IPHONE       = 'iPhone';
    const BROWSER_IPOD         = 'iPod';
    const BROWSER_IPAD         = 'iPad';
    const BROWSER_CHROME       = 'Chrome';
    const BROWSER_ANDROID      = 'Android';
    const BROWSER_GOOGLEBOT    = 'GoogleBot';
    const BROWSER_SLURP        = 'Yahoo! Slurp';
    const BROWSER_W3CVALIDATOR = 'W3C Validator';
    const BROWSER_BLACKBERRY   = 'BlackBerry';
    const BROWSER_ICECAT       = 'IceCat';
    const BROWSER_NOKIA_S60    = 'Nokia S60 OSS Browser';
    const BROWSER_NOKIA        = 'Nokia Browser';
    const BROWSER_MSN          = 'MSN Browser';
    const BROWSER_MSNBOT       = 'MSN Bot';

    const BROWSER_NETSCAPE_NAVIGATOR = 'Netscape Navigator';
    const BROWSER_GALEON             = 'Galeon';
    const BROWSER_NETPOSITIVE        = 'NetPositive';
    const BROWSER_PHOENIX            = 'Phoenix';

    const PLATFORM_UNKNOWN     = 'unknown';
    const PLATFORM_WINDOWS     = 'Windows';
    const PLATFORM_WINDOWS_CE  = 'Windows CE';
    const PLATFORM_APPLE       = 'Apple';
    const PLATFORM_LINUX       = 'Linux';
    const PLATFORM_OS2         = 'OS/2';
    const PLATFORM_BEOS        = 'BeOS';
    const PLATFORM_IPHONE      = 'iPhone';
    const PLATFORM_IPOD        = 'iPod';
    const PLATFORM_IPAD        = 'iPad';
    const PLATFORM_BLACKBERRY  = 'BlackBerry';
    const PLATFORM_NOKIA       = 'Nokia';
    const PLATFORM_FREEBSD     = 'FreeBSD';
    const PLATFORM_OPENBSD     = 'OpenBSD';
    const PLATFORM_NETBSD      = 'NetBSD';
    const PLATFORM_SUNOS       = 'SunOS';
    const PLATFORM_OPENSOLARIS = 'OpenSolaris';
    const PLATFORM_ANDROID     = 'Android';

    const OPERATING_SYSTEM_UNKNOWN = 'unknown';

    public function __construct($useragent='')
    {
        $this->reset();

        if ($useragent != '') {
            $this->setUserAgent($useragent);
        } else {
            $this->determine();
        }
    }

    /**
     * Reset all properties.
     */
    public function reset()
    {
        $this->_agent        = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
        $this->_browser_name = self::BROWSER_UNKNOWN;
        $this->_version      = self::VERSION_UNKNOWN;
        $this->_platform     = self::PLATFORM_UNKNOWN;
        $this->_os           = self::OPERATING_SYSTEM_UNKNOWN;
        $this->_is_mobile    = false;
        $this->_is_robot     = false;
    }

    /**
     * Check to see if the specific browser is valid.
     * @param string $browserName
     * @return bool
     */
    public function isBrowser($browserName)
    {
        return 0 == strcasecmp($this->_browser_name, trim($browserName));
    }

    /**
     * The name of the browser.  All return types are from the class contants.
     * @return string Name of the browser
     */
    public function getBrowser()
    {
        return $this->_browser_name;
    }

    /**
     * Set the name of the browser.
     * @param $browser The name of the Browser
     */
    public function setBrowser($browser)
    {
        return $this->_browser_name = $browser;
    }

    /**
     * The name of the platform.  All return types are from the class contants.
     * @return string Name of the browser
     */
    public function getPlatform()
    {
        return $this->_platform;
    }

    /**
     * Set the name of the platform.
     * @param $platform The name of the Platform
     */
    public function setPlatform($platform)
    {
        return $this->_platform = $platform;
    }

    /**
     * The version of the browser.
     * @return string Version of the browser (will only contain alpha-numeric characters and a period)
     */
    public function getVersion()
    {
        return $this->_version;
    }

    /**
     * Set the version of the browser.
     * @param $version The version of the Browser
     */
    public function setVersion($version)
    {
        $this->_version = preg_replace('/[^0-9,.,a-z,A-Z-]/', '', $version);
    }

    /**
     * Is the browser from a mobile device?
     * @return bool
     */
    public function isMobile()
    {
        return $this->_is_mobile;
    }

    /**
     * Is the browser from a robot (ex Slurp,GoogleBot)?
     * @return bool
     */
    public function isRobot()
    {
        return $this->_is_robot;
    }

    /**
     * Set the Browser to be mobile.
     * @param bool
     */
    protected function setMobile($value=true)
    {
        $this->_is_mobile = $value;
    }

    /**
     * Set the Browser to be a robot.
     * @param bool
     */
    protected function setRobot($value=true)
    {
        $this->_is_robot = $value;
    }

    /**
     * Get the user agent value in use to determine the browser.
     * @return string The user agent from the HTTP header
     */
    public function getUserAgent()
    {
        return $this->_agent;
    }

    /**
     * Set the user agent value (the construction will use the HTTP header value - this will overwrite it).
     * @param $agent_string The value for the User Agent
     */
    public function setUserAgent($agent_string)
    {
        $this->reset();
        $this->_agent = $agent_string;
        $this->determine();
    }

    /**
     * Used to determine if the browser is actually "chromeframe".
     * @return bool
     */
    public function isChromeFrame()
    {
        return strpos($this->_agent, 'chromeframe') !== false;
    }

    /**
     * Returns a formatted string with a summary of the details of the browser.
     * @return string formatted string with a summary of the browser
     */
    public function __toString()
    {
        return '<strong>' . __('Browser Name', sibsConstants::SIBS_TRANSLATE) . ":</strong> {$this->getBrowser()}<br/>\n" .
               '<strong>' . __('Browser Version', sibsConstants::SIBS_TRANSLATE) . ":</strong> {$this->getVersion()}<br/>\n" .
               '<strong>' . __('Browser User Agent String', sibsConstants::SIBS_TRANSLATE) . ":</strong> {$this->getUserAgent()}<br/>\n" .
               '<strong>' . __('Platform', sibsConstants::SIBS_TRANSLATE) . ":</strong> {$this->getPlatform()}<br/>";
    }

    /**
     * Protected routine to calculate and determine what the browser is in use (including platform).
     */
    protected function determine()
    {
        $this->checkPlatform();
        $this->checkBrowsers();
    }

    /**
     * Protected routine to determine the browser type.
     * @return bool
     */
    protected function checkBrowsers()
    {
        return
            $this->checkBrowserFirefox() ||
            $this->checkBrowserChrome() ||

            // common mobile
            $this->checkBrowserAndroid() ||
            $this->checkBrowseriPad() ||
            $this->checkBrowseriPod() ||
            $this->checkBrowseriPhone() ||

            // common bots
            $this->checkBrowserGoogleBot() ||
            $this->checkBrowserMSNBot() ||

            // WebKit base check (post mobile and others)
            $this->checkBrowserSafari() ||

            // everyone else
            $this->checkBrowserW3CValidator() ||
            $this->checkBrowserMozilla(); /* Mozilla is such an open standard that you must check it last */
    }

    protected function checkBrowserGoogleBot()
    {
        if (stripos($this->_agent, 'googlebot') !== false) {
            $aresult  = explode('/', stristr($this->_agent, 'googlebot'));
            $aversion = explode(' ', $aresult[1]);
            $this->setVersion(str_replace(';', '', $aversion[0]));
            $this->_browser_name = self::BROWSER_GOOGLEBOT;
            $this->setRobot(true);

            return true;
        }

        return false;
    }

    protected function checkBrowserMSNBot()
    {
        if (stripos($this->_agent, 'msnbot') !== false) {
            $aresult  = explode('/', stristr($this->_agent, 'msnbot'));
            $aversion = explode(' ', $aresult[1]);
            $this->setVersion(str_replace(';', '', $aversion[0]));
            $this->_browser_name = self::BROWSER_MSNBOT;
            $this->setRobot(true);

            return true;
        }

        return false;
    }

    protected function checkBrowserW3CValidator()
    {
        if (stripos($this->_agent, 'W3C-checklink') !== false) {
            $aresult  = explode('/', stristr($this->_agent, 'W3C-checklink'));
            $aversion = explode(' ', $aresult[1]);
            $this->setVersion($aversion[0]);
            $this->_browser_name = self::BROWSER_W3CVALIDATOR;

            return true;
        } elseif (stripos($this->_agent, 'W3C_Validator') !== false) {
            // Some of the Validator versions do not delineate w/ a slash - add it back in
            $ua       = str_replace('W3C_Validator ', 'W3C_Validator/', $this->_agent);
            $aresult  = explode('/', stristr($ua, 'W3C_Validator'));
            $aversion = explode(' ', $aresult[1]);
            $this->setVersion($aversion[0]);
            $this->_browser_name = self::BROWSER_W3CVALIDATOR;

            return true;
        }

        return false;
    }

    protected function checkBrowserChrome()
    {
        if (stripos($this->_agent, 'Chrome') !== false) {
            $aresult  = explode('/', stristr($this->_agent, 'Chrome'));
            $aversion = explode(' ', $aresult[1]);
            $this->setVersion($aversion[0]);
            $this->setBrowser(self::BROWSER_CHROME);

            return true;
        }

        return false;
    }

    protected function checkBrowserFirefox()
    {
        if (stripos($this->_agent, 'safari') === false) {
            if (preg_match("/Firefox[\/ \(]([^ ;\)]+)/i", $this->_agent, $matches)) {
                $this->setVersion($matches[1]);
                $this->setBrowser(self::BROWSER_FIREFOX);

                return true;
            } elseif (preg_match('/Firefox$/i', $this->_agent, $matches)) {
                $this->setVersion('');
                $this->setBrowser(self::BROWSER_FIREFOX);

                return true;
            }
        }

        return false;
    }

    protected function checkBrowserMozilla()
    {
        if (stripos($this->_agent, 'mozilla') !== false && preg_match('/rv:[0-9].[0-9][a-b]?/i', $this->_agent) && stripos($this->_agent, 'netscape') === false) {
            $aversion = explode(' ', stristr($this->_agent, 'rv:'));
            preg_match('/rv:[0-9].[0-9][a-b]?/i', $this->_agent, $aversion);
            $this->setVersion(str_replace('rv:', '', $aversion[0]));
            $this->setBrowser(self::BROWSER_MOZILLA);

            return true;
        } elseif (stripos($this->_agent, 'mozilla') !== false && preg_match('/rv:[0-9]\.[0-9]/i', $this->_agent) && stripos($this->_agent, 'netscape') === false) {
            $aversion = explode('', stristr($this->_agent, 'rv:'));
            $this->setVersion(str_replace('rv:', '', $aversion[0]));
            $this->setBrowser(self::BROWSER_MOZILLA);

            return true;
        } elseif (stripos($this->_agent, 'mozilla') !== false && preg_match('/mozilla\/([^ ]*)/i', $this->_agent, $matches) && stripos($this->_agent, 'netscape') === false) {
            $this->setVersion($matches[1]);
            $this->setBrowser(self::BROWSER_MOZILLA);

            return true;
        }

        return false;
    }

    protected function checkBrowserSafari()
    {
        if (stripos($this->_agent, 'Safari') !== false && stripos($this->_agent, 'iPhone') === false && stripos($this->_agent, 'iPod') === false) {
            $aresult = explode('/', stristr($this->_agent, 'Version'));

            if (isset($aresult[1])) {
                $aversion = explode(' ', $aresult[1]);
                $this->setVersion($aversion[0]);
            } else {
                $this->setVersion(self::VERSION_UNKNOWN);
            }
            $this->setBrowser(self::BROWSER_SAFARI);

            return true;
        }

        return false;
    }

    protected function checkBrowseriPhone()
    {
        if (stripos($this->_agent, 'iPhone') !== false) {
            $aresult = explode('/', stristr($this->_agent, 'Version'));

            if (isset($aresult[1])) {
                $aversion = explode(' ', $aresult[1]);
                $this->setVersion($aversion[0]);
            } else {
                $this->setVersion(self::VERSION_UNKNOWN);
            }
            $this->setMobile(true);
            $this->setBrowser(self::BROWSER_IPHONE);

            return true;
        }

        return false;
    }

    protected function checkBrowseriPad()
    {
        if (stripos($this->_agent, 'iPad') !== false) {
            $aresult = explode('/', stristr($this->_agent, 'Version'));

            if (isset($aresult[1])) {
                $aversion = explode(' ', $aresult[1]);
                $this->setVersion($aversion[0]);
            } else {
                $this->setVersion(self::VERSION_UNKNOWN);
            }
            $this->setMobile(true);
            $this->setBrowser(self::BROWSER_IPAD);

            return true;
        }

        return false;
    }

    protected function checkBrowseriPod()
    {
        if (stripos($this->_agent, 'iPod') !== false) {
            $aresult = explode('/', stristr($this->_agent, 'Version'));

            if (isset($aresult[1])) {
                $aversion = explode(' ', $aresult[1]);
                $this->setVersion($aversion[0]);
            } else {
                $this->setVersion(self::VERSION_UNKNOWN);
            }
            $this->setMobile(true);
            $this->setBrowser(self::BROWSER_IPOD);

            return true;
        }

        return false;
    }

    protected function checkBrowserAndroid()
    {
        if (stripos($this->_agent, 'Android') !== false) {
            $aresult = explode(' ', stristr($this->_agent, 'Android'));

            if (isset($aresult[1])) {
                $aversion = explode(' ', $aresult[1]);
                $this->setVersion($aversion[0]);
            } else {
                $this->setVersion(self::VERSION_UNKNOWN);
            }
            $this->setMobile(true);
            $this->setBrowser(self::BROWSER_ANDROID);

            return true;
        }

        return false;
    }

    /**
     * Determine the user's platform.
     */
    protected function checkPlatform()
    {
        if (stripos($this->_agent, 'windows') !== false) {
            $this->_platform = self::PLATFORM_WINDOWS;
        } elseif (stripos($this->_agent, 'iPad') !== false) {
            $this->_platform = self::PLATFORM_IPAD;
        } elseif (stripos($this->_agent, 'iPod') !== false) {
            $this->_platform = self::PLATFORM_IPOD;
        } elseif (stripos($this->_agent, 'iPhone') !== false) {
            $this->_platform = self::PLATFORM_IPHONE;
        } elseif (stripos($this->_agent, 'mac') !== false) {
            $this->_platform = self::PLATFORM_APPLE;
        } elseif (stripos($this->_agent, 'android') !== false) {
            $this->_platform = self::PLATFORM_ANDROID;
        } elseif (stripos($this->_agent, 'linux') !== false) {
            $this->_platform = self::PLATFORM_LINUX;
        } elseif (stripos($this->_agent, 'Nokia') !== false) {
            $this->_platform = self::PLATFORM_NOKIA;
        } elseif (stripos($this->_agent, 'BlackBerry') !== false) {
            $this->_platform = self::PLATFORM_BLACKBERRY;
        } elseif (stripos($this->_agent, 'FreeBSD') !== false) {
            $this->_platform = self::PLATFORM_FREEBSD;
        } elseif (stripos($this->_agent, 'OpenBSD') !== false) {
            $this->_platform = self::PLATFORM_OPENBSD;
        } elseif (stripos($this->_agent, 'NetBSD') !== false) {
            $this->_platform = self::PLATFORM_NETBSD;
        } elseif (stripos($this->_agent, 'OpenSolaris') !== false) {
            $this->_platform = self::PLATFORM_OPENSOLARIS;
        } elseif (stripos($this->_agent, 'SunOS') !== false) {
            $this->_platform = self::PLATFORM_SUNOS;
        } elseif (stripos($this->_agent, 'OS\/2') !== false) {
            $this->_platform = self::PLATFORM_OS2;
        } elseif (stripos($this->_agent, 'BeOS') !== false) {
            $this->_platform = self::PLATFORM_BEOS;
        } elseif (stripos($this->_agent, 'win') !== false) {
            $this->_platform = self::PLATFORM_WINDOWS;
        }
    }
}
