Alex Trofimov 12 лет назад
Родитель
Сommit
643ff9c114

+ 22 - 0
inc/classes/BxDolCronUpgradeCheck.php

@@ -0,0 +1,22 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolCron');
+
+class BxDolCronUpgradeCheck extends BxDolCron
+{
+    public function processing()
+    {
+        bx_import('BxDolUpgrader');
+        $o = new BxDolUpgrader();
+        $o->prepare();
+    }
+}
+
+/** @} */

+ 53 - 32
inc/classes/BxDolInstallerHasher.php

@@ -11,50 +11,71 @@ bx_import('BxDolInstallerUtils');
 
 class BxDolInstallerHasher extends BxDolInstallerUtils
 {
+    protected $_iSystemModuleId = 0;
+    protected $_aSystemFiles = array(
+
+        // slash at the end is necessary for directories
+        'inc/',   
+        'modules/base/',
+        'periodic/',
+        'studio/',
+        'template/',
+        
+        'cmts.php',
+        'conn.php',
+        'get_rss_feed.php',
+        'grid.php',
+        'gzip_loader.php',
+        'image_transcoder.php',
+        'index.php',
+        'logout.php',
+        'member.php',
+        'menu.php',
+        'page.php',
+        'searchKeyword.php',
+        'searchKeywordContent.php',
+        'splash.php',
+        'storage.php',
+        'storage_uploader.php',
+        'vote.php',
+        'modules/index.php',
+        'README.md',
+        'license.txt',
+    );
+
     public function getSystemFilesHash () 
     {
         $this->_aNonHashableFiles = array(
             'inc/header.inc.php',
         );
-
-        $aFiles = array(
-
-            // slash at the end is necessary for directories
-            'inc/',   
-            'modules/base/',
-            'periodic/',
-            'studio/',
-            'template/',
-            
-            'cmts.php',
-            'conn.php',
-            'get_rss_feed.php',
-            'grid.php',
-            'gzip_loader.php',
-            'image_transcoder.php',
-            'index.php',
-            'logout.php',
-            'member.php',
-            'menu.php',
-            'page.php',
-            'searchKeyword.php',
-            'searchKeywordContent.php',
-            'splash.php',
-            'storage.php',
-            'storage_uploader.php',
-            'vote.php',
-            'modules/index.php',
-            'README.md',
-            'license.txt',
-        );
         
         $aFilesHashed = array();
 
-        foreach ($aFiles as $sFile)
+        foreach ($this->_aSystemFiles as $sFile)
             $this->hashFiles(BX_DIRECTORY_PATH_ROOT . $sFile, $aFilesHashed);
 
         return $aFilesHashed;
     }
+
+    public function hashSystemFiles()
+    {
+        $aFiles = $this->getSystemFilesHash ();
+        if (!$aFiles)
+            return false;
+
+        $oDb = bx_instance('BxDolStudioInstallerQuery');
+
+        foreach($aFiles as $aFile)
+            $oDb->insertModuleTrack($this->_iSystemModuleId, $aFile);
+
+        return true;
+    }
+
+    public function checkSystemFilesHash () 
+    {
+        $aFiles = $this->getSystemFilesHash ();
+        return $this->hashCheck($aFiles, $this->_iSystemModuleId);
+    }
 }
 
 /** @} */

+ 29 - 1
inc/classes/BxDolInstallerUtils.php

@@ -122,6 +122,11 @@ class BxDolInstallerUtils extends BxDolIO
         }
     }
 
+    /**
+     * Generate hash for module files.
+     * @param $sPath module's root folder
+     * @param $aFiles array to fill with files hashes
+     */
     public function hashFiles($sPath, &$aFiles)
     {
         if (file_exists($sPath) && is_dir($sPath) && ($rSource = opendir($sPath))) {
@@ -143,11 +148,34 @@ class BxDolInstallerUtils extends BxDolIO
         }
     }
 
+    /** 
+     * Check module's files hashes. For system files use @see BxDolInstallerHasher class.
+     * @param $aFiles current files checksums 
+     * @param $iModuleId module id
+     * @return empty array on success, or array of files which checksum was changed 
+     */
+    public function hashCheck($aFiles, $iModuleId, $bBreakOnFirstError = false)
+    {
+        $oDb = bx_instance('BxDolStudioInstallerQuery');
+
+        $aFilesOrig = $oDb->getModuleTrackFiles($iModuleId);
+        $aFailedFailes = array();
+        foreach ($aFiles as $aFile) {
+            if (!isset($aFilesOrig[$aFile['file']]) || $aFilesOrig[$aFile['file']]['hash'] != $aFile['hash']) {
+                $aFailedFailes[] = $aFile['file'];
+                if ($bBreakOnFirstError)
+                    return $aFailedFailes;
+            }
+        }
+
+        return $aFailedFailes;
+    }
+
     protected function hashInfo($sPath)
     {
         return array(
             'file' => $this->filePathWithoutBase($sPath),
-            'hash' => md5(file_get_contents($sPath))
+            'hash' => md5_file($sPath)
         );
     }
 

+ 183 - 0
inc/classes/BxDolUpgrader.php

@@ -0,0 +1,183 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+class BxDolUpgrader extends BxDol
+{
+    protected $_sUrlVersionCheck = 'http://rss.boonex.com/u/';
+    protected $_sError = false;
+
+    public function prepare()
+    {
+        $this->setError(false);
+        $iUmaskSave = umask(0);
+
+        while(true) {
+
+            $aVersionUpdateInfo = $this->getVersionUpdateInfo();
+            if (null === $aVersionUpdateInfo) {
+                $this->setError(_t('_sys_upgrade_get_version_info_failed'));
+                break;
+            }
+
+            if (!$this->isUpgradeAvailable($aVersionUpdateInfo))
+                break;
+
+            // TODO: check db version and files version (files version file need to be added)
+
+            // TODO: add setting opton to force update even if some of the files were modified (3%-5%)
+            $aFailedFiles = $this->checkFilesChecksums ();
+            if (!empty($aFailedFiles)) {
+                $this->setError(_t('_sys_upgrade_files_checksum_failed', implode(',', $aFailedFiles)));
+                break;
+            }
+
+            if (!($sPatchPath = $this->downloadPatch ($aVersionUpdateInfo))) {
+                $this->setError(_t('_sys_upgrade_patch_download_failed'));
+                break;
+            }
+
+            if (!$this->isPatchChecksumCorrect ($sPatchPath, $aVersionUpdateInfo)) {
+                $this->deleteUpgradePatch($sPatchPath);
+                $this->setError(_t('_sys_upgrade_patch_checksum_failed'));
+                break;
+            }
+
+            if (!($sUnpackedPath = $this->unpackPatch ($sPatchPath, true))) {
+                $this->deleteUpgradePatch($sPatchPath);
+                $this->setError(_t('_sys_upgrade_patch_unpack_failed'));
+                break;
+            }            
+
+            $this->deleteUpgradePatch($sPatchPath);
+
+            if (!$this->isValidPatch ($sUnpackedPath, $aVersionUpdateInfo)) {
+                $this->deleteUpgradeFolder($sPatchPath);
+                $this->setError(_t('_sys_upgrade_patch_invalid'));
+                break;
+            }
+
+            break;
+        }
+
+        umask($iUmaskSave);
+
+        // TODO: check error and set transient cron for the real update of files and db!
+
+        echo 'Err:' . $this->getError() . "\n";
+    }
+
+    public function getVersionUpdateInfo ()
+    {
+        $s = bx_file_get_contents($this->_sUrlVersionCheck, array ('v' => bx_get_ver()));
+        if (!$s)
+            return null;
+
+        $a = json_decode($s, true);
+        if (!isset($a['latest_version']))
+            return null;
+
+        return $a;
+    }
+
+    public function isUpgradeAvailable ($a)
+    {
+        if (1 == version_compare(strtolower($a['latest_version']), strtolower(bx_get_ver())) && isset($a['patch']))
+            return true;
+        return false;
+    }
+
+    protected function downloadPatch ($a)
+    {
+        if (!isset($a['patch']['url']))
+            return false;
+
+        if (!($f = fopen($a['patch']['url'], "rb")))
+            return false;
+
+        $sTmpFile = BX_DIRECTORY_PATH_TMP . 'patch_' . bx_get_ver() . '_' . $a['patch']['ver'] . '.zip';
+        if (file_exists($sTmpFile) && !unlink($sTmpFile))
+            return false;        
+
+        $sRet = false;
+        if (false !== $sTmpFile && false !== file_put_contents($sTmpFile, $f))
+            $sRet = $sTmpFile;
+
+        fclose($f);
+
+        return $sRet;
+    }
+
+    protected function isPatchChecksumCorrect ($sPatchPath, $aVersionUpdateInfo)
+    {
+        return md5_file($sPatchPath) == $aVersionUpdateInfo['patch']['md5'];
+    }
+
+    protected function unpackPatch ($sPatchPath)
+    {
+        $sTmpFolder = $this->getTmpFolderFromZip($sPatchPath);
+        if (file_exists($sTmpFolder) && !bx_rrmdir($sTmpFolder))
+            return false;
+        
+        $oZip = new ZipArchive();
+        if ($oZip->open($sPatchPath) !== true)
+            return false;
+
+        $sRootFolder = $oZip->numFiles > 0 ? $oZip->getNameIndex(0) : false;
+        if (!$sRootFolder || !mkdir($sTmpFolder) || !$oZip->extractTo($sTmpFolder))
+            $sRootFolder = false;
+
+        $oZip->close();
+
+        return $sRootFolder ? $sTmpFolder . '/' . trim($sRootFolder, '/') . '/' : false;
+    }
+
+    protected function checkFilesChecksums ()
+    {
+        $oHasher = bx_instance('BxDolInstallerHasher');
+        return $oHasher->checkSystemFilesHash();
+    }
+
+    protected function isValidPatch ($sUnpackedPath, $aVersionUpdateInfo)
+    {
+        $sCheckFilePath =  $sUnpackedPath . 'upgrade/files/' . $this->normalizeVersion(bx_get_ver()) . '-' . $this->normalizeVersion($aVersionUpdateInfo['patch']['ver']) . '/check.php';
+        return file_exists($sCheckFilePath);
+    }
+
+    public function getError()
+    {
+        return $this->_sError;
+    }
+
+    protected function setError($s)
+    {
+        $this->_sError = $s;
+    }
+    
+    protected function normalizeVersion($s)
+    {
+        return str_replace(array('-', '_', ' '), '.', $s);
+    }
+
+    protected function getTmpFolderFromZip($sPatchPath)
+    {
+        return preg_replace('/\.zip$/', '', $sPatchPath);
+    }
+
+    protected function deleteUpgradeFolder($sPatchPath)
+    {
+        @bx_rrmdir($this->getTmpFolderFromZip($sPatchPath));
+    }
+
+    protected function deleteUpgradePatch($sPatchPath)
+    {
+        @unlink($sPatchPath);
+    }
+}
+
+/** @} */

+ 3 - 13
install/classes/BxDolInstallController.php

@@ -98,8 +98,9 @@ class BxDolInstallController
     function actionFinish ()
     {
         require_once(BX_INSTALL_PATH_HEADER);
-
-        $this->hashSystemFiles();
+        
+        $oHasher = bx_instance('BxDolInstallerHasher');
+        $oHasher->hashSystemFiles();
 
         $this->_oView->pageStart();
 
@@ -136,18 +137,7 @@ class BxDolInstallController
         return _t('_sys_inst_title', BX_DOL_VER);
     }
 
-    protected function hashSystemFiles()
-    {
-        bx_import('BxDolInstallerHasher');
-        $oHasher = new BxDolInstallerHasher();
-        $aFiles = $oHasher->getSystemFilesHash ();
-
-        bx_import('BxDolStudioInstallerQuery');
-        $oDb = new BxDolStudioInstallerQuery();
 
-        foreach($aFiles as $aFile)
-            $oDb->insertModuleTrack(0, $aFile);
-    }
 }
 
 /** @} */

+ 1 - 0
install/sql/system.sql

@@ -995,6 +995,7 @@ CREATE TABLE `sys_cron_jobs` (
 
 INSERT INTO `sys_cron_jobs` (`name`, `time`, `class`, `file`, `service_call`) VALUES
 ('pruning', '0 0 * * *', 'BxDolCronPruning', 'inc/classes/BxDolCronPruning.php', ''),
+('sys_upgrade', '0 3 * * *', 'BxDolCronUpgradeCheck', 'inc/classes/BxDolCronUpgradeCheck.php', ''),
 ('sys_storage', '* * * * *', 'BxDolCronStorage', 'inc/classes/BxDolCronStorage.php', '');
 
 -- --------------------------------------------------------

+ 9 - 0
modules/boonex/english/data/langs/system.xml

@@ -1972,6 +1972,15 @@ If it is not enabled then please consider implement this optimization, since it
 	<string name="_sys_txt_unsubscribe_error_occured"><![CDATA[Error occured, please login to your account and update email references there or contact site administrator to unsubscribe you manually.]]></string>
 	<string name="_sys_txt_unsubscribe_info"><![CDATA[Update email preferences for <b>{0}</b>]]></string>
 	<string name="_sys_txt_unsubscribe_wrong_link"><![CDATA[Unsubscribe link is wrong.]]></string>
+
+	<string name="_sys_upgrade_get_version_info_failed"><![CDATA[Get version info failed, please check internet connection on the web-server or web-server firewall]]></string>
+	<string name="_sys_upgrade_patch_download_failed"><![CDATA[Patch download failed, or downloaded file can't be saved]]></string>
+	<string name="_sys_upgrade_patch_unpack_failed"><![CDATA[Patch unpack failed]]></string>
+	<string name="_sys_upgrade_patch_checksum_failed"><![CDATA[Downloaded patch file checksum is invalid]]></string>
+	<string name="_sys_upgrade_files_checksum_failed"><![CDATA[Can't apply script becase the following files were modified: {0}]]></string>
+	<string name="_sys_upgrade_patch_invalid"><![CDATA[Invalid patch, it can't be applied to your version or incorrect files structure in the patch.]]></string>
+
+
 	<string name="_sys_uploader_close"><![CDATA[Close]]></string>
 	<string name="_sys_uploader_confirm_close_popup"><![CDATA[Oh no! Are you sure? This may discard uploading files, seriously.]]></string>
 	<string name="_sys_uploader_confirm_leaving_page"><![CDATA[Upload is in progress! Stay cool and don't close this page, or it will cut off half-way.]]></string>

+ 1 - 2
studio/classes/BxDolStudioInstaller.php

@@ -8,7 +8,6 @@
  */
 
 bx_import('BxDolInstallerUtils');
-bx_import('BxDolStudioInstallerQuery');
 
 define("BX_DOL_STUDIO_INSTALLER_SUCCESS", 0);
 define("BX_DOL_STUDIO_INSTALLER_FAILED", 1);
@@ -50,7 +49,7 @@ class BxDolStudioInstaller extends BxDolInstallerUtils
     {
         parent::__construct();
 
-        $this->oDb = new BxDolStudioInstallerQuery();
+        $this->oDb = bx_instance('BxDolStudioInstallerQuery');
 
         $this->_aConfig = $aConfig;
         $this->_sBasePath = BX_DIRECTORY_PATH_MODULES;

+ 13 - 2
studio/classes/BxDolStudioInstallerQuery.php

@@ -63,6 +63,18 @@ class BxDolStudioInstallerQuery extends BxDolModuleQuery
         $this->query($sQuery);
     }
 
+    function getModuleTrackFiles($iModuleId)
+    {
+        $sQuery = $this->prepare("SELECT `file`, `hash` FROM `sys_modules_file_tracks` WHERE `module_id` = ?", $iModuleId);
+        return $this->getAllWithKey($sQuery, "file");
+    }
+
+    function deleteModuleTrackFiles($iModuleId)
+    {
+        $sQuery = $this->prepare("DELETE FROM `sys_modules_file_tracks` WHERE `module_id` = ?", $iModuleId);
+        return $this->query($sQuery);
+    }
+
     function deleteModule($aConfig)
     {
         $sQuery = $this->prepare("SELECT `id` FROM `sys_modules` WHERE `vendor`=? AND `path`=? LIMIT 1", $aConfig['vendor'], $aConfig['home_dir']);
@@ -71,8 +83,7 @@ class BxDolStudioInstallerQuery extends BxDolModuleQuery
         $sQuery = $this->prepare("DELETE FROM `sys_modules` WHERE `vendor`=? AND `path`=? LIMIT 1", $aConfig['vendor'], $aConfig['home_dir']);
         $this->query($sQuery);
 
-        $sQuery = $this->prepare("DELETE FROM `sys_modules_file_tracks` WHERE `module_id`=?", $iId);
-        $this->query($sQuery);
+        $this->deleteModuleTrackFiles($iId);
 
         return $iId;
     }

+ 11 - 17
studio/classes/BxDolStudioUpdater.php

@@ -34,7 +34,7 @@ class BxDolStudioUpdater extends BxDolStudioInstaller
 
     public function update($aParams)
     {
-        $oDb = BxDolDb::getInstance();
+        $oDb = bx_instance('BxDolStudioInstallerQuery');
 
         $aResult = array(
             'operation_title' => _t('_adm_txt_modules_operation_update', $this->_aConfig['title'], $this->_aConfig['version_from'], $this->_aConfig['version_to'])
@@ -57,32 +57,26 @@ class BxDolStudioUpdater extends BxDolStudioInstaller
             ));
 
         //--- Check hash ---//
-        $sQuery = $oDb->prepare("SELECT `file`, `hash` FROM `sys_modules_file_tracks` WHERE `module_id`=?", $aModuleInfo['id']);
-        $aFilesOrig = $oDb->getAllWithKey($sQuery, "file");
-
         $aFiles = array();
-        $this->hashFiles($this->_sModulePath, $aFiles);
-        foreach($aFiles as $aFile)
-            if(!isset($aFilesOrig[$aFile['file']]) || $aFilesOrig[$aFile['file']]['hash'] != $aFile['hash'])
-                return array_merge($aResult, array(
-                    'message' => $this->_displayResult('check_module_hash', false, '_adm_err_modules_module_was_modified'),
-                    'result' => false
-                ));
+        $this->hashFiles(BX_DIRECTORY_PATH_ROOT . 'modules/' . $this->_aConfig['module_dir'], $aFiles);
+        if (!empty($this->hashCheck($aFiles, $aModuleInfo['id'], true))
+            return array_merge($aResult, array(
+                'message' => $this->_displayResult('check_module_hash', false, '_adm_err_modules_module_was_modified'),
+                'result' => false
+            ));
 
         //--- Perform action and check results ---//
         $aResult = array_merge($aResult, $this->_perform('install', 'Update'));
         if($aResult['result']) {
             $sQuery = $oDb->prepare("UPDATE `sys_modules` SET `version`=? WHERE `id`=?", $this->_aConfig['version_to'], $aModuleInfo['id']);
             $oDb->query($sQuery);
-            $sQuery = $oDb->prepare("DELETE FROM `sys_modules_file_tracks` WHERE `module_id`=?", $aModuleInfo['id']);
-            $oDb->query($sQuery);
+
+            $oDb->deleteModuleTrackFiles($aModuleInfo['id']);
 
             $aFiles = array();
             $this->hashFiles(BX_DIRECTORY_PATH_ROOT . 'modules/' . $this->_aConfig['module_dir'], $aFiles);
-            foreach($aFiles as $aFile) {
-                $sQuery = $oDb->prepare("INSERT IGNORE INTO `sys_modules_file_tracks`(`module_id`, `file`, `hash`) VALUES(?, ?, ?)", $aModuleInfo['id'], $aFile['file'], $aFile['hash']);
-                $oDb->query($sQuery);
-            }
+            foreach($aFiles as $aFile)
+                $oDb->insertModuleTrack($aModuleInfo['id'], $aFile);
 
             //--- Remove update pckage ---//
             $this->delete();