<?php

/**
 * Name          : aeSecure QuickScan - Free scanner
 * Description   : Scan your website for possible hacks, viruses, malwares, SEO black hat and exploits
 * Version       : 2.1.4
 * Date          : November 2018
 * Last update   : March 2025
 * Author        : AVONTURE Christophe (christophe@avonture.be)
 * Author website: https://www.avonture.be.
 *
 * --------------------------------------------------------------------------------------------------------
 * aeSecure QuickScan - Malware Scan.
 *
 * This script will make a quick and *superficial*, not deeply, scan and will detect the presence of
 * a few patterns in files present on your website. If such files are found, they will be reported.
 *
 * This script is a quick scan tool: only a very few patterns will be scanned and if you find
 * viruses with it, consider making a full and deeply scan to search for other malware scripts.
 *
 * If no files are reported by the script, here too, it's possible that other type of virus are
 * present.
 *
 * If you wish a full scan, contact me by surfing on https://www.avonture.be and take a look on my
 * services.
 *
 * Changelog:
 *
 * =======
 * version 2.1.4
 *	+ other.json and whitelist.json files lost (too small)
 *
 * version 2.1.3
 *	+ Add some Wordpress extensions
 *	+ make_hashes : display todo list + go button
 *
 * version 2.1.2
 *	+ Extensions directory list using github API
 *
 * version 2.1.1
 *	+ Extensions hashes load if missing
 *  + clean up other.json file
 *
 * version 2.1.0
 *	+ Moving project to afuj
 *	+ Add extensions hashes
 *
 * version 2.0.3
 *    + Prevent empty files to be scanned
 *    + Immediately show the listing of files having detected as being a virus (blacklist) or
 *      containing a virus (edited file having a virus load)
 *
 * version 2.0.2
 *    + Revert to PHP 8.0 compatibility
 *
 * version 2.0.1
 *    + Add the "_COOKIE" pattern in aesecure_quickscan_pattern.json
 *
 * version 2.0
 *    + PHP 8.2 compatibility
 *    + look for hashes in hashes directory
 *
 * version 1.2
 *    + Rewrite for downloading all settings and signatures files from GitHub
 *    + Add a lot more signatures in these lists: blacklist, whitelist, other and edited json
 *    + Ad more patterns for viruses detection
 *    + Reformat the code of the scanner
 *
 * version 1.1.12
 *    + Add support for Grr, mediawiki, piwik and pmb
 *    + Solve an issue with session_start() for some hosts
 *
 * version 1.1.11
 *    + Add support for Grav
 *
 * version 1.1.10
 *    + Add support for phpMyAdmin
 *
 * version 1.1.9
 *    + Solve an error with session_start (on some hoster, the creation of the session gives a fatal error due to incorrect path)
 *
 * version 1.1.8
 *    + Solve an error with the link to the FAQ
 *    + Better handling of languages files
 *
 * version 1.1.7
 *    + Add localizations (class aeSecureLanguage)
 *
 * version 1.1.6
 *    + Improve the detection of the list of files by immediatly skipping whitelisted files.  On a site of 4.900 files, the scanner will be able to detect that
 *      only 11 files should be scanned if 4.889 are already white listed.  This way, the scanner will be really fast.
 *
 * version 1.1.5
 *    + Small change to correctly handle Joomla 3.5.0 with a newer way to determine the version number (no more dollar sign before variables name)
 *
 * version 1.1.4
 *    + Add aesecure_quickscan.whitelist.json as a file to download from avonture.be to speed up the processing and reduce the number of false positive
 *    + Add a lot of new signatures in the blacklist
 *
 * version 1.1.3
 *    + Add a timeout for the CURL request
 *
 * version 1.1.2
 *    + Support of concrete5, contao (aka previously called Typolight), dolibarr, eFront, EspoCRM, formaLMS, phpBB, phpList,
 *         SilverStripe and x3cms
 *
 * version 1.1.1
 *    + Monitored folders for Joomla: files present in a native Joomla's folder (part of the CMS) will
 *      be analysed
 *          - If not part of the distribution (intrusion)
 *          - If part of the distribution but with an another hash (hacked file or, at least, altered one)
 *
 * version 1.1.0
 *    + Support CakePHP, Drupal, Magento, PrestaShop (on top of Joomla and WordPress)
 *    + Improved security by no more loading core Joomla files
 *    + Advanced menu (left side)
 *        + Allow to activate debug and expert mode (without any changes in the code)
 *        + Allow to specify how many files to process by cycle (without any changes in the code)
 *        + Allow to specify with type of files to ignore (archives, images, medias, ...)
 *
 * Avoid __DIR__.
 *
 *      __DIR__ is the folder where the running script is started so, perhaps, things like
 *      c:/sites/hacked/. In most of case, it's correct because the script file has been
 *      saved there... but not always: think to symbolic links.
 *      The file can be saved f.i. in c:/repository/aesecure_quickscan/aesecure_quickscan.php
 *      and a symlink has been made in c:/sites/hacked/. We want that __DIR__ points to the
 *      hacked site but won't be the case with symlink. __DIR__ is where the file IS REALLY.
 *
 *      So, don't use __DIR__ but c:/sites/hacked/
 */

define('REPO', 'https://github.com/AFUJ/quickscan/');

define('DIR', str_replace('/', DIRECTORY_SEPARATOR, dirname((string) $_SERVER['SCRIPT_FILENAME'])));
define('FILE', str_replace('/', DIRECTORY_SEPARATOR, basename((string) $_SERVER['SCRIPT_FILENAME'])));

// Don't allow to kill this script when demo mode is enabled
// Don't show the "Enable expert mode" checkbox in Demo mode
define('DEMO', false);

define('DEBUG', false);              // Enable debugging (Note: there is no progress bar in debug mode)
define('FULLDEBUG', false);          // Output a lot of information
define('VERSION', '2.1.4');          // Version number of this script
define('EXPERT', false);             // Display Kill file button and allow to specify a folder
define('MAX_SIZE', 1 * 1024 * 1024); // One megabyte: skip files when filesize is greater than this max size.
define('MAXFILESBYCYCLE', 500);      // Number of files to process by cycle, reduce this figure if you receive HTTP error 504 - Gateway timeout
define('CONTEXT_NBRCHARS', 100);     // When a suspicious pattern is found, the portion of code where this pattern is found will be displayed.  The portion is xxx characters before the pattern; the pattern and the same number of characters after it.
define('SHOWMD5', false);            // Allow to generate a hash file
define('PROGRESSBARFREQUENCY', 3);   // Frequency of updates for the progress bar. In seconds.
define('MEMORY_LIMIT', '256M');      // DEBUG MODE ONLY - Maximum memory limit that will be used
define('CURL_TIMEOUT', 2);           // Max number of seconds before the timeout when requesting a JSON file from avonture.be

// Download URL for the file with CMS hashes
define('DOWNLOAD_URL', 'https://raw.githubusercontent.com/AFUJ/quickscan/master/');
define('DOWNLOAD_URL_DIR', 'https://api.github.com/repos/AFUJ/quickscan/contents/');
define('MD5', '');
define('DIRNOTFOUND', 'Directory not found');

// List of extensions, by "category". Add an extension if you want to skip that files when
// skipping the category
define('ExtArchives', '7z, bak, gz, gzip, jpa, tar, zip');
define('ExtDocuments', 'doc, docx, pdf, ppt, pptx, xls, xlsx');
define('ExtFonts', 'eot, otf, ttf, ttf2, woff, woff2');
define('ExtImages', 'bmp, eps, gif, ico, icon, jpeg, jpg, png, psd, svg, tiff, webp');
define('ExtMedia', 'css, js, less');
define('ExtSoundMovies', 'aiff, asf, avi, fla, flv, f4v, m4v, mkv, mov, mp3, mp4, mpeg, mpg, ogg, ogv, swf, wav, webm, wma');
define('ExtText', 'ini, json, log, md, mo, po, sql, text, txt, xml, xsl');

define('CRLF', "\r\n");
define('DS', DIRECTORY_SEPARATOR);

// Register error handling functions
set_error_handler(function ($code, $string, $file, $line): never {
    throw new ErrorException($string, 0, $code, $file, $line);
});

register_shutdown_function(function () {
    $memory = 'ini_get memory_limit=' . ini_get('memory_limit') . ' | ' .
        'memory used=' . aeSecureFct::getMemoryUsed();

    $error = error_get_last();
});

class aeSecureDebug
{
    /**
     * Debugging mode state (On / Off).
     *
     *
     * @access private
     */
    private static bool $debugMode = false;

    /**
     * Instantiate the class.
     *
     * @param bool $debugMode False will hide errors in the browser
     *                        True will activate a verbose mode
     *
     * @return void
     */
    public function __construct($debugMode = false)
    {
        // Informs PHP where to store errors
        ini_set('error_log', DIR . 'aesecure_quickscan_error_log');

        // Initialize the debug mode
        self::setDebugMode($debugMode);
    }

    /**
     * Set the debugging mode.
     *
     * @param bool $onOff
     *
     * @return void
     */
    public static function setDebugMode($onOff = false)
    {
        static::$debugMode = $onOff;

        // When debug mode is on, we want to see every messages; even notice.
        if (true === static::$debugMode) {
            ini_set('display_errors', '1');
            ini_set('display_startup_errors', '1');
            ini_set('html_errors', '1');
            ini_set('docref_root', 'http://www.php.net/');

            ini_set(
                'error_prepend_string',
                "<div style='color:red; font-family:verdana;" .
                    "border:1px solid red; padding:5px;'>"
            );
            ini_set('error_append_string', '</div>');
            error_reporting(E_ALL);
        } else {
            error_reporting(E_ALL & ~E_NOTICE);
        }
    }
}

class Download
{
    // Timeout delay in seconds
    public const CURL_TIMEOUT = 2;
    public const ERROR_CURL   = 1001;

    private static $sAppName              = '';
    private static string $sFileName      = '';
    private static string $sSourceURL     = '';
    private static bool $bDebug           = false;
    private static string $sDebugFileName = '';

    public function __construct($ApplicationName)
    {
        static::$bDebug   = false;
        static::$sAppName = $ApplicationName;
    }

    /**
     * Enable the debug mode for this class.
     *
     * @param mixed $bOnOff
     */
    public function debugMode($bOnOff)
    {
        static::$bDebug = $bOnOff;
        if ($bOnOff) {
            // A debug.log file will be created in
            // the folder of the calling script
            static::$sDebugFileName = DIR . 'debug.log';
        }
    }

    // URL where the script will find a file to download
    public function setURL($sURL)
    {
        static::$sSourceURL = trim((string) $sURL);
    }

    /**
     * Once download, a file will be created on the disk.
     * Use this property to specify the name of that file.
     *
     * @param mixed $sName
     */
    public function setFileName($sName)
    {
        static::$sFileName = trim((string) $sName);
    }

    /**
     * Download a github file.
     *
     * @param type $url
     * @param type $file
     *
     * @return string
     */
    public function download()
    {
        $wError = 0;

        // Try to use CURL, if installed
        if (self::iscURLEnabled()) {
            // $sFileName is the fullname of the file to create f.i.
            // /home/www/username/rootweb/downloaded-file.zip

            $fp = @fopen(static::$sFileName, 'w');
            if (!$fp) {
                throw new Exception(static::$sAppName . ' - Could not open the file!');
            }

            if (!file_exists(static::$sFileName)) {
                $wError = self::ERROR_CURL;
            } else {
                @fclose($fp);
                @chmod(static::$sFileName, 0644);
            }

            if (0 === $wError) {
                // Ok, try to download the file
                $ch = curl_init(static::$sSourceURL);

                if ($ch) {
                    // Start the download process
                    @set_time_limit(0);
                    $fp = @fopen(static::$sFileName, 'w');

                    if (!curl_setopt($ch, CURLOPT_URL, static::$sSourceURL)) {
                        fclose($fp);
                        curl_close($ch);
                        $wError = self::ERROR_CURL;
                    } else {
                        // Download

                        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64) ' .
                            'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 ' .
                            'Safari/537.36 FirePHP/4Chrome');
                        curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
                        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::CURL_TIMEOUT);

                        // Output curl debugging messages into a text file
                        if (static::$bDebug) {
                            // output debugging info in a txt file
                            curl_setopt($ch, CURLOPT_VERBOSE, true);
                            $fdebug = fopen(static::$sDebugFileName, 'w');
                            curl_setopt($ch, CURLOPT_STDERR, $fdebug);
                        }

                        // Add CURLOPT_SSL if the protocol is https
                        if ('https' == substr((string) static::$sSourceURL, 0, 5)) {
                            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
                            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
                        }

                        curl_setopt($ch, CURLOPT_HEADER, false);
                        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
                        curl_setopt($ch, CURLOPT_FILE, $fp);
                        curl_setopt($ch, CURLOPT_MAXREDIRS, 3);

                        $rc = curl_exec($ch);

                        curl_close($ch);
                        fclose($fp);

                        if (!$rc) {
                            $wError = self::ERROR_CURL;
                        }

                        @chmod(static::$sFileName, 0644);
                    }
                }
            }
        }

        self::removeIfNull();

        if (!file_exists(static::$sFileName)) {
            // Unsuccessful, try with fopen()
            // Use a context to be able to define a timeout
            $context = stream_context_create(
                ['http' => ['timeout' => self::CURL_TIMEOUT]]
            );

            // Get the content if fopen() is enabled
            $content = @fopen(static::$sSourceURL, 'r', false, $context);
            if ('' !== $content) {
                @file_put_contents(static::$sFileName, $content);
            }

            self::removeIfNull();

            if (file_exists(static::$sFileName)) {
                $wError = 0;
            }
        }

        return $wError;
    }
    /**
     * Get one github directory content.
     *
     * @param type $url
     *
     * @return string
     */
    public function downloadGetDir()
    {
        $wError = 0;

        // Try to use CURL, if installed
        if (self::iscURLEnabled()) {
            // Ok, try to download the file
            $ch = curl_init(static::$sSourceURL);
            if ($ch) {
                // Start the download process
                @set_time_limit(0);
                // Download
                curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64) ' .
                    'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 ' .
                    'Safari/537.36 FirePHP/4Chrome');
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1000);
                // Output curl debugging messages into a text file
                if (static::$bDebug) {
                    // output debugging info in a txt file
                    curl_setopt($ch, CURLOPT_VERBOSE, true);
                    $fdebug = fopen(static::$sDebugFileName, 'w');
                    curl_setopt($ch, CURLOPT_STDERR, $fdebug);
                }
                // Add CURLOPT_SSL if the protocol is https
                if ('https' == substr((string) static::$sSourceURL, 0, 5)) {
                    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
                    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
                }
                curl_setopt($ch, CURLOPT_HEADER, false);
                curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
                curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
                $rc = curl_exec($ch);
                curl_close($ch);
                if (!$rc) {
                    $wError = self::ERROR_CURL;
                } else {
                    return $rc;
                }
            }
        }

        return $wError;
    }

    /**
     * Return a text for the encountered error.
     *
     * @param mixed $code
     */
    public function getErrorMessage($code)
    {
        $sReturn =
            '<p>Your system configuration doesn\'t allow to download the file.</p>' .
            '<p>Please click ' .
            '<a href="' . static::$sSourceURL . '">here</a> to ' .
            'manually download the file, then open your ' .
            'FTP client and send the downloaded file to your ' .
            'website folder.</p>' .
            '<p>Once this is done, just refresh this page.</p>' .
            '<p><em>Note: the filename should be ' . static::$sFileName . '</em></p>';

        return $sReturn;
    }

    /**
     * Detect if the CURL library is loaded.
     */
    private function iscURLEnabled()
    {
        return  (!function_exists('curl_init') && !function_exists('curl_setopt') &&
            !function_exists('curl_exec') && !function_exists('curl_close')) ? false : true;
    }

    /**
     * If the file is there and has a size of 0 byte,
     * it's a failure, the file wasn't downloaded.
     */
    private function removeIfNull()
    {
        if (file_exists(static::$sFileName)) {
            if (filesize(static::$sFileName) < 10) {
                unlink(static::$sFileName);
            }
        }
    }
}

class aeSecureDownload
{
    /**
     * Download a file from GitHub like "aesecure_quickscan_pattern.json", ...
     * See the DOWNLOAD_URL constant for the URL.
     *
     * @param [type] $file
     * @param mixed $uri
     *
     * @return void
     */
    public static function get($file, $uri)
    {
        try {
            // Try to download
            $aeDownload = new Download('Quickscan');
            $aeDownload->debugMode(DEBUG);

            // Be sure to have only one "/" and not two
            if (trim('' !== $uri)) {
                $uri = ltrim(rtrim((string) $uri, '/'), '/') . '/';
            }

            $url = rtrim(DOWNLOAD_URL, '/') . '/' . $uri . basename((string) $file);

            $aeDownload->setURL($url);
            $aeDownload->setFileName($file);
            $wReturn = $aeDownload->download();

            if (0 !== $wReturn) {
                $sErrorMsg = $aeDownload->getErrorMessage($wReturn);
            }
        } catch (Exception $e) {
            $wReturn   = 1001;
            $sErrorMsg = $e->getMessage();
        }

        unset($aeDownload);
    }
    /**
     * Download a directory from GitHub like "hashes/J!extensions", ...
     * See the DOWNLOAD_URL_DIR constant for the URL.
     *
     * @param [type] $dir
     * @param mixed $uri
     *
     * @return void
     */
    public static function getDir($dir)
    {
        try {
            // Try to download
            $aeDownload = new Download('Quickscan');
            $aeDownload->debugMode(DEBUG);

            // Be sure to have only one "/" and not two

            $url = rtrim(DOWNLOAD_URL_DIR, '/') . '/' . $dir.'?ref=master';

            $aeDownload->setURL($url);
            $wReturn = $aeDownload->downloadGetDir();

            if (is_int($wReturn)) { // error
                $sErrorMsg = $aeDownload->getErrorMessage($wReturn);
            } else { // contains a directory
                $json_array = json_decode($wReturn);
                unset($aeDownload);
                return  $json_array ;
            }

        } catch (Exception $e) {
            $wReturn   = 1001;
            $sErrorMsg = $e->getMessage();
        }

        unset($aeDownload);
    }
}

/**
 * Add localization; read an external json file with translations.
 */
class aeSecureLanguage
{
    public const DEFAULT_LANGUAGE = 'en-GB';

    // Filename pattern for languages files
    public const LANG_FILE = 'aesecure_quickscan_lang_%s.json';

    // Hard-coded list of supported languages
    // @See https://github.com/afuj/quickscan for xxx_lang_xxxx.json files
    public const SUPPORTED_LANGUAGES = 'en;en-GB;fr;fr-FR;nl;nl-BE';

    private string $_filename              = '';
    private $_lang                         = null;
    private $_arrLanguage                  = null;
    private bool $_bLoaded                 = false;
    private $supportedLanguages            = null;
    private $browserLanguages              = null;

    protected static $instance = null;

    public function __construct($lang = null)
    {
        $aeSession = aeSecureSession::getInstance();

        if (null == $lang) {
            $lang = str_replace('_', '-', (string) aeSecureFct::getParam('lang', 'string', '', 5));
        }

        // Initialize the list of supported languages
        $this->supportedLanguages = explode(';', self::SUPPORTED_LANGUAGES);

        // Get the list of languages supported by the Browser and by aeSecure
        // (presence of the language's file)
        self::getBrowserLanguage();

        if (in_array($lang, $this->supportedLanguages)) {
            // Perfect match
            // The language (f.i. nl-BE) is supported; we've a nl-BE.json file; use it
            $result = $lang;
        } elseif (in_array(substr((string) $lang, 0, 2), $this->supportedLanguages)) {
            // If the user ask for f.i. en-US and we've a file for "en", use that file.
            // For instance en-GB
            $result = substr((string) $lang, 0, 2);
        } else {
            // No, not found. Use the languages supported by the browser and check if aeSecure
            // support that language
            $result = '';

            // Search for a perfect match so if the language is en_US, try to find en_US.json
            // and not en_GB.json

            foreach ($this->browserLanguages as $lang => $value) {
                if (in_array($lang, $this->supportedLanguages)) {
                    $result = $lang;

                    break;
                }
            }

            // If $result is still empty, no perfect match so search on the language and not
            // language and country. So, if the language is en-US and if a file en-GB is found, get it.
            if ('' == $result) {
                $result = 'en-GB';
                foreach ($this->browserLanguages as $lang => $value) {
                    // Check if there is a language file (f.i. if $lang is "fr"
                    // (and not "fr_FR"), the glob function will return
                    // the list of files like fr*.json
                    if (in_array(substr((string) $lang, 0, 2), $this->supportedLanguages)) {
                        $result = substr((string) $lang, 0, 2);

                        break;
                    }
                }
            }

            // Still not? Use en-GB by default
            if ('' == $result) {
                $result = self::DEFAULT_LANGUAGE;
            }
        }

        $aeSession->set('Lang', $lang);

        // Max 5 characters
        $lang = substr((string) $lang, 0, 5);

        // Just be sure to have en-GB and not f.i. EN-GB or en_gb
        $lang = strtolower(substr($lang, 0, 2)) . '-' . strtoupper(substr($lang, -2));

        if ('en-US' == $lang) {
            $lang = 'en-GB';
        }

        $this->_lang     = $lang;
        $this->_filename = DIR . DS . sprintf(self::LANG_FILE, $this->_lang);

        $this->_bLoaded = false;

        if (!file_exists($this->_filename)) {
            // Try to download if not present
            aeSecureDownload::get($this->_filename, 'settings/');
        }

        if (file_exists($this->_filename)) {
            $string = file_get_contents($this->_filename);
            $string = str_replace('\\u', '\u', $string);

            if (null === json_decode($string, true, 512, JSON_THROW_ON_ERROR)) {
                die('There is a problem in ' . $this->_filename .
                    '. Probably an invalid json file <pre>' .
                    html_entity_decode($string) . '</pre>');
            }

            $this->_arrLanguage = json_decode($string, true, 512, JSON_THROW_ON_ERROR);
            $this->_bLoaded     = true;
        }

        // If the parametrized file isn't found (f.i. the user set fr-FR has
        // preferred language and the file is not
        // present), then use by default en-GB
        if (!$this->_bLoaded) {
            // Try to download if not present
            $this->_filename = DIR . DS . sprintf(self::LANG_FILE, self::DEFAULT_LANGUAGE);

            if (!file_exists($this->_filename)) {
                aeSecureDownload::get($this->_filename, 'settings/');
            }

            if (file_exists($this->_filename)) {
                $string = file_get_contents($this->_filename);
                $string = str_replace('\\u', '\u', $string);

                if (null === json_decode($string, true, 512, JSON_THROW_ON_ERROR)) {
                    die('There is a problem in ' . $this->_filename . '. ' .
                        'Probably an invalid json file <pre>' .
                        html_entity_decode($string) . '</pre>');
                }

                $this->_arrLanguage = json_decode($string, true, 512, JSON_THROW_ON_ERROR);
                $this->_bLoaded     = true;
            }
        }

        // Still not? Use the first language file that is present
        if ((!$this->_bLoaded) && (count($this->supportedLanguages) > 0)) {
            foreach ($this->supportedLanguages as $key => $value) {
                $this->_filename = DIR . DS . sprintf(self::LANG_FILE, $value);

                if (file_exists($this->_filename)) {
                    $string = file_get_contents($this->_filename);
                    $string = str_replace('\\u', '\u', $string);

                    if (null === json_decode($string, true, 512, JSON_THROW_ON_ERROR)) {
                        die('There is a problem in ' . $this->_filename .
                            '. Probably an invalid json file <pre>' .
                            html_entity_decode($string) . '</pre>');
                    }

                    $this->_arrLanguage = json_decode($string, true, 512, JSON_THROW_ON_ERROR);
                    $this->_bLoaded     = true;
                    $this->_lang        = $value;

                    break;
                }
            }
        }

        return true;
    }

    public function ready(): bool
    {
        return $this->_bLoaded;
    }

    /**
     * Translation functionality, search the CODE in the json file and returns its
     * value (the translated text).
     */
    public function get(string $code): string
    {
        $sText = '';
        if (isset($this->_arrLanguage[$code])) {
            $sText = $this->_arrLanguage[$code];
        }

        return $sText;
    }

    public function getlang(): string
    {
        return $this->_lang;
    }

    /**
     * $language can be initialized or not.  If not, the script will detect supported
     * languages as defined in the user's browser. If initialized, should be something
     * like 'en-GB', 'fr-FR', ...
     */
    public static function getInstance(?string $lang = null): self
    {
        if (null === self::$instance) {
            self::$instance = new aeSecureLanguage($lang);
        }

        return self::$instance;
    }

    /**
     * Read the HTTP_ACCEPT_LANGUAGE browser info to determine the best language
     * to use for aeSecure based on the browser's preferences.
     *
     * @return string Returns f.i. en-GB, fr-FR, nl-NL, ...
     */
    private function getBrowserLanguage(): string
    {
        $default       = null;
        $httplanguages = $_SERVER['HTTP_ACCEPT_LANGUAGE'];

        if (empty($httplanguages)) {
            return $default;
        }

        $this->browserLanguages     = [];
        $result                     = '';

        // $this->browserLanguages is an array, sorted by priority order, of the
        // supported languages; for instance:
        // array
        //   'fr' => float 1
        //   'en_US' => float 0.8
        //   'en' => float 0.6

        foreach (preg_split('/,\s*/', (string) $httplanguages) as $accept) {
            $result = preg_match('/^([a-z]{1,8}(?:[-_][a-z]{1,8})*)(?:;\s*' .
                'q=(0(?:\.[0-9]{1,3})?|1(?:\.0{1,3})?))?$/i', (string) $accept, $match);

            if (!$result) {
                continue;
            }

            $quality = (isset($match[2]) ? (float)$match[2] : 1.0);

            $countries   = explode('-', $match[1]);
            $region      = array_shift($countries);
            $country_sub = explode('_', $region);
            $region      = array_shift($country_sub);

            foreach ($countries as $country) {
                $this->browserLanguages[$region . '-' . strtoupper($country)] = $quality;
            }
            foreach ($country_sub as $country) {
                $this->browserLanguages[$region . '-' . strtoupper($country)] = $quality;
            }

            $this->browserLanguages[$region] = $quality;
        }

        return true;
    }
}

/**
 * A few helping functions.
 */
class aeSecureFct
{
    /**
     * Remove special characters, f.i clean('a|"bc!@£de^&$f g') will return 'abcdef-g'.
     */
    public static function sanitize(string $string): string
    {
        // Replaces all spaces with hyphens.
        $string = str_replace(' ', '-', $string);
        // Removes special chars.
        return (string) preg_replace('/[^A-Za-z0-9\-]/', '', $string);
    }

    /**
     * Generic function for adding a js in the HTML response.
     *
     * @param type  $localfile
     * @param type  $weblocation
     * @param mixed $defer
     *
     * @return string
     */
    public static function addJavascript($localfile, $weblocation = '', $defer = false)
    {
        $return = '';

        // Perhaps the script (aesecure_quickscan.php) is a symbolic link so __DIR__
        // is the folder where the real file can be found and SCRIPT_FILENAME his link,
        // the line below should therefore not be used anymore
        if (is_file(str_replace('/', DS, dirname((string) $_SERVER['SCRIPT_FILENAME'])) . DS . $localfile)) {
            $return = '<script ' . (true == $defer ? 'defer="defer" ' : '') .
                'type="text/javascript" src="../' . $localfile . '"></script>';
        } else {
            if ('' != $weblocation) {
                $return = '<script ' . (true == $defer ? 'defer="defer" ' : '') .
                    'type="text/javascript" src="' . $weblocation . '"></script>';
            }
        }

        return $return;
    }

    /**
     * Generic function for adding a css in the HTML response.
     *
     * @param type $localfile
     * @param type $weblocation
     *
     * @return string
     */
    public static function addStylesheet($localfile, $weblocation = '')
    {
        $return = '';

        // Perhaps the script (aesecure_quickscan.php) is a symbolic link so __DIR__ is the
        // folder where the real file can be found and SCRIPT_FILENAME his link, the line
        // below should therefore not be used anymore
        if (is_file(str_replace('/', DS, dirname((string) $_SERVER['SCRIPT_FILENAME'])) . DS . $localfile)) {
            $return = '<link href="../' . $localfile . '" rel="stylesheet" />';
        } else {
            if ('' != $weblocation) {
                $return = '<link href="' . $weblocation . '" rel="stylesheet" />';
            }
        }

        return $return;
    }

    public static function human_filesize($bytes, $decimals = 2)
    {
        $sz     = 'BKMGTP';
        $factor = intval(floor((strlen((string) $bytes) - 1) / 3));

        return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
    }

    /**
     * Return a string like '1 an 10 mois 6 jours 3 heures'... ie the age of f.i. a file.
     *
     *    echo aeSecureFct::time_elapsed_string(filemtime($filename))
     */
    public static function time_elapsed_string(int $ptime): string
    {
        $diff       = time() - $ptime;
        $calc_times = [];
        $timeleft   = [];

        // Prepare array, depending on the output we want to get.
        $calc_times[] = ['an',      'ans',      31557600];
        $calc_times[] = ['mois',    'mois',     2592000];
        $calc_times[] = ['jour',    'jour',     86400];
        $calc_times[] = ['heure',   'heures',   3600];
        $calc_times[] = ['minute',  'minutes',  60];
        $calc_times[] = ['seconde', 'secondes', 1];

        foreach ($calc_times as $timedata) {
            [$time_sing, $time_plur, $offset] = $timedata;

            if ($diff >= $offset) {
                $left = floor($diff / $offset);
                $diff -= ($left * $offset);
                $timeleft[] = "{$left} " . (1 == $left ? $time_sing : $time_plur);
            }
        }

        return $timeleft ? (time() > $ptime ? null : '-') . implode(' ', $timeleft) : 0;
    }

    /**
     * Return true when the call to the php script has been done through an ajax request.
     */
    public static function isAjaxRequest(): bool
    {
        $bAjax = (isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
            ('XMLHttpRequest' == $_SERVER['HTTP_X_REQUESTED_WITH']));

        return $bAjax;
    }

    /**
     * Return the memory usage when this function is called.  By calling this function
     * at different place in the code, it's then possible to determine which part is
     * eating a lot of memory.
     *
     * @return type
     */
    public static function getMemoryUsed()
    {
        $mem_usage = memory_get_peak_usage(true);

        return ($mem_usage < 1048576)
            ? round($mem_usage / 1024, 2) . ' kb'
            : round($mem_usage / 1048576, 2) . ' mb';
    }

    /**
     * Safely read values from posted forms ($_POST).
     *
     * @param mixed $type
     */
    public static function getParam(string $name, $type = 'string', mixed $default = '', int $maxlen = 0): mixed
    {
        $tmp    = '';
        $return = $default;

        if (isset($_POST[$name])) {
            if (in_array($type, ['int', 'integer'])) {
                $return = htmlspecialchars((string) $_POST[$name], ENT_QUOTES); // filter_input(INPUT_POST, $name, FILTER_SANITIZE_NUMBER_INT);
            } elseif ('boolean' == $type) {
                // false = 5 characters
                $tmp    = substr(htmlspecialchars((string) $_POST[$name], ENT_QUOTES), 0, 5); // substr(filter_input(INPUT_POST, $name, FILTER_SANITIZE_STRING), 0, 5);
                $return = (in_array(strtolower($tmp), ['on', 'true'])) ? true : false;
            } elseif ('string' == $type) {
                $return = htmlspecialchars((string) $_POST[$name], ENT_QUOTES); //filter_input(INPUT_POST, $name, FILTER_SANITIZE_STRING);
                if ($maxlen > 0) {
                    $return = substr($return, 0, $maxlen);
                }
            } elseif ('unsafe' == $type) {
                $return = $_POST[$name];
            }
        } else {
            $aeSession = aeSecureSession::getInstance();

            // Get from the $_GET only in debug mode or for very few parameters like "lang" (to allow to switch between
            // languages) and "aes" (boolean set to 1 when QuickScan is started from within the aeSecure Firewall interface)

            if ((true === $aeSession->get('Debug', DEBUG)) || in_array($name, ['aes', 'lang'])) {
                if (isset($_GET[$name])) {
                    if (in_array($type, ['int', 'integer'])) {
                        $return = htmlspecialchars((string) $_GET[$name], ENT_QUOTES); //filter_input(INPUT_GET, $name, FILTER_SANITIZE_NUMBER_INT);
                    } elseif ('boolean' == $type) {
                        // false = 5 characters
                        $tmp    = substr(htmlspecialchars((string) $_GET[$name], ENT_QUOTES), 0, 5);
                        $return = (in_array(strtolower($tmp), ['1', 'on', 'true'])) ? true : false;
                    } elseif ('string' == $type) {
                        $return = htmlspecialchars((string) $_GET[$name], ENT_QUOTES);
                    } elseif ('unsafe' == $type) {
                        $return = $_GET[$name];
                    }
                }
            }
        }

        if ('boolean' == $type) {
            $return = (in_array($return, ['on', '1']) ? true : false);
        }

        return $return;
    }
}

/**
 * Logging functionality.
 */
class aeSecureLog
{
    private $_sLogFile = null;

    protected static $instance = null;

    public function __construct($sLogFile, $killFile = true)
    {
        $this->_sLogFile = $sLogFile;

        if ((true == $killFile) && (file_exists($this->_sLogFile)) && (is_writable($this->_sLogFile))) {
            unlink($this->_sLogFile);
        }

        return true;
    }

    public function kill()
    {
        if ((file_exists($this->_sLogFile)) && (is_writable($this->_sLogFile))) {
            unlink($this->_sLogFile);
        }
    }

    public function filename()
    {
        return $this->_sLogFile;
    }

    /**
     * Add a line in the $sLogFile log file.
     *
     * @param type $sLine
     *
     * @return type
     */
    public function addLog($sLine)
    {
        if (!is_writable(dirname((string) $this->_sLogFile))) {
            return;
        }
        if ('' != $this->_sLogFile) {
            if ($handle = fopen($this->_sLogFile, 'a')) {
                fwrite($handle, (string) ($sLine . "\n"));
                fclose($handle);
            }
        }
    }

    /**
     * @param type $sLogFile Name of the logfile that will be used
     * @param type $killFile Default True : kill the logfile if present when starting the run
     *
     * @return type
     */
    public static function getInstance($sLogFile = null, $killFile = false)
    {
        if (null != $sLogFile) {
            if (null === self::$instance) {
                self::$instance = new aeSecureLog($sLogFile, $killFile);
            }
        }

        return self::$instance;
    }
}

/**
 * Working with files and folders.
 */
class aeSecureFiles
{
    protected $aeSession = null;
    protected $aeLog     = null;

    protected static $instance = null;

    public function __construct()
    {
        $this->aeSession = aeSecureSession::getInstance();
    }

    public function SeeFile(?string $filename = null): ?string
    {
        $return = null;

        if (null != $filename) {
            if ((is_file($filename)) && is_readable($filename)) {
                $return = file_get_contents($filename);
            }
        }

        return $return;
    }

    /**
     * Kill physically a file.
     *
     * @return type -1 if the file has been removed successfully
     */
    public function KillFile(?string $filename = null): int
    {
        $return = 0;

        if (null == $filename) {
            return $return;
        }

        if ((is_file($filename)) && is_writable($filename)) {
            try {
                if (true === $this->aeSession->get('Debug', DEBUG)) {
                    if (null == $this->aeLog) {
                        $this->aeLog = aeSecureLog::getInstance();
                    }
                    $this->aeLog->addLog('*** Kill ' . $filename . ' ***');
                }
                unlink($filename);
                if (!is_file($filename)) {
                    $return = -1;
                }
            } catch (Exception $e) {
                $return = -999;
            }
        } else {
            $return = -50;
        }

        echo $return;
    }

    /**
     * Remove recursively folders
     * (f.i. rrmdir(__DIR__/hashes/cms/joomla/2.5.27) will kill the full tree below
     * the specified folder).
     *
     * @param bool $killroot If true, the folder himself will be removed.
     *                       rrmdir(__DIR__/hashes/cms/joomla/2.5.27, true) ==>
     *                       remove folder 2.5.27 too and not only his children
     */
    public function rrmdir(string $folder, bool $killroot = false, array $arrIgnoreFiles = ['.htaccess', 'index.html']): bool
    {
        try {
            if (!is_dir($folder) && file_exists($folder)) {
                try {
                    if (!is_writable($folder)) {
                        $bResult = @chmod($folder, octdec('755'));
                    }

                    return @unlink($folder);
                } catch (Exception $e) {
                    return false;
                }
            }

            foreach (scandir($folder) as $file) {
                if ('.' == $file || '..' == $file || in_array($file, $arrIgnoreFiles)) {
                    continue;
                }
                if (!self::rrmdir($folder . DS . $file, $killroot, $arrIgnoreFiles)) {
                    if (!is_writable($folder . DS . $file)) {
                        $bResult = chmod($folder . DS . $file, octdec('755'));
                    }
                    if (!self::rrmdir($folder . DS . $file, $killroot, $arrIgnoreFiles)) {
                        return false;
                    }
                }
            }

            // Remove the folder only if not read-only and empty
            if ((is_writable($folder)) && (0 === count(glob("$folder/*")))) {
                @rmdir($folder);
            }

            return true;
        } catch (Exception $ex) {
            if (true === $this->aeSession->get('Debug', DEBUG)) {
                echo '<pre>' . print_r($ex, true) . '</pre>';
            }

            return false;
        }
    }

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

        return self::$instance;
    }

    public static function getFileMimeType(string $filename): ?string
    {
        $mime_type = null;

        if (is_file($filename)) {
            $finfo = null;

            if (class_exists('info')) {
                // return mime type
                $finfo = new finfo(FILEINFO_MIME);
            }

            if ($finfo) {
                $file_info = $finfo->file($filename);
                $mime_type = substr($file_info, 0, strpos($file_info, ';'));
            } else {
                // Requires to enable "extension=php_fileinfo.dll" on a Windows machine
                if (function_exists('finfo_open')) {
                    $finfo = finfo_open(FILEINFO_MIME_TYPE);
                }
                if ($finfo) {
                    $mime_type = finfo_file($finfo, $filename);
                    finfo_close($finfo);
                }
            }
        }

        return $mime_type;
    }

    /**
     * Return true if the file contains text content.
     */
    public static function isTextFileContent(string $filename): string
    {
        $mimeType   = aeSecureFiles::getFileMimeType($filename);
        $isTextType = false;

        // Try to determine if it's a text file; in that case the MIME type is
        // something like text/plain or text/richtext i.e. starting with the "text/" prefix
        $isTextType = ('text/' == substr((string) $mimeType, 0, 5));

        // A few mimetype are also text like application/javascript
        if (!$isTextType) {
            $isTextType = in_array($mimeType, ['application/xml']);

            // Still not? Try to use the file's extension to determine this
            if (!$isTextType) {
                $ext        = pathinfo($filename, PATHINFO_EXTENSION);
                $isTextType = in_array($ext, ['css', 'csv', 'eot', 'html', 'htm', 'ini',
                    'js', 'json', 'php', 'sh', 'svg', 'txt', 'xml']);
            }
        }

        return $isTextType;
    }
}

/**
 * Session helper.
 */
class aeSecureSession
{
    protected static $instance = null;
    protected static $prefix   = 'aeS_';

    public function __construct($bDestroy = false)
    {
        // server should keep session data for AT LEAST 1 hour
        try {
            ini_set('session.gc_maxlifetime', 3600);
            // each client should remember their session id for EXACTLY 1 hour
            session_set_cookie_params(3600);
        } catch (\Exception $exception) {
        }

        if (!isset($_SESSION)) {
            try {
                session_start();
            } catch (Exception $e) {
                // 1.1.9 - On some hoster the path where to store session is incorrectly
                // set and this gives a fatal error
                // Handle this and use the /tmp folder in this case.
                try {
                    session_destroy();
                    session_save_path(sys_get_temp_dir());

                    try {
                        session_start();
                    } catch (Exception $e) {
                        // 1.1.12
                        // Still not? Use the current dir
                        session_destroy();
                        session_save_path(DIR);
                        session_start();
                    }
                } catch (\Exception $exception) {
                }
            }
        }

        if ($bDestroy) {
            session_destroy();
        }

        return true;
    }

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

        return self::$instance;
    }

    public static function set(string $name, mixed $value): void
    {
        $_SESSION[static::$prefix . $name] = $value;
    }

    public static function get(string $name, mixed $defaultvalue): mixed
    {
        return $_SESSION[static::$prefix . $name] ?? $defaultvalue;
    }
}

/**
 * CMS functionnalities.
 */
class aeSecureCMS
{
    public const SUPPORTED_CMS = 'aesecure_quickscan_supported_cms.json';

    /**
     * Try to determine if the site is a CMS site and in that case, get the CMS version.
     */
    public static function getInfo(string $directory): array
    {
        $CMS         = '';
        $MainVersion = '';
        $Version     = '';
        $FullVersion = '';

        // Try to derive the root folder
        $root = rtrim($directory, DS) . DS;

        // Get the list of CMS (it's a json string stored in a constant)
        // and try to find a CMS
        $file = DIR . DS . self::SUPPORTED_CMS;
        if (!is_file($file)) {
            aeSecureDownload::get($file, 'settings/');
        }

        if (!is_file($file)) {
            die(sprintf('Sorry, the file [%s] is missing', basename($file)));
        }

        $arrCMS = json_decode(file_get_contents($file), true, 512, JSON_THROW_ON_ERROR);

        foreach ($arrCMS as $key => $value) {
            if (method_exists('aeSecureCMS', 'is' . $key)) {
                $method = self::class . '::is' . $key; // PHP 8.2

                [$return, $CMS, $Filename, $FullVersion, $MainVersion, $Version] = call_user_func($method, $root);

                if (true === $return) {
                    break;
                }
            }
        }

        return [(string) $CMS, (string) $FullVersion, (string) $MainVersion, (string) $Version, (string) $root];
    }

    /**
     * Detect if the CMS is Cake.
     */
    private static function iscake(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'admin' . DS . 'cake' . DS . 'config' . DS . 'config.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            preg_match('/.*\\Cake\.version\'\] *= *\'(.*)\'/', $content, $arrMatches, PREG_OFFSET_CAPTURE);
            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'Cake', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is Concrete5.
     */
    private static function isConcrete5(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'concrete' . DS . 'config' . DS . 'concrete.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            preg_match('/.*\'version\' *\=\> *\'(.*)\'/', $content, $arrMatches, PREG_OFFSET_CAPTURE);
            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'Concrete5', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is Contao.
     */
    private static function isContao(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'system' . DS . 'config' . DS . 'constants.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            preg_match('/.*VERSION\', *\'(.*)\'/', $content, $arrMatches, PREG_OFFSET_CAPTURE);
            $MainVersion = (count($arrMatches) > 0) ? $arrMatches[1][0] : '';

            preg_match('/.*BUILD\', *\'(.*)\'/', $content, $arrMatches, PREG_OFFSET_CAPTURE);
            $FullVersion = $MainVersion . '.' . ((count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '');

            return [true, 'Contao', $filename, $FullVersion, $MainVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is Dolibarr.
     */
    private static function isDolibarr(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'htdocs' . DS . 'filefunc.inc.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            preg_match('/.*DOL_VERSION\', *\'(.*)\'/', $content, $arrMatches, PREG_OFFSET_CAPTURE);
            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'Dolibarr', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is Drupal.
     */
    private static function isDrupal(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'modules' . DS . 'system' . DS . 'system.module';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            preg_match('/.*define\(\'VERSION\', *\'(.*)\'/', $content, $arrMatches, PREG_OFFSET_CAPTURE);
            $FullVersion = (count($arrMatches) > 0) ? $arrMatches[1][0] : '';

            return [true, 'Drupal', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is eFront.
     */
    private static function iseFront(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'libraries' . DS . 'configuration.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            preg_match('/.*G_VERSION_NUM\', *\'(.*)\'/', $content, $arrMatches, PREG_OFFSET_CAPTURE);
            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'eFront', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is EspoCRM.
     */
    private static function isEspoCRM(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'data' . DS . 'config.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            preg_match('/.*version\' \=\> *\'(.*)\'/', $content, $arrMatches, PREG_OFFSET_CAPTURE);
            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'EspoCRM', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is FormaLMS.
     */
    private static function isFormaLMS(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'appCore' . DS . 'index.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            preg_match('/.*_file_version_\', *\'(.*)\'/', $content, $arrMatches, PREG_OFFSET_CAPTURE);
            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'FormaLMS', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is Grav.
     */
    private static function isGrav(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'system' . DS . 'defines.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            preg_match('/.*define\(\'GRAV_VERSION\', *\'(.*)\'/', $content, $arrMatches, PREG_OFFSET_CAPTURE);
            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'Grav', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is GRR.
     */
    private static function isGrr(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'include' . DS . 'misc.inc.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            $pattern = '/\\$version_grr[[:blank:]]=[[:blank:]]"(.*)"/';

            preg_match($pattern, $content, $arrMatches, PREG_OFFSET_CAPTURE);

            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'GRR', $filename, $FullVersion, $FullVersion, $FullVersion, $root];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is Joomla.
     */
    private static function isJoomla(string $root): bool|array
    {
        if (
            (($wpos = strpos($root, DS . 'administrator' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'bin' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'cache' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'cli' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'components' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'images' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'includes' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'language' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'layouts' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'libraries' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'logs' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'media' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'modules' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'plugins' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'templates' . DS)) > 0) ||
            (($wpos = strpos($root, DS . 'tmp' . DS)) > 0)
        ) {
            $root = substr($root, 0, $wpos);
        }

        // Now, $root is probably the website root.  Check if we can found a Joomla!® instance
        $filename = rtrim($root, DS) . DS . 'libraries' . DS . 'src' . DS . 'Version.php';

        // Now, $root is probably the website root.  Check if we can found a Joomla!® instance
        if (!file_exists($filename)) {
            $filename = rtrim($root, DS) . DS . 'libraries' . DS . 'cms' . DS . 'version' . DS . 'version.php';
        }

        if (!file_exists($filename)) {
            $filename = rtrim($root, DS) . DS . 'libraries' . DS . 'joomla' . DS . 'version.php';
        }

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            $pattern = '/MAJOR_VERSION = (\d+)/';

            if (preg_match($pattern, $content, $arrMatches, PREG_OFFSET_CAPTURE) > 0) {
                // As from Joomla 4, RELEASE, DEV_LEVEL, ... are removed.
                $arr = ['MAJOR_VERSION' => 0, 'MINOR_VERSION' => 0, 'PATCH_VERSION' => 0, 'RELDATE' => 0, 'RELTIME' => 0, 'RELTZ' => 0];
            } else {
                $arr = ['RELEASE' => 0, 'DEV_LEVEL' => 0, 'DEV_STATUS' => 0, 'RELDATE' => 0, 'RELTIME' => 0, 'RELTZ' => 0];
            }

            foreach ($arr as $key => $value) {
                // Use [[:blank:]] and not just a space character because sometimes the
                // version.php file contains something other than a space
                // this is the case for J1.5.26
                // Note : since J3.5, variables are now constants and without the preceding dollar sign so
                //     before J3.5, it was $RELEASE f.i., since 3.5, it's just RELEASE
                $pattern = '/.*\$?' . $key . "[[:blank:]]*=[[:blank:]]*'?([0-9A-Za-z\-\.]*)'?/";

                preg_match($pattern, $content, $arrMatches, PREG_OFFSET_CAPTURE);
                if (count($arrMatches) > 0) {
                    $arr[$key] = $arrMatches[1][0];
                }
            }

            if (isset($arr['MAJOR_VERSION'])) {
                // Joomla 3.8.2 or greater
                $MainVersion = $arr['MAJOR_VERSION'] . '.' . $arr['MINOR_VERSION'];
                $Version     = $arr['MAJOR_VERSION'] . '.' . $arr['MINOR_VERSION'] . '.' . $arr['PATCH_VERSION'];
                $FullVersion = $arr['MAJOR_VERSION'] . '.' . $arr['MINOR_VERSION'] . '.' . $arr['PATCH_VERSION'] . ' ' . '(' . $arr['RELDATE'] . ' ' . $arr['RELTIME'] . ' ' . $arr['RELTZ'] . ')';
            } else {
                $MainVersion = $arr['RELEASE'];
                $Version     = $arr['RELEASE'] . '.' . $arr['DEV_LEVEL'];
                $FullVersion = $arr['RELEASE'] . '.' . $arr['DEV_LEVEL'] . ' (' . $arr['DEV_STATUS'] . ') ' . '(' . $arr['RELDATE'] . ' ' . $arr['RELTIME'] . ' ' . $arr['RELTZ'] . ')';
            }

            return [true, 'Joomla', $filename, (string) $FullVersion, (string) $MainVersion, (string) $Version];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is Magento.
     */
    private static function isMagento(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'app' . DS . 'Mage.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            $arr = ['major' => 0, 'minor' => 0, 'revision' => 0, 'patch' => 0];
            foreach ($arr as $key => $value) {
                preg_match('/.*\'' . $key . '\' *=\> *\'(\\d+)\'/', $content, $arrMatches, PREG_OFFSET_CAPTURE);
                if (count($arrMatches) > 0) {
                    (string) $arr[$key] = $arrMatches[1][0];
                }
            }

            $FullVersion = $arr['major'] . '.' . $arr['minor'] . '.' . $arr['revision'] . '.' . $arr['patch'];

            return [true, 'Magento', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is MediaWiki.
     */
    private static function isMediaWiki(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'includes' . DS . 'DefaultSettings.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            $pattern = '/.*\$wgVersion \= \'(\\d+\\.\\d+\\.\\d+)\'/';

            preg_match($pattern, $content, $arrMatches, PREG_OFFSET_CAPTURE);

            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'MediaWiki', $filename, $FullVersion, $FullVersion, $FullVersion, $root];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is phpBB.
     */
    private static function isphpBB(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'styles' . DS . 'prosilver' . DS . 'style.cfg';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            $pattern = '/.*phpbb_version \= (\\d+\\.\\d+\\.\\d+)/';

            preg_match($pattern, $content, $arrMatches, PREG_OFFSET_CAPTURE);

            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'phpBB', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is phpList.
     */
    private static function isphpList(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'public_html' . DS . 'lists' . DS . 'admin' . DS . 'init.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            preg_match('/.*"VERSION", "(.*)"/', $content, $arrMatches, PREG_OFFSET_CAPTURE);

            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'phpList', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the framework is phpMyAdmin.
     */
    private static function isphpmyadmin(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'libraries' . DS . 'config.class.php';
        if (!file_exists($filename)) {
            $filename = rtrim($root, DS) . DS . 'libraries' . DS . 'config.php';
        }

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            $pattern = '/.*PMA_VERSION\', *\'(\\d+\\.\\d+\\.\\d+)\'/';

            preg_match($pattern, $content, $arrMatches, PREG_OFFSET_CAPTURE);

            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'phpmyadmin', $filename, $FullVersion, $FullVersion, $FullVersion, $root];
        } else {
            return false;
        }
    }

    /**
     * Detect if the framework is PMP.
     */
    private static function isPMB(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'includes' . DS . 'config.inc.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            $pattern = '/\\$pmb_version_brut[[:blank:]]=[[:blank:]]"(\\d+\\.\\d+\\.\\d+(.*))"/';

            preg_match($pattern, $content, $arrMatches, PREG_OFFSET_CAPTURE);

            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'PMB', $filename, $FullVersion, $FullVersion, $FullVersion, $root];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is Prestashop.
     */
    private static function isPrestashop(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'config' . DS . 'settings.inc.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            $pattern = '/.*_PS_VERSION_\', *\'(.*)\'/';

            preg_match($pattern, $content, $arrMatches, PREG_OFFSET_CAPTURE);

            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'Prestashop', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is SilverStripe.
     */
    private static function isSilverStripe(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'cms' . DS . 'silverstripe_version';

        if (file_exists($filename)) {
            $FullVersion = (string) file_get_contents($filename);

            return [true, 'silverstripe', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is WordPress.
     */
    private static function isWordPress(string $root): bool|array
    {
        $filename = '';
        if (strpos($root, 'wp-admin') > 0) {
            $filename = rtrim(substr($filename, 0, strpos($root, 'wp-admin')), DS) . DS . 'wp-includes' . DS . 'version.php';
        } elseif (strpos($root, 'wp-content') > 0) {
            $filename = rtrim(substr($filename, 0, strpos($root, 'wp-content')), DS) . DS . 'wp-includes' . DS . 'version.php';
        } elseif (strpos($root, 'wp-includes') > 0) {
            $filename = rtrim(substr($filename, 0, strpos($root, 'wp-includes')), DS) . DS . 'wp-includes' . DS . 'version.php';
        } else {
            $filename = rtrim($root, DS) . DS . 'wp-includes' . DS . 'version.php';
            if (!file_exists($filename)) {
                $filename = rtrim(dirname($root), DS) . DS . 'wp-includes' . DS . 'version.php';
            }
        }

        if (file_exists($filename)) {
            // ---------------------------
            // ---      WordPress      ---
            // ---------------------------

            $CMS = 'Wordpress';

            $configuration = file_get_contents($filename);

            $pattern = '/.*\\$wp_version *= *\'(.*)\'/';

            preg_match($pattern, $configuration, $arrMatches, PREG_OFFSET_CAPTURE);

            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            // Get the website root folder
            $root = dirname(dirname($filename));

            return [true, $CMS, $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }

    /**
     * Detect if the CMS is x3cms.
     */
    private static function isx3cms(string $root): bool|array
    {
        $filename = rtrim($root, DS) . DS . 'INSTALL' . DS . 'index.php';

        if (file_exists($filename)) {
            $content = file_get_contents($filename);

            $pattern = '/.*\'X4VERSION\', *\'(\\d+\\.\\d+\\.\\d+)\'/';

            preg_match($pattern, $content, $arrMatches, PREG_OFFSET_CAPTURE);

            $FullVersion = (count($arrMatches) > 0) ? (string) $arrMatches[1][0] : '';

            return [true, 'x3cms', $filename, $FullVersion, $FullVersion, $FullVersion];
        } else {
            return false;
        }
    }
}

/**
 * PHP, JS and HTML code related to the progress bar functionnality.
 */
class aeSecureProgressBar
{
    protected $aeSession = null;

    private $_filename       = null;
    private $_ID             = null;
    private $_CSS            = 'progress-bar';
    private int $_frequency  = 1000; // refresh the progress bar each xx seconds  (1000=one second)
    private int $_start      = 0;    // f.i. 0   (current step in the progress bar)
    private int $_end        = 0;    // f.i. 100 (number of steps)
    private int $_pct        = 0;    // calculated progression in percentage

    protected static $instance = null;

    public function __construct($ID, $Class)
    {
        $this->_ID        = $ID;
        $this->_CSS       = $Class;
        $this->_filename  = str_replace('.php', '.tmp', __FILE__);
        $this->_start     = 0;
        $this->_end       = 100;
        $this->_pct       = 0;
        // Refresh frequency
        $this->_frequency = PROGRESSBARFREQUENCY * 1000;

        $this->aeSession = aeSecureSession::getInstance();

        return true;
    }

    /**
     * Getter & Setter for the Start position of the progress bar (by default, 0).
     */
    public function getStart(): int
    {
        return $this->_start;
    }

    public function setStart(int $value = 0): void
    {
        $this->_start = $value;
    }

    /**
     * Getter & Setter for the End position of the progress bar (by default, 100).
     */
    public function getEnd(): int
    {
        return 0 == $this->_end ? 1 : $this->_end;
    }

    public function setEnd(int $value = 100): void
    {
        $this->_end = $value;
    }

    /**
     * Remove the temporary file with the progress indicator.
     */
    public function clean()
    {
        if (file_exists($this->_filename)) {
            unlink($this->_filename);
        }
    }

    /**
     * Increment the position of the progress bar percentage (write the percentage
     * in the temporary file that will be used by the ajax Progress bar).
     *
     * @return bool
     */
    public function incTaskCount()
    {
        if (true === $this->aeSession->get('Debug', DEBUG)) {
            return false;
        }

        ++$this->_start;

        if (($this->_start / $this->getEnd()) > $this->_pct) {
            $this->_pct = intval(intval($this->_start) / $this->getEnd());

            // *******************************************************************
            // *******************************************************************
            // *******************************************************************
            //
            // The session should be closed otherwise Ajax request won't be called
            // asynchronously and the progress bar won't be incremented
            // http://stackoverflow.com/questions/3506574
            session_write_close();
            //
            // *******************************************************************

            if ($handle = fopen($this->_filename, 'w+')) {
                fwrite($handle, (int)($this->_pct * 100));
                fclose($handle);
            }
        }

        return true;
    }

    /**
     * Return the current progress value; read it from a file.
     */
    public function getProgress()
    {
        if (true === $this->aeSession->get('Debug', DEBUG)) {
            return false;
        }

        header('Content-Type: application/json');
        header('Cache-Control: no-cache');

        if (file_exists($this->_filename)) {
            echo json_encode(['pct' => file_get_contents($this->_filename)], JSON_THROW_ON_ERROR);
        } else {
            echo json_encode(['pct' => '100']);
        }

        try {
            ob_end_flush();
            flush();
        } catch (Exception $e) {
        }

        die();
    }

    /**
     * Generate the HTML code for the progress bar.
     *
     * @return type
     */
    public function getHTML()
    {
        if (true === $this->aeSession->get('Debug', DEBUG)) {
            return false;
        }

        echo
            '<div id="' . $this->_ID . '" class="progress" style="display:none;">' .
                '<div class="progress progress-striped active">' .
                    '<div class="' . $this->_CSS . '" aria-valuenow="1" aria-valuemin="1" ' .
                        'aria-valuemax="100">' .
                    '</div>' .
                '</div>' .
            '</div>';
    }

    /**
     * Generate the JS code for the progress bar (initialization and show evolution
     * during the scanning).
     *
     * @param mixed $what
     */
    public function getJSFunction($what = 'function')
    {
        if (true === $this->aeSession->get('Debug', DEBUG)) {
            return false;
        }

        switch ($what) {
            case 'initialize':
                // Variables needed for the progress bar JS code

                echo 'progressFct=null;previousPct=0;';

                break;
            case 'ajax_before':
                // The long process will start; initialize the progress bar and show it

                echo
                 'previousPct=0;' .
                 '$(".' . $this->_CSS . '").attr("aria-valuenow", 0);' .
                 '$(".' . $this->_CSS . '").css("width","0%");' .
                 '$(".' . $this->_CSS . '").html($(".' . $this->_CSS . '").attr("aria-valuenow") + "%");' .
                 '$("#' . $this->_ID . '").fadeIn(300); ' .
                 'progressFct=setInterval( function () {getProgress();},' . $this->_frequency . ');';

                break;
            case 'ajax_success':
                // The long process is now finished, put the progress bar to 100% and then hide it

                echo
                  '$(".' . $this->_CSS . '").attr("aria-valuenow", 100);' .
                  '$(".' . $this->_CSS . '").css("width","100%");' .
                  '$(".' . $this->_CSS . '").html($(".' . $this->_CSS . '").attr("aria-valuenow") + "%");' .
                  'clearTimeout(progressFct);' .
                  '$("#' . $this->_ID . '").fadeOut(300);';

                break;
            case 'function':
                // The long process is running, update the progress bar

                echo
                    'function getProgress() {
                        $.ajax({
                            url:"' . FILE . '",
                            data:"task=progress",
                            type:"' . ((true === $this->aeSession->get('Debug', DEBUG)) ? 'GET' : 'POST') . '",
                            async:true,
                            timeout: 600000,  // Scanning a site can be very long
                            cache:false,
                            dataType:"json",
                            success: function(json) {
                                percentage=parseInt(json.pct);
                                if (percentage>=100) {
                                    $(".' . $this->_CSS . '").attr("aria-valuenow", 100);
                                    $(".' . $this->_CSS . '").css("width","100%");
                                    $(".' . $this->_CSS . '").html($(".' . $this->_CSS . '").attr("aria-valuenow") + "%");
                                    clearTimeout(progressFct);
                                    $("#' . $this->_ID . '").fadeOut(300);
                                } else {
                                    if (percentage>previousPct) {
                                        if ($("#gettingFiles").length) $("#gettingFiles").hide();
                                        if (percentage>100) percentage=100;
                                        $(".' . $this->_CSS . '").attr("aria-valuenow", Math.round(percentage));
                                        $(".' . $this->_CSS . '").css("width", percentage + "%");
                                        $(".' . $this->_CSS . '").html($(".' . $this->_CSS . '").attr("aria-valuenow") + "%");
                                        $("#' . $this->_ID . '").show();
                                        previousPct=percentage;
                                    }
                                }
                            } // success
                        });
                        return;
                    } // function getProgress()';

                break;
        }
    }

    /**
     * @param type $ID    ID to give to the HTML progress bar container
     * @param type $Class CSS class to give to the HTML container
     *
     * @return type
     */
    public static function getInstance($ID = 'ajaxResultPct', $Class = 'progress-bar')
    {
        if (null === self::$instance) {
            self::$instance = new aeSecureProgressBar($ID, $Class);
        }

        return self::$instance;
    }
}

/**
 * The scanner himself.
 */
class aeSecureScan
{
    // hash of files already scanned and are viruses (the file is a virus)
    public const BLACKLIST = 'aesecure_quickscan_blacklist.json';

    // JSON for the detected CMS (f. i. aesecure_quickscan_J!3.9.0.json for a Joomla 3.9.0 version)
    public const CMS = 'aesecure_quickscan_%s.json';

    // hash of files already scanned and where a virus was found (the file contains a virus)
    public const EDITED = 'aesecure_quickscan_edited.json';

    // hash of files that can be considered as safe
    public const OTHER = 'aesecure_quickscan_other.json';

    // JSON with patterns to scan for finding viruses
    public const PATTERN   = 'aesecure_quickscan_pattern.json';

    // hash of files that can be considered as safe
    public const WHITELIST = 'aesecure_quickscan_whitelist.json';

    // List of supported CMS
    public const SUPPORTED_CMS = 'aesecure_quickscan_supported_cms.json';

    public const FOLDERS = 'aesecure_quickscan_folders.json';

    protected $aeFiles    = null;
    protected $aeLanguage = null;
    protected $aeLog      = null;
    protected $aeSession  = null;
    protected $aeProgress = null;

    private $_directory      = null; // Folder to scan
    private $_start          = 0;    // When processing files by block (files #1 till #2500, #2501 till #5000, ...) start is the "from" part f.i. 2501
    private int $_end        = 0;    //    and _end will be the end part f.i. 5000
    private $_arrCMS         = null; // Used by the hash functions
    private $_arrRegex       = null; // Array with regex patterns to match

    private $_arrCMSHashes       = null; // Array with the hash of the installed CMS
    private $_arrWhiteListHashes = null; // Array with whitelisted files (files from the CMS core)
    private $_arrOtherHashes     = null; // Array with whitelisted files (files whitelisted during aeSecure DeepScan runs)
    private $_arrBlackListHashes = null; // Array with blacklisted files (the file is a virus)
    private $_arrEditedHashes    = null; // Array with hash of edited files (a virus was appended in a file)
    private $_arrExtHashes       = null; // Array with the hash of the installed CMS extensions

    public function __construct()
    {
        date_default_timezone_set('Europe/Brussels');
        setlocale(LC_TIME, 'fr_FR.utf8', 'fra');

        // Instanciate objects
        $this->aeLanguage = aeSecureLanguage::getInstance();
        $this->aeFiles    = aeSecureFiles::getInstance();
        $this->aeProgress = aeSecureProgressBar::getInstance();

        $this->aeSession = aeSecureSession::getInstance();

        // Create the logfile
        if (true === $this->aeSession->get('Debug', DEBUG)) {
            $this->aeLog = aeSecureLog::getInstance(str_replace('.php', '.files.tmp', __FILE__));
        }

        $rootFolder = DIR;
        $this->aeSession->set('folder', '');

        // By default, scan the current directory.
        // In Expert mode, allow to use a session to store the name of the folder
        // Get the folder to process
        if (true === $this->aeSession->get('Expert', EXPERT)) {
            $folder = base64_decode((string) aeSecureFct::getParam('folder', 'string', ''));
            if ('' == $folder) {
                $folder = $this->aeSession->get('folder', $rootFolder);
            }

            // Check if the user has specified a folder in the user entry form
            if (is_dir($folder)) {
                $this->aeSession->set('folder', $folder);
            }
        } else {
            $tmp = $this->aeSession->get('folder', '');
            if ('' == $tmp) {
                $this->aeSession->set('folder', $rootFolder);
            }
        }

        // When running the scan for f.i. only 1000 files and not all files present on the server,
        // the start parameter will f.i. be set to 0 while the end parameter will be set to 1000.
        // This is just like a pagination so processing the 1000 next files will be :
        // start=1000 and end=1000.
        $this->_start = aeSecureFct::getParam('start', 'integer', 0);
        $this->_end   = $this->_start + aeSecureFct::getParam('end', 'integer', 0);

        $this->_directory = $rootFolder;
        if ($this->aeSession->get('Expert', EXPERT)) {
            $this->_directory =  trim((string) $this->aeSession->get('folder', $rootFolder));
            if ('' == $this->_directory) {
                $this->_directory = $rootFolder;
            }
        }

        $this->_arrCMS = [['joomla' => 'J!'], ['wordpress' => 'WP']];

        if (!is_file($file = DIR . DS . self::PATTERN)) {
            aeSecureDownload::get($file, 'settings/');
        }

        return true;
    }

    public function directory()
    {
        return $this->_directory;
    }

    /**
     * The aesecure_quickscan_pattern.json is using constant for the disclaimer info.
     * These constants should be replaced by their translated text.
     *
     * @param type $disclaimer
     *
     * @return string
     */
    public function getDisclaimerText($disclaimer)
    {
        $return = match ($disclaimer) {
            'HIGHPROBALITY'               => $this->aeLanguage->get('HIGHPROBALITY'),
            'HIGHPROBALITYFALSEGIF'       => $this->aeLanguage->get('HIGHPROBALITYFALSEGIF'),
            'WARNINGBASE64ENCODEDPATTERN' => $this->aeLanguage->get('WARNINGBASE64ENCODEDPATTERN'),
            'HIGHPROBALITYBADSITE'        => $this->aeLanguage->get('HIGHPROBALITYBADSITE'),
            'HIGHPROBALITYBASE64KEYWORD'  => $this->aeLanguage->get('HIGHPROBALITYBASE64KEYWORD'),
            'WARNINGNOTMANDATORYAVIRUS'   => $this->aeLanguage->get('WARNINGNOTMANDATORYAVIRUS'),
            default                       => $disclaimer . ((true === $this->aeSession->get('Debug', DEBUG))
                ? ' *please add translation*'
                : ''),
        };

        return $return;
    }

    /**
     * Add a file in the whitelist.
     */
    public function WhiteList(?string $filename = null)
    {
        // Don't white list files in demo mode, simulate that everything was ok (return -1)
        if (DEMO) {
            return -1;
        }

        if (!is_file($file = DIR . DS . self::WHITELIST)) {
            aeSecureDownload::get($file, 'settings/');
        }

        if (!is_file($file)) {
            die(sprintf('Sorry, the file [%s] is missing', basename($file)));
        }

        if (file_exists($filename)) {
            $json = json_decode(file_get_contents($file), true, 512, JSON_THROW_ON_ERROR);

            $sha = md5_file($filename);

            if (!(isset($json[$sha]))) {
                $json[$sha] = 1;
            }

            asort($json);

            // Output the file with all hashes
            $fp = fopen($file, 'w');
            fwrite($fp, json_encode($json, JSON_THROW_ON_ERROR));
            fclose($fp);
            unset($fp);
        }

        return -1;
    }

    /**
     * Process the action (like running the scan or displaying the progress bar).
     */
    public function Process()
    {
        if (!is_file($file = DIR . DS . self::PATTERN)) {
            aeSecureDownload::get($file, 'settings/');
        }

        if (!is_file($file)) {
            die(sprintf('Sorry, the file [%s] is missing', basename($file)));
        }

        $this->_arrRegex = json_decode(file_get_contents($file), true, 512, JSON_THROW_ON_ERROR);

        // Process the task if any, from POST or GET depending on the debug mode state
        // Get the folder to process
        $this->_directory = DIR;
        if ($this->aeSession->get('Expert', EXPERT)) {
            $this->_directory = trim((string) $this->aeSession->get('folder', DIR));
            if ('' == $this->_directory) {
                $this->_directory = DIR;
            }
        }

        $task = aeSecureFct::getParam('task', 'string', '');

        if ('' !== $task) {
            switch ($task) {
                case 'checkwhitelist': {
                    // Detect the existence of the whitelist.json file
                    $filename = DIR . DS . self::WHITELIST;
                    echo file_exists($filename) ? 1 : 0;
                    die();

                    break;
                }

                case 'byebye': {
                    // Don't kill files in demo mode, simulate that everything was ok (return -1)
                    if (DEMO) {
                        die(-1);
                    }

                    // keepwhitelist is a variable posted by the Ajax request and will
                    // inform if the script should or not remove the user's whitelist file.
                    $bKeepWhiteList = aeSecureFct::getParam('keepwhitelist', 'boolean', true);

                    // get CMS for later use
                    $file = DIR . DS . self::SUPPORTED_CMS;
                    $arrCMS = json_decode(file_get_contents($file), true, 512, JSON_THROW_ON_ERROR);
                    $arr = aeSecureCMS::getInfo($this->_directory);
                    $CMS = $arr[0];
                    $prefix = $arrCMS[strtolower($CMS)]['prefix'];

                    if (true !== $this->aeSession->get('Debug', DEBUG)) {
                        // Get the list of aeSecure QuickScan JSON
                        // Need to use "DIR . DS" so filenames will be absolute which is needed
                        $arrDeleteFiles = glob(DIR . DS . 'aesecure_quickscan_*.json');

                        // And don't scan this script also
                        $arrDeleteFiles[] = DIR . DS . FILE;

                        $arrDeleteFiles[] = DIR . DS . 'aesecure.png';
                        $arrDeleteFiles[] = DIR . DS . 'banner.svg';
                        $arrDeleteFiles[] = DIR . DS . 'LICENCE';
                        $arrDeleteFiles[] = DIR . DS . 'make_hashes.php';
                        $arrDeleteFiles[] = DIR . DS . 'octocat.tmpl';
                        $arrDeleteFiles[] = DIR . DS . 'readme.md';
                        $arrDeleteFiles[] = DIR . DS . 'images' . DS . 'expert.png';
                        $arrDeleteFiles[] = DIR . DS . 'images' . DS . 'files.png';
                        $arrDeleteFiles[] = DIR . DS . 'images' . DS . 'files_extended.png';
                        $arrDeleteFiles[] = DIR . DS . 'images' . DS . 'interface.png';
                        $arrDeleteFiles[] = DIR . DS . 'images' . DS . 'nothing_to_scan.png';
                        $arrDeleteFiles[] = DIR . DS . 'images' . DS . 'virus_of_mine.png';

                        // Kill the debug log file if present
                        if (null !== $this->aeLog) {
                            $arrDeleteFiles[] = $this->aeLog->filename();
                        }

                        foreach ($arrDeleteFiles as $filename) {
                            $bDelete = true;

                            if ($filename == DIR . DS . self::WHITELIST) {
                                // If $bKeepWhiteList=true, we can't delete the file
                                $bDelete = !$bKeepWhiteList;
                            }

                            if ($bDelete) {
                                if (is_file($filename) && is_readable($filename)) {
                                    unlink($filename);
                                }
                            }
                        }
                        // remove quickscan directories
                        $arrDeleteFolders = ['settings',
                                             'utils'];
                        if (!$bKeepWhiteList) { // delete all hashes directories
                            $arrDeleteFolders[] = 'hashes';
                        } else { // delete only CMS hashes directories
                            $arrDeleteFolders[] = 'hashes'.DS.'joomla';
                            $arrDeleteFolders[] = 'hashes'.DS.'wordpress';
                        }
                        foreach ($arrDeleteFolders as $folder) {
                            $this->aeFiles->rrmdir(DIR . DS . $folder, true, []);
                        }
                        if ($bKeepWhiteList) { // just keep customer extensions hashes
                            // get std extensions list
                            $url = 'hashes/'. $prefix . 'extensions';
                            $dir = self::remotedir($url);

                            foreach ($dir as $filename) {
                                $file = DIR . DS . 'hashes'.DS.$prefix.'extensions'.DS.$filename;
                                if (is_file($file) && is_readable($file)) { // std extension hash : remove it
                                    unlink($file);
                                }
                            }
                        }
                    }

                    die('<div class="alert alert-info" role="alert">' .
                        '<strong>Success</strong> The file ' . FILE .
                        ' has been removed from the server.</div>');

                    break;
                }

                case 'doscan': {
                    die($this->doScan());

                    break;
                }

                case 'getcountfiles': {
                    die($this->getCountFiles());

                    break;
                }

                case 'killfile': {
                    // Don't kill files in demo mode, simulate that everything was ok (return -1)
                    if (DEMO) {
                        die(-1);
                    }

                    $filename = base64_decode((string) aeSecureFct::getParam('filename', 'string', ''));

                    if ('' != $filename) {
                        die($this->aeFiles->KillFile($filename));
                    }

                    break;
                }

                case 'seedebug': {
                    $filename = $this->aeLog->filename();
                    if (file_exists($filename)) {
                        $src = htmlentities((string) $this->aeFiles->SeeFile($filename));
                    } else {
                        $src = 'File ' . $filename . ' not found';
                    }
                    die('<pre>' . $src . '</pre>');

                    break;
                }

                case 'seefile': {
                    if (DEMO) {
                        // Don't return source code in DEMO mode
                        echo '<div class="alert alert-warning" role="alert">' .
                            '<strong>Demo mode</strong>&nbsp;This functionnality is not enabled ' .
                            'during the demo mode; sorry.</div>';
                        die();
                    }

                    $filename = base64_decode((string) aeSecureFct::getParam('filename', 'string', ''));
                    $src      = htmlentities((string) $this->aeFiles->SeeFile($filename));

                    // Highlight patterns in the file source code
                    foreach ($this->_arrRegex as $regex) {
                        $arrMatch = [];

                        preg_match_all('/' . $regex['pattern'] . '/im', $src, $arrMatch, PREG_OFFSET_CAPTURE);

                        // Something found? Greater than zero means; yes, the regex has been matched.
                        if (count($arrMatch[0]) > 0) {
                            $disclaimer = (key_exists('disclaimer', $regex)
                                ? $this->getDisclaimerText($regex['disclaimer'])
                                : '');

                            $patternFound = count($arrMatch[1]);

                            for ($i = 0; $i < $patternFound; $i++) {
                                // Get the found keyword (f.i. AnonGhost then, in the second loop,
                                // bash_history in our example)
                                $keyword = (isset($arrMatch[1][$i][0])
                                    ? $keyword = $arrMatch[1][$i][0]
                                    : '');

                                if (!in_array($arrMatch[0], [null, ''])) {
                                    $src = str_replace($keyword, '<span class="blink alert ' .
                                        'alert-danger text-danger highlight" role="alert" title="' .
                                        $disclaimer . '">' . $keyword . '</span>', $src);
                                }
                            }
                        }
                    }

                    $src = '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr"><head>' .
                        '<meta charset="utf-8" />' .
                        '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />' .
                        '<meta http-equiv="X-UA-Compatible" content="IE=edge" />' .
                        '<meta name="robots" content="noindex, nofollow" />' .
                        '<meta name="viewport" content="width=device-width, initial-scale=1" />' .
                        '<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" />' .
                        '<title>aeSecure QuickScan | ' . basename($filename) . '</title>' .
                        '<link href="https://www.avonture.be/images/medias/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon"/>' .
                        '<style type="text/css">' .
                            '.highlight {border-bottom:dotted;color:red;padding:0px;font-weight:bolder;}' .
                            '.blink {animation: blink 1s steps(5, start) infinite; -webkit-animation: blink 1s steps(5, start) infinite; } @keyframes blink {to {visibility: hidden;}} @-webkit-keyframes blink {to {visibility: hidden;}}' .
                            '.footer{padding-top:50px;padding-left:10px;}' .
                        '</style>' .
                        '</head>' .
                        '<body>' .
                        '<div class="container-fluid" role="main">' .
                        '<h1>' . $filename . '</h1><pre><code>' . $src . '</code></pre></div>' .
                        self::getHTMLFooter() .
                        '</body></html>';

                    die($src);

                    break;
                }

                case 'progress': {
                    die($this->aeProgress->getProgress());

                    break;
                }

                case 'cleansite': {
                    die($this->doCleanSite());

                    break;
                }

                case 'chgfolder': {
                    // Get the CMS if any
                    [$CMS, $CMSFullVersion, $CMSMainVersion, $CMSVersion, $SiteRoot] = aeSecureCMS::getInfo($this->_directory);

                    // reset the list of files to be sure that the scanner will process them again
                    $this->aeSession->set('arrFiles', null);
                    $this->aeSession->set('folder', null);

                    header('Content-Type: application/json');
                    header('Cache-Control: no-cache');
                    echo json_encode(
                        [
                            'CMS' => $CMS . ' ' . $CMSFullVersion,
                        ],
                        JSON_THROW_ON_ERROR
                    );
                    die('');

                    break;
                }

                case 'whitelist': {
                    $filename = base64_decode((string) aeSecureFct::getParam('filename', 'string', ''));

                    die($this->WhiteList($filename));

                    break;
                }

                default: {
                    die('<div class="alert alert-danger" role="alert"><strong>Invalid call</strong>' .
                        'Sorry, the call to ' . FILE . ' is invalid.</div>');
                }
            }
        }

        // No given task given, display the HTML page
    }
    public function remotedir($dir)
    {
        $githubdir = aeSecureDownload::getDir($dir);

        if (!is_array($githubdir)) {
            return [];
        }
        $ret = [];
        foreach ($githubdir as $obj) {
            if (isset($obj->name)) {
                if (!in_array($obj->name, $ret)) {
                    $ret[] = $obj->name;
                }
            }
        }
        return $ret;
    }

    public function getExtHashes(string $CMS): array
    {
        if (null == $CMS) {
            return [null, null];
        }

        $file = DIR . DS . self::SUPPORTED_CMS;
        if (!is_file($file)) {
            return [null, null];
        }

        $arrCMS = json_decode(file_get_contents($file), true, 512, JSON_THROW_ON_ERROR);
        $prefix = $arrCMS[strtolower($CMS)]['prefix'];

        $hashes = DIR . DS . 'hashes' . DS . $prefix . 'extensions';
        if (!is_dir($hashes)) { // extensions hashes not loaded ?
            if (!is_dir(DIR . DS . 'hashes')) { // hashes dir ok ?
                @mkdir(DIR . DS . 'hashes');  // no : create it
            }
            @mkdir($hashes); // create extensions dir
        }
        $files = array_diff(scandir($hashes), array('..', '.')); // current list
        // get extensions directory content from github
        $dir = 'hashes/'. $prefix . 'extensions';
        $dir = self::remotedir($dir);
        foreach ($dir as $file) {
            if (in_array($file, $files)) {
                continue;// already loaded : next please
            }
            aeSecureDownload::get($file, 'hashes/'.$prefix . 'extensions');
            if (file_exists(DIR.DS.$file)) { // copy ok
                rename(DIR.DS.$file, $hashes.DS.$file); // move it to hashes dir
            }
        }
        $arrHashes = [];
        $files = array_diff(scandir($hashes), array('..', '.'));
        foreach ($files as $key => $json) {
            $file_info = pathinfo($hashes.DS.$json);
            if (isset($file_info['extension']) && $file_info['extension'] === 'json') {
                $arr = json_decode(file_get_contents($hashes.DS.$json), true, 512, JSON_THROW_ON_ERROR);
                $arrHashes = array_merge($arrHashes, $arr);
            }
        }

        return [$arrHashes];
    }


    /**
     * Download from avonture.be a json file with the hash of natives files of,
     * f.i., Joomla 3.9.0. If the download is successfull, the downloaded file will be stored
     * in the same folder than this script, name : aesecure_quickscan_CMS.json. This file will be killed
     * when the user will click on the "Kill this script" button available on the user's form.
     *
     * @param string $CMS     f.i. "Joomla"
     * @param string $version f.i. "2.5.27"
     *
     * @return array array of hashes like below.  All these hashes are native files => no scan needed
     *               array(3707) {
     *               ["a62f6525e7418dc679ad1c6c2ebe662dc67207cb"]=> int(1)
     *               ["43ae5dae1df4bec4ecb2879146518283f55eab67"]=> int(1)
     *               ["9b0661ab4d6d640ef8275995dfc2830c974af781"]=> int(1)
     *               ["6e71c8f8ba7b9318d57773c4cdb60e98ffb43eb0"]=> int(1)
     */
    public function gethashes(string $CMS, string $version): array
    {
        if (null == $CMS) {
            return [null, null];
        }

        $file = DIR . DS . self::SUPPORTED_CMS;
        if (!is_file($file)) {
            return [null, null];
        }

        $arrCMS = json_decode(file_get_contents($file), true, 512, JSON_THROW_ON_ERROR);

        // arrCMS is an array like this :
        //  ['joomla']=array("prefix"=>"J!","archives"=>"http://")
        //  One entry by CMS with an array with, at least, "prefix" and "archives"
        $prefix = $arrCMS[strtolower($CMS)]['prefix'];

        // Build the name of the JSON file with whitelisted hash for the CMS found on the system
        // Build a filename like c:/site/hacked/aesecure_quickscan_J!3.9.0.json
        $json = DIR . DS . sprintf(self::CMS, $prefix . $version);
        // Pascal : recherche dans repertoire hashes :
        // $json = DIR . DS . 'hashes'. DS.strtolower($CMS).DS.$prefix . $version.'.json';

        // If the file has zero byte, remove it, not normal.

        // @TODO : file_exists while raise a fatal error when there is an open_basedir
        // restriction and when aeSecure QuickScan has been stored in the website rootfolder
        // => by searching one folder above, open_basedir will block the request and will return
        // an empty page
        if ((null != $json) && file_exists($json) && (0 == filesize($json))) {
            @unlink($json);
        }

        if (!file_exists($json)) {
            // Try to download the JSON for the CMS and the version found
            // On GitHub the file with CMS hashes doesn't start with the
            // "aesecure_quickscan_" prefix
            $json = rtrim(dirname($json), DS) . DS .
                str_replace('aesecure_quickscan_', '', basename($json));

            aeSecureDownload::get($json, 'hashes/' . strtolower($CMS) . '/');

            if (file_exists($json)) {
                // rename the file from f.i. "J!3.9.0.json" to "aesecure_quickscan_J!3.9.0.json"
                // so all Quickscan files are using the "aesecure_quickscan_" prefix
                $old  = $json;
                $json = rtrim(dirname($old), DS) . DS . 'aesecure_quickscan_' . basename($old);
                rename($old, $json);
            }
        }

        // Do we have the json file with all hashes?
        // Created just now or already there from a previous run?
        $arrHashes = [];

        if (file_exists($json)) {
            $arrHashes = json_decode(file_get_contents($json), true, 512, JSON_THROW_ON_ERROR);
        }

        return [$arrHashes];
    }

    public function getHTMLFooter(): string
    {
        $aeLanguage = aeSecureLanguage::getInstance();

        return '<footer class="footer"><a href=' . REPO . ' target="_blank">' .
         '&copy; Avonture Christophe 2013-' . date('Y') . ' | <span style="font-style:italic;">' .
         'aeSecure QuickScan v.' . VERSION . '</span>' .
        '</a></footer>';
    }

    public function getCountPatterns(): int
    {
        return count($this->_arrRegex);
    }

    /**
     * Clean the site by emptying the cache folders and temporary folders.
     *
     * @return type
     */
    private function doCleanSite(): void
    {
        $output = '';

        // Try to clean these folders
        $arr = [
            'administrator' . DS . 'cache',
            'aesecure' . DS . 'cache',
            'aesecure' . DS . 'tmp',
            'cache',
            'temp'
        ];

        foreach ($arr as $tmp) {
            if (is_dir($folder = rtrim((string) $this->_directory, DS) . DS . $tmp)) {
                if (!DEMO) {
                    $this->aeFiles->rrmdir(
                        $dir            = $folder,
                        $killroot       = false,
                        $arrIgnoreFiles = ['.htaccess', 'index.html']
                    );
                }
                // Don't give full path in demo mode
                if (DEMO) {
                    $folder = str_replace(DIR, '', $folder);
                }
                $output .= '<li><span class="glyphicon glyphicon-thumbs-up">&nbsp;</span>' .
                    sprintf($this->aeLanguage->get('CLEANFOLDER'), $folder) . '</li>';
            }
        }

        echo '<ul class="list-unstyled text-success">' . $output . '</ul>';
    }

    /**
     * Read the json files with hashes (whitelist, other and blacklist) and initialize arrays
     * This function is called by the GetCountFiles() and doScan() functions.
     */
    private function initializeHashes(): bool
    {
        // Try to determine the CMS used and, if found one, try to get a json file
        // with hashes of native files. If found, this is a tremendous news since
        // these files are known as safe (native ones!) so should not be scanned
        // when the file present on the website has the same hash meaning that this
        // file was never altered at all.
        [$CMS, $CMSFullVersion, $CMSMainVersion, $CMSVersion, $SiteRoot] = aeSecureCMS::getInfo($this->_directory);

        [$this->_arrCMSHashes] = $this->gethashes($CMS, $CMSVersion);

        [$this->_arrExtHashes] = $this->getExtHashes($CMS);

        // Get whitelist.json and other.json
        for ($i = 0; $i < 2; $i++) {
            $file = (0 == $i ? self::WHITELIST : self::OTHER);

            $arr = [];

            if (file_exists(DIR . DS . $file)) {
                $arr = json_decode(file_get_contents(DIR . DS . $file), true, 512, JSON_THROW_ON_ERROR);
            } else {
                // Try to download the file

                $json = DIR . DS . $file;

                // Download the whitelist file if possible
                if (!file_exists($json)) {
                    aeSecureDownload::get($json, 'settings/');
                }

                // If the file has zero byte, delete it, the download was not successfull
                try {
                    if (file_exists($json) && (0 == filesize($json))) {
                        unlink($json);
                    }
                } catch (Exception $ex) {
                }

                if (file_exists($json)) {
                    $arr = json_decode(file_get_contents($json), true, 512, JSON_THROW_ON_ERROR);
                }
            }

            if (0 == $i) {
                $this->_arrWhiteListHashes = $arr;
            } else {
                $this->_arrOtherHashes = $arr;
            }
        }

        // Check if there is a blacklist hash file
        if (!file_exists($file = DIR . DS . self::BLACKLIST)) {
            aeSecureDownload::get($file, 'settings/');
        }

        // If so, load the file
        if (file_exists($file)) {
            $this->_arrBlackListHashes = json_decode(file_get_contents($file), true, 512, JSON_THROW_ON_ERROR);
        }

        // Check if there is a edited hash file
        if (!file_exists($file = DIR . DS . self::EDITED)) {
            aeSecureDownload::get($file, 'settings/');
        }

        // If so, load the file
        if (file_exists($file)) {
            $this->_arrEditedHashes = json_decode(file_get_contents($file), true, 512, JSON_THROW_ON_ERROR);
        }

        return true;
    }

    /**
     * Scan the disk and search for each files that will be then processed by the scanner.
     * This function will initialize the arrFiles session variable.
     */
    private function getCountFiles(mixed $echo = true, mixed &$arrFiles = null): bool
    {
        try {
            clearstatcache();

            if (!get_cfg_var('safe_mode')) {
                // set_time_limit isn't used when safe_mode is active
                // No max execution time
                @ini_set('max_execution_time', '0');
                // Remove time limit; avoid 504 HTTP errors
                @ini_set('set_time_limit', '0');
            }
        } catch (Exception $e) {
        }

        // Allocate the maximum allowed memory to the script (-1 = no limit)
        @ini_set('memory_limit', (true !== $this->aeSession->get('Debug', DEBUG)) ? -1 : MEMORY_LIMIT);

        if (!is_dir($this->_directory)) {
            echo '<hr/><div class="alert alert-danger" role="alert"><strong>' .
                sprintf(DIRNOTFOUND, $this->_directory) . '</strong></div>';

            return false;
        }

        $previousFolder = $this->aeSession->get('Folder', null);

        $arrFiles        = [];
        $wNbrBlacklisted = 0;
        $wNbrEdited      = 0;
        $wNbrSkipped     = 0;
        $wNbrWhitelisted = 0;

        if ((null == $arrFiles) || (0 == count($arrFiles))) {
            // The "arrFiles" session variable is either not found or equal to NULL ==>
            // Get the list of files in the current folder and subfolders by scanning
            // files and folders on the disk
            $dir   = new RecursiveDirectoryIterator($this->_directory, RecursiveDirectoryIterator::SKIP_DOTS);
            $files = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::LEAVES_ONLY);

            // Release a few memory
            unset($dir);

            $arrFiles = [];
            $arrFilesBlacklisted = [];
            $arrFilesEditlisted = [];

            // Collect all files but ignore this script
            $IgnoreArchives    = $this->aeSession->get('IgnoreArchives', true);
            $IgnoreDocuments   = $this->aeSession->get('IgnoreDocuments', true);
            $IgnoreFonts       = $this->aeSession->get('IgnoreFonts', true);
            $IgnoreImages      = $this->aeSession->get('IgnoreImages', true);
            $IgnoreMedia       = $this->aeSession->get('IgnoreMedia', true);
            $IgnoreSoundMovies = $this->aeSession->get('IgnoreSoundMovies', true);
            $IgnoreText        = $this->aeSession->get('IgnoreText', true);

            // Prepare the arrays with extensions, by category
            $arrArchive     = explode(',', str_replace(' ', '', ExtArchives));
            $arrDocuments   = explode(',', str_replace(' ', '', ExtDocuments));
            $arrFonts       = explode(',', str_replace(' ', '', ExtFonts));
            $arrImages      = explode(',', str_replace(' ', '', ExtImages));
            $arrMedias      = explode(',', str_replace(' ', '', ExtMedia));
            $arrSoundMovies = explode(',', str_replace(' ', '', ExtSoundMovies));
            $arrText        = explode(',', str_replace(' ', '', ExtText));

            // Initialize arrays with hashes
            self::initializeHashes();

            // Don't scan aeSecure QuickScan files
            // Need to use "DIR . DS" so filenames will be absolute which is needed
            $arrSkipFiles = glob(DIR . DS . 'aesecure_quickscan_*.json');

            // And don't scan this script also
            $arrSkipFiles[] = DIR . DS . FILE;

            foreach ($files as $filename => $object) {
                // Don't process these files
                if (in_array($filename, $arrSkipFiles)) {
                    continue;
                }

                try {
                    // Only files and only when size is greater than zero
                    // (A virus can't be an empty file)
                    if (is_file($filename) && (filesize($filename) !== 0)) {
                        $md5 = md5_file($filename);

                        if (isset($this->_arrBlackListHashes[$md5])) {
                            // Already known as bad?
                            $arrFiles[] = $filename;
                            $arrFilesBlacklisted[] = str_replace(DIR . '/', '', $filename);
                            ++$wNbrBlacklisted;
                        } elseif (isset($this->_arrEditedHashes[$md5])) {
                            // Already known as having a virus in it?
                            $arrFiles[] = $filename;
                            $arrFilesEditlisted[] = str_replace(DIR . '/', '', $filename);
                            ++$wNbrEdited;
                        } elseif (isset($this->_arrCMSHashes[$md5]) || isset($this->_arrWhiteListHashes[$md5]) || isset($this->_arrOtherHashes[$md5]) || isset($this->_arrExtHashes[$md5])) {
                            // if the hash of file is listed in the CMS core file,
                            // white list or other hashes, don't process it, the file is safe
                            ++$wNbrWhitelisted;
                        } else {
                            // Get file's extension
                            $ext = $object->getExtension();

                            $bAdd = true;

                            if (($IgnoreArchives) && (in_array($ext, $arrArchive))) {
                                $bAdd = false;
                            }
                            if (($IgnoreDocuments) && (in_array($ext, $arrDocuments))) {
                                $bAdd = false;
                            }
                            if (($IgnoreFonts) && (in_array($ext, $arrFonts))) {
                                $bAdd = false;
                            }
                            if (($IgnoreImages) && (in_array($ext, $arrImages))) {
                                $bAdd = false;
                            }
                            if (($IgnoreMedia) && (in_array($ext, $arrMedias))) {
                                $bAdd = false;
                            }
                            if (($IgnoreSoundMovies) && (in_array($ext, $arrSoundMovies))) {
                                $bAdd = false;
                            }
                            if (($IgnoreText) && (in_array($ext, $arrText))) {
                                $bAdd = false;
                            }

                            if (true === $bAdd) {
                                $arrFiles[] = $filename;
                            } else {
                                ++$wNbrSkipped;
                            }
                        }
                    }
                } catch (Exception $ex) {
                    if (true === $this->aeSession->get('Debug', DEBUG)) {
                        echo '<pre>' . print_r($ex, true) . '</pre>';
                    }
                }
            }

            unset($arrSkipFiles);

            // Put the array in a session variable and remember the processed folder
            $this->aeSession->set('Folder', $this->_directory);
            $this->aeSession->set('arrFiles', json_encode($arrFiles, JSON_THROW_ON_ERROR));
        } else {
            // The user is running the script once more for the same folder
            // ==> don't scan the disk again, just user the session variable to speed up the process

            $arrFiles = json_decode((string) $arrFiles, null, 512, JSON_THROW_ON_ERROR);
        }

        if (null == $arrFiles) {
            $arrFiles = json_decode((string) $this->aeSession->get('arrFiles', null), null, 512, JSON_THROW_ON_ERROR);
        }

        unset($this->arrOtherHashes, $this->arrWhiteListHashes, $this->arrBlackListHashes, $this->arrCMSHashes);

        if (true == $echo) {
            try {
                header('Content-Type: application/json');
                header('Cache-Control: no-cache');
            } catch (\Exception $exception) {
            }

            sort($arrFilesBlacklisted);

            echo sprintf(
                '{"count":%d,"whitelisted":%d,"blacklisted":%d,"blacklisting":%s,"edited":%d,"editlisting":%s,"skipped":%d}',
                count($arrFiles),
                $wNbrWhitelisted ?? 0,
                $wNbrBlacklisted ?? 0,
                json_encode($arrFilesBlacklisted),
                $wNbrEdited ?? 0,
                json_encode($arrFilesEditlisted),
                $wNbrSkipped ?? 0
            );

            // Prevent a warning; flush only if there is something to flush
            try {
                while (ob_get_level() > 0) {
                    ob_end_flush();
                }

                flush();
            } catch (\Exception $e) {
            }

            die();
        } else {
            return true;
        }
    }

    /**
     * Start the scan.
     *
     * @global type $sLogFile
     * @global type $sProgressFile
     */
    private function doScan(): string|bool
    {
        $aeLanguage = aeSecureLanguage::getInstance();

        try {
            if (!get_cfg_var('safe_mode')) {
                // set_time_limit isn't used when safe_mode is active
                // No max execution time
                @ini_set('max_execution_time', '0');
                // Remove time limit; avoid 504 HTTP errors
                @ini_set('set_time_limit', '0');
            }
        } catch (Exception $e) {
        }

        // Allocate the maximum allowed memory to the script (-1 = no limit)
        @ini_set('memory_limit', (true !== $this->aeSession->get('Debug', DEBUG)) ? -1 : MEMORY_LIMIT);

        $wFile          = 0;
        $wCount         = 0;
        $wFound         = 0;
        $wProcessedFile = 0;
        $wSkipSize      = 0;
        $wSkipChmod     = 0;
        $wSkipHashes    = 0;
        $wUnreadable    = 0;

        if (!is_dir($this->_directory)) {
            echo '<hr/><div class="alert alert-danger" role="alert"><strong>' .
                sprintf(DIRNOTFOUND, $this->_directory) . '</strong></div>';

            return false;
        }

        $previousFolder = $this->aeSession->get('Folder', null);

        // If the folder isn't the same, clear the arrFiles session variable
        if ((null == $previousFolder) || ($previousFolder != $this->_directory)) {
            $this->aeSession->set('arrFiles', null);
        }

        $arrFiles = $this->aeSession->get('arrFiles', null);
        if (null == $arrFiles) {
            $this->getCountFiles($echo = false, $arrFiles);
        }

        $arrFiles = json_decode((string) $this->aeSession->get('arrFiles', null), null, 512, JSON_THROW_ON_ERROR);

        $wCount   = count($arrFiles);

        // $this->_end can be 0 => process all files
        if ((0 == $this->_end) || ($this->_end > $wCount)) {
            $this->_end = $wCount;
        }

        $bFound = false;
        $output = '';

        // Initialize arrays with hashes
        self::initializeHashes();

        // Start the scan, process all files
        if (($wCount = count($arrFiles)) > 0) {
            if (true === $this->aeSession->get('Debug', DEBUG)) {
                // Reset the log file
                $this->aeLog->kill();
                $this->aeLog->addLog('#' . $wCount . " files to process\n");
            }

            $output = '<ol>';

            $this->aeProgress->setStart(0);
            $this->aeProgress->setEnd($this->_end - $this->_start);

            for ($wFile = $this->_start; $wFile < $this->_end; $wFile++) {
                $filename = $arrFiles[$wFile];

                // Skip aesecure_quickscan to avoid a ton of false positive.
                // aesecure_quickscan.php is supposed to be just download,
                // installed on the website and thus supposed to be clean.
                // Once aeSecure QuickScan has finished his job, the script
                // is supposed to be deleted so, yes, to avoid a ton of false
                // positive, don't scan this script.
                if (FILE == (basename((string) $filename))) {
                    continue;
                }

                if (!file_exists($filename)) {
                    continue;
                }

                ++$wProcessedFile;

                // Consider the file safe
                $bInfected = false;

                $this->aeProgress->incTaskCount();

                $output_line = '';

                if (is_readable($filename)) {
                    // Empty file
                    if (0 == filesize($filename)) {
                        if (true === $this->aeSession->get('Debug', DEBUG)) {
                            $this->aeLog->addLog('Scanning #' . ($wFile + 1) . '. ' . $filename .
                                '   SKIP;   Filesize=0');
                        }

                        continue;
                    }

                    if (filesize($filename) <= MAX_SIZE) {
                        $md5 = md5_file($filename);

                        // Check if the file was never altered.  If yes, great news,
                        // the processing of that file can be avoided => faster
                        // Be sure that the hash isn't in the blacklist; for security.
                        if (!isset($this->_arrBlackListHashes[$md5])) {
                            if (isset($this->_arrCMSHashes[$md5])) {
                                if (FULLDEBUG && !aeSecureFct::isAjaxRequest()) {
                                    echo sprintf(
                                        '%s is an original CMS file; not altered thus safe<br/>',
                                        $filename
                                    );
                                }

                                ++$wSkipHashes;
                                if (true === $this->aeSession->get('Debug', DEBUG)) {
                                    $this->aeLog->addLog('Scanning #' . ($wFile + 1) . '. ' .
                                        $filename . '   SKIP;   Original CMS file');
                                }

                                continue;
                            }

                            // Check if the file is a white listed one or found in
                            // the Other hashes (also white listed)
                            if (isset($this->_arrWhiteListHashes[$md5]) || isset($this->_arrOtherHashes[$md5]) || isset($this->_arrExtHashes[$md5])) {
                                ++$wSkipHashes;

                                if (FULLDEBUG && !aeSecureFct::isAjaxRequest()) {
                                    echo sprintf(
                                        '%s is whitelisted, healthy file<br/>',
                                        $filename
                                    );
                                }

                                if (true === $this->aeSession->get('Debug', DEBUG)) {
                                    $this->aeLog->addLog('Scanning #' . ($wFile + 1) . '. ' . $filename .
                                        '   SKIP;   Hash whitelisted' .
                                        (isset($this->_arrOtherHashes[$md5])
                                        ? ' in the other.json file'
                                        : ''));
                                }

                                continue;
                            }
                        }

                        $OutputTemplate =
                            '<li>' .
                                '<span class="newline">' .
                                    '<span class="filename">' . (DEMO ? str_replace(DIR, '', (string) $filename) : $filename) . '</span>' .  // filename
                                    '<span class="filesize">(' . aeSecureFct::human_filesize(filesize($filename)) . ')</span>' . // filesize
                                    '<span class="filedate">(' . $aeLanguage->get('LASTMOD') . ' ' . date('F d Y H:i:s.', filemtime($filename)) . ')</span>' . // filedate
                                '</span>' .
                                '<span class="md5" title="MD5" data-toggle="popover" data-content="' . $aeLanguage->get('MD5') . '">' . $md5 . '</span>' .
                                '$FOUND$' .
                            '</li>';

                        // Check if the file is a black listed one
                        if (isset($this->_arrBlackListHashes[$md5])) {
                            // The file being scanned is blacklisted
                            $bInfected = true;
                            $bFound    = true;

                            $FOUND =
                                '<span class="label label-danger blink">' . $aeLanguage->get('DANGER') . '</span>&nbsp;' . $aeLanguage->get('BLACKLISTED') .
                                '<span class="newline"><pre>' . trim(str_replace('<', '&lt;', file_get_contents($filename))) . '</pre>' .
                                '</span>';

                            $output_line = str_replace('$FOUND$', $FOUND, $OutputTemplate);

                            if (FULLDEBUG && !aeSecureFct::isAjaxRequest()) {
                                echo sprintf(
                                    '%s IS BLACKLISTED, VIRUS FOUND<br/>',
                                    $filename
                                );
                            }

                            if (true === $this->aeSession->get('Debug', DEBUG)) {
                                $this->aeLog->addLog('Scanning #' . ($wFile + 1) . '. ' . $filename .
                                    '. This file is in the blacklist');
                            }
                        }

                        // Check if the file contains a virus
                        if (isset($this->_arrEditedHashes[$md5])) {
                            // The file being scanned contains a virus
                            $bInfected = true;
                            $bFound    = true;

                            $FOUND =
                                '<span class="label label-danger blink">' . $aeLanguage->get('DANGER') . '</span>&nbsp;' . $aeLanguage->get('EDITED') .
                                '<span class="newline"><pre style="white-space: pre-wrap;">' . trim(str_replace('<', '&lt;', file_get_contents($filename))) . '</pre>' .
                                '</span>';

                            $output_line = str_replace('$FOUND$', $FOUND, $OutputTemplate);

                            if (FULLDEBUG && !aeSecureFct::isAjaxRequest()) {
                                echo sprintf(
                                    '%s CONTAINS A VIRUS<br/>',
                                    $filename
                                );
                            }

                            if (true === $this->aeSession->get('Debug', DEBUG)) {
                                $this->aeLog->addLog('Scanning #' . ($wFile + 1) . '. ' .
                                    $filename . '. This file is in the edited list i.e. ' .
                                    'contains a known virus');
                            }
                        }

                        if (false == $bInfected) {
                            $content = '';

                            try {
                                $content = file_get_contents($filename);
                            } catch (Exception $ex) {
                                ++$wUnreadable;

                                if (true === $this->aeSession->get('Debug', DEBUG)) {
                                    $this->aeLog->addLog('Scanning #' . ($wFile + 1) . '. ' .
                                        $filename . '   SKIP;   Unreadable content');
                                }

                                $bFound      = true;
                                $output_line = '<li>' .
                                    '<span class="label label-warning">' . $aeLanguage->get('WARNING') . '</span>&nbsp;' .
                                    '<span class="filename">' . (DEMO ? str_replace(DIR, '', (string) $filename) : $filename) . '</span>' .
                                    ($aeLanguage->get('SHOWMD5') ? '<span class="md5" title="MD5" data-toggle="popover" data-content="' . $aeLanguage->get('MD5') . '">' . md5_file($filename) . '</span>' : '') .
                                    '<span class="disclaimer text-info newline">' . $aeLanguage->get('UNREADABLE') . '</span>' .
                                    '</li>';
                            }

                            if ('' != $content) {
                                $FOUND = '';

                                // Template on how the file's informations should be reported
                                // $FOUND$ will be replaced by every occurences found in the
                                // file (severall occurences are indeed possible)

                                if (true === $this->aeSession->get('Debug', DEBUG)) {
                                    $this->aeLog->addLog('Scanning #' . ($wFile + 1) . '. ' .
                                        $filename . '   Processing... (filesize=' .
                                        aeSecureFct::human_filesize(filesize($filename)) . ')');
                                }

                                // Process every regex for the processing file
                                foreach ($this->_arrRegex as $regex) {
                                    if (FULLDEBUG === true) {
                                        $this->aeLog->addLog('   scan pattern ' . $regex['pattern']);
                                    }

                                    $arrMatch = [];

                                    try {
                                        preg_match_all('/' . $regex['pattern'] . '/im', $content, $arrMatch, PREG_OFFSET_CAPTURE);
                                    } catch (Exception $ex) {
                                        if (true === $this->aeSession->get('Debug', DEBUG)) {
                                            echo '<h5>' . __LINE__ . '  EXCEPTION ENCOUNTERED = ' . $ex->getMessage() . '</h5>';
                                        }
                                    }

                                    // Something found?  Greater than zero means; yes, the regex
                                    // has been matched.
                                    if (is_array($arrMatch) && (count($arrMatch) > 0)) {
                                        if (count($arrMatch[0]) > 0) {
                                            // Something has been found in that file.
                                            $bInfected = true;

                                            // The regex is always composed of three things
                                            //          1.     2.      3.
                                            //        (.*)(VIRUS_CODE)(.*)
                                            //
                                            // (1) Something before followed by the virus code
                                            // (2) followed by something
                                            // (3) so the regex match f.i. the entire line
                                            // and not only the keyword (2)
                                            //
                                            // When the regex is something of severall codes like
                                            //     1.              2.          3.
                                            //    (.*)(AnonGhost|bash_history)(.*)
                                            //
                                            // the $arrMatch[2] position will return the number
                                            // of matches so if these two words are found in the file
                                            // $patternFound will be set to two and the code below
                                            // will process these two matches
                                            $patternFound = count($arrMatch[1]);

                                            // Process every matches
                                            for ($i = 0; $i < $patternFound; $i++) {
                                                // Get the found keyword (f.i. AnonGhost then, in the
                                                // second loop, bash_history in our example)
                                                $keyword = (isset($arrMatch[1][$i][0])
                                                ? $keyword = $arrMatch[1][$i][0]
                                                : '');

                                                // Get the full line where the keyword was found
                                                $code = $arrMatch[0][$i][0];

                                                // And get the position in the file (start position of
                                                // the keyword)
                                                $position = $arrMatch[1][$i][1];

                                                // When outputting the result, get the context i.e. a
                                                // specific number of characters before the found pattern
                                                // and the same number of characters after so QuickScan
                                                // can display the portion of code where this suspicious
                                                // pattern is found, making easier to read and determine
                                                // the dangerosity of the code
                                                $sContext = '';

                                                try {
                                                    $wStart   = ($position > CONTEXT_NBRCHARS ? $position - CONTEXT_NBRCHARS : 0);
                                                    $wEnd     = ($position + strlen($keyword) + CONTEXT_NBRCHARS) - $wStart;
                                                    $sContext = substr($content, $wStart, $wEnd);
                                                } catch (Exception $e) {
                                                    if (true === $this->aeSession->get('Debug', DEBUG)) {
                                                        echo '<h5>' . __LINE__ . '  EXCEPTION ENCOUNTERED = ' . $e->getMessage() . '</h5>';
                                                    }
                                                }

                                                if (!in_array($arrMatch[0], [null, ''])) {
                                                    $bFound = true;

                                                    if (FULLDEBUG && !aeSecureFct::isAjaxRequest()) {
                                                        echo sprintf(
                                                            '%s contains [%s] risk [%s], so needs to be ' .
                                                            'analyzed<br/>',
                                                            $filename,
                                                            $regex['risk'],
                                                            $keyword
                                                        );
                                                    }

                                                    $disclaimer = (key_exists('disclaimer', $regex) ? $this->getDisclaimerText($regex['disclaimer']) : '');

                                                    $FOUND .=
                                                        '<span class="label label-' . $regex['risk'] . '">' . ('danger' == $regex['risk'] ? $this->aeLanguage->get('DANGER') : ('warning' == $regex['risk'] ? $this->aeLanguage->get('WARNING') : $this->aeLanguage->get('INFO'))) . '</span>&nbsp;' . $disclaimer .
                                                        '<span class="regex newline" title="regex = ' . $regex['pattern'] . '">' . $this->aeLanguage->get('PATTERN') . ' : <strong>' . $keyword . '</strong></span>' .
                                                        '<span class="position newline">' . sprintf($this->aeLanguage->get('FOUNDPOSITION'), $position) . '</strong></span>' .
                                                        '<span class="regexresult"><pre>' . str_replace(
                                                            $keyword,
                                                            '<strong class="double_underline" style="color:red;" data-html="true" ' .
                                                            'data-toggle="popover" data-content="<span class=\'text-' . $regex['risk'] . ' ' . $regex['risk'] . '\'>' .
                                                            $disclaimer . '</span>">' . $keyword . '</strong>',
                                                            trim(str_replace('<', '&lt;', $sContext))
                                                        ) . '</pre>' .
                                                        '</span>';
                                                }
                                            }
                                        }
                                    }

                                    unset($arrMatch);
                                }

                                if ($bInfected) {
                                    $output_line = str_replace('$FOUND$', $FOUND, $OutputTemplate);
                                }

                                unset($content);
                            }
                        }

                        if ($bInfected) {
                            ++$wFound;
                        }

                        unset($content);
                    } else {
                        ++$wSkipSize;

                        if (true === $this->aeSession->get('Debug', DEBUG)) {
                            $this->aeLog->addLog('Scanning #' . ($wFile + 1) . '. ' .
                                $filename . '   SKIP;   Too big.');
                        }

                        // The filename wasn't whitelisted, show it.
                        $bFound      = true;
                        $output_line = '<li>' .
                            '<span class="label label-warning">' . $this->aeLanguage->get('WARNING') . '</span>&nbsp;' .
                            '<span class="filename">' . (DEMO ? str_replace(DIR, '', (string) $filename) : $filename) . '</span>' .
                            (SHOWMD5 ? '<span class="md5" title="MD5" data-toggle="popover" data-content="' . $this->aeLanguage->get('MD5') . '">' . md5_file($filename) . '</span>' : '') .
                            '<span class="disclaimer text-info newline">' . sprintf($this->aeLanguage->get('TOOBIG'), aeSecureFct::human_filesize(filesize($filename), 0)) . '</span>' .
                            '</li>';
                    }
                } else {
                    ++$wSkipChmod;

                    if (true === $this->aeSession->get('Debug', DEBUG)) {
                        $this->aeLog->addLog('Scanning #' . ($wFile + 1) . '. ' . $filename . '   SKIP;   Chmod too restrictive.');
                    }

                    $bFound      = true;
                    $output_line = '<li>' .
                        '<span class="label label-danger">' . $this->aeLanguage->get('DANGER') . '</span>&nbsp;' .
                        '<span class="filename">' . (DEMO ? str_replace(DIR, '', (string) $filename) : $filename) . '</span>' .
                        (SHOWMD5 ? '<span class="md5" title="MD5" data-toggle="popover" data-content="' . MD5 . '">' . md5_file($filename) . '</span>' : '') .
                        '<span class="disclaimer text-info newline">' . $this->aeLanguage->get('BADCHMOD') . '</span>' .
                        '</li>';
                }

                if ('' != $output_line) {
                    // Add action buttons :
                    //
                    //    1. See the file (only if text file)
                    //    2. Kill the file (only if expert mode enabled)
                    //
                    $isTextType = aeSecureFiles::isTextFileContent($filename);

                    if ($isTextType) {
                        // The "See the source file" button is only displayed for files that are detected with text content and
                        // thus not for images or archive files f.i.
                        $buttonSee = '<button type="button" class="seefile btn btn-success btn-xs" data-toggle="popover" data-html="true" data-old-caption="<span class=\'glyphicon glyphicon-eye-open\'></span>" data-caption="<span class=\'glyphicon glyphicon-eye-open\'>&nbsp;</span>' . $this->aeLanguage->get('SEE_FILE') . '" data-content="' . $this->aeLanguage->get('SEE_FILE_HINT') . '" data-filename="' . base64_encode((string) $filename) . '"><span class="glyphicon glyphicon-eye-open"></span></button>';
                    } else {
                        $buttonSee = '<span style="display:inline-block;min-width:26px;"></span>';
                    }

                    // Add to white list button
                    $buttonWhitelist = '<button type="button" class="whitelist btn ' .
                        'btn-primary btn-xs" data-toggle="popover" data-html="true" ' .
                        'data-old-caption="<span class=\'glyphicon glyphicon-heart\'></span>" ' .
                        'data-caption="<span class=\'glyphicon glyphicon-heart\'>&nbsp;</span>' .
                        $this->aeLanguage->get('WHITE_LIST') . '" data-content="' .
                        $this->aeLanguage->get('WHITE_LIST_HINT') . '" data-filename="' .
                        base64_encode((string) $filename) . '"><span class="glyphicon glyphicon-heart">' .
                        '</span></button>';

                    if (true === $this->aeSession->get('Expert', EXPERT)) {
                        // Kill file button
                        $buttonDelete = '<button type="button" class="killfile btn ' .
                            'btn-danger btn-xs" data-toggle="popover" data-html="true" ' .
                            'data-old-caption="<span class=\'glyphicon glyphicon-trash\'></span>" ' .
                            'data-caption="<span class=\'glyphicon glyphicon-trash\'>&nbsp;</span>' .
                            $this->aeLanguage->get('KILL_FILE') . '" data-content="' .
                            $this->aeLanguage->get('KILL_FILE_HINT') . '" data-filename="' .
                            base64_encode((string) $filename) . '"><span class="glyphicon glyphicon-trash">' .
                            '</span></button>';
                    } else {
                        $buttonDelete = '';
                    }

                    // Add a dismissal div so the user can hide this result
                    $output .= '<div class="alert-dismissible fade in" role="alert">' .
                        '<button type="button" class="close hidefile" data-dismiss="alert" ' .
                        'aria-label="Close" data-toggle="popover" data-content="' .
                        $this->aeLanguage->get('HIDERESULT') . '"><span aria-hidden="true">×</span>' .
                        '</button>' . $output_line . '<span class="newline">' . $buttonSee .
                        ' ' . $buttonWhitelist . ' ' . $buttonDelete .
                        '</span></div>';
                }
            }

            // Release the array
            unset($arrFiles);
        }

        unset($this->arrOtherHashes, $this->arrWhiteListHashes, $this->arrBlackListHashes, $this->arrCMSHashes);

        if (true === $this->aeSession->get('Debug', DEBUG)) {
            $this->aeLog->addLog("\nEND OF SCAN");
        }

        // The scan is now finished
        //
        // --------------------------------------------

        $output .= '</ol>';

        if (false == $bFound) {
            // Nothing found, congrats!
            $output = '<div class="alert alert-success" role="alert"><strong>' .
                $aeLanguage->get('SUCCESS') . '</strong> ' . $aeLanguage->get('NOTHINGFOUND') .
                '</div>' . $output;
        } else {
            // Something has been found
            $output =
             '<div class="stats">' . sprintf($aeLanguage->get('FILESFOUND'), $wProcessedFile, $wFound, str_replace('"', '\'', (string) $aeLanguage->get('POTENTIALLY')), $wSkipSize, aeSecureFct::human_filesize(MAX_SIZE, 0), $wSkipChmod) . '</div>' .
             '<div class="text-info">' . sprintf($aeLanguage->get('VIRUSFOUND'), $aeLanguage->get('HOME'), $aeLanguage->get('HOME')) . '</span><hr style="height:20px;"/></div>' . $output;
        }

        // Add script for popovers
        $output .= '<script>' .
            '$(\'[data-toggle="popover"]\').popover({trigger:\'hover\',placement:\'top\',html:true});' .
            'initButtons();' .
            '</script>';

        $this->aeProgress->clean();

        return $output;
    }
}

// --------------------------
// PHP ENTRY POINT
// --------------------------

$showInfo = '';

aeSecureDebug::setDebugMode(DEBUG);

$aeSession = aeSecureSession::getInstance();
$aeSession->set('Debug', DEBUG);

$lang = str_replace('_', '-', (string) aeSecureFct::getParam('lang', 'string', '', 5));

$aeLanguage = aeSecureLanguage::getInstance($lang);

// SaveSession is the action behind the "Apply" button of the Advanced form
if ('SaveSession' === aeSecureFct::getParam('formTask', 'string', '', strlen('SaveSession'))) {
    $aeSession->set('Expert', (!DEMO ? aeSecureFct::getParam('chkExpert', 'boolean', EXPERT) : false));
    $aeSession->set('Debug', (!DEMO ? aeSecureFct::getParam('chkDebug', 'boolean', DEBUG) : false));

    if (true !== $aeSession->get('Expert', EXPERT)) {
        unset($aeSession);
        $aeSession = aeSecureSession::getInstance(true);
    }

    $aeSession->set('IgnoreArchives', true == aeSecureFct::getParam('chkIgnoreArchives', 'boolean', true) ? 1 : 0);
    $aeSession->set('IgnoreDocuments', true == aeSecureFct::getParam('chkIgnoreDocuments', 'boolean', true) ? 1 : 0);
    $aeSession->set('IgnoreFonts', true == aeSecureFct::getParam('chkIgnoreFonts', 'boolean', true) ? 1 : 0);
    $aeSession->set('IgnoreImages', true == aeSecureFct::getParam('chkIgnoreImages', 'boolean', true) ? 1 : 0);
    $aeSession->set('IgnoreMedia', true == aeSecureFct::getParam('chkIgnoreMedia', 'boolean', true) ? 1 : 0);
    $aeSession->set('IgnoreSoundMovies', true == aeSecureFct::getParam('chkIgnoreSoundMovies', 'boolean', true) ? 1 : 0);
    $aeSession->set('IgnoreText', true == aeSecureFct::getParam('chkIgnoreText', 'boolean', true) ? 1 : 0);

    // Max. number of files by cycle
    // Be sure to have only positive numbers.
    // zero is allowed : no limitation
    $aeSession->set('MaxFilesByCycle', abs(aeSecureFct::getParam('nbFiles', 'integer', MAXFILESBYCYCLE)));
}

$aeScan = new aeSecureScan();

$script = $_SERVER['SCRIPT_FILENAME'];

$aeScan->Process();

$aeLog      = aeSecureLog::getInstance();
$aeProgress = aeSecureProgressBar::getInstance();

// Try to obtain a few informations about the site
[$CMS, $CMSFullVersion, $CMSMainVersion, $CMSVersion, $SiteRoot] = aeSecureCMS::getInfo($aeScan->directory());

// Is it a website made with a supported CMS? (Joomla, WordPress, Drupal, CakePHP,
//  PrestaShop, Magento, ...)
if ('' != $CMS) {
    // Yes so try to retrieve the hash of that CMS and for the installed version
    [$arrCMSHashes] = $aeScan->gethashes($CMS, $CMSVersion);

    $usingHashes = '<strong style="color:%s;height:48px;line-height:48px;vertical-align:center;">' .
        '<span class="glyphicon glyphicon-thumbs-%s"></span>&nbsp;%s</strong>';

    if (null != $arrCMSHashes) {
        // Hashes found
        if (true === $aeSession->get('Expert', EXPERT)) {
            $usingHashes = sprintf($usingHashes, 'yellow', 'up', $aeLanguage->get('USINGHASHESSHORT'));
        }

        $showInfo = '<div class="alert alert-success">' .
            '<strong><span class="glyphicon glyphicon-thumbs-up"></span>&nbsp;' .
            $aeLanguage->get('USINGHASHES') . '</strong>&nbsp;' .
            sprintf($aeLanguage->get('USINGHASHESINFO'), $CMS, $CMSVersion) . '</div>';
    } else {
        // No hashes found for that version of the CMS
        if (true === $aeSession->get('Expert', EXPERT)) {
            $usingHashes = sprintf(
                $usingHashes,
                'red',
                'down',
                $aeLanguage->get('NOTUSINGHASHESSHORT')
            );
        }

        $showInfo = '<div class="alert alert-warning">' .
            sprintf($aeLanguage->get('SORRYNOHASHES'), $CMSVersion, $CMS) .
            '</div>';
    }

    // If a CMS has been detected, display the CMS used + version as navbar
    $siteInfos = '<nav class="navbar navbar-inverse navbar-fixed-top"><div class="container-fluid">' .
        '<div class="navbar-header"><span class="navbar-brand">' . $CMS . ' ' . $CMSFullVersion . '</span></div>' .
        '<div class="navbar-right">' . $usingHashes . '</div>' .
        '</div></nav><br/><br/><br/>';
} else {
    $siteInfos = '';
}

// Get the GitHub corner
$github = '';
if (is_file($cat = __DIR__ . DIRECTORY_SEPARATOR . 'octocat.tmpl')) {
    $github = str_replace('%REPO%', REPO, file_get_contents($cat));
}

?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
    <head>
        <meta charset="utf-8" />
        <meta name="robots" content="noindex, nofollow" />
        <meta name="author" content="Christophe Avonture" />
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta http-equiv="X-UA-Compatible" content="IE=9; IE=8;" />
        <meta property="og:title" content="<?php echo $aeLanguage->get('PAGETITLE');?>" />
        <meta property="og:description" content="<?php echo $aeLanguage->get('DESCRIPTION');?>" />
        <meta property="og:image" content="aesecure.png" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />

        <title><?php echo $aeLanguage->get('PAGETITLE');?> | AVONTURE Christophe - www.avonture.be</title>

        <link href="favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon"/>
        <?php
            echo aeSecureFct::addStylesheet('libs/bootstrap/css/bootstrap.min.css', '//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css');
echo aeSecureFct::addStylesheet('libs/tablesorter/css/theme.ice.min.css', 'https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.24.5/css/theme.ice.min.css');
echo aeSecureFct::addStylesheet('libs/alertify/css/alertify.core.css', 'https://cdnjs.cloudflare.com/ajax/libs/alertify.js/0.3.11/alertify.core.css');
echo aeSecureFct::addStylesheet('libs/alertify/css/alertify.bootstrap.css', 'https://cdnjs.cloudflare.com/ajax/libs/alertify.js/0.3.11/alertify.bootstrap.css');
?>
        <style type="text/css">
            #DebugMode{background-color:red;color:yellow;padding:5px;margin:10px;right:0px;}
            #DemoMode{background-color:orange;color:white;padding:5px;margin:10px;}
            #result li{padding-top:10px;}
            #result{overflow:auto;padding-top:20px;margin:5px;}
            #result button.close{top:10px;color:red;}
            #ALERTINFO{min-height:90px;}
            .ignoredext{font-size:x-small;}
            .bottomright{position:fixed;right:0;bottom:0;}
            span.regexresult .popover{min-width:350px;max-width:500px;}
            span.regexresult .popover-content{word-wrap: break-word;}
            .danger{font-weight:bold;}
            .danger:before {content:"<?php echo $aeLanguage->get('DANGER');?>";background-color:#d9534f;color:#fff;font-weight:bold;padding:.2em .6em .3em;text-align:center;border-radius:.25em;margin-right:0.5em;}
            .warning:before {content:"<?php echo $aeLanguage->get('WARNING');?>";background-color:#f0ad4e;color:#fff;font-weight:bold;padding:.2em .6em .3em;text-align:center;border-radius:.25em;margin-right:0.5em;}
            .md5{padding-left:10px;color:gray;float:right;}
            .disclaimer{font-style:italic;}
            .double_underline{text-decoration:underline;border-bottom:1px solid #000;}
            .filename{color:red;white-space:nowrap;padding-bottom:10px;display:inline-block;}
            .filesize{padding-left:5px;color:red;white-space:nowrap;padding-bottom:10px;display:inline-block;font-style:italic;font-size:x-small;}
            .filedate{padding-left:5px;color:red;white-space:nowrap;padding-bottom:10px;display:inline-block;font-style:italic;font-size:x-small;}
            .newline{display:block;}
            .seefile{margin-right:25px;}
            .whitelist{margin-right:25px;}
            .killfile{margin-left:100px;}
            .btnscan{margin:5px;min-width:140px;}
            .footer{padding-top:50px;padding-left:10px;}
            .stats{margin-bottom:25px;border:1px solid green;padding:5px;color:green;}
            .underline{text-decoration:underline;}
            .blink {animation: blink 1s steps(5, start) infinite; -webkit-animation: blink 1s steps(5, start) infinite; } @keyframes blink {to {visibility: hidden;}} @-webkit-keyframes blink {to {visibility: hidden;}}
            .border{border:2px dotted #C9CBFF;padding:5px;margin-bottom:10px;}
            #frmAdvanced{color:#31708f;}
            /* OffCanvasMenuEffects from http://tympanus.net/Development/OffCanvasMenuEffects/ */
            .container,.content-wrap{overflow:hidden;height:100%}
            /* Menu Button */
            .menu-button{position:fixed;z-index:1000;margin:1em;padding:0;width:2.5em;height:2.25em;border:none;text-indent:2.5em;font-size:1.5em;color:transparent;background:0 0}
            .menu-button::before{position:absolute;top:.5em;right:.5em;bottom:.5em;left:.5em;background:linear-gradient(#2e6da4 20%,transparent 20%,transparent 40%,#2e6da4 40%,#337ab7 60%,transparent 60%,transparent 80%,#2e6da4 80%);content:''}
            .menu-button:hover{opacity:.6}
            .close-button{width:1em;height:1em;position:absolute;right:1em;top:1em;overflow:hidden;text-indent:1em;font-size:.75em;border:none;background:0 0;color:transparent}
            .close-button::after,.close-button::before{content:'';position:absolute;width:3px;height:100%;top:0;left:50%;background:#bdc3c7}
            .close-button::before{-webkit-transform:rotate(45deg);transform:rotate(45deg)}
            .close-button::after{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}
            .menu-wrap{position:absolute;top:0px;z-index:1001;width:300px;height:100%;background:#d9edf7;padding:2.5em 1.5em 0;font-size:1.15em;-webkit-transform:translate3d(-320px,0,0);transform:translate3d(-320px,0,0);-webkit-transition:-webkit-transform .4s;transition:transform .4s;-webkit-transition-timing-function:cubic-bezier(.7,0,.3,1);transition-timing-function:cubic-bezier(.7,0,.3,1)}
            /* Shown menu */
            .show-menu .menu-wrap{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);-webkit-transition:-webkit-transform .8s;transition:transform .8s;-webkit-transition-timing-function:cubic-bezier(.7,0,.3,1);transition-timing-function:cubic-bezier(.7,0,.3,1)}
            .show-menu .icon-list,.show-menu .icon-list a{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);-webkit-transition:-webkit-transform .8s;transition:transform .8s;-webkit-transition-timing-function:cubic-bezier(.7,0,.3,1);transition-timing-function:cubic-bezier(.7,0,.3,1)}
            .show-menu .icon-list a{-webkit-transition-duration:.9s;transition-duration:.9s}
        </style>
    </head>
    <body>

        <?php echo $github; ?>

        <div class="container-full">

            <?php
    if (DEMO) {
        echo '<span id="DemoMode" class="blink img-rounded bottomright">' .
        'Demo Mode Enabled</span>';
    }

if (DEBUG || true === $aeSession::get('Debug', DEBUG)) {
    echo '<span id="DebugMode" class="bottomright blink img-rounded" ' .
    'style="cursor:pointer;">Debug Mode Enabled</span>';
}
?>

            <!-- Advanced menu -->
            <div class="menu-wrap">
                <div class="menu">
                    <form id="frmAdvanced" name="frmAdvanced" method="POST">

                        <div class="border">
                            <?php echo $aeLanguage->get('SELECT_LANGUAGE');?>
                            &nbsp;:

                            <div>
                                <select class="form-control" id="lang" name="lang">
                                    <?php
    // Retrieve the list of JSON files that match
    // aesecure_quickscan_*.json
    // f.i. aesecure_quickscan
                        $script  = str_replace('.php', '', basename(__FILE__));

// f.i. aesecure_quickscan_lang_*.json
$pattern = str_replace('.php', '_lang_*.json', basename(__FILE__));

$arr = glob($pattern);

if (count($arr) > 0) {
    foreach ($arr as $filename) {
        $lang = str_replace(
            '.json',
            '',
            str_replace($script . '_lang_', '', (string) $filename)
        );
        echo '<option value="' . $lang . '"' .
            ($aeLanguage->getlang() == $lang
            ? ' selected="selected"'
            : '') . '>' . $lang . '</option>';
    }
}
?>
                                </select>
                            </div>
                        </div>

                        <div class="border">
                            <?php echo $aeLanguage->get('ADVANCED_OPTIONS');?>
                            &nbsp;:

                            <div class="checkbox">

                                <?php if (!DEMO) { ?>
                                    <label>
                                        <input type="checkbox" id="chkExpert" name="chkExpert" title=""
                                            <?php
        if (true === $aeSession::get('Expert', EXPERT)) {
            echo 'checked="checked" ';
        }
                                    ?>
                                        >
                                        <?php echo $aeLanguage->get('EXPERT_MODE');?>
                                    </label>
                                    <br/>
                                <?php }?>
                                <label>
                                    <input type="checkbox" id="chkDebug" name="chkDebug" title=""
                                        <?php
                                        if (true === $aeSession::get('Debug', DEBUG)) {
                                            echo 'checked="checked" ';
                                        }
?>
                                    >
                                    <?php echo $aeLanguage->get('DEBUG_MODE');?>
                                </label>
                                <br/>
                                <br/>

                                <?php
                                    $select = '';
$wMax                                       = $aeSession::get('MaxFilesByCycle', MAXFILESBYCYCLE);

$arr    = [
'250'  => 250,
'500'  => 500,
'1000' => 1000,
'1250' => 1250,
'1500' => 1500,
'2500' => 2500,
'5000' => 5000,
'0'    => $aeLanguage->get('ALL')
];

foreach ($arr as $key => $value) {
    $select .= '<option value="' . $key . '" ' .
         (($wMax == $key) ? 'selected="SELECTED"' : '') . '>' .
         $value . '</option>';
}

$select = sprintf(
    $aeLanguage->get('EXPERT_MAXFILES'),
    '<select id="nbrFilesCycle" name="nbrFilesCycle">' .
    $select . '</select>'
);

echo '<label>' . $select . '</label>';
?>

                            </div>
                        </div>

                        <div class="border">
                            <?php echo $aeLanguage->get('IGNORE_TITLE');?>
                            &nbsp;:

                            <div class="checkbox">
                                <label title="
                                    <?php
    echo sprintf($aeLanguage->get('IGNORE_EXTENSIONS'), ExtArchives);
?>"
                                >
                                <input type="checkbox" id="chkIgnoreArchives" name="chkIgnoreArchives"
                                    <?php
if (1 === $aeSession::get('IgnoreArchives', 1)) {
    echo 'checked="checked" ';
}
?>
                                >
                                <?php
                                echo $aeLanguage->get('IGNORE_ARCHIVE') .
                                '<br/><span class="ignoredext">' . ExtArchives . '</span>';
?>

                                </label>

                                <br/>

                                <label title="
                                    <?php
    echo sprintf($aeLanguage->get('IGNORE_EXTENSIONS'), ExtDocuments);
?>"
                                >
                                <input type="checkbox" id="chkIgnoreDocuments" name="chkIgnoreDocuments"
                                    <?php
if (1 === $aeSession::get('IgnoreDocuments', 1)) {
    echo 'checked="checked" ';
}
?>
                                >
                                <?php
                                echo $aeLanguage->get('IGNORE_DOCUMENTS') . '<br/>' .
                                '<span class="ignoredext">' . ExtDocuments . '</span>';
?>

                                </label>

                                <br/>

                                <label title="
                                    <?php
    echo sprintf($aeLanguage->get('IGNORE_EXTENSIONS'), ExtFonts);
?>"
                                >
                                <input type="checkbox" id="chkIgnoreFonts" name="chkIgnoreFonts"
                                    <?php
if (1 === $aeSession::get('IgnoreFonts', 1)) {
    echo 'checked="checked" ';
}
?>
                                >
                                <?php
                                echo $aeLanguage->get('IGNORE_FONT') . '<br/>' .
                                '<span class="ignoredext">' . ExtFonts . '</span>';
?>

                                </label>

                                <br/>

                                <label title="
                                    <?php
    echo sprintf($aeLanguage->get('IGNORE_EXTENSIONS'), ExtImages);
?>"
                                >
                                <input type="checkbox" id="chkIgnoreImages" name="chkIgnoreImages"
                                    <?php
if (1 === $aeSession::get('IgnoreImages', 1)) {
    echo 'checked="checked" ';
}
?>
                                >
                                <?php echo $aeLanguage->get('IGNORE_IMAGES') . '<br/>' .
                                '<span class="ignoredext">' . ExtImages . '</span>';
?>

                                </label>

                                <br/>

                                <label title="
                                    <?php
    echo sprintf($aeLanguage->get('IGNORE_EXTENSIONS'), ExtMedia);
?>"
                                >
                                <input type="checkbox" id="chkIgnoreMedia" name="chkIgnoreMedia"
                                    <?php
if (1 === $aeSession::get('IgnoreMedia', 1)) {
    echo 'checked="checked" ';
}
?>
                                >
                                <?php
                                echo $aeLanguage->get('IGNORE_MEDIA') . '<br/>' .
                                '<span class="ignoredext">' . ExtMedia . '</span>';
?>

                                </label>

                                <br/>

                                <label title="
                                    <?php
    echo sprintf($aeLanguage->get('IGNORE_EXTENSIONS'), ExtSoundMovies);
?>
                                ">
                                <input type="checkbox" id="chkIgnoreSoundMovies"
                                    name="chkIgnoreSoundMovies"
                                    <?php
if (1 === $aeSession::get('IgnoreSoundMovies', 1)) {
    echo 'checked="checked" ';
}
?>
                                >
                                <?php
                                echo $aeLanguage->get('IGNORE_MOVIES') . '<br/>' .
                                '<span class="ignoredext">' . ExtSoundMovies . '</span>';
?>

                                </label>

                                <br/>

                                <label title="
                                    <?php
    echo sprintf($aeLanguage->get('IGNORE_EXTENSIONS'), ExtText);
?>"
                                >
                                <input type="checkbox" id="chkIgnoreText" name="chkIgnoreText"
                                    <?php
if (1 === $aeSession::get('IgnoreText', 1)) {
    echo 'checked="checked" ';
}
?>
                                >
                                <?php echo $aeLanguage->get('IGNORE_TEXTES') . '<br/>' .
                                '<span class="ignoredext">' . ExtText . '</span>';
?>

                            </div>
                        </div>

                        <button type="button" id="btnSubmit" class="btn btn-primary">
                            <?php echo $aeLanguage->get('APPLY');?>
                        </button>

                    </form>
                </div>
                <button class="close-button" id="close-button"></button>
            </div> <!-- Advanced menu -->

            <div class="">

                <div class="col-md-2" >
                    <button class="menu-button" id="open-button"></button>
                </div>

                <div class="col-md-9">

                    <?php

                        // Houston, we've a serious problem; no signatures to scan =>
                        // there was an error in the json PATTERN
                    if (0 == $aeScan->getCountPatterns()) {
                        die();
                    }

?>

                    <?php echo $siteInfos;?>

                    <div style="margin-top:10px;">

                        <div class="alert alert-info fade in" role="alert" id="ALERTINFO">
                            <a href="<?php echo REPO; ?>" target="_blank">
                                <img style="float:left;margin-right:10px;" title="
                                    <?php echo $aeLanguage->get('HOMETITLE'); ?>"
                                    src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAA8CAYAAAAgwDn8AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwQAADsEBuJFr7QAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC41ZYUyZQAAFENJREFUaEPVWglYU3e2p+10melrbetSWxStYlUgJCEQwhpZsrNKIosgypINsPKsU7tM7UxHx67a9jlttXtnXqe209a6sLmhglpxQUFUVBRZRfYkgELOO+d6g4mA2qrf++Z83/lC/vd/7/2d/Zx/cPr/pCbO6KQmjzGXGt3HlDZxxk1hl/9zqJEzpqyJMxYaOWOtTW5jEtnl/xxq4o5f0cR9sq+JM6a6wX20D7v82+iU6xOPNnmMW9zgMea/m93G/he7fFfJND9F2RYre+kib8y09U5O97HLv42a3ccamzzG9jd6jB1o8BitByene9hLd43MRv1bZoNBwH69PcJgepX8kWH8+24LYNFqXczZunzr4uSH2aXbI8oCDZzRBc0+zxxv16jS7qYAy5Ytu7fHYFjeY9SnsUt3hhD0vSZj6nhzlmFLh9Hoyi7fcTLrdM6mbMP3zUbj3Yk1U5ZeZjEavoe79AJLtkFjydKn3lU37c7WLzQbdbkAcEdfAhrNfQj+u65s7QzQau9nl+88taSlPWIy6tabs/RRtyoEoG8TKGtu7u+tS9IeseamP2HO1Tmb9XpRt1GXaMnSvdylz9yEz+xBN23BLHTenG1Y2a3Xj7sr1rAY5k9Ed/rOZDTy2CWGCGRXTs7YXr1+umWhMdBi1M1BUIvM2foV+LkW7/kReR9q+gICvIx/W4nr5sbDUXk4NKYkAboRw3gdLFmGst6sjGfZx98y3bB2oDZ/17h48cO9OYZIS7ZuIwZdujlL97bFqC9A7Z02ZevrEWwLcicCtSCQfhsoe8br0LJgHuyfFQhbvblQJPCEw9JQQEvY7yEBPyD3Yl/vQHncGapCvoc3+5Whn6ZPf2Szp9sq9qsjdRsMnghqLfJFAsZo6Tdwy4IUOKqQwDZvHuzw9YKyMDHUJmoYoYbu1zfWouuxEBwon+fuj/zOxwLBYMwU8jyUBXyPoQL0ZGc/g4CPIlvtX0AaG/7FI3ONJhZKAkUIPBi2C/kM7xQJGEHaM9Mc9tL7zBkZE1gYDvSz4Nkx+Vz3U4WcmWH0fROH83gBz31/Ad89gdlgT+iPr9pr/FJaKpSj35L59wT6wvFIBaPFtowFNxUIkwCciFLC3gQ1NGzZBH2trdBdXQ1Vb/4NSiUhzLPt9/dk6WaxMBxoi6vrg/k8t2/QCr1FfE4+ftYX8DwaigRuLuyWa4Ruc8T+oQTy9Oxo2Ira25eSCAdzDFCMPrxT5M1o8kLSnBEFaZo3F3aFzwJLfT0mMgBTTQ1c6eqCgcuXoXrNB3AgNJgR0ra/J9uYycIYQgW8mYqjinATPXOf2N+az3VbV2bnUoOEwVljD6I5NRn2oAWaCvLA2t8PYLUyAOp/+gF2oGuciokY3Hs9n4qNhNMf/g9YBwagYdNGKEGXKpaFgaWuDq50dsIucQC0ps8f3N+dpX+DhTGErOmYkrP0TbTv0oLUhjyvGU+xlxwJNxXbHkh8UBYKF/69HgBBtJUdgPI/LoambUVgvXIFTn+0Bg6Qhu3223OFSgaNKHi/xQL70+bBtiARYOBB/cYNjEVK8PpFDHTbfpNB/xMLYwiVYepGYb/v1Gf2YP0wsMtDqSfb8IXtgeQau6Qh0HXyBPS1t0EzAi/CINwe7Ad9bW3QfvgQFIv9HVKiPZ+Oi2b8naxW8/lnsAsB75uXBH2XWqCnuQl2oUAUS7b9nQb9UbFYM2L7gv7/1laBZ+mmQM7j7NJQsmQbl9keyAiAFug+Xc0A3pecwGhwGwYzBWRHxTEUIAA6demDIOyZ8v+u0CC03C+MBS11F+Ayuk5/Tw8c+9NLcATdyT5+zmRm9noHKdc+6SkZtsUu5Lmvz+e7by7w9By5BTcZ9Sn2IPajizRvLWK02FKyG448nwsNmzcx8VD77TewNyRoxCAmJiuQq1z493fQdaIK2g6WQcWrL8M+vK9Tl+G4V6cFv9DogUl+su+f9gkbzUIapDye2wFMn/9Y7+b2ALs0lHqM2jAENFhVKcvsi4+D7lMnGb+lgCRhCEwpgqtHq9iDGI6pdThG7oPWoJghoTq0Q612KDOz30uqhmlBkVcm+yk+mSQWP0SYqE/CbiAA68qlkzGqr27YBG5PmS/rMOp6bQ+lNHc8Ug670dzVa96HxvwtcHLV20x6pCxj0z59EpNWuwyZmEKTmAxTGSEfLIL0LPu0ac94va80I+MlL0XCMY4kDoWI6Hfxlb3iJBb/rk2rHYV7qLjSvs5e7GRZuI70uCB8lCIkau85NOV1D4cGrAEnolVwTCmDk9ERjOZpndIsWakKC9aZuBjM0QHM36VBfnBWHQPlKHjHdVV3CCMoTN/LSLPeynh/L0V8nXvYbJgSqOpy8ZOpOrOyRmOr0UZ7TVnYIeQYxCxke9LcN8FHstpLHGk9nOHomzYmwDa2rRHoY0oJY43z8WpGIHIPsgTtwzbaip9XsAHsQw2asdK3IzfhWgW219sQ/BqTXs+v0Ghsfn2PMCJeypfP6Z4ZEgOu/oqSb7z58WdmR3Wi9Trwvk0dBsPQLPS0j4Q3yV/RRDe9mZBSi93nZXvwIzGBJLfAzwFsp7sQUAMygtMX4PVPce5dgYlhSU+OPg21rOg2Gt3hudTHalJTHxqp+3SV5zzorUp8zyd8dv9C30DrJp5HD2a/f56Ji/OkWYPd5kguQulq16AIK/pfT7AqIYqmMdIWao0Cup+0iKB6SYv4dxOa8hiuFyKo91GA+R3ZOmEnzgetBsPErpwFY9te0I6imbdxcfLDhxHwL6/op1e+kBZ8enFmQtWSzL/XPq8tbHtO92r3woVPrkdB1msce/tFQaFeH/iKTBu8PCHX279T4RXMYS8NJWeheIKLSN5B2ufKNMWCcM0omq76snVcc7Yu15Sle5HmgR6DIcRiTJtE0xq1vmUrtaMqnjeOP74049nKpZnhtYv1Cc3PZRoaF2n/3JKjXd2Uq/u5IVd3+lKOrr0rW9+DjFZysJ4VXexgSYDPu4V89xU/CwR/oIxT6MURFXA9SjbwPVtSg6UDM4Ijr7iIZEsQ6r1XEV9H6D7LpwaqgCNRA0+ekMouDxI9lPjQEt20o0szF556PvNfzYt0xR05+qPINV05+kbkLpyj+9Ai/chWe6A3Y8pU6CIDWKTiC7gzkwu4bvXYrB14398/SKCIr3QLjYVnAhTbp/tHPcJCukZPCOWPThDJKp8VR4GndE6dSK0e1se6sw3JCLB1OAC3y+ex3aYKn8dzr8JW+RLyt9QqizGFCpSJq9Ctrahg8ySBdGj6fNo7jD/ZX3GJpOTK41ewy4NEvXi5JCQYY8IBPAUv9TGUQmsRAE1fJqNj+rVnCnTKTDTIdBsc9zXPTwZ0G8Aqay7gu+Xatwo+kUkRXNkc8/RZ0TDJV76UXb5GzgKJeoq/st89fPZlT7kmkF0epC08d8PeYH8KYuZl9NmKwGv0GXD+nTfhUmE+tBXvhLrP1sGZl1+AcwuzoA731GfpoA6Bns1cAFVYiQ9jT0WTGTFVYnsBaLChgSmf73GYApp9NUN+kuRxXsr4izNRwZNF8hJ2+RpNFEr/4hoUCR7hcRe8VUlDfmRAc/6wy98HNbcA6ubOgWosYPXYRvdcqMX24hSc/eRjOImCNOZtgZ6GeuhraYFe/KTrDGPv34n9TyW2I2ewsFGFvr57bcNn06iZz/P4in2tPd3jrUzcQYVtsr+yYxJX/Bi7fpUmCKXfMv4vUR/hydVj2WWnIxLPhw+pYwxH5ifXHc/NgXMIsnXHdujB6cp09gxUf7AatmJXWkCmRy4UeGKb7c+0zKUJGqZtLkHAe2ZHoUANUI3zAFXmuqR4B/DE6Fp9pcF+Ozby3Lns6x0IFbuS2gsmDrBesctXyUUo2z1dHA1cqWaXICLiD+yy067AwLHVr758uO9iM9O41f/8Exxf8TqUoZ8X44RGgCnwboXNKPAxdKMj6Eb1c4c2f+iWnd2ZaSHsq4eQIDIp3VOqQQEieif7yWTs8lWaKJIeZwSQqQsp6tllp608N7ej6fPPWtANdmMPVIg+ymh7GIA3485jR6EaGzzydVssOQiQjW1GjiGSffUQEirnqlDBgIW2z0Uo17DLV8lFJD01AyOcK1fnObEBhJnn0QKue+GeWQFWS+15dImrae5mvB2ns8rlf4Yzn6yFPdgb2dZb9++HCnShCqV0xOkN240Rj/TRhcJZAS5PEkmT2OWrNEEoqZjO1AB1kUCgvf9bkej3mI8/w5RmKRZ5F7bvLRk4iFqzBzock0tV//0DHDMPQv2GH6EEfd927VLJHqhG4NefBdlzhy79nd2BgY8PJ8SgC6EALj7SuezyVZroKy2+WsQ0e9xwHs3jumVh5jFjJfxkX6DQ7cK6j3pPrn7HwX2KfL0YbZNbUZCeePsN2JukgXNffwnN27fC2U/XwgEcM237W0tL4HzO8MBtXBM/21LI5+ygSnz9cYm3KuFF6hKmYAw4+4Yr2eWrNFEo+ce04EgMYvWxtX7CRMzFXflc9+IiwZRRpI2qJYv2Ne/YBoU+PAYMudMlBEQT2gFMf0yAo8a7jlfCLnSRytdfgwvff8scndgCvePQQai7iQANyYlXixnfw4LvX1FhNzZ6qRI+wTQPUwKUPRN95Y61aoKv5BUPFGClOrm7PDayvSRQdHYLBjBdo4bubLb+s+7KCuYwi8A05G2G48tfh5ZdxXDirTdw2G+FxoJ8aNi4AfYmaqBq5XKo+fIzMJ+rYSxFVjKdOAHNNxGApjc6+KV3YE9k2uLpnsC4E8A9fGXCCTeqA36KrvE8+WCqZ2i8UKJZHZdo7UI/pwzRoc84WjorMKCA55ZaFhr8RVtmmrlj21YEu5J5OB1onXj7TeaIhWrBxeIdWBfOMicUhxYaoearL+DMuo+gRBPD7Kdg7sYs1DZM9rFnajVsAjDMdS8qxXj0VSVO4yvi2ynRUMZkYV8jZy+J74EFae22B7VmzB/Y6edlLuB5tJYGiY516zPb6l77U3/H0XLmYIp8nk4YziHQoy+9ANuC/aAEWwM6pRt8uR3TuVA7Cttx3QnEcLzdl99guw/j0EKnb97KpBTMQL3k5hN9Za+xsK+RC0f1eK4y7mQHWQAf0qbN2LxT6BVU5M2Zgk3VOJMxg3c8VvXX9l/295dhJingc6BIyL+lQkY+TWdCje+9O6SBu57R+m3bfLir7e9fL/AMFKgSPif/fwb7NawBjGsPoacE4WskIZGQotJAsFzzArvM0I5Jkx4q4nNyjy/J7Sc/LxJ6OYC8EZO1LOfPwfl0x1Noe0bgA8g4+elymXnA7v7VIlE6Tz6nltxnsp98jyu2/iwsR5ogCnV2Fob3YakGbJoOe0pix9E6BRH64h8pre7EtNlWshvKly5xADkyc5ggb/jn19h2zwf6icls1FtwLG0zGw2nEPwPOEP/tSfLsOByltbbmpPzYJ7XzCD7Z6SHyA9Q+sQC1j9RKHsJIY38O5qzt+TDSSK5FfvuK+5h6nnU1ubx3VOwoPXRw8gdyo3aga6qSocqOxLvlIRAR/kRqNKlb7Nk6+ZZcgz+CHIqnfEMV6wAh/wqlSxqHyqKfo76ISgQAmRqK425qP3GCSLpjX+3fspLKnD2kVyk0XJmSGzTcwGhCjTpGRsgtMKVrUL+mpp332m+uGvn4EnzcLwV22+qHc0bfmw4HBfpzr5iWAJMk13ZugCMv42YieopVlr1WnhRnYwjbhxpH6b7Sl9mt9+AMOdTLODwbKXKHCVWtNKJgA0UVuiSIp8Zow+oY1UXN2+8RACpKyVXsQdPBe8UpteuY+UDRzSxr18/oNgTaZ18H2PAPBgTyJsXpIOPfA5Qi+MWoDwgHMn3r6dROCw4e4eXYsEADwycxQGzaEoiYP1bBG5MBdyBHWt5cvz8+q+/NHWUHwbqk5hOFfdt9fPGNmIddJYfsVYZtP+7Ty684YsR8DyMg24beOIdaRkQokqAGeg6U/2VpqAgZTRuvfXfkCfwwnywYWrCsg3u+JBXAsRQ4Cs4UGs37C9zcrq3LFoRU7v2w1rzuXNWOi8tQ9PXYZEzna4eqMpduGV/2NDTZXui0zUM6EYbcNL8Ya0WIiOTgOZzdOXLokDV33DrrYMn0qDJAwIU81xF0stTMB74oTHwl9kJLa0G3Xv0LzLsNkaI/RIxD8Guu/D5pybTyRPQUlTQX6lP/3yfWDzsr432hFkpg1KoDXwBaj4iMpEB7xqoGuAGKNdFCK4NWL+KljktuzcgQJI+VSQxTwlQgQc+dNHsuQMntZmHmo3pDtmgQuP2wEGVVIC8br88fGlZhOCWXmrO0i3HtNrQrNea/jUvDYIU8Zg8YrBhU13hBCjyY8JibmjBm5LGTfNAUIBMN10k7cAqCFRMYlXxsCph3i+r4lMns9t+M53LzHzqeXXKoujIxNM8zPV0bIJDey8dcaql6ifYbbdH5E5ykVw83VdycjLWiGlBkcAJjbV6SuIqubL4+TNQSzQEsdtvTpjp/KOiHuErEryxwuZ5SjRmdBkrncnS0ebTPuGLXV3lD7K77xxJsYhwfKUfTvWVtZM1qLFCc1s9wuJOeUjiPsZmK5MnTxR7S+Mn0lxNgtuYGx39GAH2UibM4cs1K7jSObuxul4mX5+GafIZf8UANml5430kwfiqXxewv4bIpcKCI/x8xJElLr7SXhKEKTJo+pmhMX0eYbM7sem66KWMv+AblXzOLyqlxkeVVIuDUiMCbvcIn21xC4u1zpgVg/dFUnN25dngyFqfyLmpw5553i3SarX3B6lSZAh87bQgVcVUDLrJ/gr6NYURiKxDhdDG9J3W6fqUQKUVv5+fGRK90UeVkBmTlXV7gXo7RO7hF5s8blpwzEwsfs+h/36Nrch5Z6HkMs7ZVnQLRgBPiboXtf+Lb0TSKmHMXClXpXaWJC++M/+p6OTk9H895/Dti65t0gAAAABJRU5ErkJggg=="/>                            
                            </a>
                            <?php
        echo sprintf(
            $aeLanguage->get('ALERTINFO'),
            aeSecureFct::human_filesize(MAX_SIZE, 0)
        );
?>
                        </div>

                        <?php

                        if (!$aeLanguage->ready()) {
                            echo '<div class="alert alert-warning fade in" >';
                            echo '<div class="text-danger">Error. No translation file found. ' .
                                'Please download the <a href="https://github.com/afuj/aesecure_quickscan" ' .
                                'target="_blank">aeSecure QuickScan</a> archive again and take ' .
                                'the json file that match your preferred language ' .
                                '(for instance aesecure_quickscan_en-GB.json). ' .
                                'You can find this file in the archive and you need to save ' .
                                'it in the same folder of aesecure_quickscan.php, then refresh ' .
                                'this page.</div>';
                            echo '</div>';
                        } else {
                            if (true !== $aeSession::get('Expert', EXPERT)) {
                                echo '<div class="alert alert-warning alert-dismissible fade in" role="alert">' .
                                '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>';
                                echo $aeLanguage->get('ALERTWARNING');
                                echo '</div>';
                            }

                            echo '<div class="row">';

                            if (true === $aeSession::get('Expert', EXPERT)) {
                                echo '<div class="col-md-12" style="padding-bottom:16px;">' . $aeLanguage->get('SCANFOLDER') . ' <input type="text" name="folder" id="folder" value="' . $aeScan->directory() . '" size="80" /></div>';
                            }

                            echo '<button type="button" id="cleansite" class="btn btn-primary" ' .
                                'data-toggle="popover" data-placement="top" data-html="true" ' .
                                ' data-content="' . $aeLanguage->get('BTNCLEANHINT') . '" ' .
                                'data-old-caption="1. ' . $aeLanguage->get('BTNCLEAN') . '">1. ' .
                                $aeLanguage->get('BTNCLEAN') . '</button>&nbsp;';

                            echo '<button type="button" id="getcountfiles" disabled="disabled" ' .
                                'class="btn btn-primary" data-toggle="popover" ' .
                                'data-placement="bottom" data-html="true" ' .
                                'data-content="<span class=\'text-info\'>' .
                                $aeLanguage->get('BTNGETLISTHINT') . '</span>" ' .
                                'data-old-caption="2. ' . $aeLanguage->get('BTNGETLIST') .
                                '">2. ' . $aeLanguage->get('BTNGETLIST') . '</button>&nbsp;';
                            echo '<button type="button" id="startscan" data-start="0" ' .
                                'data-end="0" disabled="disabled" class="btn btn-primary" ' .
                                'data-toggle="popover" data-placement="bottom" data-html="true" ' .
                                'data-content="<span class=\'text-info\'>' .
                                $aeLanguage->get('BTNSCANHINT') . '</p>" data-old-caption="3. ' .
                                $aeLanguage->get('BTNSCAN') . '">3. ' .
                                $aeLanguage->get('BTNSCAN') . '</button>&nbsp;';

                            $killnr = 4;

                            echo '<button type="button" id="destroy" class="btn btn-warning" ' .
                                'data-toggle="popover" data-placement="bottom" data-html="true" ' .
                                'data-content="<strong class=\'text-danger\'>' .
                                $aeLanguage->get('BTNKILLMEHINT') . '</strong>">' .
                                '<span class="glyphicon glyphicon-trash">&nbsp;</span>' .
                                $killnr . '. ' . $aeLanguage->get('BTNKILLME') . '</button>';

                            echo '<div id="resultGetCountFiles" style="display:none;padding-top:25px;" class="text-info">' .
                            '<div id="resultGetCountFilesNumber" style="padding-bottom:15px;"></div>' .
                            '<div id="resultGetCountFilesButtons"></div>' .
                            '</div>';
                            echo '<div id="result">' . $showInfo . '</div>';
                            echo '</div>';

                            $aeProgress->getHTML();
                        }
?>
                    </div>

                    <?php echo $aeScan->getHTMLFooter(); ?>

                </div>
            </div>
        </div>

        <?php
            echo aeSecureFct::addJavascript(
                'libs/jquery/js/jquery.min.js',
                '//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js'
            );

echo aeSecureFct::addJavascript(
    'libs/bootstrap/js/bootstrap.min.js',
    '//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js'
);

echo aeSecureFct::addJavascript(
    'libs/tablesorter/js/jquery.tablesorter.combined.js',
    'https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.24.5/js/jquery.tablesorter.combined.js',
    true
);

echo aeSecureFct::addJavascript(
    'libs/alertify/js/alertify.min.js',
    'https://cdnjs.cloudflare.com/ajax/libs/alertify.js/0.3.11/alertify.min.js',
    true
);
?>

        <script defer="defer">
            $(document).ready(function() {

                var $body   = $(document.body);
                var navHeight = $('.navbar').outerHeight(true) + 10;

                // For esthetic purpose; scroll back to top-left before
                // displaying the Expert menu
                $('#open-button').bind('click', function() { window.scrollTo(0,0); });

                // Set the TOP position of the advanced menu button and area
                // depending on the top horizontal navigation bar
                $top = $("nav").height();

                if($top!==null) {
                    $("#open-button").css({ top: $top +'px' });
                    $(".menu-wrap").css({ top: $top +'px' });
                }

                try {
                    // OffCanvasMenuEffects from http://tympanus.net/Development/OffCanvasMenuEffects/
                    !function(s){"use strict";function e(s){return new RegExp("(^|\\s+)"+s+"(\\s+|$)")}function n(s,e){var n=a(s,e)?c:t;n(s,e)}var a,t,c;"classList"in document.documentElement?(a=function(s,e){return s.classList.contains(e)},t=function(s,e){s.classList.add(e)},c=function(s,e){s.classList.remove(e)}):(a=function(s,n){return e(n).test(s.className)},t=function(s,e){a(s,e)||(s.className=s.className+" "+e)},c=function(s,n){s.className=s.className.replace(e(n)," ")});var i={hasClass:a,addClass:t,removeClass:c,toggleClass:n,has:a,add:t,remove:c,toggle:n};"function"==typeof define&&define.amd?define(i):s.classie=i}(window);
                    !function(){function e(){n()}function n(){d.addEventListener("click",t),u&&u.addEventListener("click",t),o.addEventListener("click",function(e){var n=e.target;i&&n!==d&&t()})}function t(){i?classie.remove(c,"show-menu"):classie.add(c,"show-menu"),i=!i}var c=document.body,o=document.querySelector(".content-wrap"),d=document.getElementById("open-button"),u=document.getElementById("close-button"),i=!1;e()}();
                } catch (e) {
                }

            });
        </script>

        <script defer="defer">

            var $maxFilesByCycle=parseInt($('#nbrFilesCycle').val());
            var $debug=<?php echo true === $aeSession::get('Debug', DEBUG) ? 'true' : 'false'; ?>;
            var $demo=<?php echo DEMO ? 'true' : 'false'; ?>;
            var $rootfolder='<?php echo str_replace('\\', '\\\\', (string) $aeScan->directory()); ?>';

            /**
             * Format a number in javascript; display 15.202 (with thousand separator) instead 15202
             * @param {type} x
             * @returns {unresolved}
             */
            function numberWithCommas(x) {
                return x.toString().replace(
                    /\B(?=(\d{3})+(?!\d))/g,
                    "<?php echo $aeLanguage->get('THOUSANDSEPARATOR'); ?>"
                );
            }

            $('[data-toggle="popover"]').popover({trigger:'hover',html:true});

            <?php $aeProgress->getJSFunction('initialize'); ?>

            // Submit button of the advanced form
            $('#btnSubmit').click(function(e) {

                e.stopImmediatePropagation();

                var $data = '';

                $data += "lang=" + $('#lang').val() + "&";

                // Prepare a string with all checkboxes and their status (on or off).
                $('input[type=checkbox]').each(function () {
                    $data+=this.id + "=" + (this.checked ? "on" : "off") + "&";
                });

                if($demo==true) {
                    alert('Expert and Debug modes aren\'t allowed in demo mode. ' +
                        'These options won\'t be accessible at this time');
                }

                $data += "formTask=SaveSession&nbFiles=" + parseInt($('#nbrFilesCycle').val());

                // Post the data and reload the page to take the new settings into account
                $.post("<?php echo FILE; ?>", $data).done(function( data ) {
                    var $url = window.location.href;
                    $url = removeURLParameter($url,'lang') + 'lang=' + $('#lang').val();
                    window.location.href=$url;
                });

            });

            // Start the scan process
            $('#cleansite').click(function (e) {
                e.stopImmediatePropagation();
                var btn=this;
                $.ajax({
                    beforeSend: function() {
                        if(!$debug) $('#cleansite').prop("disabled", true);
                        $('.popover').popover('hide');
                        $('#result').empty();
                        $('#resultGetCountFilesNumber').empty();
                        <?php $aeProgress->getJSFunction('ajax_before'); ?>
                    },
                    async:true,
                    type:($debug?'GET':'POST'),
                    url: "<?php echo FILE; ?>",
                    data:"task=cleansite&folder="+(btoa($('#folder').val())),
                    success: function (data) {
                        <?php $aeProgress->getJSFunction('ajax_success'); ?>
                        $('#cleansite').html("1. <?php echo $aeLanguage->get('BTNCLEANDONE');?>");
                        $('#result').html(data);
                        $('#getcountfiles').prop("disabled", false);
                        // To remember that we've already click on this button
                        $(btn).addClass('btn-success');

                    }
                });
            });

            // Get the number of files that will be analyzed during the scan
            $('#getcountfiles').click(function (e) {
                e.stopImmediatePropagation();
                var $data = new Object;
                $data.task = "getcountfiles"
                $data.folder=btoa($('#folder').val());

                // Define the "toString()" method to get a string representation of the object
                Object.prototype.toString = function dogToString() {
                    var ret = 'task=' + this.task + '&folder=' + this.folder;
                    return ret;
                }

                var btn=this;

                $.ajax({
                    beforeSend: function() {
                        if(!$debug) $('#getcountfiles').prop("disabled", true);
                        $('.popover').popover('hide');
                        $('#getcountfiles').html("2. <?php echo $aeLanguage->get('RUNNING');?>");
                        $('#result').empty();
                        $('#resultGetCountFilesNumber').empty();
                        $('#result').html('<div class="blink" id="gettingFiles"><?php echo str_replace("'", "\'", (string) $aeLanguage->get('GETTINGFILES'));?></div>');
                    },
                    async:true,
                    cache:false,
                    type:($debug?'GET':'POST'),
                    url: "<?php echo FILE; ?>",
                    data:$data,
                    dataType:"json",
                    success: function (json) {

                        var $msg="<?php echo str_replace('"', '\"', (string) $aeLanguage->get('GETCOUNTFILESDONE'));?>".replace("%s",numberWithCommas(json.count));

                        $msg=$msg.replace("%s",numberWithCommas(json.blacklisted));
                        $msg=$msg.replace("%s",numberWithCommas(json.edited));
                        $msg=$msg.replace("%s",numberWithCommas(json.whitelisted));
                        $msg=$msg.replace("%s",numberWithCommas(json.skipped));

                        var $tmp="<?php echo $aeLanguage->get('FILES');?>".replace("%s",numberWithCommas(json.count));

                        $('#startscan').html("3. <?php echo $aeLanguage->get('SCANFILES');?>".replace("%s",numberWithCommas(json.count)));

                        if (json.blacklisting != "") {
                            $blacklisting='';
                            $.each(json.blacklisting, function(){
                                $blacklisting+="<li><?php echo $aeLanguage->get('ISAVIRUS');?>: "+this+"</li>";
                            });
                            $('#resultGetCountFilesButtons').append("<ol>"+$blacklisting+"</ol>");
                        }
                        
                        if (json.editlisting != "") {
                            $editlisting='';
                            $.each(json.editlisting, function(){
                                $editlisting+="<li><?php echo $aeLanguage->get('CONTAINAVIRUS');?>: "+this+"</li>";
                            });
                            $('#resultGetCountFilesButtons').append("<ol>"+$editlisting+"</ol>");
                        }

                        if(json.count<=$maxFilesByCycle) {

                            $('button[id^=startscan_]').hide();

                        } else {

                            // There are for instance 5.560 files and we process 1.000 files at a time
                            // We need then dynamically generate six buttons
                            //  1.  Files 1 -> 999
                            //  2.  Files 1.000 -> 1.999
                            //  3.  Files 2.000 -> 2.999
                            //  4.  Files 3.000 -> 3.999
                            //  5.  Files 4.000 -> 4.999
                            //  6.  Files 5.000 -> 5.560

                            var $wBtn=0;
                            var $start=0;
                            var $end=0;

                            while ($start<json.count) {
                                $wBtn+=1;
                                $end=$start+$maxFilesByCycle;

                                if ($end>json.count) $end=json.count;

                                btn='<button type="button" id="startscan_'+$wBtn+'" data-start="'+$start+'" data-end="'+$maxFilesByCycle+'" disabled="disabled" class="btn btnscan btn-primary" data-toggle="popover" data-placement="bottom" data-html="true" data-content="" data-old-caption="'+numberWithCommas($start+1)+' -> '+numberWithCommas($end)+'">'+
                                numberWithCommas($start+1)+' -> '+numberWithCommas($end)+'</button>&nbsp;';

                                $('#resultGetCountFilesButtons').append(btn);

                                $start+=$maxFilesByCycle;
                            }

                        } // if(json.count<=$maxFilesByCycle)

                        // Call initButtons to set the onClick event for these buttons
                        initButtons();

                        $('#result').empty();
                        $('#getcountfiles').html("2. "+$tmp);
                        $('#resultGetCountFilesNumber').html($msg);
                        $('#resultGetCountFiles').show();
                        $('button[id^=startscan]').prop("disabled", false);

                        // No viruses immediatly detected after the count files?
                        // Ok, great, remove the warning
                        if(json.blacklisted==0) {
                            $('#virusalreadyfound').remove();
                        }

                        // No files having virus in it immediatly detected after the count files?
                        // Ok, great, remove the warning
                        if(json.edited==0) {
                            $('#virusaddedfound').remove();
                        }

                        // To remember that we've already click on this button
                        $(btn).addClass('btn-success');

                    },
                    error: function(Request, textStatus, errorThrown) {
                        // Restore the caption of the button and put it in red
                        $(btn).text($(btn).attr('data-old-caption'));
                        $(btn).removeClass('btn-warning').addClass('btn-danger');

                        // Re-enable the button so the user can restart the scan once he made some changes (like f.i. modifying the
                        // number of files to scan in one pass
                        $(btn).prop("disabled", false);

                        // Display an error message to inform the user about the problem
                        var $msg = '<div class="bg-danger text-danger img-rounded" style="margin-top:25px;padding:10px;">';
                        $msg = $msg + '<strong>An error has occured :</strong><br/>';
                        $msg = $msg + 'Internal status: '+textStatus+'<br/>';
                        $msg = $msg + 'HTTP Status: '+Request.status+' ('+Request.statusText+')<br/>';
                        $msg = $msg + 'XHR ReadyState: ' + Request.readyState + '<br/>';
                        $msg = $msg + 'Raw server response:<br/>'+Request.responseText+'<br/>';

                        if($debug) {
                            $url='<?php echo FILE; ?>?'+$data.toString();
                            $msg = $msg + 'URL that has returned the error : <a target="_blank" href="'+$url+'">'+$url+'</a><br/><br/>';
                        }

                        $msg = $msg + '<?php echo str_replace("'", "\'", sprintf($aeLanguage->get('QUICKSCANFAQ'), $aeLanguage->get('QUICKSCANURL'))); ?>';
                        $msg = $msg + '</div>';

                        $('#result').html($msg);
                    }
                });


            });

            // Destroy, delete this script on the server
            $('#destroy').click(function (e) {
                e.stopImmediatePropagation();

                var $bExists=0;
                var $keepWhiteList=1;

                // Check if a whitelist file exists; the checkwhitelist task will return 1 or 0
                $.ajax({
                    async:false,
                    type:($debug?'GET':'POST'),
                    url: "<?php echo FILE; ?>",
                    data:"task=checkwhitelist",
                    success: function (data) {
                        $bExists=(data==1?true:false);
                    }
                });

                // If there is a whitelist file, ask if we need
                if ($bExists) var $keepWhiteList=confirm("<?php echo $aeLanguage->get('JS_KEEPWHITELIST');?>");

                // Now, start the request for the deletion of this script
                $.ajax({
                    beforeSend: function() {
                        $('#cleansite').prop("disabled", true);
                        $('#getcountfiles').prop("disabled", true);
                        $('#startscan').prop("disabled", true);
                        $('#destroy').prop("disabled", true);
                        $('.popover').popover('hide');
                        $('#result').empty();
                    },
                    async:true,
                    type:($debug?'GET':'POST'),
                    url: "<?php echo FILE; ?>",
                    data:"task=byebye&keepwhitelist="+$keepWhiteList,
                    success: function (data) {
                        $('#cleansite').html("<?php echo $aeLanguage->get('BTNKILLMEDONE');?>");
                        $('#getcountfiles').html("<?php echo $aeLanguage->get('BTNKILLMEDONE');?>");
                        $('#startscan').html("<?php echo $aeLanguage->get('BTNKILLMEDONE');?>");
                        $('#destroy').html("<?php echo $aeLanguage->get('BTNKILLMEDONE');?>");
                        $('#result').html(data);
                    }
                });
            });

            // Change the folder to scan
            $('#folder').change(function (e) {
                e.stopImmediatePropagation();

                $.ajax({
                    beforeSend: function() {
                        $('#folder').prop("disabled", true);
                        $('#result').empty();
                    },
                    async:true,
                    type:($debug?'GET':'POST'),
                    url: "<?php echo FILE; ?>",
                    data:"task=chgfolder&folder="+btoa($('#folder').val()),
                    success: function (data) {
                        $('#folder').prop("disabled", false);
                        $('#cleansite').text($('#cleansite').attr('data-old-caption'));
                        $('#cleansite').prop("disabled", false);
                        $('#getcountfiles').text($('#getcountfiles').attr('data-old-caption'));
                        $('#getcountfiles').prop("disabled", false);
                        $('#resultGetCountFiles').hide();
                        $('#result').html(data);
                        // By changing of folder, the returned value, if there, is the
                        // name of the CMS in that folder
                        $('.navbar-brand').html(data.CMS);
                    }
                });
            });

            if($debug){
                $('#DebugMode').click(function(e) {
                    e.stopImmediatePropagation();
                    $.ajax({
                        async:true,
                        type:($debug?'GET':'POST'),
                        url: "<?php echo FILE; ?>",
                        data:"task=seedebug",
                        datatype:"html",
                        success: function (data) {
                            var w = window.open("", "_blank");
                            if(w!=undefined) { var $w = $(w.document.body); $w.html(data); }
                        }
                    });
                });
            }

            function initButtons() {

                // See file handler
                $('.seefile').click(function(e) {
                    e.preventDefault();
                    e.stopImmediatePropagation();
                    var $filename=$(this).attr('data-filename');
                    var $button=$(this);
                    $.ajax({
                        async:true,
                        type:($debug?'GET':'POST'),
                        url: "<?php echo FILE; ?>",
                        data:"task=seefile&filename="+$filename,
                        datatype:"html",
                        success: function (data) {
                            var w = window.open("", "_blank");
                            if(w!=undefined) { var $w = $(w.document.body); $w.html(data); }
                        }
                    });
                });

                // Hide the file
                $('.hidefile').click(function(e) {
                    e.preventDefault();
                    e.stopImmediatePropagation();
                    $(this).parent().fadeOut('slow');
                });

                // Add to the white list handler
                $('.whitelist').click(function(e) {
                    e.preventDefault();
                    e.stopImmediatePropagation();
                    // Add to the white list file handler
                    var $filename=$(this).attr('data-filename');
                    var $button=$(this);
                    $.ajax({
                        async:true,
                        type:($debug?'GET':'POST'),
                        url: "<?php echo FILE; ?>",
                        data:"task=whitelist&filename="+$filename,
                        success: function (data) {
                            $button.parent().parent().fadeOut(500);
                        }
                    });
                });

                // Trigger click on the StartScan buttons
                $("[id^=startscan]").click(function(e) {

                    e.stopImmediatePropagation();

                    // Check if the startscan button has data-start and/or data-end attributes.  If yes, use it to limit the scan action
                    // For instance data-start=100 data-end=50  ==> process files from the file number 100 and process 50 files max.
                    var $start=0;
                    var $end=0;
                    if ($(this).attr('data-start')) $start=$(this).attr('data-start');
                    if ($(this).attr('data-end')) $end=$(this).attr('data-end');

                    var $data = new Object;
                    $data.task = "doscan"
                    $data.folder=btoa($('#folder').val());
                    $data.start=$start;
                    $data.end=$end;

                    // Define the "toString()" method to get a string representation of the object
                    Object.prototype.toString = function dogToString() {
                        var ret = 'task=' + this.task + '&folder=' + this.folder + '&start=' + this.start + '&end=' + this.end;
                        return ret;
                    }

                    var btn=this;

                    $.ajax({
                        beforeSend: function() {
                            $("[id^=startscan_]").each(function() {
                                $(this).removeClass('btn-warning');
                            });

                            $(btn).addClass('btn-warning');
                            $(btn).prop("disabled", true);
                            $('.popover').popover('hide');

                            <?php $aeProgress->getJSFunction('ajax_before'); ?>

                            $(btn).html("3. <?php echo $aeLanguage->get('RUNNING');?>");
                            $('#result').empty();
                        },
                        async:true,
                        cache:false,
                        type:($debug?'GET':'POST'),
                        url: "<?php echo FILE; ?>",
                        data:$data,
                        success: function (data) {
                            <?php $aeProgress->getJSFunction('ajax_success'); ?>
                            $('#result').html(data);
                            // To remember that we've already click on this button
                            $(btn).removeClass('btn-warning');
                            // To remember that we've already click on this button
                            $(btn).addClass('btn-success');
                            $(btn).prop("disabled", false);
                            $(btn).text($(btn).attr('data-old-caption'));
                        },
                        error: function(Request, textStatus, errorThrown) {
                            // Hide the progress bar
                            <?php $aeProgress->getJSFunction('ajax_success'); ?>

                            // Restore the caption of the button and put it in red
                            $(btn).text($(btn).attr('data-old-caption'));
                            $(btn).removeClass('btn-warning').addClass('btn-danger');

                            // Re-enable the button so the user can restart the scan once he made some changes (like f.i. modifying the
                            // number of files to scan in one pass
                            $(btn).prop("disabled", false);

                            // Display an error message to inform the user about the problem
                            var $msg = '<div class="bg-danger text-danger img-rounded" style="margin-top:25px;padding:10px;">';
                            $msg = $msg + '<strong>An error has occured :</strong><br/>';
                            $msg = $msg + 'Internal status: '+textStatus+'<br/>';
                            $msg = $msg + 'HTTP Status: '+Request.status+' ('+Request.statusText+')<br/>';
                            $msg = $msg + 'XHR ReadyState: ' + Request.readyState + '<br/>';
                            $msg = $msg + 'Raw server response:<br/>'+Request.responseText+'<br/>';

                            if ($debug) {
                                $url='<?php echo FILE; ?>?'+$data.toString();
                                $msg = $msg + 'URL that has returned the error : <a target="_blank" href="'+$url+'">'+$url+'</a><br/><br/>';
                            }

                            $msg = $msg + '<?php echo str_replace("'", "\'", (string) $aeLanguage->get('QUICKSCANFAQ')); ?>';
                            $msg = $msg + '</div>';

                            $('#result').html($msg);
                        }
                    });
                });

                <?php if (true === $aeSession::get('Expert', EXPERT)) {?>
                    $('.killfile').mouseover(function(e) {
                        $(this).html($(this).attr("data-caption"));
                    });

                    $('.killfile').mouseleave(function(e) {
                        $(this).html($(this).attr("data-old-caption"));
                    });

                    $('.killfile').click(function(e) {
                        e.stopImmediatePropagation();
                        // Kill file handler
                        var $filename=$(this).attr('data-filename');
                        var $button=$(this);
                        var $confirm="<?php echo $aeLanguage->get('JS_CONFIRMKILL'); ?>".replace("%s",atob($filename));
                        if (confirm($confirm)) {
                            $.ajax({
                                async:true,
                                type:($debug?'GET':'POST'),
                                url: "<?php echo FILE; ?>",
                                data:"task=killfile&filename="+$filename,
                                success: function (data) {
                                    if(data==-1) {
                                        // Delete successfull when returned value is -1
                                        $button.parent().parent().fadeOut(500);
                                        $button.parent().html("<?php echo str_replace('"', '\"', (string) $aeLanguage->get('JS_UNLINKSUCCESS')); ?>");
                                    } else {
                                        if(data==-50) {
                                            $button.parent().html("<?php echo str_replace('"', '\"', (string) $aeLanguage->get('JS_FILENOTFOUND')); ?>");
                                        } else {
                                            $button.parent().html("<?php echo str_replace('"', '\"', (string) $aeLanguage->get('JS_UNLINKERROR')); ?>");
                                        }
                                    }
                                }
                            });
                        }
                    });

                <?php }?>

            }

            function removeURLParameter(url, parameter) {
                var rtn = url.split("?")[0], param, params_arr = [], queryString = (url.indexOf("?") !== -1) ? url.split("?")[1] : "";

                if (queryString !== "") {
                    params_arr = queryString.split("&");
                    for (var i = params_arr.length - 1; i >= 0; i -= 1) {
                        param = params_arr[i].split("=")[0];
                        if (param === parameter) params_arr.splice(i, 1);
                    }
                    rtn = rtn + "?" + params_arr.join("&");
                } else {
                    rtn += "?";
                }

                return rtn;
            }

            <?php $aeProgress->getJSFunction('function'); ?>

        </script>
    </body>
</html>