<?php

class Waindigo_InstallUpgrade_Model_InstallUpgrade extends XenForo_Model
{

    protected static $_skippedFiles = array();

    protected $_ftp = null;

    public function extractFromFile($fileName, $type = '')
    {
        if (!file_exists($fileName) || !is_readable($fileName)) {
            throw new XenForo_Exception(new XenForo_Phrase('please_enter_valid_file_name_requested_file_not_read'), true);
        }
        
        $fh = fopen($fileName, "r");
        
        $blob = fgets($fh, 5);
        
        if (strpos($blob, 'Rar') !== false) {
            $fileName = $this->_extractFromArchive($fileName, $type, 'Rar');
        } elseif (strpos($blob, 'PK') !== false) {
            $fileName = $this->_extractFromArchive($fileName, $type, 'Zip');
        }
        
        return $fileName;
    }

    protected function _extractFromArchive($source, $type, $adapter)
    {
        @set_time_limit(120);
        ignore_user_abort(true);
        XenForo_Application::get('db')->setProfiler(false);
        
        $destination = XenForo_Helper_File::getTempDir() . DIRECTORY_SEPARATOR . pathinfo($source, PATHINFO_FILENAME) .
             XenForo_Application::$time;
        
        try {
            XenForo_Helper_File::createDirectory($destination);
        } catch (Exception $e) {
            throw new XenForo_Exception(new XenForo_Phrase('waindigo_unable_to_create_temp_dir_installupgrade'), true);
        }
        
        $archive = new Zend_Filter_Decompress(
            array(
                'adapter' => $adapter,
                'options' => array(
                    'target' => $destination
                )
            ));
        
        $archive->filter($source);
        @unlink($source);
        
        $xmlFileNames = array();
        $noUnlink = array();
        if ($type) {
            $fileNames = $this->_sortXmlFiles($this->_getAllXmlFilesOfType($destination, $type));
            
            foreach ($fileNames as $xmlFileName) {
                $rootDir = XenForo_Application::getInstance()->getRootDir();
                $title = $this->_getTitleFromXmlFile($xmlFileName);
                if ($type == 'addon') {
                    $addOnId = $this->_getAddOnIdFromXmlFile($xmlFileName);
                    $fileName = 'install/data/addon-' . $addOnId . '.xml';
                } else {
                    $fileName = 'install/data/' . $type . '-' . $title . '.xml';
                }
                if (@copy($xmlFileName, $rootDir . '/' . $fileName)) {
                    $xmlFileNames[md5($fileName)] = array(
                        $fileName,
                        $title
                    );
                } else {
                    $xmlFileNames[md5($fileName)] = array(
                        $xmlFileName,
                        $title
                    );
                    $noUnlink[] = $xmlFileName;
                }
            }
        }
        
        $uploadDirectory = $this->_getBestGuessUploadDirectoryFromList($this->_getAllDirectories($destination));
        
        if ($uploadDirectory) {
            $this->_copyRecursively($uploadDirectory, XenForo_Application::getInstance()->getRootDir());
        }
        
        $this->_unlinkRecursively($destination, $noUnlink);
        
        if (count($xmlFileNames) == 1) {
            $xmlFileNames = reset($xmlFileNames);
            $xmlFileNames = $xmlFileNames[0];
        } elseif (empty($xmlFileNames) && $type) {
            throw new XenForo_Exception(new XenForo_Phrase('provided_file_was_not_valid_xml_file'), true);
        }
        
        return $xmlFileNames;
    }

    protected function _getAllDirectories($path)
    {
        $directory = new Waindigo_InstallUpgrade_Helper_SkipDotsRecursiveDirectoryIterator($path);
        $iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
        
        $directories = array();
        foreach ($iterator as $path => $directory) {
            if ($directory->isDir()) {
                $directories[substr_count(str_replace('\\', '/', $path), '/')][] = $path;
            }
        }
        
        return $directories;
    }

    protected function _getAllXmlFilesOfType($path, $type = '')
    {
        if (substr($path, -1) != '/') {
            $path .= '/';
        }
        
        $xmlFiles = array();
        
        if ($type == 'kml') {
            $ext = 'kml';
        } else {
            $ext = 'xml';
        }
        
        $directory = new RecursiveDirectoryIterator($path);
        $iterator = new RecursiveIteratorIterator($directory);
        foreach (new RegexIterator($iterator, '/^.+\.' . $ext . '$/i', RecursiveRegexIterator::GET_MATCH) as $xmlFile) {
            $fileName = $xmlFile[0];
            if ($this->_getXmlType($fileName) == $type) {
                $xmlFiles[substr_count(str_replace('\\', '/', $fileName), '/')][] = $fileName;
            }
        }
        return $xmlFiles;
    }

    protected function _getXmlType($fileName)
    {
        try {
            $xml = new SimpleXMLElement($fileName, 0, true);
            
            return (string) $xml->getName();
        } catch (Exception $e) {
            return false;
        }
    }

    protected function _getAddOnIdFromXmlFile($fileName)
    {
        try {
            $xml = new SimpleXMLElement($fileName, 0, true);
            
            return (string) $xml['addon_id'];
        } catch (Exception $e) {
            return false;
        }
    }

    protected function _getTitleFromXmlFile($fileName)
    {
        try {
            $xml = new SimpleXMLElement($fileName, 0, true);
            
            return (string) $xml['title'];
        } catch (Exception $e) {
            return false;
        }
    }

    protected function _sortXmlFiles(array $xmlFiles)
    {
        ksort($xmlFiles);
        
        $sortedXmlFiles = array();
        foreach ($xmlFiles as $depth => &$depthXmlFiles) {
            foreach ($depthXmlFiles as $fileName) {
                $sortedXmlFiles[] = $fileName;
            }
        }
        
        return $sortedXmlFiles;
    }

    protected function _getBestGuessUploadDirectoryFromList(array $directories)
    {
        $possibleUploadDirectories = $this->_getPossibleUploadDirectories($directories);
        $bestGuesses = reset($possibleUploadDirectories);
        
        if (!$bestGuesses) {
            return false;
        }
        
        return reset($bestGuesses);
    }

    protected function _getPossibleUploadDirectories($directories)
    {
        foreach ($directories as $level => $levelDirectories) {
            foreach ($levelDirectories as $key => $path) {
                $fileName = pathinfo($path, PATHINFO_FILENAME);
                if ($fileName == 'upload') {
                    continue;
                }
                if (in_array($fileName, 
                    array(
                        'data',
                        'install',
                        'internal_data',
                        'js',
                        'library',
                        'styles'
                    ))) {
                    $directories[$level - 1][] = pathinfo($path, PATHINFO_DIRNAME);
                }
                unset($directories[$level][$key]);
            }
            $directories[$level] = array_unique($directories[$level]);
        }
        return array_filter($directories);
    }

    protected function _copyRecursively($source, $destination)
    {
        $addOnFiles = array();
        
        foreach ($iterator = new RecursiveIteratorIterator(
            new Waindigo_InstallUpgrade_Helper_SkipDotsRecursiveDirectoryIterator($source), 
            RecursiveIteratorIterator::SELF_FIRST) as $item) {
            @set_time_limit(120);
            if ($item->isDir()) {
                $this->_mkdir($destination . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
            } else {
                $fileName = $destination . DIRECTORY_SEPARATOR . $iterator->getSubPathName();
                if (!$this->_copy($item, $fileName)) {
                    self::$_skippedFiles[md5($fileName)] = substr($fileName, strlen($destination) + 1);
                } else {
                    $addOnFiles[] = $fileName;
                }
            }
        }
        
        return $addOnFiles;
    }

    protected function _copy($item, $fileName)
    {
        $xenOptions = XenForo_Application::get('options');
        
        if (!$xenOptions->waindigo_installUpgrade_ftpHost) {
            return @copy($item, $fileName);
        } else {
            $ftp = $this->_getFtp();
            $fileName = str_replace(XenForo_Application::getInstance()->getRootDir(), 
                $xenOptions->waindigo_installUpgrade_ftpPath, $fileName);
            return @ftp_put($this->_getFtp(), $fileName, $item, FTP_BINARY);
        }
    }

    protected function _mkdir($fileName)
    {
        $xenOptions = XenForo_Application::get('options');
        
        if (!$xenOptions->waindigo_installUpgrade_ftpHost) {
            try {
                return XenForo_Helper_File::createDirectory($fileName);
            } catch (Exception $e) {
                return false;
            }
        } else {
            $ftp = $this->_getFtp();
            $fileName = str_replace(XenForo_Application::getInstance()->getRootDir(), 
                $xenOptions->waindigo_installUpgrade_ftpPath, $fileName);
            return @ftp_mkdir($this->_getFtp(), $fileName);
        }
    }

    protected function _getFtp()
    {
        $xenOptions = XenForo_Application::get('options');
        
        if ($this->_ftp === null) {
            $this->_ftp = ftp_connect($xenOptions->waindigo_installUpgrade_ftpHost);
            
            $username = $xenOptions->waindigo_installUpgrade_ftpUsername;
            $password = $xenOptions->waindigo_installUpgrade_ftpPassword;
            
            $result = ftp_login($this->_ftp, $username, $password);
        }
        
        return $this->_ftp;
    }

    protected function _unlinkRecursively($path, array $noUnlink)
    {
        foreach (new RecursiveIteratorIterator(
            new Waindigo_InstallUpgrade_Helper_SkipDotsRecursiveDirectoryIterator($path), 
            RecursiveIteratorIterator::CHILD_FIRST) as $item) {
            @set_time_limit(120);
            $pathName = $item->getPathName();
            if (!in_array($pathName, $noUnlink)) {
                if ($item->isFile()) {
                    @unlink($pathName);
                } else {
                    @rmdir($pathName);
                }
            }
        }
        @rmdir($path);
    }

    public function getSkippedFiles()
    {
        return self::$_skippedFiles;
    }

    public function addCredentials($username, $password, $host, $path, $encrypt = false)
    {
        $userId = 0;
        
        if (function_exists('mcrypt_encrypt') && $encrypt &&
             Waindigo_Listener_ControllerPreDispatch::isAddOnPremium('Waindigo_InstallUpgrade')) {
            $token = XenForo_Helper_Cookie::getCookie('install_credentials_token');
            
            if ($token) {
                $key = pack('H*', $token);
                
                $key_size = strlen($key);
                
                $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
                $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
                
                $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $password, MCRYPT_MODE_CBC, $iv);
                
                $ciphertext = $iv . $ciphertext;
                
                $password = base64_encode($ciphertext);
                
                $userId = XenForo_Visitor::getUserId();
            }
        }
        
        $uniqueKey = md5($userId . $host . $path);
        
        $db = $this->_getDb();
        
        if ($userId) {
            $db->delete('xf_install_upgrade_login', 
                'host = ' . $db->quote($host) . ' AND path = ' . $db->quote($path) . ' AND user_id = 0');
        } else {
            $db->delete('xf_install_upgrade_login', 
                'host = ' . $db->quote($host) . ' AND path = ' . $db->quote($path) . ' AND user_id != 0');
        }
        
        $db->query(
            '
            INSERT INTO xf_install_upgrade_login
            (username, password, host, path, unique_key, user_id)
            VALUES (?, ?, ?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE username = VALUES(username), password = VALUES(password)
        ', 
            array(
                $username,
                $password,
                $host,
                $path,
                $uniqueKey,
                $userId
            ));
    }

    public function deleteCredentials($loginId)
    {
        $db = $this->_getDb();
        
        $db->delete('xf_install_upgrade_login', 'login_id = ' . $db->quote($loginId));
    }

    public function getCredentials($fileName)
    {
        $host = parse_url($fileName, PHP_URL_HOST);
        
        $userIds = array(
            0
        );
        
        if ($this->canEncrypt()) {
            $userIds[] = XenForo_Visitor::getUserId();
        }
        
        try {
            $credentials = $this->fetchAllKeyed(
                '
                    SELECT *
                    FROM xf_install_upgrade_login
                    WHERE host = ?
                        AND user_id IN (' . $this->_getDb()
                    ->quote($userIds) . ')
                ', 'login_id', $host);
        } catch (Exception $e) {
            $credentials = array();
        }
        
        $selectedCredential = array(
            'username' => '',
            'password' => '',
            'path' => ''
        );
        
        foreach ($credentials as $credential) {
            if (strlen($credential['path']) < strlen($selectedCredential['path'])) {
                continue;
            }
            $credential = $this->prepareCredential($credential);
            if (strpos($fileName, $host . $credential['path']) !== false) {
                $selectedCredential = $credential;
            }
        }
        
        return array(
            $selectedCredential['username'],
            $selectedCredential['password']
        );
    }

    public function prepareCredential($credential)
    {
        if (!empty($credential['user_id']) && function_exists('mcrypt_decrypt') &&
             Waindigo_Listener_ControllerPreDispatch::isAddOnPremium('Waindigo_InstallUpgrade')) {
            $token = XenForo_Helper_Cookie::getCookie('install_credentials_token');
            
            if ($token) {
                $key = pack('H*', $token);
                
                $ciphertext_dec = base64_decode($credential['password']);
                
                $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
                
                $iv_dec = substr($ciphertext_dec, 0, $iv_size);
                
                $ciphertext_dec = substr($ciphertext_dec, $iv_size);
                
                $credential['password'] = trim(
                    mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec));
            }
        }
        
        return $credential;
    }

    public function getCredentialById($loginId)
    {
        $userIds = array(
            0
        );
        
        if ($this->canEncrypt()) {
            $userIds[] = XenForo_Visitor::getUserId();
        }
        
        $db = $this->_getDb();
        
        return $db->fetchRow(
            '
            SELECT *
            FROM xf_install_upgrade_login
            WHERE login_id = ? AND user_id IN (' . $this->_getDb()
                ->quote($userIds) . ')
            ', $loginId);
    }

    public function getCredentialByHostAndPath($host, $path)
    {
        $userIds = array(
            0
        );
        
        if ($this->canEncrypt()) {
            $userIds[] = XenForo_Visitor::getUserId();
        }
        
        $db = $this->_getDb();
        
        return $db->fetchRow(
            '
            SELECT *
            FROM xf_install_upgrade_login
            WHERE host = ? AND path = ? AND user_id IN (' . $this->_getDb()
                ->quote($userIds) . ')
            ', array(
                $host,
                $path
            ));
    }

    public function getAllCredentials()
    {
        $userIds = array(
            0
        );
        
        if ($this->canEncrypt()) {
            $userIds[] = XenForo_Visitor::getUserId();
        }
        
        return $this->fetchAllKeyed(
            '
            SELECT *
            FROM xf_install_upgrade_login
            WHERE user_id IN (' . $this->_getDb()
                ->quote($userIds) . ')
            ', 'login_id');
    }

    public function checkForUrl(&$options = array(), &$errorPhraseKey = '')
    {
        @set_time_limit(120);
        $options = array_merge(
            array(
                'original_server_file' => '',
                'server_file' => '',
                'status' => '',
                'redirect' => '',
                'post_params' => array(),
                'username' => '',
                'password' => '',
                'auth_type' => '',
                'store_credentials' => false,
                'cookie_path' => '',
                'return' => '',
                'headers' => array(),
                'redirects' => -1,
                'encrypt' => 0
            ), $options);
        
        $options['redirects']++;
        
        if (filter_var($options['server_file'], FILTER_VALIDATE_URL)) {
            $errors = array();
            
            $client = Waindigo_InstallUpgrade_Helper_Http::getClient($options['server_file']);
            if (isset($options['cookie_jar'])) {
                $client->setCookieJar($options['cookie_jar']);
            }
            if ($options['status'] == 401) {
                if ($options['auth_type'] == 'basic') {
                    $client->setAuth($options['username'], $options['password'], Zend_Http_Client::AUTH_BASIC);
                }
            } elseif ($options['status'] == 403) {
                $client->setCookieJar();
                $client->setParameterPost(
                    array(
                        'login' => $options['username'],
                        'username' => $options['username'],
                        'email' => $options['username'],
                        'password' => $options['password'],
                        'redirect' => $options['redirect']
                    ));
            } elseif (!empty($options['post_params'])) {
                $client->setParameterPost($options['post_params']);
            }
            
            if ($options['status'] != 403 && empty($options['post_params'])) {
                $head = $client->request('HEAD');
                $options['status'] = $head->getStatus();
            } else {
                $response = $client->request('POST');
                $options['status'] = $response->getStatus();
            }
            
            $this->_storeCredentials($client, $options);
            
            if ($options['status'] == 200) {
                $contentType = $client->getLastResponse()->getHeader('Content-Type');
                if (stripos($contentType, 'text/html') !== false) {
                    return $this->_processHtmlResponse($client, $options, $errorPhraseKey);
                } else {
                    return $this->_processNonHtmlResponse($client, $options, $errorPhraseKey);
                }
            }
            if ($options['status'] == 401 || $options['status'] == 403) {
                return $this->_processNoAuthResponse($client, $options, $errorPhraseKey);
            }
        }
    }

    protected function _processHtmlResponse(Zend_Http_Client $client, &$options = array(), &$errorPhraseKey = '')
    {
        $response = $client->getLastResponse();
        
        if (!$response->getRawBody()) {
            if (!empty($options['post_params'])) {
                $response = $client->request('POST');
            } else {
                $response = $client->request('GET');
            }
        }
        
        $body = $response->getBody();
        
        $downloadUrl = $this->_getDownloadUrlFromContent($body, $options['server_file']);
        if (!$downloadUrl && $options['redirect'] != $options['server_file']) {
            $downloadUrl = $options['redirect'];
        }
        if ($downloadUrl) {
            // TODO: check if hosts match
            // TODO: check if we have already visited this URL
            $options['server_file'] = $downloadUrl;
            $options['status'] = '';
            $options['cookie_jar'] = $client->getCookieJar();
            return $this->checkForUrl($options, $errorPhraseKey);
        }
        
        $loginUrl = $this->_getLoginUrlFromLoginForm($body, $options['server_file']);
        if ($loginUrl) {
            $options['status'] = 403;
            return $this->_processNoAuthResponse($client, $options, $errorPhraseKey);
        }
        
        if ($options['return'] == 'body') {
            return $body;
        }
        
        $errorPhraseKey = 'waindigo_no_resource_found_at_specified_url_installupgrade';
        return false;
    }

    protected function _processNonHtmlResponse(Zend_Http_Client $client, &$options = array(), &$errorPhraseKey = '')
    {
        $response = $client->getLastResponse();
        
        if ($options['return'] == 'headers') {
            return $response->getHeaders();
        }
        
        if (!$response->getRawBody()) {
            if (!empty($options['post_params'])) {
                $response = $client->request('POST');
            } else {
                $response = $client->request('GET');
            }
        }
        
        $tempFile = tempnam(XenForo_Helper_File::getTempDir(), md5($options['server_file']));
        $fp = fopen($tempFile, 'w');
        if (isset($response)) {
            fwrite($fp, $response->getRawBody());
        } else {
            fwrite($fp, $client->request('GET')->getRawBody());
        }
        fclose($fp);
        
        $options['server_file'] = $tempFile;
        
        return true;
    }

    protected function _processNoAuthResponse(Zend_Http_Client $client, &$options = array(), &$errorPhraseKey = '')
    {
        $response = $client->getLastResponse();
        
        $options['host'] = parse_url($options['server_file'], PHP_URL_HOST);
        if ($options['status'] == 401) {
            if (isset($options['username']) || isset($options['password'])) {
                $options['errors'][] = new XenForo_Phrase('incorrect_password');
            }
            $authenticate = $response->getHeader('WWW-Authenticate');
            preg_match('#(Basic) realm="(.*)"#s', $authenticate, $matches);
            if (isset($matches[1])) {
                $options['auth_type'] = 'basic';
            }
            if (isset($matches[2])) {
                $options['realm'] = $matches[2];
            }
        } elseif ($options['status'] == 403) {
            $body = $client->request('GET')->getBody();
            $loginUrl = $this->_getLoginUrlFromLoginForm($body, $options['server_file']);
            if ($options['redirect']) {
                if (isset($options['username']) || isset($options['password'])) {
                    if (!$loginUrl && !$options['redirects']) {
                        $options['server_file'] = $options['redirect'];
                        return $this->checkForUrl($options, $errorPhraseKey);
                    }
                    $options['errors'][] = new XenForo_Phrase('incorrect_password');
                }
            } else {
                $options['redirect'] = $options['server_file'];
                $options['server_file'] = $loginUrl;
            }
        }
        if (!$options['username'] || !$options['password']) {
            list($username, $password) = $this->getCredentials($options['server_file']);
            if ($username) {
                $options['username'] = $username;
                $options['password'] = $password;
                if (XenForo_Application::get('options')->waindigo_installUpgrade_useStoredCredentials) {
                    return $this->checkForUrl($options, $errorPhraseKey);
                }
            }
        }
        if ($options['status'] == 403 && $options['redirect'] == $options['server_file'] &&
             $loginUrl != $options['server_file'] && !$options['redirects']) {
            $options['redirect'] = $options['server_file'];
            $options['server_file'] = $loginUrl;
            return $this->checkForUrl($options, $errorPhraseKey);
        }
        $errorPhraseKey = 'do_not_have_permission';
        return false;
    }

    protected function _getLoginUrlFromLoginForm($body, $fileName)
    {
        preg_match('#<form[^>]*action="([^"]*)"[^>]*id="pageLogin"[^>]*>#Us', $body, $matches);
        if (isset($matches[1])) {
            return $this->_getBasedUrlFromContent($body, $fileName, $matches[1]);
        }
        preg_match_all('#<form[^>]*action="([^"]*)"[^>]*>#Us', $body, $matches);
        if (!empty($matches[1])) {
            foreach ($matches[1] as $match) {
                if (strpos($match, 'login') !== false) {
                    return $this->_getBasedUrlFromContent($body, $fileName, $match);
                }
            }
        }
        
        return false;
    }

    protected function _getDownloadUrlFromContent($content, $currentUrl)
    {
        $dom = new Zend_Dom_Query($content);
        $downloadButtons = $dom->query('label.downloadButton a');
        if ($downloadButtons->count()) {
            foreach ($downloadButtons as $downloadButton) {
                $href = $downloadButton->getAttribute('href');
                if ($href != $currentUrl) {
                    return $this->_getBasedUrlFromContent($content, $currentUrl, $href);
                }
            }
        }
    }

    protected function _getBasedUrlFromContent($content, $currentUrl, $link)
    {
        if (filter_var($link, FILTER_VALIDATE_URL)) {
            return $link;
        }
        preg_match('#<base[^>]*href="([^"]*)"[^>]*>#Us', $content, $matches);
        if (isset($matches[1])) {
            return $matches[1] . $link;
        }
        
        $parsedUrl = parse_url($currentUrl);
        $baseUrl = $parsedUrl['scheme'] . '://' . $parsedUrl['host'];
        if (strlen($link) && substr($link, 0, 1) == '/') {
            return $baseUrl;
        }
        
        return $baseUrl . '/' . pathinfo($parsedUrl['path'], PATHINFO_DIRNAME);
    }

    /**
     *
     * @param Zend_Http_Client $client
     * @param string $fileName
     */
    protected function _storeCredentials(Zend_Http_Client $client, &$options = array())
    {
        if (!$options['store_credentials']) {
            return false;
        }
        
        $path = $options['cookie_path'];
        if (!$path) {
            $path = '/';
        }
        
        if ($client->getCookieJar()) {
            $cookieJar = $client->getCookieJar();
            $cookies = $cookieJar->getAllCookies();
            foreach ($cookies as $cookie) {
                /* @var $cookie Zend_Http_Cookie */
                if (strpos($options['server_file'], $cookie->getPath()) !== false &&
                     strlen($cookie->getPath()) > strlen($path)) {
                    $path = $cookie->getPath();
                }
            }
        }
        
        $username = $options['username'];
        $password = $options['password'];
        $host = parse_url($options['server_file'], PHP_URL_HOST);
        
        $encrypt = $options['encrypt'];
        
        $this->addCredentials($username, $password, $host, $path, $encrypt);
    }

    public function checkForAddOnUpdates(array &$addOns, &$errorString = null, $overrideCheck = false)
    {
        $xenOptions = XenForo_Application::get('options');
        
        if (!$xenOptions->waindigo_installUpgrade_uniqueIdentifier) {
            if ($xenOptions->waindigo_premium_uniqueIdentifier) {
                $uniqueIdentifier = $xenOptions->waindigo_premium_uniqueIdentifier;
            } else {
                $uniqueIdentifier = XenForo_Application::generateRandomString(30);
            }
            $dw = XenForo_DataWriter::create('XenForo_DataWriter_Option');
            $dw->setExistingData('waindigo_installUpgrade_uniqueIdentifier');
            $dw->set('option_value', $uniqueIdentifier);
            $dw->save();
            XenForo_Application::get('options')->set('waindigo_installUpgrade_uniqueIdentifier', $uniqueIdentifier);
        }
        
        /* @var $optionModel XenForo_Model_Option */
        $optionModel = $this->getModelFromCache('XenForo_Model_Option');
        
        $installUpgradeOptions = $optionModel->getOptionsByAddOn('Waindigo_InstallUpgrade');
        if (XenForo_Application::$versionId > 1020000) {
            $installUpgradeOptionValues = XenForo_Application::arrayColumn($installUpgradeOptions, 'option_value', 
                'option_id');
        } else {
            $installUpgradeOptionValues = Waindigo_InstallUpgrade_Application::arrayColumn($installUpgradeOptions, 
                'option_value', 'option_id');
        }
        
        $addOnsEncrypted = array();
        foreach ($addOns as $addOnId => $addOn) {
            if (!$xenOptions->waindigo_installUpgrade_checkNonWaindigoAddOns) {
                if (strlen($addOnId) < strlen('Waindigo_') || substr($addOnId, 0, strlen('Waindigo_')) != 'Waindigo_') {
                    continue;
                }
            }
            $addOnsEncrypted[md5($addOnId)] = array(
                'version_id' => $addOn['version_id'],
                'version_string' => $addOn['version_string'],
                'active' => $addOn['active']
            );
        }
        
        $url = 'https://waindigo.org/updates/index.json';
        
        list($username, $password) = $this->getCredentials($url);
        
        $visitor = XenForo_Visitor::getInstance();
        $language = $visitor->getLanguage();
        
        $boardTotals = $this->getModelFromCache('XenForo_Model_DataRegistry')->get('boardTotals');
        if (empty($boardTotals['activeUsers'])) {
            $boardTotals = $this->getModelFromCache('XenForo_Model_Counters')->rebuildBoardTotalsCounter();
        }
        
        try {
            $checker = Waindigo_InstallUpgrade_Helper_Http::getClient($url);
            if ($username && $password) {
                $checker->setParameterPost('username', $username);
                $checker->setParameterPost('password', $password);
                if ($xenOptions->waindigo_installUpgrade_allowSendMemberCount && !empty($boardTotals['activeUsers'])) {
                    $checker->setParameterPost('user_count', $boardTotals['activeUsers']);
                }
            }
            $checker->setParameterPost('language', 
                !empty($language['language_code']) ? $language['language_code'] : 'en-US');
            $checker->setParameterPost('addons_encrypted', $addOnsEncrypted);
            $checker->setParameterPost('board_url', $xenOptions->boardUrl);
            $checker->setParameterPost('current_version_id', $xenOptions->currentVersionId);
            $checker->setParameterPost('options', $installUpgradeOptionValues);
            if (XenForo_Application::debugMode()) {
                $checker->setParameterPost('debug_mode', XenForo_Application::debugMode());
            }
            
            $checkerResponse = $checker->request('POST');
            
            if (!$checkerResponse || $checkerResponse->getStatus() != 200) {
                $errorString = 'Error with Waindigo update server';
                return false;
            }
        } catch (Zend_Http_Client_Exception $e) {
            $errorString = 'Connection to Waindigo failed';
            return false;
        }
        
        $body = $checkerResponse->getBody();
        $updatedAddOnIds = array();
        $serverErrors = array();
        if ($body) {
            $responseArray = json_decode($body, true);
            if (!empty($responseArray['addOnIds'])) {
                $updatedAddOnIds = $responseArray['addOnIds'];
            }
            if (!empty($responseArray['errorString'])) {
                $errorString = $responseArray['errorString'];
            }
            if (!empty($responseArray['options'])) {
                $options = $responseArray['options'];
            }
            if (!empty($responseArray['broadcast'])) {
                $installUpgradeBroadcast = XenForo_Application::getSimpleCacheData('installUpgradeBroadcast');
                if (!$installUpgradeBroadcast || $installUpgradeBroadcast['message'] != $responseArray['broadcast'] ||
                     $installUpgradeBroadcast['class'] != $responseArray['broadcastClass']) {
                    $installUpgradeBroadcast['message'] = $responseArray['broadcast'];
                    $installUpgradeBroadcast['class'] = $responseArray['broadcastClass'];
                    unset($installUpgradeBroadcast['dismiss']);
                }
                $installUpgradeBroadcast['time'] = XenForo_Application::$time;
                XenForo_Application::setSimpleCacheData('installUpgradeBroadcast', $installUpgradeBroadcast);
            }
        }
        
        $this->_getDb()->query('
                UPDATE xf_addon SET install_upgrade_updated = 0
            ');
        
        if (!empty($updatedAddOnIds)) {
            $this->_getDb()->query(
                '
                UPDATE xf_addon SET install_upgrade_updated = 1
                WHERE MD5(addon_id) IN (' . $this->_getDb()
                    ->quote($updatedAddOnIds) . ')
            ');
            
            foreach ($addOns as $addOnId => $addOn) {
                if (in_array(md5($addOnId), $updatedAddOnIds)) {
                    $addOns[$addOnId]['install_upgrade_updated'] = 1;
                } else {
                    $addOns[$addOnId]['install_upgrade_updated'] = 0;
                }
            }
        }
        
        if (!empty($options)) {
            foreach ($options as $optionName => $optionValue) {
                $dw = XenForo_DataWriter::create('XenForo_DataWriter_Option', XenForo_DataWriter::ERROR_SILENT);
                $dw->setExistingData('waindigo_installUpgrade_' . $optionName);
                $dw->set('option_value', $optionValue);
                $dw->save();
            }
        }
        
        return true;
    }

    public function validateLicenseToken($licenseValidationToken)
    {
        if (!$licenseValidationToken) {
            return false;
        }
        
        $xenOptions = XenForo_Application::get('options');
        
        $domain = parse_url($xenOptions->boardUrl, PHP_URL_HOST);
        
        try {
            $checker = Waindigo_InstallUpgrade_Helper_Http::getClient('https://xenforo.com/api/license-lookup.json');
            $checker->setParameterPost('token', $licenseValidationToken);
            $checker->setParameterPost('domain', $domain);
            $checkerResponse = $checker->request('POST');
            
            if (!$checkerResponse || $checkerResponse->getStatus() != 200) {
                return false;
            }
        } catch (Zend_Http_Client_Exception $e) {
            return false;
        }
        
        $body = $checkerResponse->getBody();
        if (!$body) {
            return false;
        }
        
        $responseObject = json_decode($body);
        
        if (empty($responseObject->is_valid)) {
            return false;
        }
        
        if (empty($responseObject->domain_match) && $domain != 'localhost') {
            return false;
        }
        
        return true;
    }

    public function canEncrypt($checkToken = true)
    {
        if (function_exists('mcrypt_encrypt') && function_exists('mcrypt_decrypt') &&
             Waindigo_Listener_ControllerPreDispatch::isAddOnPremium('Waindigo_InstallUpgrade')) {
            if (!$checkToken) {
                return true;
            }
            $token = XenForo_Helper_Cookie::getCookie('install_credentials_token');
            if ($token) {
                return true;
            }
        }
    }
}