<?php

namespace Sibs\Sibs\Utils;

/**
 * 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 $_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';

    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->_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()
    {
        $string = '<strong>' . __('Browser Name') . ':</strong> <span>' . $this->getBrowser() . '</span><br/>';
        $string .= '<strong>' . __('Browser Version') . ':</strong> <span>' . $this->getVersion() . '</span><br/>';
        $string .= '<strong>' . __('Browser User Agent String') . ':</strong> <span>' . $this->getUserAgent() . '</span><br/>';
        $string .= '<strong>' . __('Platform') . ':</strong> <span>' . $this->getPlatform() . '</span><br/>';

        return $string;
    }

    /**
     * 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() ||
             $this->checkBrowserSlurp() ||

             // 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 checkBrowserSlurp()
    {
        if (stripos($this->_agent, 'slurp') !== false) {
            $aresult = explode('/', stristr($this->_agent, 'Slurp'));
            $aversion = explode(' ', $aresult[1]);
            $this->setVersion($aversion[0]);
            $this->_browser_name = self::BROWSER_SLURP;
            $this->setRobot(true);
            $this->setMobile(false);
            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;
        }
    }
}
