From 39f2f53db27ddadbbf4b22a79d5dc598663c3ef5 Mon Sep 17 00:00:00 2001 From: rMalie Date: Tue, 6 Sep 2011 12:38:55 +0000 Subject: [PATCH] // Merge -> revision 8363 git-svn-id: http://dev.prestashop.com/svn/v1/branches/1.5.x@8365 b9a71923-0436-4b27-9f14-aed3839534dd --- admin-dev/tabs/AdminUpgrade.php | 2072 +---------------- classes/Cart.php | 9 +- classes/FrontController.php | 2 +- classes/PDF.php | 9 +- modules/autoupgrade/AdminPreferences.php | 506 +++++ modules/autoupgrade/AdminSelfTab.php | 2148 ++++++++++++++++++ modules/autoupgrade/AdminSelfUpgrade.php | 2116 ++++++++++++++++++ modules/autoupgrade/Backup.php | 305 +++ modules/autoupgrade/ConfigurationTest.php | 230 ++ modules/autoupgrade/SelfModule.php | 1102 +++++++++ modules/autoupgrade/Tools14.php | 2449 +++++++++++++++++++++ modules/autoupgrade/Upgrader.php | 195 ++ modules/autoupgrade/ajax-upgradetab.php | 87 + modules/autoupgrade/autoupgrade.php | 97 + modules/autoupgrade/config.xml | 12 + modules/autoupgrade/de.php | 56 + modules/autoupgrade/es.php | 148 ++ modules/autoupgrade/fr.php | 155 ++ modules/autoupgrade/functions.php | 390 ++++ modules/autoupgrade/init.php | 84 + modules/autoupgrade/it.php | 56 + modules/autoupgrade/jquery-1.6.2.min.js | 18 + modules/autoupgrade/logo.gif | Bin 0 -> 652 bytes modules/blocklayered/blocklayered.php | 1128 ++++++++-- modules/blocklayered/fr.php | 21 +- 25 files changed, 11187 insertions(+), 2208 deletions(-) create mode 100644 modules/autoupgrade/AdminPreferences.php create mode 100644 modules/autoupgrade/AdminSelfTab.php create mode 100644 modules/autoupgrade/AdminSelfUpgrade.php create mode 100644 modules/autoupgrade/Backup.php create mode 100644 modules/autoupgrade/ConfigurationTest.php create mode 100644 modules/autoupgrade/SelfModule.php create mode 100644 modules/autoupgrade/Tools14.php create mode 100644 modules/autoupgrade/Upgrader.php create mode 100644 modules/autoupgrade/ajax-upgradetab.php create mode 100644 modules/autoupgrade/autoupgrade.php create mode 100644 modules/autoupgrade/config.xml create mode 100644 modules/autoupgrade/de.php create mode 100644 modules/autoupgrade/es.php create mode 100644 modules/autoupgrade/fr.php create mode 100644 modules/autoupgrade/functions.php create mode 100644 modules/autoupgrade/init.php create mode 100644 modules/autoupgrade/it.php create mode 100644 modules/autoupgrade/jquery-1.6.2.min.js create mode 100644 modules/autoupgrade/logo.gif diff --git a/admin-dev/tabs/AdminUpgrade.php b/admin-dev/tabs/AdminUpgrade.php index d50bdad94..dc8042784 100755 --- a/admin-dev/tabs/AdminUpgrade.php +++ b/admin-dev/tabs/AdminUpgrade.php @@ -29,2050 +29,60 @@ require_once(_PS_ADMIN_DIR_.'/tabs/AdminPreferences.php'); class AdminUpgrade extends AdminPreferences { - public $ajax = false; - public $nextResponseType = 'json'; // json, xml - public $next = 'N/A'; - public $isModule = false; - - /** - * set to false if the current step is a loop - * - * @var boolean - */ - public $stepDone = true; - public $status = true; - public $error ='0'; - public $nextDesc = '.'; - public $nextParams = array(); - public $nextQuickInfo = array(); - public $currentParams = array(); - /** - * @var array theses values will be automatically added in "nextParams" - * if their properties exists - */ - public $ajaxParams = array( - // autoupgrade options - 'dontBackupImages', - 'keepDefaultTheme', - 'keepTrad', - 'manualMode', - 'desactivateCustomModule', - - // - 'backupDbFilename', - 'backupFilesFilename', - - - ); - public $autoupgradePath = null; - /** - * autoupgradeDir - * - * @var string directory relative to admin dir - */ - public $autoupgradeDir = 'autoupgrade'; - public $latestRootDir = ''; - public $prodRootDir = ''; - public $adminDir = ''; - public $rootWritable = false; - public $svnDir = 'svn'; - public $destDownloadFilename = 'prestashop.zip'; - public $toUpgradeFileList = array(); - public $backupFileList = array(); - public $sampleFileList = array(); - private $backupIgnoreFiles = array(); - private $backupIgnoreAbsoluteFiles = array(); - private $excludeFilesFromUpgrade = array(); - private $excludeAbsoluteFilesFromUpgrade = array(); - - private $backupFilesFilename = ''; - private $backupDbFilename = ''; - -/** - * int loopBackupFiles : if your server has a low memory size, lower this value - * @TODO remove the static, add a const, and use it like this : min(AdminUpgrade::DEFAULT_LOOP_ADD_FILE_TO_ZIP,Configuration::get('LOOP_ADD_FILE_TO_ZIP'); - */ - public static $loopBackupFiles = 1000; -/** - * int loopUpgradeFiles : if your server has a low memory size, lower this value - */ - public static $loopUpgradeFiles = 1000; -/** - * intloopRemoveSamples : if your server has a low memory size, lower this value - */ - public static $loopRemoveSamples = 1000; - -// public static $skipAction = array('unzip'=>'listSampleFiles'); - public static $skipAction; - public $useSvn; - - protected $_includeContainer = false; - - public function encrypt($string) - { - return md5(_COOKIE_KEY_.$string); - } - public function checkToken() - { - // simple checkToken in ajax-mode, to be free of Cookie class (and no Tools::encrypt() too ) - if ($this->ajax) - return ($_COOKIE['autoupgrade'] == $this->encrypt($_COOKIE['id_employee'])); - else - return parent::checkToken(); - } - - /** - * create cookies id_employee, id_tab and autoupgrade (token) - */ - public function createCustomToken() - { - // ajax-mode for autoupgrade, we can't use the classic authentication - // so, we'll create a cookie in admin dir, based on cookie key - global $cookie; - $id_employee = $cookie->id_employee; - - $cookiePath = __PS_BASE_URI__.str_replace($this->prodRootDir,'',trim($this->adminDir,'/')); - setcookie('id_employee', $id_employee, time()+3600, $cookiePath); - setcookie('id_tab', $this->id, time()+3600, $cookiePath); - setcookie('autoupgrade', $this->encrypt($id_employee), time()+3600, $cookiePath); - return false; - } - - - - public function viewAccess($disable = false){ - if ($this->ajax) - return true; - else - { - // simple access : we'll allow only admin - global $cookie; - if ($cookie->profile == 1) - return true; - } - return false; - } - - public function __construct() - { - @set_time_limit(0); - @ini_set('max_execution_time', '0'); - - $this->init(); - // retrocompatibility when used in module : Tab can't work, - // but we saved the tab id in a cookie. - if(class_exists('Tab',false)) - parent::__construct(); - else - $this->id = $_COOKIE['id_tab']; - } - - protected function l($string, $class = 'AdminTab', $addslashes = FALSE, $htmlentities = TRUE) - { - if($this->isModule) - { - $currentClass = get_class($this); - // need to be called in order to populate $classInModule - return SelfModule::findTranslation('autoupgrade', $string, 'AdminSelfUpgrade'); - } - else - return parent::l($string, $class, $addslashes, $htmlentities); - } - - /** - * _setFields function to set fields (only when we need it). - * - * @return void - */ - private function _setFields() - { - $this->_fieldsAutoUpgrade['PS_AUTOUP_DONT_SAVE_IMAGES'] = array( - 'title' => $this->l('Don\'t save images'), 'cast' => 'intval', 'validation' => 'isBool', - 'type' => 'bool', 'desc'=>$this->l('You can exclude the image directory from backup if you already saved it by another method (not recommended)'), - ); - - $this->_fieldsAutoUpgrade['PS_AUTOUP_KEEP_DEFAULT_THEME'] = array( - 'title' => $this->l('Keep theme "prestashop"'), 'cast' => 'intval', 'validation' => 'isBool', - 'type' => 'bool', 'desc'=>$this->l('If you have customized PrestaShop default theme, you can protect it from upgrade (not recommended)'), - ); - - $this->_fieldsAutoUpgrade['PS_AUTOUP_KEEP_TRAD'] = array( - 'title' => $this->l('Keep translations'), 'cast' => 'intval', 'validation' => 'isBool', - 'type' => 'bool', 'desc'=>$this->l('If set too yes, you will keep all your translations'), - ); - - $this->_fieldsAutoUpgrade['PS_AUTOUP_CUSTOM_MOD_DESACT'] = array( - 'title' => $this->l('Desactivate custom modules'), 'cast' => 'intval', 'validation' => 'isBool', - 'type' => 'bool', 'desc'=>$this->l('If you don\'t desactivate your modules, you can have some compatibility problem and the Modules page might not load correctly.'), - ); - // allow manual mode only for dev - if (defined('_PS_MODE_DEV_') AND _PS_MODE_DEV_) - $this->_fieldsAutoUpgrade['PS_AUTOUP_MANUAL_MODE'] = array( - 'title' => $this->l('Manual mode'), 'cast' => 'intval', 'validation' => 'isBool', - 'type' => 'bool', 'desc'=>$this->l('Check this if you want to stop after each step'), - ); - - if (defined('_PS_ALLOW_UPGRADE_UNSTABLE_') AND _PS_ALLOW_UPGRADE_UNSTABLE_ AND function_exists('svn_checkout')) - { - $this->_fieldsAutoUpgrade['PS_AUTOUP_USE_SVN'] = array( - 'title' => $this->l('Use Subversion'), 'cast' => 'intval', 'validation' => 'isBool', - 'type' => 'bool', 'desc' => $this->l('check this if you want to use unstable svn instead of official release'), - ); - } - } - - public function configOk() - { - $allowed = (ConfigurationTest::test_fopen() && $this->rootWritable); - $allowed &= !Configuration::get('PS_SHOP_ENABLE'); - - return $allowed; - } - /** - * isUpgradeAllowed checks if all server configuration is valid for upgrade - * - * @return void - */ - public function isUpgradeAllowed() - { - $allowed = (ConfigurationTest::test_fopen() && $this->rootWritable); - - if (!defined('_PS_MODE_DEV_') OR !_PS_MODE_DEV_) - $allowed &= $this->upgrader->autoupgrade; - - return $allowed; - } - - /** - * init to build informations we need - * - * @return void - */ - public function init() - { - // For later use, let's set up prodRootDir and adminDir - // This way it will be easier to upgrade a different path if needed - $this->prodRootDir = _PS_ROOT_DIR_; - $this->adminDir = _PS_ADMIN_DIR_; - - // test writable recursively - if ($this->isModule) - { - require_once('ConfigurationTest.php'); - if(!class_exists('ConfigurationTest', false) AND class_exists('ConfigurationTestCore')) - eval('class ConfigurationTest extends ConfigurationTestCore{}'); - } - if (ConfigurationTest::test_dir($this->prodRootDir,true)) - $this->rootWritable = true; - - // checkPSVersion will be not - $this->upgrader = new Upgrader(true); - $this->upgrader->checkPSVersion(); - // If you have defined this somewhere, you know what you do - if (defined('_PS_ALLOW_UPGRADE_UNSTABLE_') AND _PS_ALLOW_UPGRADE_UNSTABLE_ AND function_exists('svn_checkout')) - { - if (!$this->isModule OR class_exists('Configuration',false)) - $this->useSvn = Configuration::get('PS_AUTOUP_USE_SVN'); - } - else - $this->useSvn = false; - - // from $_POST or $_GET - $this->action = empty($_REQUEST['action'])?null:$_REQUEST['action']; - $this->currentParams = empty($_REQUEST['params'])?null:$_REQUEST['params']; - - // If not exists in this sessions, "create" - // session handling : from current to next params - if (isset($this->currentParams['removeList'])) - $this->nextParams['removeList'] = $this->currentParams['removeList']; - - if (isset($this->currentParams['filesToUpgrade'])) - $this->nextParams['filesToUpgrade'] = $this->currentParams['filesToUpgrade']; - - if (class_exists('Configuration',false)) - { - $time = time(); - $this->backupDbFilename = Configuration::get('UPGRADER_BACKUPDB_FILENAME'); - if(!file_exists($this->backupDbFilename)) - { - // If not exists, the filename is generated by Backup.php - $this->backupDbFilename = ''; - Configuration::updateValue('UPGRADER_BACKUPDB_FILENAME', $this->backupDbFilename); - } - - $this->backupFilesFilename = Configuration::get('UPGRADER_BACKUPFILES_FILENAME'); - if(!file_exists($this->backupFilesFilename)) - { - $this->backupFilesFilename = $this->autoupgradePath . DIRECTORY_SEPARATOR . 'backupfile-'.date('Y-m-d').'-'.$time.'.zip'; - Configuration::updateValue('UPGRADER_BACKUPFILES_FILENAME', $this->backupFilesFilename); - } - } - else{ - // backupDbFilename should never be empty - $this->backupDbFilename = $this->currentParams['backupDbFilename']; - // backupFilesFilename should never etc. - $this->backupFilesFilename = $this->currentParams['backupFilesFilename']; - } - $this->autoupgradePath = $this->adminDir.DIRECTORY_SEPARATOR.$this->autoupgradeDir; - - if (!file_exists($this->autoupgradePath)) - if (!@mkdir($this->autoupgradePath,0777)) - $this->_errors[] = Tools::displayError(sprintf($this->l('unable to create directory %s'),$this->autoupgradePath)); - - $latest = $this->autoupgradePath.DIRECTORY_SEPARATOR.'latest'; - if (!file_exists($latest)) - if (!@mkdir($latest,0777)) - $this->_errors[] = Tools::displayError(sprintf($this->l('unable to create directory %s'),$latest)); - - $this->latestRootDir = $latest.DIRECTORY_SEPARATOR.'prestashop'; - $this->adminDir = str_replace($this->prodRootDir,'',$this->adminDir); - // @TODO future option - // $this->testRootDir = $this->autoupgradePath.DIRECTORY_SEPARATOR.'test'; - - /* option */ - if (class_exists('Configuration',false)) - { - $this->dontBackupImages = Configuration::get('PS_AUTOUP_DONT_SAVE_IMAGES'); - $this->keepDefaultTheme = Configuration::get('PS_AUTOUP_KEEP_DEFAULT_THEME'); - $this->keepTrad = Configuration::get('PS_AUTOUP_KEEP_TRAD'); - $this->manualMode = Configuration::get('PS_AUTOUP_MANUAL_MODE'); - $this->desactivateCustomModule = Configuration::get('PS_AUTOUP_CUSTOM_MOD_DESACT'); - } - else - { - $this->dontBackupImages = $this->currentParams['dontBackupImages']; - $this->keepDefaultTheme = $this->currentParams['keepDefaultTheme']; - $this->keepTrad = $this->currentParams['keepTrad']; - $this->manualMode = $this->currentParams['manualMode']; - $this->desactivateCustomModule = $this->current['desactivateCustomModule']; - } - // We can add any file or directory in the exclude dir : theses files will be not removed or overwritten - // @TODO cache should be ignored recursively, but we have to reconstruct it after upgrade - // - compiled from smarty - // - .svn - $this->backupIgnoreAbsoluteFiles[] = "/tools/smarty_v2/compile"; - $this->backupIgnoreAbsoluteFiles[] = "/tools/smarty_v2/cache"; - $this->backupIgnoreAbsoluteFiles[] = "/tools/smarty/compile"; - $this->backupIgnoreAbsoluteFiles[] = "/tools/smarty/cache"; - - $this->excludeFilesFromUpgrade[] = '.'; - $this->excludeFilesFromUpgrade[] = '..'; - $this->excludeFilesFromUpgrade[] = '.svn'; - $this->excludeFilesFromUpgrade[] = 'install'; - $this->excludeFilesFromUpgrade[] = 'settings.inc.php'; - $this->excludeFilesFromUpgrade[] = 'autoupgrade'; - $this->backupIgnoreFiles[] = '.'; - $this->backupIgnoreFiles[] = '..'; - $this->backupIgnoreFiles[] = '.svn'; - $this->backupIgnoreFiles[] = 'autoupgrade'; - - if ($this->dontBackupImages) - $this->backupIgnoreAbsoluteFiles[] = "/img"; - - - if ($this->keepDefaultTheme) - $this->excludeAbsoluteFilesFromUpgrade[] = "/themes/prestashop"; - - if ($this->keepTrad) - $this->excludeFilesFromUpgrade[] = "translations"; - } - - /** - * getFilePath return the path to the zipfile containing prestashop. - * - * @return void - */ - private function getFilePath() - { - return $this->autoupgradePath.DIRECTORY_SEPARATOR.$this->destDownloadFilename; - } - - public function postProcess() - { - $this->_setFields(); - - if (sizeof($_POST)>0) - { - $this->_postConfig($this->_fieldsAutoUpgrade); - } - } - - public function ajaxProcessUpgradeComplete() - { - $this->nextDesc = $this->l('Upgrade process done. Congratulations ! You can now reactive your shop.'); - $this->next = ''; - } - - public function ajaxProcessUpgradeNow() - { - $this->nextDesc = $this->l('Starting upgrade ...'); - - if ($this->useSvn) - { - $this->next = 'svnCheckout'; - $this->nextDesc = $this->l('switching to svn checkout (useSvn set to true)'); - } - else - { - $this->next = 'download'; - $this->nextDesc = $this->l('Shop desactivated. Now downloading (this can takes some times )...'); - } - } - - public function ajaxProcessSvnExport() - { - if ($this->useSvn) - { - // first of all, delete the content of the latest root dir just in case - if (is_dir($this->latestRootDir)) - Tools::deleteDirectory($this->latestRootDir, false); - - if (!file_exists($this->latestRootDir)) - { - @mkdir($this->latestRootDir); - } - - if (svn_export($this->autoupgradePath . DIRECTORY_SEPARATOR . $this->svnDir, $this->latestRootDir)) - { - - // export means svn means install-dev and admin-dev. - // let's rename admin to the correct admin dir - // and rename install-dev to install - $adminDir = str_replace($this->prodRootDir, '', $this->adminDir); - rename($this->latestRootDir.DIRECTORY_SEPARATOR.'install-dev', $this->latestRootDir.DIRECTORY_SEPARATOR.'install'); - rename($this->latestRootDir.DIRECTORY_SEPARATOR.'admin-dev', $this->latestRootDir.DIRECTORY_SEPARATOR.$adminDir); - - // Unsetting to force listing - unset($this->nextParams['removeList']); - $this->next = "removeSamples"; - $this->nextDesc = $this->l('Export svn complete. removing sample files...'); - return true; - } - else - { - $this->next = 'error'; - $this->nextDesc = $this->l('error when svn export '); - } - } - } - - public function ajaxProcessUnzip(){ - if ($this->isModule AND !class_exists('Tools',false)) - require_once('Tools.php'); - - $filepath = $this->getFilePath(); - $destExtract = $this->autoupgradePath.DIRECTORY_SEPARATOR.'latest'; - if (file_exists($destExtract)) - Tools::deletedirectory($destExtract); - - if (Tools::ZipExtract($filepath,$destExtract)) - { - $adminDir = str_replace($this->prodRootDir, '', $this->adminDir); - rename($this->latestRootDir.DIRECTORY_SEPARATOR.'admin', $this->latestRootDir.DIRECTORY_SEPARATOR.$adminDir); - // Unsetting to force listing - unset($this->nextParams['removeList']); - $this->next = "removeSamples"; - $this->nextDesc = $this->l('Extract complete. removing sample files...'); - return true; - } - else{ - $this->next = "error"; - $this->nextDesc = sprintf($this->l('unable to extract %2$s into %2$s ...'),$filepath,$destExtract); - return true; - } - } - - - /** - * _listSampleFiles will make a recursive call to scandir() function - * and list all file which match to the $fileext suffixe (this can be an extension or whole filename) - * - * @TODO maybe $regex instead of $fileext ? - * @param string $dir directory to look in - * @param string $fileext suffixe filename - * @return void - */ - private function _listSampleFiles($dir, $fileext = '.jpg'){ - $res = true; - $dir = rtrim($dir,'/').DIRECTORY_SEPARATOR; - - $toDel = scandir($dir); - // copied (and kind of) adapted from AdminImages.php - foreach ($toDel AS $file) - { - if ($file!='.' AND $file != '..' AND $file != '.svn') - { - - if (preg_match('#'.preg_quote($fileext,'#').'$#i',$file)) - { - $this->sampleFileList[] = $dir.$file; - } - else if (is_dir($dir.$file)) - { - $res &= $this->_listSampleFiles($dir.$file); - } - } - } - return $res; - } - - public function _listBackupFiles($dir) - { - $allFiles = scandir($dir); - foreach ($allFiles as $file) - { - $fullPath = $dir.DIRECTORY_SEPARATOR.$file; - - if (!$this->_skipFile($file, $fullPath,'backup')) - { - if (is_dir($fullPath)) - $this->_listBackupFiles($fullPath); - else - $this->backupFileList[] = $fullPath; - } - else - $this->backupIgnoreFiles[] = $fullPath; - - } - } - - public function _listFilesToUpgrade($dir) - { - $allFiles = scandir($dir); - foreach ($allFiles as $file) - { - $fullPath = $dir.DIRECTORY_SEPARATOR.$file; - - if (!$this->_skipFile($file, $fullPath, "upgrade")) - { - if (is_dir($fullPath)) - { - // if is_dir, we will create it :)e it :) - $this->toUpgradeFileList[] = $fullPath; - if (strpos($dir.DIRECTORY_SEPARATOR.$file, 'install') === false) - { - $this->_listFilesToUpgrade($fullPath); - } - } - else - $this->toUpgradeFileList[] = $fullPath; - } - } - - $this->nextParams['filesToUpgrade'] = $this->toUpgradeFileList; - } - - - public function ajaxProcessUpgradeFiles(){ - // @TODO : - $this->nextParams = $this->currentParams; - if (!isset($this->nextParams['filesToUpgrade'])) - $this->_listFilesToUpgrade($this->latestRootDir); - - // later we could choose between _PS_ROOT_DIR_ or _PS_TEST_DIR_ - $this->destUpgradePath = $this->prodRootDir; - - // upgrade files one by one like for the backup - // with a 1000 loop because it's funny - // @TODO : - // foreach files in latest, copy - $this->next = 'upgradeFiles'; - if (!is_array($this->nextParams['filesToUpgrade'])) - { - error($this->nextParams); - $this->next = 'error'; - $this->nextDesc = $this->l('filesToUpgrade is not an array'); - $this->nextQuickInfo[] = $this->l('filesToUpgrade is not an array'); - return false; - } - - // @TODO : does not upgrade files in modules, translations if they have not a correct md5 (or crc32, or whatever) from previous version - for ($i=0;$inextParams['filesToUpgrade'])<=0) - { - $this->next = 'upgradeDb'; - $this->nextDesc = $this->l('All files upgraded. Now upgrading database'); - $this->nextResponseType = 'xml'; - break; - } - - //$file = array_shift($this->nextParams['filesToUpgrade']); - $file = array_shift($this->nextParams['filesToUpgrade']); - if (!$this->upgradeThisFile($file)) - { - // put the file back to the begin of the list - $totalFiles = array_unshift($this->nextParams['filesToUpgrade'],$file); - $this->next = 'error'; - $this->nextQuickInfo[] = sprintf($this->l('error when trying to upgrade %s'),$file); - break; - } - else{ - // @TODO : maybe put several files at the same times ? - $this->nextDesc = sprintf($this->l('%2$s files left to upgrade.'),$file,sizeof($this->nextParams['filesToUpgrade'])); - } - } - } - - /** - * _modelDoUpgrade prepare the call to doUpgrade.php file (like model.php) - * - * @return void - */ - public function _modelDoUpgrade() - { - // a. set logger - // it will be used later - global $logger; - $logger = new FileLogger(); - if (function_exists('date_default_timezone_set')) - date_default_timezone_set('Europe/Paris'); - // use autoupgrade as log dir - $logger->setFilename($this->latestRootDir.'/'.date('Ymd').'_autoupgrade.log'); - - // init env. - @set_time_limit(0); - @ini_set('max_execution_time', '0'); - // setting the memory limit to 128M only if current is lower - $memory_limit = ini_get('memory_limit'); - if (substr($memory_limit,-1) != 'G' - AND ((substr($memory_limit,-1) == 'M' AND substr($memory_limit,0,-1) < 128) - OR is_numeric($memory_limit) AND (intval($memory_limit) < 131072)) - ){ - @ini_set('memory_limit','128M'); - } - - /* Redefine REQUEST_URI if empty (on some webservers...) */ - if (!isset($_SERVER['REQUEST_URI']) || $_SERVER['REQUEST_URI'] == '') - $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME']; - - if ($tmp = strpos($_SERVER['REQUEST_URI'], '?')) - $_SERVER['REQUEST_URI'] = substr($_SERVER['REQUEST_URI'], 0, $tmp); - - $_SERVER['REQUEST_URI'] = str_replace('//', '/', $_SERVER['REQUEST_URI']); - - /////////////////////// - // Copy from model.php - /////////////////////// - $upgrader = $this->upgrader; - $upgrader->checkPSVersion(true); - - define('INSTALL_VERSION', $upgrader->version_num); - // now the install dir to use is in a subdirectory of the admin dir - define('INSTALL_PATH', realpath($this->latestRootDir.DIRECTORY_SEPARATOR.'install')); - - define('PS_INSTALLATION_IN_PROGRESS', true); - // Note : we don't need ToolsInstall.php - // include_once(INSTALL_PATH.'/classes/ToolsInstall.php'); - - define('SETTINGS_FILE', $this->prodRootDir . '/config/settings.inc.php'); - define('DEFINES_FILE', $this->prodRootDir .'/config/defines.inc.php'); - define('INSTALLER__PS_BASE_URI', substr($_SERVER['REQUEST_URI'], 0, -1 * (strlen($_SERVER['REQUEST_URI']) - strrpos($_SERVER['REQUEST_URI'], '/')) - strlen(substr(dirname($_SERVER['REQUEST_URI']), strrpos(dirname($_SERVER['REQUEST_URI']), '/')+1)))); - - // Note : INSTALLER__PS_BASE_URI_ABSOLUTE is not used for upgrade - // define('INSTALLER__PS_BASE_URI_ABSOLUTE', 'http://'.ToolsInstall::getHttpHost(false, true).INSTALLER__PS_BASE_URI); - - // XML Header - header('Content-Type: text/xml'); - require_once(INSTALL_PATH.DIRECTORY_SEPARATOR.'xml'.DIRECTORY_SEPARATOR.'doUpgrade.php'); - - ////////////////////////////// - // End of copy from model.php - ////////////////////////////// - - } - - public function ajaxProcessUpgradeDb(){ - // @TODO : 1/2/3 have to be done at the beginning !!!!!!!!!!!!!!!!!!!!!! - - // use something like actual in install-dev - // Notice : xml used here ... - - // 1) confirm version is correct(DB) - // install/model.php?method=getVersionFromDb&language=0 - // later - - // 2) confirm config is correct (r/w rights) - // install/model.php?method=checkConfig&firsttime=0 - // later - - // 3) save current activated modules in nextParams, or don't desactivate them ? - // @TODO - // 4) upgrade - // install/model.php?_=1309193641470&method=doUpgrade&customModule=desactivate - if (!empty($this->currentParams['desactivateCustomModule'])) - $_GET['customModule'] = 'desactivate'; - - if (!$this->_modelDoUpgrade()) - { - $this->next = 'error'; - $this->nextDesc = $this->l('error during upgrade Db'); - } - - // 5) compare activated modules and reactivate them - // @TODO - - } - - - /** - * upgradeThisFile - * - * @param mixed $file - * @return void - */ - public function upgradeThisFile($file) - { - // @TODO : later, we could handle customization with some kind of diff functions - // for now, just copy $file in str_replace($this->latestRootDir,_PS_ROOT_DIR_) - // $file comes from scandir function, no need to lost time and memory with file_exists() - if ($this->_skipFile('', $file,'upgrade')) - { - $this->nextQuickInfo[] = $this->l('%s ignored'); - return true; - } - else - { - $dest = str_replace($this->latestRootDir, $this->destUpgradePath,$file); - - if (is_dir($file)) - { - if (!file_exists($dest)) - { - if (@mkdir($dest)) - { - $this->nextQuickInfo[] = sprintf($this->l('created dir %2$s. %3$s files left to upgrade.'),$file, $dest, sizeof($this->nextParams['filesToUpgrade'])); - return true; - } - else - { - $this->next = 'error'; - $this->nextQuickInfo[] = sprintf($this->l('error when creating directory %s'),$dest); - $this->nextDesc = sprintf($this->l('error when creating directory %s'),$dest); - return false; - } - } - else - { - // directory already exists - return true; - } - } - else - { - if (copy($file,$dest)) - { - $this->nextQuickInfo[] = sprintf($this->l('copied %1$s in %2$s. %3$s files left to upgrade.'),$file, $dest, sizeof($this->nextParams['filesToUpgrade'])); - return true; - } - else - { - $this->next = 'error'; - $this->nextQuickInfo[] = sprintf($this->l('error for copy %1$s in %2$s'),$file,$dest); - $this->nextDesc = sprintf($this->l('error for copy %1$s in %2$s'),$file,$dest); - return false; - } - } - } - - } - - public function ajaxProcessRollback() - { - // 1st, need to analyse what was wrong. - - $this->nextParams = $this->currentParams; - if (!empty($this->backupFilesFilename) AND file_exists($this->backupFilesFilename)) - { - $this->next = 'restoreFiles'; - $this->status = 'ok'; - $this->nextDesc = $this->l('Files restored, now restoring database.'); - } - else - { - if (!empty($this->backupDbFilename) AND file_exists($this->backupDbFilename)) - { - $this->next = 'restoreDb'; - $this->status = 'ok'; - $this->nextDesc = $this->l('Database restored'); - } - else - { - // 2nd case if upgradeFiles made an error - // 3rd case if no upgrade has been done - // all theses cases are handled by the method ajaxRequestRollback() - $this->next = ''; // next is empty : nothing next :) - $this->status = 'ok'; - $this->nextDesc = $this->l('All your site is restored... '); - } - } - } - - /** - * ajaxProcessRestoreFiles restore the previously saved files. - * - * @return boolean true if succeed - */ - public function ajaxProcessRestoreFiles() - { - // @TODO : workaround max_execution_time / ajax batch unzip - if (!empty($this->backupFilesFilename) AND file_exists($this->backupFilesFilename)) - { - // cleanup current PS tree - $list = $this->_listArchivedFiles(); - if (count($list) > 0) - { - $this->_cleanUp($this->prodRootDir.'/'); - $this->nextQuickInfo[] = $this->l('root directory cleaned.'); - - $filepath = $this->backupFilesFilename; - $destExtract = $this->prodRootDir; - - if (self::ZipExtract($filepath, $destExtract)) - { - // once it's restored, delete the file ! - unlink($this->backupFilesFilename); - Configuration::updateValue('UPGRADER_BACKUPFILES_FILENAME', ''); - if (!empty($this->backupDbFilename)) - { - $this->nextDesc = $this->l('Files restored. No database backup found. Restoration done.'); - $this->next = ''; - } - else - { - $this->nextDesc = $this->l('Files restored.'); - $this->next = 'rollback'; - } - return true; - } - else - { - $this->next = "error"; - $this->nextDesc = sprintf($this->l('unable to extract $1$s into %2$s .'), $filepath, $destExtract); - return false; - } - } - } - else - { - $this->next = 'error'; - $this->nextDesc = $this->l('no known backup. nothing to restore.'); - return false; - } - } - - /** - * try to restore db backup file - * @return type : hey , what you expect ? well mysql errors array ..... - * @TODO : maybe this could be in the Backup class - */ - public function ajaxProcessRestoreDb() - { - $exts = explode('.', $this->backupDbFilename); - $fileext = $exts[count($exts)-1]; - $requests = array(); - $errors = array(); - $content = ''; - switch ($fileext) - { - case 'bz': - case 'bz2': - if ($fp = bzopen($this->backupDbFilename, 'r')) - { - while(!feof($fp)) - $content .= bzread($fp, filesize($this->backupDbFilename)); - bzclose($fp); - } - break; - case 'gz': - if ($fp = gzopen($this->backupDbFilename, 'r')) - { - while(!feof($fp)) - $content = gzread($fp, filesize($this->backupDbFilename)); - gzclose($fp); - } - break; - // default means sql ? - default : - if ($fp = fopen($this->backupDbFilename, 'r')) - { - while(!feof($fp)) - $content = fread($fp, filesize($this->backupDbFilename)); - fclose($fp); - } - } - - if ($content=='') - return false; - - // preg_match_all is better than preg_split (what is used in doUpgrade.php) - // This way we avoid extra blank lines - // option s (PCRE_DOTALL) added - // @TODO need to check if a ";" in description could block that (I suppose it can at the end of a line) - preg_match_all('/.*;[\n]\+/s', $content, $requests); - /* @TODO maybe improve regex pattern ... */ - $db = Db::getInstance(); - if (count($requests)>0) - { - foreach ($requests as $request) - if (!empty($request)) - if (!$db->Execute($request)) - $this->nextQuickInfo[] = $db->getMsgError(); - - // once it's restored, delete the file ! - unlink($this->backupDbFilename); - Configuration::updateValue('UPGRADER_BACKUPDB_FILENAME',''); - } - else - $this->nextQuickInfo[] = $this->l('Nothing to restore (no request found)'); - - $this->next = 'rollback'; - $this->nextDesc = 'Database restore done.'; - } - - public function ajaxProcessBackupDb() - { - if(!class_exists('ObjectModel',false)) - { - require_once(_PS_ROOT_DIR_.'/classes/ObjectModel.php'); - if(!class_exists('ObjectModel',false)) - eval('Class ObjectModel extends ObjectModelCore{}'); - } - - if(!class_exists('Language',false)) - { - require_once(_PS_ROOT_DIR_.'/classes/Language.php'); - if(!class_exists('Language',false)) - eval('Class Language extends LanguageCore{}'); - } - if(!class_exists('Validate',false)) - { - require_once(_PS_ROOT_DIR_.'/classes/Validate.php'); - if(!class_exists('Validate',false)) - eval('Class Validate extends ValidateCore{}'); - } - if(!class_exists('Db',false)) - { - require_once(_PS_ROOT_DIR_.'/classes/Db.php'); - if(!class_exists('Db',false)) - eval('abstract Class Db extends DbCore{}'); - } - if(!class_exists('MySQL',false)) - { - require_once(_PS_ROOT_DIR_.'/classes/MySQL.php'); - if(!class_exists('MySQL',false)) - eval('Class MySQL extends MySQLCore{}'); - } - if(!class_exists('Configuration',false)) - { - require_once(_PS_ROOT_DIR_.'/classes/Configuration.php'); - if(!class_exists('Configuration',false)) - eval('Class Configuration extends ConfigurationCore{}'); - } - if(!class_exists('Backup',false)) - { - require_once('Backup.php'); - if(!class_exists('ObjectModel',false)) - eval('Class ObjectModel extends ObjectModelCore{}'); - } - - if(!defined('_PS_MAGIC_QUOTES_GPC_')) - define('_PS_MAGIC_QUOTES_GPC_', get_magic_quotes_gpc()); - if(!defined('_PS_MYSQL_REAL_ESCAPE_STRING_')) - define('_PS_MYSQL_REAL_ESCAPE_STRING_', function_exists('mysql_real_escape_string')); - - $backup = new Backup(); - // for backup db, use autoupgrade directory - // @TODO : autoupgrade must not be static - $backup->setCustomBackupPath('autoupgrade'); - // maybe for big tables we should save them in more than one file ? - $res = $backup->add(); - if ($res) - { - $this->nextParams['backupDbFilename'] = $backup->id; - // We need to load configuration to use it ... - Configuration::loadConfiguration(); - Configuration::updateValue('UPGRADER_BACKUPDB_FILENAME', $backup->id); - - $this->next = 'upgradeFiles'; - $this->nextDesc = sprintf($this->l('Database backup done in %s. Now updating files'),$backup->id); - } - // if an error occur, we assume the file is not saved - } - - public function ajaxProcessBackupFiles() - { - $this->nextParams = $this->currentParams; - $this->stepDone = false; - ///////////////////// - - if (!isset($this->nextParams['filesForBackup'])) - { - $list = $this->_listBackupFiles($this->prodRootDir); - $this->nextQuickInfo[] = sprintf($this->l('%s Files to backup.'), sizeof($this->backupFileList)); - $this->nextParams['filesForBackup'] = $this->backupFileList; - - // delete old backup, create new - if (file_exists($this->backupFilesFilename)) - unlink($this->backupFilesFilename); - - $this->nextQuickInfo[] = sprintf($this->l('backup files initialized in %s'), $this->backupFilesFilename); - } - - ///////////////////// - $this->next = 'backupFiles'; - // @TODO : display % instead of this - $this->nextDesc = sprintf($this->l('Backup files in progress. %s files left'), sizeof($this->nextParams['filesForBackup'])); - if (is_array($this->nextParams['filesForBackup'])) - { - // @TODO later - // 1) calculate crc32 of next file - // 2) use the provided xml with crc32 calculated from previous versions ? - // or simply use the latest dir ? - //$current = crc32(file_get_contents($file)); - //$file = $this->nextParams['filesForBackup'][0]; - //$latestFile = str_replace(_PS_ROOT_DIR_,$this->latestRootDir,$file); - - // if (file_exists($latestFile)) - // $latest = crc32($latestFile); - // else - // $latest = ''; - - $zip = new ZipArchive(); - if ($zip->open($this->backupFilesFilename, ZIPARCHIVE::CREATE)) - { - $this->next = 'backupFiles'; - // @TODO all in one time will be probably too long - // 1000 ok during test, but 10 by 10 to be sure - $this->stepok = false; - // @TODO min(self::$loopBackupFiles, sizeof()) - for($i=0;$inextParams['filesForBackup'])<=0) - { - $this->stepok = true; - $this->status = 'ok'; - $this->next = 'backupDb'; - $this->nextDesc = $this->l('All files saved. Now backup Database'); - $this->nextQuickInfo[] = $this->l('all files have been added to archive.'); - break; - } - // filesForBackup already contains all the correct files - $file = array_shift($this->nextParams['filesForBackup']); - $archiveFilename = str_replace($this->prodRootDir,'',$file); - // @TODO : maybe put several files at the same times ? - if ($zip->addFile($file,$archiveFilename)) - $this->nextQuickInfo[] = sprintf($this->l('%1$s added to archive. %2$s left.'),$file, sizeof($this->nextParams['filesForBackup'])); - else - { - // if an error occur, it's more safe to delete the corrupted backup - if (file_exists($this->backupFilesFilename)) - unlink($this->backupFilesFilename); - $this->next = 'error'; - $this->nextDesc = sprintf($this->l('error when trying to add %1$s to archive %2$s.'),$file, $backupFilePath); - break; - } - } - $zip->close(); - return true; - } - else{ - $this->next = 'error'; - $this->nextDesc = $this->l('unable to open archive'); - return false; - } - } - else - { - $this->next = 'backupDb'; - $this->nextDesc = 'All files saved. Now backup Database'; - return true; - } - // 4) save for display. - } - - - private function _removeOneSample($removeList) - { - if (is_array($removeList) AND sizeof($removeList)>0) - { - if (file_exists($removeList[0]) AND unlink($removeList[0])) - { - $item = array_shift($removeList); - $this->next = 'removeSamples'; - $this->nextParams['removeList'] = $removeList; - $this->nextQuickInfo[] = sprintf($this->l('%1$s removed. %2$s items left'), $item, sizeof($removeList)); - } - else - { - $this->next = 'error'; - $this->nextParams['removeList'] = $removeList; - $this->nextQuickInfo[] = sprintf($this->l('error when removing %1$s, %2$s items left'), $removeList[0], sizeof($removeList)); - return false; - } - } - return true; - } - - public function ajaxProcessRemoveSamples(){ - $this->stepDone = false; - // @TODO : list exaustive list of files to remove : - // all images from img dir exept admin ? - // all images like logo, favicon, ?. - // all custom image from modules ? - // all custom image from theme ? - if (!isset($this->currentParams['removeList'])) - { - $this->_listSampleFiles($this->autoupgradePath.'/latest/prestashop/img', 'jpg'); - $this->_listSampleFiles($this->autoupgradePath.'/latest/prestashop/modules/editorial/', 'homepage_logo.jpg'); - // @TODO handle this bad thing - $this->nextQuickInfo[] = sprintf($this->l('Starting to remove %1$s sample files'), sizeof($this->sampleFileList)); - $this->nextParams['removeList'] = $this->sampleFileList; - } - - - // @TODO : removing @, adding if file_exists -// @unlink(_PS_ROOT_DIR_.'modules'.DIRECTORY_SEPARATOR.'editorial'.DIRECTORY_SEPARATOR.'editorial.xml'); -// @unlink(_PS_ROOT_DIR_.'modules'.DIRECTORY_SEPARATOR.'editorial'.DIRECTORY_SEPARATOR.'homepage_logo.jpg'); // homepage custom ? -// @unlink(_PS_ROOT_DIR_.'img'.DIRECTORY_SEPARATOR.'logo.jpg'); -// @unlink(_PS_ROOT_DIR_.'img'.DIRECTORY_SEPARATOR.'favicon.ico'); - $resRemove = true; - for($i=0;$inextParams['removeList']) <= 0 ) - { - $this->stepDone = true; - $this->next = 'backupFiles'; - $this->nextDesc = $this->l('All sample files removed. Now backup files.'); - // break the loop, all sample already removed - return true; - } - $resRemove &= $this->_removeOneSample($this->nextParams['removeList']); - if (!$resRemove) - break; - } - - return $resRemove; - } - - public function ajaxProcessSvnCheckout() - { - $this->nextParams = $this->currentParams; - if ($this->useSvn){ - $svnLink = 'http://svn.prestashop.com/trunk'; - $dest = $this->autoupgradePath . DIRECTORY_SEPARATOR . $this->svnDir; - - $svnStatus = svn_status($dest); - if (is_array($svnStatus)) - { - if (sizeof($svnStatus) == 0) - { - $this->next = 'svnExport'; - $this->nextDesc = sprintf($this->l('working copy already %s up-to-date. now exporting it into latest dir'),$dest); - } - else - { - // we assume no modification has been done - // @TODO a svn revert ? - if ($svnUpdate = svn_update($dest)) - { - $this->next = 'svnExport'; - $this->nextDesc = sprintf($this->l('SVN Update done for working copy %s . now exporting it into latest...'),$dest); - } - } - } - else - { - // no valid status found - // @TODO : is 0777 good idea ? - if (!file_exists($dest)) - if (!@mkdir($dest,0777)) - { - $this->next = 'error'; - $this->nextDesc = sprintf($this->l('unable to create directory %s'),$dest); - return false; - } - - if (svn_checkout($svnLink, $dest)) - { - $this->next = 'svnExport'; - $this->nextDesc = sprintf($this->l('SVN Checkout done from %s . now exporting it into latest...'),$svnLink); - return true; - } - else - { - $this->next = 'error'; - $this->nextDesc = $this->l('SVN Checkout error...'); - } - } - } - else - { - $this->next = 'error'; - $this->nextDesc = $this->l('not allowed to use svn'); - } - } - - public function ajaxProcessDownload() - { - if (@ini_get('allow_url_fopen')) - { - $res = $this->upgrader->downloadLast($this->autoupgradePath,$this->destDownloadFilename); - if ($res){ - $this->next = 'unzip'; - $this->nextDesc = $this->l('Download complete. Now extracting'); - } - else - { - $this->next = 'error'; - $this->nextDesc = $this->l('Error during download'); - } - } - else - { - // @TODO : ftp mode - $this->next = 'error'; - $this->nextDesc = sprintf($this->l('you need allow_url_fopen for automatic download. You can also manually upload it in %s'),$this->autoupgradePath.$this->destDownloadFilename); - } - } - - public function buildAjaxResult() - { - $return['error'] = $this->error; - $return['stepDone'] = $this->stepDone; - $return['next'] = $this->next; - $return['status'] = $this->next == 'error' ? 'error' : 'ok'; - $return['nextDesc'] = $this->nextDesc; - - foreach($this->ajaxParams as $v) - if(property_exists($this,$v)) - $this->nextParams[$v] = $this->$v; - - $return['nextParams'] = $this->nextParams; - - $return['nextParams']['typeResult'] = $this->nextResponseType; - - $return['nextQuickInfo'] = $this->nextQuickInfo; - return Tools::jsonEncode($return); - } - - /** - * displayConf - * - * @return void - */ - public function displayConf() - { - if(!$this->isModule) - { - if (version_compare(_PS_VERSION_,'1.4.4.0','<') AND false) - $this->_errors[] = Tools::displayError('This class depends of several files modified in 1.4.4.0 version and should not be used in an older version'); - } - parent::displayConf(); - - } - - public function ajaxPreProcess() - { - if (!empty($_POST['responseType']) AND $_POST['responseType'] == 'json') - header('Content-Type: application/json'); - - if(!empty($_POST['action'])) - { - $action = $_POST['action']; - if (isset(self::$skipAction[strtolower($action)])) - { - $this->next = self::$skipAction[$action]; - $this->nextDesc = sprintf($this->l('action %s skipped'),$action); - $this->nextQuickInfo[] = sprintf($this->l('action %s skipped'),$action); - unset($_POST['action']); - } - else if (!method_exists(get_class($this), 'ajaxProcess'.$action)) - { - $this->nextDesc = sprintf($this->l('action "%1$s" not found'), $action); - $this->next = 'error'; - $this->error = '1'; - } - } - - if ($this->apacheModExists('mod_evasive')) - sleep(1); - } - /** - * apacheModExists return true if the apache module $name is loaded - * @TODO move this method in class Information (when it will exist) - * - * @param string $name module name - * @return boolean true if exists - */ - function apacheModExists($name) - { - if(function_exists('apache_get_modules')) - { - static $apacheModuleList = null; - - if (!is_array($apacheModuleList)) - $apacheModuleList = apache_get_modules(); - - // we need strpos (example can be evasive20 - foreach($apacheModuleList as $module) - if (strpos($name, $module)!==false) - return true; - } - else{ - // If apache_get_modules does not exists, - // one solution should be parsing httpd.conf, - // but we could simple parse phpinfo(INFO_MODULES) return string - ob_start(); - phpinfo(INFO_MODULES); - $phpinfo = ob_get_contents(); - ob_end_clean(); - if (strpos($phpinfo, $module) !== false) - return true; - } - - return false; - } - - private function _getJsErrorMsgs() - { - $INSTALL_VERSION = $this->upgrader->version_num; - $ret = ' -var txtError = new Array(); -txtError[0] = "'.$this->l('Required field').'"; -txtError[1] = "'.$this->l('Too long!').'"; -txtError[2] = "'.$this->l('Fields are different!').'"; -txtError[3] = "'.$this->l('This email adress is wrong!').'"; -txtError[4] = "'.$this->l('Impossible to send the email!').'"; -txtError[5] = "'.$this->l('Can\'t create settings file, if /config/settings.inc.php exists, please give the public write permissions to this file, else please create a file named settings.inc.php in config directory.').'"; -txtError[6] = "'.$this->l('Can\'t write settings file, please create a file named settings.inc.php in config directory.').'"; -txtError[7] = "'.$this->l('Impossible to upload the file!').'"; -txtError[8] = "'.$this->l('Data integrity is not valided. Hack attempt?').'"; -txtError[9] = "'.$this->l('Impossible to read the content of a MySQL content file.').'"; -txtError[10] = "'.$this->l('Impossible the access the a MySQL content file.').'"; -txtError[11] = "'.$this->l('Error while inserting data in the database:').'"; -txtError[12] = "'.$this->l('The password is incorrect (alphanumeric string at least 8 characters).').'"; -txtError[14] = "'.$this->l('A Prestashop database already exists, please drop it or change the prefix.').'"; -txtError[15] = "'.$this->l('This is not a valid file name.').'"; -txtError[16] = "'.$this->l('This is not a valid image file.').'"; -txtError[17] = "'.$this->l('Error while creating the /config/settings.inc.php file.').'"; -txtError[18] = "'.$this->l('Error:').'"; -txtError[19] = "'.$this->l('This PrestaShop database already exists. Please revalidate your authentication informations to the database.').'"; -txtError[22] = "'.$this->l('An error occurred while resizing the picture.').'"; -txtError[23] = "'.$this->l('Database connection is available!').'"; -txtError[24] = "'.$this->l('Database Server is available but database is not found').'"; -txtError[25] = "'.$this->l('Database Server is not found. Please verify the login, password and server fields.').'"; -txtError[26] = "'.$this->l('An error occurred while sending email, please verify your parameters.').'"; -txtError[37] = "'.$this->l('Impossible to write the image /img/logo.jpg. If this image already exists, please delete it.').'"; -txtError[38] = "'.$this->l('The uploaded file exceeds the upload_max_filesize directive in php.ini').'"; -txtError[39] = "'.$this->l('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form').'"; -txtError[40] = "'.$this->l('The uploaded file was only partially uploaded').'"; -txtError[41] = "'.$this->l('No file was uploaded.').'"; -txtError[42] = "'.$this->l('Missing a temporary folder').'"; -txtError[43] = "'.$this->l('Failed to write file to disk').'"; -txtError[44] = "'.$this->l('File upload stopped by extension').'"; -txtError[45] = "'.$this->l('Cannot convert your database\'s data to utf-8.').'"; -txtError[46] = "'.$this->l('Invalid shop name').'"; -txtError[47] = "'.$this->l('Your firstname contains some invalid characters').'"; -txtError[48] = "'.$this->l('Your lastname contains some invalid characters').'"; -txtError[49] = "'.$this->l('Your database server does not support the utf-8 charset.').'"; -txtError[50] = "'.$this->l('Your MySQL server doesn\'t support this engine, please use another one like MyISAM').'"; -txtError[51] = "'.$this->l('The file /img/logo.jpg is not writable, please CHMOD 755 this file or CHMOD 777').'"; -txtError[52] = "'.$this->l('Invalid catalog mode').'"; -txtError[999] = "'.$this->l('No error code available').'"; -//upgrader -txtError[27] = "'.$this->l('This installer is too old.').'"; -txtError[28] = "'.sprintf($this->l('You already have the %s version.'),$INSTALL_VERSION).'"; -txtError[29] = "'.$this->l('There is no older version. Did you delete or rename the config/settings.inc.php file?').'"; -txtError[30] = "'.$this->l('The config/settings.inc.php file was not found. Did you delete or rename this file?').'"; -txtError[31] = "'.$this->l('Can\'t find the sql upgrade files. Please verify that the /install/sql/upgrade folder is not empty)').'"; -txtError[32] = "'.$this->l('No upgrade is possible.').'"; -txtError[33] = "'.$this->l('Error while loading sql upgrade file.').'"; -txtError[34] = "'.$this->l('Error while inserting content into the database').'"; -txtError[35] = "'.$this->l('Unfortunately,').'"; -txtError[36] = "'.$this->l('SQL errors have occurred.').'"; -txtError[37] = "'.$this->l('The config/defines.inc.php file was not found. Where did you move it?').'";'; - return $ret; - } - - public function displayAjax(){ - echo $this->buildAjaxResult(); - } - - private function _displayRollbackForm() - { - echo '
'.$this->l('Rollback').' -
'; - if (empty($this->backupFilesFilename) AND empty($this->backupDbFilename)) - echo $this->l('No rollback available'); - else if (!empty($this->backupFilesFilename) OR !empty($this->backupDbFilename)) - { - echo '
'; - } - if (!empty($this->backupFilesFilename) AND file_exists($this->backupFilesFilename)) - echo '
restoreFiles '.sprintf($this->l('click to restore %s'),$this->backupFilesFilename).'

'; - if (!empty($this->backupDbFilename) AND file_exists($this->backupDbFilename)) - echo '
restoreDb '.sprintf($this->l('click to restore %s'), $this->backupDbFilename).'

'; - - echo '
'; - } - - private function _displayUpgraderForm() - { - global $cookie; - $pleaseUpdate = $this->upgrader->checkPSVersion(); - - echo '
'; - echo ''.$this->l('Your current configuration').''; - echo ''.$this->l('Root directory').' : '.$this->prodRootDir.'

'; - - if ($this->rootWritable) - $srcRootWritable = '../img/admin/enabled.gif'; - else - $srcRootWritable = '../img/admin/disabled.gif'; - echo ''.$this->l('Root directory status').' : '.' '.($this->rootWritable?$this->l('fully writable'):$this->l('not writable recursively')).'

'; - - if ($this->upgrader->needUpgrade) - { - if ($this->upgrader->autoupgrade) - $srcAutoupgrade = '../img/admin/enabled.gif'; - else - $srcAutoupgrade = '../img/admin/disabled.gif'; - echo ''.$this->l('Autoupgrade allowed').' : '.' '.($this->upgrader->autoupgrade?$this->l('This release allow autoupgrade.'):$this->l('This release does not allow autoupgrade')).'.

'; - } - - if (Configuration::get('PS_SHOP_ENABLE')) - { - $srcShopStatus = '../img/admin/disabled.gif'; - $label = $this->l('No'); - } - else - { - $srcShopStatus = '../img/admin/enabled.gif'; - $label = $this->l('Yes'); - } - echo ''.$this->l('Shop desactivated').' : '.''.$label.'

'; - - $max_exec_time = ini_get('max_execution_time'); - if ($max_exec_time == 0) - $srcExecTime = '../img/admin/enabled.gif'; - else - $srcExecTime = '../img/admin/warning.gif'; - echo ''.$this->l('PHP time limit').' : '.''.($max_exec_time == 0?$this->l('disabled'):$max_exec_time.' '.$this->l('seconds')).'

'; - - if ($this->rootWritable) - $srcRootWritable = '../img/admin/enabled.gif'; - else - $srcRootWritable = '../img/admin/disabled.gif'; - echo ''.$this->l('Root directory').' : '.' '.($this->rootWritable?$this->l('writable recursively'):$this->l('not writable recursively')).'.

'; - - echo ''.$this->l('Modify your options').''; - echo '
'; - - echo '
'; - - echo '
'.$this->l('Update').''; - - - echo '
-

'.sprintf($this->l('Your current prestashop version : %s '),_PS_VERSION_).'

'; - echo '

'.sprintf($this->l('Last version is %1$s (%2$s) '), $this->upgrader->version_name, $this->upgrader->version_num).'

'; - - // @TODO : this should be checked when init() - if ($this->isUpgradeAllowed()) { - if ($pleaseUpdate) { - echo '
  • information '.$this->l('Latest Prestashop version available is:').' '.$pleaseUpdate['name'].'
  • '; - } -// echo ''; -// echo ''; - echo '
    '; - if ($this->upgrader->needUpgrade) - { - if($this->configOk()) - echo ''.$this->l('Upgrade PrestaShop now !').''; - else - echo $this->displayWarning('Your current configuration does not allow upgrade.'); - } - else - { - echo ''.$this->l('Your shop is already up to date.').' '; - } - echo ''.sprintf($this->l('last datetime check : %s '),date('Y-m-d H:i:s',Configuration::get('PS_LAST_VERSION_CHECK'))).' - '.$this->l('Please click to refresh').' - '; - - echo'
    - '; - - echo '
    '; - - - echo '
    '; - - if (defined('_PS_MODE_DEV_') AND _PS_MODE_DEV_) - { - echo '
    '.$this->l('Step').''; - echo '

    '.$this->l('Upgrade steps').'

    '; - echo '
    '; - echo 'download'; - echo 'unzip'; // unzip in autoupgrade/latest - echo 'removeSamples'; // remove samples (iWheel images) - echo 'backupFiles'; // backup files - echo 'backupDb'; - echo 'upgradeFiles'; - echo 'upgradeDb'; - echo '
    '; - - if (defined('_PS_ALLOW_UPGRADE_UNSTABLE_') AND _PS_ALLOW_UPGRADE_UNSTABLE_ ) - { - echo '

    Development tools

    '; - } - } - - echo '
    '; - echo'
    quick info
    '; - // for upgradeDb - echo '

    '; - echo '

    '; - } - else - echo '

    '.$this->l('Your current configuration does not allow upgrade.').'

    '; - - echo '
    '; -/* echo '
    - Error -
    no error yet
    -
    '; - * - */ - // information to keep will be in #infoStep - // temporary infoUpdate will be in #tmpInformation - echo ''; - } public function display() { - if(isset($_GET['refreshCurrentVersion'])) + global $cookie, $currentIndex; + echo '
    '.$this->l('Upgrade').''; + if(is_dir('autoupgrade')) { - $upgrader = new Upgrader(); - $upgrader->checkPSVersion(true); - } - echo ''; - $this->displayWarning($this->l('This function is experimental. It\'s highly recommended to make a backup of your files and database before starting the upgrade process.')); - - // update['name'] = version name - // update['num'] = only the version - // update['link'] = download link - // @TODO - - if ($this->isUpgradeAllowed()) - { - $this->createCustomToken(); - if ($this->useSvn) - echo '

    '.$this->l('Unstable upgrade').'

    -

    '.$this->l('Your current configuration indicate you want to upgrade your system from the unstable development branch, with no version number. If you upgrade, you will not be able to follow the official release process anymore').'.

    -
    '; - $this->_displayUpgraderForm(); - - echo '
    '; - $this->_displayRollbackForm(); - - echo '
    '; - $this->_displayForm('autoUpgradeOptions',$this->_fieldsAutoUpgrade,''.$this->l('Options').'', '','prefs'); - - if ($this->isModule) - { - echo ' - '; - } - echo ''; - } - else - { - echo '
    - '.$this->l('Update').''; - echo '

    '.$this->l('You currently don\'t need to use this feature.').'

    '; - echo '
    '; - } - - } - - private function _getJsInit() - { - $js = ''; - $js .= ' -function ucFirst(str) { - if (str.length > 0) { - return str[0].toUpperCase() + str.substring(1); - } - else { - return str; - } -} - -function cleanInfo(){ - $("#infoStep").html("reset
    "); -} - -function updateInfoStep(msg){ - if (msg) - { - $("#infoStep").html(msg); - $("#infoStep").attr({ scrollTop: $("#infoStep").attr("scrollHeight") }); - } -} - -function addError(msg){ - if (msg) - $("#errorWindow").html(msg); -} - -function addQuickInfo(arrQuickInfo){ - if (arrQuickInfo) - { - $("#quickInfo").show(); - for(i=0;i"); - - $("#quickInfo").attr({ scrollTop: $("#quickInfo").attr("scrollHeight") }); - } -}'; - - if ($this->manualMode) - $js .= 'var manualMode = true;'; - else - $js .= 'var manualMode = false;'; - - $js .= ' -var firstTimeParams = '.$this->buildAjaxResult().'; -firstTimeParams = firstTimeParams.nextParams; - -$(document).ready(function(){ - $(".upgradestep").click(function(e) - { - e.preventDefault(); - // $.scrollTo("#options") - }); - - // more convenient to have that param for handling success and error - var requestParams; - - // set timeout to 5 minutes (download can be long)? - $.ajaxSetup({timeout:300000}); - - - // prepare available button here, without params ? - prepareNextButton("#upgradeNow",firstTimeParams); - prepareNextButton("#rollback",firstTimeParams); - prepareNextButton("#restoreDb",firstTimeParams); - prepareNextButton("#restoreFiles",firstTimeParams); - -}); - -/** - * parseXMLResult is used to handle the return value of the doUpgrade method - * - */ -function parseXMLResult(xmlRet) -{ - ret = $(xmlRet); - ret = ret.find("action")[0]; - if (ret.getAttribute("result") == "ok") - { - $("#dbResultCheck") - .addClass("ok") - .removeClass("fail") - .html("

    '.$this->l('upgrade complete. Please check your front-office (try to make an order, check theme)').'

    ") - .show("slow"); - $("#dbCreateResultCheck") - .hide("slow"); - - // difference with the original function - ret = {next:"upgradeComplete",nextParams:{typeResult:"json"},status:"ok"}; - - } - else - { - $("#dbResultCheck") - .addClass("fail") - .removeClass("ok") - .html(txtError[parseInt(ret.getAttribute("error"))]) - .show("slow"); - $("#dbCreateResultCheck") - .hide("slow"); - - // propose rollback if there is an error - if (confirm(txtError[parseInt(ret.getAttribute("error"))]+"\r\n\r\n'.$this->l('Do you want to rollback ?').'")) - ret = {next:"rollback",nextParams:{typeResult:"json"},status:"error"}; - } - - return ret -}; - -function afterUpgradeComplete() -{ - $("#pleaseWait").hide(); - $("#infoStep").html("

    '.$this->l('Upgrade Complete ! ').'

    "); -} -/** - * afterBackupDb display the button - * - */ -function afterBackupDb() -{ - $("#restoreDbContainer").html("restoreDb '.$this->l('click to restore database').'"); - prepareNextButton("#restoreDb",{}); -} - -function afterRestoreDb() -{ - $("#restoreDbContainer").html(""); -} - -function afterRestoreFiles() -{ - $("#restoreFilesContainer").html(""); -} - -function afterBackupFiles() -{ - $("#restoreFilesContainer").html("
    restoreFiles '.$this->l('click to restore files').'"); - prepareNextButton("#restoreFiles",{}); - -} - -function doAjaxRequest(action, nextParams){ - req = $.ajax({ - type:"POST", - url : "'.($this->isModule? __PS_BASE_URI__ . trim($this->adminDir,'/').'/autoupgrade/ajax-upgradetab.php' : str_replace('index','ajax-tab',self::$currentIndex)).'", - async: true, - data : { - dir:"'.trim($this->adminDir,'/').'", - ajaxMode : "1", - token : "'.$this->token.'", - tab : "'.get_class($this).'", - action : action, - params : nextParams - }, + echo '

    '.$this->l('To make an upgrade, you need to activate the autoupgrade module').'

    '; + $tokenModule = Tools::getAdminToken('AdminModules'.(int)(Tab::getIdFromClassName('AdminModules')).(int)$cookie->id_employee); + $tokenAdminTabs = Tools::getAdminToken('AdminTabs'.(int)(Tab::getIdFromClassName('AdminTabs')).(int)$cookie->id_employee); + $tokenAdminTools = Tools::getAdminToken('AdminTools'.(int)(Tab::getIdFromClassName('AdminTools')).(int)$cookie->id_employee); + echo '

    + ' + .$this->l('Activate the module').'

    '; + echo ''; + } + else{ + echo "please download autoupgrade at this url"; + } + echo '
    '; + } + } diff --git a/classes/Cart.php b/classes/Cart.php index dc05e979a..eb4547b5d 100644 --- a/classes/Cart.php +++ b/classes/Cart.php @@ -250,6 +250,8 @@ class CartCore extends ObjectModel /** * Return cart discounts * + * @param bool true will return discounts with basic informations + * @param bool true will erase the cache * @result array Discounts */ public function getDiscounts($lite = false, $refresh = false) @@ -260,10 +262,15 @@ class CartCore extends ObjectModel if (!$this->id) return array(); - if (!$lite AND !$refresh AND isset(self::$_discounts[$this->id])) + + if (!$refresh) + { + if (!$lite AND isset(self::$_discounts[$this->id])) return self::$_discounts[$this->id]; + if ($lite AND isset(self::$_discountsLite[$this->id])) return self::$_discountsLite[$this->id]; + } $result = Db::getInstance()->ExecuteS(' SELECT d.*, `id_cart` diff --git a/classes/FrontController.php b/classes/FrontController.php index 5d6224c47..f3d93f494 100755 --- a/classes/FrontController.php +++ b/classes/FrontController.php @@ -234,7 +234,7 @@ class FrontControllerCore // @todo check here $page_name = Dispatcher::getInstance()->getController(); $page_name = (preg_match('/^[0-9]/', $page_name)) ? 'page_'.$page_name : $page_name; - + // Arf we in a module ? if (preg_match('#^'.preg_quote($this->context->shop->getPhysicalURI(), '#').'modules/([a-zA-Z0-9_-]+?)/(.*)$#', $_SERVER['REQUEST_URI'], $m)) $page_name = 'module-'.$m[1].'-'.str_replace(array('.php', '/'), array('', '-'), $m[2]); diff --git a/classes/PDF.php b/classes/PDF.php index 0d9535574..5310a71dc 100644 --- a/classes/PDF.php +++ b/classes/PDF.php @@ -427,6 +427,7 @@ class PDFCore extends PDF_PageGroupCore */ public static function generateHeaderAddresses(&$pdf, $order, $addressType, $patternRules, $width) { + $maxY = 0; $pdf->setY($pdf->GetY() + 5); foreach(array_keys($addressType) as $type) @@ -451,8 +452,12 @@ class PDFCore extends PDF_PageGroupCore if ($pattern == 'State:name' && Context::getContext()->country->iso_code == 'US') { - $state = &$addressType[$type]['addressFormatedValues'][$pattern]; - $state = strtoupper(substr($state, 0, 2)); + $state_name = &$addressType[$type]['addressFormatedValues'][$pattern]; + $state = new State((int)State::getIdByName($state_name)); + if (Validate::isLoadedObject($state)) + $state_name = $state->iso_code; + else + $state_name = strtoupper(substr($state_name, 0, 2)); } $tmp .= ((isset($addressType[$type]['addressFormatedValues'][$pattern]) && !empty($addressType[$type]['addressFormatedValues'][$pattern])) ? diff --git a/modules/autoupgrade/AdminPreferences.php b/modules/autoupgrade/AdminPreferences.php new file mode 100644 index 000000000..6380f9b73 --- /dev/null +++ b/modules/autoupgrade/AdminPreferences.php @@ -0,0 +1,506 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +class AdminPreferences extends AdminTab +{ + public function __construct() + { + global $cookie; + + $this->className = 'Configuration'; + $this->table = 'configuration'; + + $timezones = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS('SELECT name FROM '._DB_PREFIX_.'timezone'); + $taxes[] = array('id' => 0, 'name' => $this->l('None')); +/* foreach (Tax::getTaxes((int)($cookie->id_lang)) as $tax) + $taxes[] = array('id' => $tax['id_tax'], 'name' => $tax['name']); +*/ + $order_process_type = array( + array( + 'value' => PS_ORDER_PROCESS_STANDARD, + 'name' => $this->l('Standard (5 steps)') + ), + array( + 'value' => PS_ORDER_PROCESS_OPC, + 'name' => $this->l('One page checkout') + ) + ); + + $round_mode = array( + array( + 'value' => PS_ROUND_UP, + 'name' => $this->l('superior') + ), + array( + 'value' => PS_ROUND_DOWN, + 'name' => $this->l('inferior') + ), + array( + 'value' => PS_ROUND_HALF, + 'name' => $this->l('classical') + ) + ); + + $cms_tab = array(0 => + array( + 'id' => 0, + 'name' => $this->l('None') + ) + ); + foreach (CMS::listCms($cookie->id_lang) as $cms_file) + $cms_tab[] = array('id' => $cms_file['id_cms'], 'name' => $cms_file['meta_title']); + $this->_fieldsGeneral = array( + 'PS_SHOP_ENABLE' => array('title' => $this->l('Enable Shop'), 'desc' => $this->l('Activate or deactivate your shop. Deactivate your shop while you perform maintenance on it. Please note that the webservice will not be disabled'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool'), + 'PS_MAINTENANCE_IP' => array('title' => $this->l('Maintenance IP'), 'desc' => $this->l('IP addresses allowed to access the Front Office even if shop is disabled. Use a comma to separate them (e.g., 42.24.4.2,127.0.0.1,99.98.97.96)'), 'validation' => 'isGenericName', 'type' => 'maintenance_ip', 'size' => 30, 'default' => ''), + 'PS_SSL_ENABLED' => array('title' => $this->l('Enable SSL'), 'desc' => $this->l('If your hosting provider allows SSL, you can activate SSL encryption (https://) for customer account identification and order processing'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool', 'default' => '0'), + 'PS_COOKIE_CHECKIP' => array('title' => $this->l('Check IP on the cookie'), 'desc' => $this->l('Check the IP address of the cookie in order to avoid your cookie being stolen'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool', 'default' => '0'), + 'PS_COOKIE_LIFETIME_FO' => array('title' => $this->l('Lifetime of the Front Office cookie'), 'desc' => $this->l('Indicate the number of hours'), 'validation' => 'isInt', 'cast' => 'intval', 'type' => 'text', 'default' => '480'), + 'PS_COOKIE_LIFETIME_BO' => array('title' => $this->l('Lifetime of the Back Office cookie'), 'desc' => $this->l('Indicate the number of hours'), 'validation' => 'isInt', 'cast' => 'intval', 'type' => 'text', 'default' => '480'), + 'PS_TOKEN_ENABLE' => array('title' => $this->l('Increase Front Office security'), 'desc' => $this->l('Enable or disable token on the Front Office in order to improve PrestaShop security'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool', 'default' => '0'), + 'PS_HELPBOX' => array('title' => $this->l('Back Office help boxes'), 'desc' => $this->l('Enable yellow help boxes which are displayed under form fields in the Back Office'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool'), + 'PS_ORDER_PROCESS_TYPE' => array('title' => $this->l('Order process type'), 'desc' => $this->l('You can choose the order process type as either standard (5 steps) or One Page Checkout'), 'validation' => 'isInt', 'cast' => 'intval', 'type' => 'select', 'list' => $order_process_type, 'identifier' => 'value'), + 'PS_GUEST_CHECKOUT_ENABLED' => array('title' => $this->l('Enable guest checkout'), 'desc' => $this->l('Your guest can make an order without registering'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool'), + 'PS_CONDITIONS' => array('title' => $this->l('Terms of service'), 'desc' => $this->l('Require customers to accept or decline terms of service before processing the order'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool', 'js' => array('on' => 'onchange="changeCMSActivationAuthorization()"', 'off' => 'onchange="changeCMSActivationAuthorization()"')), + 'PS_CONDITIONS_CMS_ID' => array('title' => $this->l('Conditions of use CMS page'), 'desc' => $this->l('Choose the Conditions of use CMS page'), 'validation' => 'isInt', 'type' => 'select', 'list' => $cms_tab, 'identifier' => 'id', 'cast' => 'intval'), + 'PS_GIFT_WRAPPING' => array('title' => $this->l('Offer gift-wrapping'), 'desc' => $this->l('Suggest gift-wrapping to customer and possibility of leaving a message'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool'), + 'PS_GIFT_WRAPPING_PRICE' => array('title' => $this->l('Gift-wrapping price'), 'desc' => $this->l('Set a price for gift-wrapping'), 'validation' => 'isPrice', 'cast' => 'floatval', 'type' => 'price'), + 'PS_GIFT_WRAPPING_TAX' => array('title' => $this->l('Gift-wrapping tax'), 'desc' => $this->l('Set a tax for gift-wrapping'), 'validation' => 'isInt', 'cast' => 'intval', 'type' => 'select', 'list' => $taxes, 'identifier' => 'id'), + 'PS_ATTACHMENT_MAXIMUM_SIZE' => array('title' => $this->l('Attachment maximum size'), 'desc' => $this->l('Set the maximum size of attachment files (in MegaBytes)'), 'validation' => 'isInt', 'cast' => 'intval', 'type' => 'text'), + 'PS_RECYCLABLE_PACK' => array('title' => $this->l('Offer recycled packaging'), 'desc' => $this->l('Suggest recycled packaging to customer'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool'), + 'PS_CART_FOLLOWING' => array('title' => $this->l('Cart re-display at login'), 'desc' => $this->l('After customer logs in, recall and display contents of his/her last shopping cart'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool'), + 'PS_PRICE_ROUND_MODE' => array('title' => $this->l('Round mode'), 'desc' => $this->l('You can choose how to round prices: always round superior; always round inferior, or classic rounding'), 'validation' => 'isInt', 'cast' => 'intval', 'type' => 'select', 'list' => $round_mode, 'identifier' => 'value'), + 'PRESTASTORE_LIVE' => array('title' => $this->l('Automatically check for module updates'), 'desc' => $this->l('New modules and updates are displayed on the modules page'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool'), + 'PS_HIDE_OPTIMIZATION_TIPS' => array('title' => $this->l('Hide optimization tips'), 'desc' => $this->l('Hide optimization tips on the back office homepage'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool'), + 'PS_DISPLAY_SUPPLIERS' => array('title' => $this->l('Display suppliers and manufacturers'), 'desc' => $this->l('Display manufacturers and suppliers list even if corresponding blocks are disabled'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool'), + 'PS_FORCE_SMARTY_2' => array('title' => $this->l('Use Smarty 2 instead of 3'), 'desc' => $this->l('Enable if your theme is incompatible with Smarty 3 (you should update your theme, since Smarty 2 will be unsupported from PrestaShop v1.5)'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool'), + ); + if (function_exists('date_default_timezone_set')) + $this->_fieldsGeneral['PS_TIMEZONE'] = array('title' => $this->l('Time Zone:'), 'validation' => 'isAnything', 'type' => 'select', 'list' => $timezones, 'identifier' => 'name'); + + // No HTTPS activation if you haven't already. + if (empty($_SERVER['HTTPS']) OR strtolower($_SERVER['HTTPS']) == 'off') + { + $this->_fieldsGeneral['PS_SSL_ENABLED']['type'] = 'disabled'; + $this->_fieldsGeneral['PS_SSL_ENABLED']['disabled'] = ''.$this->l('Please click here to use HTTPS protocol before enabling SSL.').''; + } + + parent::__construct(); + } + + public function display() + { + $this->_displayForm('general', $this->_fieldsGeneral, $this->l('General'), '', 'tab-preferences'); + } + + public function postProcess() + { + global $currentIndex; + + if (isset($_POST['submitGeneral'.$this->table])) + { + Module::hookExec('categoryUpdate'); // We call this hook, for regenerate cache of categories + if (Tools14::getValue('PS_CONDITIONS') == true AND (Tools14::getValue('PS_CONDITIONS_CMS_ID') == 0 OR !Db::getInstance()->getValue(' + SELECT `id_cms` FROM `'._DB_PREFIX_.'cms` + WHERE id_cms = '.(int)(Tools14::getValue('PS_CONDITIONS_CMS_ID'))))) + $this->_errors[] = Tools14::displayError('Assign a valid CMS page if you want it to be read.'); + if ($this->tabAccess['edit'] === '1') + $this->_postConfig($this->_fieldsGeneral); + else + $this->_errors[] = Tools14::displayError('You do not have permission to edit here.'); + } + elseif (isset($_POST['submitShop'.$this->table])) + { + if ($this->tabAccess['edit'] === '1') + $this->_postConfig($this->_fieldsShop); + else + $this->_errors[] = Tools14::displayError('You do not have permission to edit here.'); + } + elseif (isset($_POST['submitAppearance'.$this->table])) + { + if ($this->tabAccess['edit'] === '1') + $this->_postConfig($this->_fieldsAppearance); + else + $this->_errors[] = Tools14::displayError('You do not have permission to edit here.'); + } + elseif (isset($_POST['submitThemes'.$this->table])) + { + if ($this->tabAccess['edit'] === '1') + { + if ($val = Tools14::getValue('PS_THEME')) + { + if (rewriteSettingsFile(NULL, $val, NULL)) + Tools14::redirectAdmin($currentIndex.'&conf=6'.'&token='.$this->token); + else + $this->_errors[] = Tools14::displayError('Cannot access settings file.'); + } + else + $this->_errors[] = Tools14::displayError('You must choose a graphical theme.'); + } + else + $this->_errors[] = Tools14::displayError('You do not have permission to edit here.'); + } + parent::postProcess(); + } + + /** + * Update settings in database and configuration files + * + * @params array $fields Fields settings + * + * @global string $currentIndex Current URL in order to keep current Tab + */ + protected function _postConfig($fields) + { + global $currentIndex, $smarty; + + $languages = Language::getLanguages(false); + Tools14::clearCache($smarty); + + /* Check required fields */ + foreach ($fields AS $field => $values) + if (isset($values['required']) AND $values['required']) + if (isset($values['type']) AND $values['type'] == 'textLang') + { + foreach ($languages as $language) + if (($value = Tools14::getValue($field.'_'.$language['id_lang'])) == false AND (string)$value != '0') + $this->_errors[] = Tools14::displayError('field').' '.$values['title'].' '.Tools14::displayError('is required.'); + } + elseif (($value = Tools14::getValue($field)) == false AND (string)$value != '0') + $this->_errors[] = Tools14::displayError('field').' '.$values['title'].' '.Tools14::displayError('is required.'); + + /* Check fields validity */ + foreach ($fields AS $field => $values) + if (isset($values['type']) AND $values['type'] == 'textLang') + { + foreach ($languages as $language) + if (Tools14::getValue($field.'_'.$language['id_lang']) AND isset($values['validation'])) + if (!Validate::$values['validation'](Tools14::getValue($field.'_'.$language['id_lang']))) + $this->_errors[] = Tools14::displayError('field').' '.$values['title'].' '.Tools14::displayError('is invalid.'); + } + elseif (Tools14::getValue($field) AND isset($values['validation'])) + if (!Validate::$values['validation'](Tools14::getValue($field))) + $this->_errors[] = Tools14::displayError('field').' '.$values['title'].' '.Tools14::displayError('is invalid.'); + + /* Default value if null */ + foreach ($fields AS $field => $values) + if (!Tools14::getValue($field) AND isset($values['default'])) + $_POST[$field] = $values['default']; + + /* Save process */ + if (!sizeof($this->_errors)) + { + if (Tools14::isSubmit('submitAppearanceconfiguration')) + { + if (isset($_FILES['PS_LOGO']['tmp_name']) AND $_FILES['PS_LOGO']['tmp_name']) + { + if ($error = checkImage($_FILES['PS_LOGO'], 300000)) + $this->_errors[] = $error; + if (!$tmpName = tempnam(_PS_TMP_IMG_DIR_, 'PS') OR !move_uploaded_file($_FILES['PS_LOGO']['tmp_name'], $tmpName)) + return false; + elseif (!@imageResize($tmpName, _PS_IMG_DIR_.'logo.jpg')) + $this->_errors[] = 'an error occurred during logo copy'; + unlink($tmpName); + } + if (isset($_FILES['PS_LOGO_MAIL']['tmp_name']) AND $_FILES['PS_LOGO_MAIL']['tmp_name']) + { + if ($error = checkImage($_FILES['PS_LOGO_MAIL'], 300000)) + $this->_errors[] = $error; + if (!$tmpName = tempnam(_PS_TMP_IMG_DIR_, 'PS_MAIL') OR !move_uploaded_file($_FILES['PS_LOGO_MAIL']['tmp_name'], $tmpName)) + return false; + elseif (!@imageResize($tmpName, _PS_IMG_DIR_.'logo_mail.jpg')) + $this->_errors[] = 'an error occurred during logo copy'; + unlink($tmpName); + } + if (isset($_FILES['PS_LOGO_INVOICE']['tmp_name']) AND $_FILES['PS_LOGO_INVOICE']['tmp_name']) + { + if ($error = checkImage($_FILES['PS_LOGO_INVOICE'], 300000)) + $this->_errors[] = $error; + if (!$tmpName = tempnam(_PS_TMP_IMG_DIR_, 'PS_INVOICE') OR !move_uploaded_file($_FILES['PS_LOGO_INVOICE']['tmp_name'], $tmpName)) + return false; + elseif (!@imageResize($tmpName, _PS_IMG_DIR_.'logo_invoice.jpg')) + $this->_errors[] = 'an error occurred during logo copy'; + unlink($tmpName); + } + if (isset($_FILES['PS_STORES_ICON']['tmp_name']) AND $_FILES['PS_STORES_ICON']['tmp_name']) + { + if ($error = checkImage($_FILES['PS_STORES_ICON'], 300000)) + $this->_errors[] = $error; + if (!$tmpName = tempnam(_PS_TMP_IMG_DIR_, 'PS_STORES_ICON') OR !move_uploaded_file($_FILES['PS_STORES_ICON']['tmp_name'], $tmpName)) + return false; + elseif (!@imageResize($tmpName, _PS_IMG_DIR_.'logo_stores.gif')) + $this->_errors[] = 'an error occurred during logo copy'; + unlink($tmpName); + } + $this->uploadIco('PS_FAVICON', _PS_IMG_DIR_.'favicon.ico'); + } + + /* Update settings in database */ + if (!sizeof($this->_errors)) + { + foreach ($fields AS $field => $values) + { + unset($val); + if (isset($values['type']) AND $values['type'] == 'textLang') + foreach ($languages as $language) + $val[$language['id_lang']] = isset($values['cast']) ? $values['cast'](Tools14::getValue($field.'_'.$language['id_lang'])) : Tools14::getValue($field.'_'.$language['id_lang']); + else + $val = isset($values['cast']) ? $values['cast'](Tools14::getValue($field)) : Tools14::getValue($field); + + Configuration::updateValue($field, $val); + } + Tools14::redirectAdmin($currentIndex.'&conf=6'.'&token='.$this->token); + } + } + } + + private function getVal($conf, $key) + { + return Tools14::getValue($key, (isset($conf[$key]) ? $conf[$key] : '')); + } + + private function getConf($fields, $languages) + { + foreach ($fields AS $key => $field) + { + if ($field['type'] == 'textLang') + foreach ($languages as $language) + $tab[$key.'_'.$language['id_lang']] = Tools14::getValue($key.'_'.$language['id_lang'], Configuration::get($key, $language['id_lang'])); + else + $tab[$key] = Tools14::getValue($key, Configuration::get($key)); + } + $tab['__PS_BASE_URI__'] = __PS_BASE_URI__; + $tab['_MEDIA_SERVER_1_'] = _MEDIA_SERVER_1_; + $tab['_MEDIA_SERVER_2_'] = _MEDIA_SERVER_2_; + $tab['_MEDIA_SERVER_3_'] = _MEDIA_SERVER_3_; + $tab['PS_THEME'] = _THEME_NAME_; + $tab['db_type'] = _DB_TYPE_; + $tab['db_server'] = _DB_SERVER_; + $tab['db_name'] = _DB_NAME_; + $tab['db_prefix'] = _DB_PREFIX_; + $tab['db_user'] = _DB_USER_; + $tab['db_passwd'] = ''; + + return $tab; + } + + private function getDivLang($fields) + { + $tab = array(); + foreach ($fields AS $key => $field) + if ($field['type'] == 'textLang' || $field['type'] == 'selectLang') + $tab[] = $key; + return implode('¤', $tab); + } + + /** + * Display configuration form + * + * @params string $name Form name + * @params array $fields Fields settings + * + * @global string $currentIndex Current URL in order to keep current Tab + */ + protected function _displayForm($name, $fields, $tabname, $size, $icon) + { + global $currentIndex; + + $defaultLanguage = (int)(Configuration::get('PS_LANG_DEFAULT')); + $languages = Language::getLanguages(false); + $confValues = $this->getConf($fields, $languages); + $divLangName = $this->getDivLang($fields); + $required = false; + + echo ' + +
    +
    '.$tabname.''; + foreach ($fields AS $key => $field) + { + /* Specific line for e-mails settings */ + if (get_class($this) == 'Adminemails' AND $key == 'PS_MAIL_SERVER') + echo '
    '; + if (isset($field['required']) AND $field['required']) + $required = true; + $val = $this->getVal($confValues, $key); + + if (!in_array($field['type'], array('image', 'radio', 'container', 'container_end')) OR isset($field['show'])) + echo '
    '.($field['title'] ? '' : '').'
    '; + + /* Display the appropriate input type for each field */ + switch ($field['type']) + { + case 'disabled': echo $field['disabled'];break; + case 'select': + echo ' + '; + break; + + case 'selectLang': + foreach ($languages as $language) + { + echo ' +
    + +
    '; + } + $this->displayFlags($languages, $defaultLanguage, $divLangName, $key); + break; + + case 'bool': + echo ' + + + + + '; + break; + + case 'radio': + foreach ($field['choices'] AS $cValue => $cKey) + echo '
    '; + echo '
    '; + break; + + case 'image': + echo ' + + '; + if ($name == 'themes') + echo ' + + + + '; + $i = 0; + foreach ($field['list'] AS $theme) + { + echo ''; + if (isset($field['max']) AND ($i+1) % $field['max'] == 0) + echo ''; + $i++; + } + echo ' +
    + '.$this->l('In order to use a new theme, please follow these steps:', get_class()).' + +
    + + +
    + +
    '; + break; + + case 'price': + $default_currency = new Currency((int)(Configuration::get("PS_CURRENCY_DEFAULT"))); + echo $default_currency->getSign('left').''.$default_currency->getSign('right').' '.$this->l('(tax excl.)'); + break; + + case 'textLang': + foreach ($languages as $language) + echo ' +
    + +
    '; + $this->displayFlags($languages, $defaultLanguage, $divLangName, $key); + break; + + case 'file': + if (isset($field['thumb']) AND $field['thumb'] AND $field['thumb']['pos'] == 'before') + echo ''.$field['title'].'
    '; + echo ''; + break; + + case 'textarea': + echo ''; + break; + + case 'container': + echo '
    '; + break; + + case 'container_end': + echo (isset($field['content']) === true ? $field['content'] : '').'
    '; + break; + + case 'maintenance_ip': + echo ''.(isset($field['next']) ? ' '.strval($field['next']) : '').'  '.$this->l('Add my IP').''; + break; + + case 'text': + default: + echo ''.(isset($field['next']) ? ' '.strval($field['next']) : ''); + } + echo ((isset($field['required']) AND $field['required'] AND !in_array($field['type'], array('image', 'radio'))) ? ' *' : ''); + echo (isset($field['desc']) ? '

    '.((isset($field['thumb']) AND $field['thumb'] AND $field['thumb']['pos'] == 'after') ? ''.$field['title'].'' : '' ).$field['desc'].'

    ' : ''); + if (!in_array($field['type'], array('image', 'radio', 'container', 'container_end')) OR isset($field['show'])) + echo '
    '; + } + + /* End of specific div for e-mails settings */ + if (get_class($this) == 'Adminemails') + echo '
    '; + + if (!is_writable(PS_ADMIN_DIR.'/../config/settings.inc.php') AND $name == 'themes') + echo '

    '.$this->l('if you change the theme, the settings.inc.php file must be writable (CHMOD 755 / 777)').'

    '; + + echo '
    + +
    + '.($required ? '
    * '.$this->l('Required field', 'AdminPreferences').'
    ' : '').' +
    +
    '; + + if (get_class($this) == 'AdminPreferences') + echo ''; + } +} + diff --git a/modules/autoupgrade/AdminSelfTab.php b/modules/autoupgrade/AdminSelfTab.php new file mode 100644 index 000000000..3dde2d3df --- /dev/null +++ b/modules/autoupgrade/AdminSelfTab.php @@ -0,0 +1,2148 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +abstract class AdminSelfTab +{ + /** @var integer Tab id */ + public $id = -1; + + /** @var string Associated table name */ + public $table; + + /** @var string Object identifier inside the associated table */ + protected $identifier = false; + + /** @var string Tab name */ + public $name; + + /** @var string Security token */ + public $token; + + /** @var boolean Automatically join language table if true */ + public $lang = false; + + /** @var boolean Tab Automatically displays edit/delete icons if true */ + public $edit = false; + + /** @var boolean Tab Automatically displays view icon if true */ + public $view = false; + + /** @var boolean Tab Automatically displays delete icon if true */ + public $delete = false; + + /** @var boolean Table records are not deleted but marked as deleted */ + public $deleted = false; + + /** @var boolean Tab Automatically displays duplicate icon if true */ + public $duplicate = false; + + /** @var boolean Content line is clickable if true */ + public $noLink = false; + + /** @var boolean select other required fields */ + public $requiredDatabase = false; + + /** @var boolean Tab Automatically displays '$color' as background color on listing if true */ + public $colorOnBackground = false; + + /** @var string Add fields into data query to display list */ + protected $_select; + + /** @var string Join tables into data query to display list */ + protected $_join; + + /** @var string Add conditions into data query to display list */ + protected $_where; + + /** @var string Group rows into data query to display list */ + protected $_group; + + /** @var string Having rows into data query to display list */ + protected $_having; + + /** @var array Name and directory where class image are located */ + public $fieldImageSettings = array(); + + /** @var string Image type */ + public $imageType = 'jpg'; + + /** @var array Fields to display in list */ + public $fieldsDisplay = array(); + + /** @var array Cache for query results */ + protected $_list = array(); + + /** @var integer Number of results in list */ + protected $_listTotal = 0; + + /** @var array WHERE clause determined by filter fields */ + protected $_filter; + + /** @var array Temporary SQL table WHERE clause determinated by filter fields */ + protected $_tmpTableFilter = ''; + + /** @var array Number of results in list per page (used in select field) */ + protected $_pagination = array(20, 50, 100, 300); + + /** @var string ORDER BY clause determined by field/arrows in list header */ + protected $_orderBy; + + /** @var string Default ORDER BY clause when $_orderBy is not defined */ + protected $_defaultOrderBy = false; + + /** @var string Order way (ASC, DESC) determined by arrows in list header */ + protected $_orderWay; + + /** @var integer Max image size for upload */ + protected $maxImageSize = 2000000; + + /** @var array Errors displayed after post processing */ + public $_errors = array(); + + /** @var array Confirmations displayed after post processing */ + protected $_conf; + + /** @var object Object corresponding to the tab */ + protected $_object = false; + + /** @var array tabAccess */ + public $tabAccess; + + /** @var string specificConfirmDelete */ + public $specificConfirmDelete = NULL; + + protected $identifiersDnd = array('id_product' => 'id_product', 'id_category' => 'id_category_to_move','id_cms_category' => 'id_cms_category_to_move', 'id_cms' => 'id_cms'); + + /** @var bool Redirect or not ater a creation */ + protected $_redirect = true; + + protected $_languages = NULL; + protected $_defaultFormLanguage = NULL; + + protected $_includeObj = array(); + protected $_includeVars = false; + protected $_includeContainer = true; + + public $ajax = false; + + public static $tabParenting = array( + 'AdminProducts' => 'AdminCatalog', + 'AdminCategories' => 'AdminCatalog', + 'AdminCMS' => 'AdminCMSContent', + 'AdminCMSCategories' => 'AdminCMSContent', + 'AdminOrdersStates' => 'AdminStatuses', + 'AdminAttributeGenerator' => 'AdminProducts', + 'AdminAttributes' => 'AdminAttributesGroups', + 'AdminFeaturesValues' => 'AdminFeatures', + 'AdminReturnStates' => 'AdminStatuses', + 'AdminStatsTab' => 'AdminStats' + ); + + public function __construct() + { + global $cookie; + $this->id = Tab::getCurrentTabId(); + $this->_conf = array( + 1 => $this->l('Deletion successful'), 2 => $this->l('Selection successfully deleted'), + 3 => $this->l('Creation successful'), 4 => $this->l('Update successful'), + 5 => $this->l('Status update successful'), 6 => $this->l('Settings update successful'), + 7 => $this->l('Image successfully deleted'), 8 => $this->l('Module downloaded successfully'), + 9 => $this->l('Thumbnails successfully regenerated'), 10 => $this->l('Message sent to the customer'), + 11 => $this->l('Comment added'), 12 => $this->l('Module installed successfully'), + 13 => $this->l('Module uninstalled successfully'), 14 => $this->l('Language successfully copied'), + 15 => $this->l('Translations successfully added'), 16 => $this->l('Module transplanted successfully to hook'), + 17 => $this->l('Module removed successfully from hook'), 18 => $this->l('Upload successful'), + 19 => $this->l('Duplication completed successfully'), 20 => $this->l('Translation added successfully but the language has not been created'), + 21 => $this->l('Module reset successfully'), 22 => $this->l('Module deleted successfully'), + 23 => $this->l('Localization pack imported successfully'), 24 => $this->l('Refund Successful'), + 25 => $this->l('Images successfully moved')); + if (!$this->identifier) $this->identifier = 'id_'.$this->table; + if (!$this->_defaultOrderBy) $this->_defaultOrderBy = $this->identifier; + $className = get_class($this); + if ($className == 'AdminCategories' OR $className == 'AdminProducts') + $className = 'AdminCatalog'; + $this->token = Tools14::getAdminToken($className.(int)$this->id.(int)$cookie->id_employee); + + } + + + private function getConf($fields, $languages) + { + foreach ($fields AS $key => $field) + { + if ($field['type'] == 'textLang') + foreach ($languages as $language) + $tab[$key.'_'.$language['id_lang']] = Tools14::getValue($key.'_'.$language['id_lang'], Configuration::get($key, $language['id_lang'])); + else + $tab[$key] = Tools14::getValue($key, Configuration::get($key)); + } + $tab['__PS_BASE_URI__'] = __PS_BASE_URI__; + $tab['_MEDIA_SERVER_1_'] = defined('_MEDIA_SERVER_1_')?_MEDIA_SERVER_1_:''; + $tab['_MEDIA_SERVER_2_'] = defined('_MEDIA_SERVER_2_')?_MEDIA_SERVER_2_:''; + $tab['_MEDIA_SERVER_3_'] = defined('_MEDIA_SERVER_3_')?_MEDIA_SERVER_3_:''; + $tab['PS_THEME'] = _THEME_NAME_; + $tab['db_type'] = _DB_TYPE_; + $tab['db_server'] = _DB_SERVER_; + $tab['db_name'] = _DB_NAME_; + $tab['db_prefix'] = _DB_PREFIX_; + $tab['db_user'] = _DB_USER_; + $tab['db_passwd'] = ''; + + return $tab; + } + private function getDivLang($fields) + { + $tab = array(); + foreach ($fields AS $key => $field) + if ($field['type'] == 'textLang' || $field['type'] == 'selectLang') + $tab[] = $key; + return implode('¤', $tab); + } + + private function getVal($conf, $key) + { + return Tools14::getValue($key, (isset($conf[$key]) ? $conf[$key] : '')); + } + + protected function _displayForm($name, $fields, $tabname, $size, $icon) + { + global $currentIndex; + + $defaultLanguage = (int)(Configuration::get('PS_LANG_DEFAULT')); + $languages = Language::getLanguages(false); + $confValues = $this->getConf($fields, $languages); + $divLangName = $this->getDivLang($fields); + $required = false; + + echo ' + +
    +
    '.$tabname.''; + foreach ($fields AS $key => $field) + { + /* Specific line for e-mails settings */ + if (get_class($this) == 'Adminemails' AND $key == 'PS_MAIL_SERVER') + echo '
    '; + if (isset($field['required']) AND $field['required']) + $required = true; + $val = $this->getVal($confValues, $key); + + if (!in_array($field['type'], array('image', 'radio', 'container', 'container_end')) OR isset($field['show'])) + echo '
    '.($field['title'] ? '' : '').'
    '; + + /* Display the appropriate input type for each field */ + switch ($field['type']) + { + case 'disabled': echo $field['disabled'];break; + case 'select': + echo ' + '; + break; + + case 'selectLang': + foreach ($languages as $language) + { + echo ' +
    + +
    '; + } + $this->displayFlags($languages, $defaultLanguage, $divLangName, $key); + break; + + case 'bool': + echo ' + + + + + '; + break; + + case 'radio': + foreach ($field['choices'] AS $cValue => $cKey) + echo '
    '; + echo '
    '; + break; + + case 'image': + echo ' + + '; + if ($name == 'themes') + echo ' + + + + '; + $i = 0; + foreach ($field['list'] AS $theme) + { + echo ''; + if (isset($field['max']) AND ($i+1) % $field['max'] == 0) + echo ''; + $i++; + } + echo ' +
    + '.$this->l('In order to use a new theme, please follow these steps:', get_class()).' + +
    + + +
    + +
    '; + break; + + case 'price': + $default_currency = new Currency((int)(Configuration::get("PS_CURRENCY_DEFAULT"))); + echo $default_currency->getSign('left').''.$default_currency->getSign('right').' '.$this->l('(tax excl.)'); + break; + + case 'textLang': + foreach ($languages as $language) + echo ' +
    + +
    '; + $this->displayFlags($languages, $defaultLanguage, $divLangName, $key); + break; + + case 'file': + if (isset($field['thumb']) AND $field['thumb'] AND $field['thumb']['pos'] == 'before') + echo ''.$field['title'].'
    '; + echo ''; + break; + + case 'textarea': + echo ''; + break; + + case 'container': + echo '
    '; + break; + + case 'container_end': + echo (isset($field['content']) === true ? $field['content'] : '').'
    '; + break; + + case 'maintenance_ip': + echo ''.(isset($field['next']) ? ' '.strval($field['next']) : '').'  '.$this->l('Add my IP').''; + break; + + case 'text': + default: + echo ''.(isset($field['next']) ? ' '.strval($field['next']) : ''); + } + echo ((isset($field['required']) AND $field['required'] AND !in_array($field['type'], array('image', 'radio'))) ? ' *' : ''); + echo (isset($field['desc']) ? '

    '.((isset($field['thumb']) AND $field['thumb'] AND $field['thumb']['pos'] == 'after') ? ''.$field['title'].'' : '' ).$field['desc'].'

    ' : ''); + if (!in_array($field['type'], array('image', 'radio', 'container', 'container_end')) OR isset($field['show'])) + echo '
    '; + } + + /* End of specific div for e-mails settings */ + if (get_class($this) == 'Adminemails') + echo '
    '; + + if (!is_writable(PS_ADMIN_DIR.'/../config/settings.inc.php') AND $name == 'themes') + echo '

    '.$this->l('if you change the theme, the settings.inc.php file must be writable (CHMOD 755 / 777)').'

    '; + + echo '
    + +
    + '.($required ? '
    * '.$this->l('Required field', 'AdminPreferences').'
    ' : '').' +
    +
    '; + + if (get_class($this) == 'AdminPreferences') + echo ''; + } + + /** + * use translations files to replace english expression. + * + * @param mixed $string term or expression in english + * @param string $class + * @param boolan $addslashes if set to true, the return value will pass through addslashes(). Otherwise, stripslashes(). + * @param boolean $htmlentities if set to true(default), the return value will pass through htmlentities($string, ENT_QUOTES, 'utf-8') + * @return string the translation if available, or the english default text. + */ + protected function l($string, $class = 'AdminTab', $addslashes = FALSE, $htmlentities = TRUE) + { + // if the class is extended by a module, use modules/[module_name]/xx.php lang file + $currentClass = get_class($this); + if (Module::getModuleNameFromClass($currentClass)) + { + $string = str_replace('\'', '\\\'', $string); + return Module::findTranslation(Module::$classInModule[$currentClass], $string, $currentClass); + } + global $_LANGADM; + + if ($class == __CLASS__) + $class = 'AdminTab'; + + $key = md5(str_replace('\'', '\\\'', $string)); + $str = (key_exists(get_class($this).$key, $_LANGADM)) ? $_LANGADM[get_class($this).$key] : ((key_exists($class.$key, $_LANGADM)) ? $_LANGADM[$class.$key] : $string); + $str = $htmlentities ? htmlentities($str, ENT_QUOTES, 'utf-8') : $str; + return str_replace('"', '"', ($addslashes ? addslashes($str) : stripslashes($str))); + } + + /** + * ajaxDisplay is the default ajax return sytem + * + * @return void + */ + public function displayAjax() + { + } + /** + * Manage page display (form, list...) + * + * @global string $currentIndex Current URL in order to keep current Tab + */ + public function display() + { + global $currentIndex, $cookie; + + // Include other tab in current tab + if ($this->includeSubTab('display', array('submitAdd2', 'add', 'update', 'view'))){} + + // Include current tab + elseif ((Tools::getValue('submitAdd'.$this->table) AND sizeof($this->_errors)) OR isset($_GET['add'.$this->table])) + { + if ($this->tabAccess['add'] === '1') + { + $this->displayForm(); + if ($this->tabAccess['view']) + echo '

    '.((Tools::getValue('back')) ? $this->l('Back') : $this->l('Back to list')).'
    '; + } + else + echo $this->l('You do not have permission to add here'); + } + elseif (isset($_GET['update'.$this->table])) + { + if ($this->tabAccess['edit'] === '1' OR ($this->table == 'employee' AND $cookie->id_employee == Tools::getValue('id_employee'))) + { + $this->displayForm(); + if ($this->tabAccess['view']) + echo '

    '.((Tools::getValue('back')) ? $this->l('Back') : $this->l('Back to list')).'
    '; + } + else + echo $this->l('You do not have permission to edit here'); + } + elseif (isset($_GET['view'.$this->table])) + $this->{'view'.$this->table}(); + + else + { + $this->getList((int)($cookie->id_lang)); + $this->displayList(); + $this->displayOptionsList(); + $this->displayRequiredFields(); + $this->includeSubTab('display'); + } + } + + public function displayRequiredFields() + { + global $currentIndex; + if (!$this->tabAccess['add'] OR !$this->tabAccess['delete'] === '1' OR !$this->requiredDatabase) + return; + $rules = call_user_func_array(array($this->className, 'getValidationRules'), array($this->className)); + $required_class_fields = array($this->identifier); + foreach ($rules['required'] AS $required) + $required_class_fields[] = $required; + + echo '
    +

    '.$this->l('Set required fields for this section').'

    + '; + } + + public function includeSubTab($methodname, $actions = array()) + { + if (!isset($this->_includeTab) OR !is_array($this->_includeTab)) + return false; + $key = 0; + $inc = false; + foreach ($this->_includeTab as $subtab => $extraVars) + { + /* New tab loading */ + $classname = 'Admin'.$subtab; + if ($module = Db::getInstance()->getValue('SELECT `module` FROM `'._DB_PREFIX_.'tab` WHERE `class_name` = \''.pSQL($classname).'\'') AND file_exists(_PS_MODULE_DIR_.'/'.$module.'/'.$classname.'.php')) + include_once(_PS_MODULE_DIR_.'/'.$module.'/'.$classname.'.php'); + elseif (file_exists(PS_ADMIN_DIR.'/tabs/'.$classname.'.php')) + include_once('tabs/'.$classname.'.php'); + if (!isset($this->_includeObj[$key])) + $this->_includeObj[$key] = new $classname; + $adminTab = $this->_includeObj[$key]; + $adminTab->token = $this->token; + + /* Extra variables addition */ + if (!empty($extraVars) AND is_array($extraVars)) + foreach ($extraVars AS $varKey => $varValue) + $adminTab->$varKey = $varValue; + + /* Actions management */ + foreach ($actions as $action) + { + switch ($action) + { + + case 'submitAdd1': + if (Tools::getValue('submitAdd'.$adminTab->table)) + $ok_inc = true; + break; + case 'submitAdd2': + if (Tools::getValue('submitAdd'.$adminTab->table) AND sizeof($adminTab->_errors)) + $ok_inc = true; + break; + case 'submitDel': + if (Tools::getValue('submitDel'.$adminTab->table)) + $ok_inc = true; + break; + case 'submitFilter': + if (Tools::isSubmit('submitFilter'.$adminTab->table)) + $ok_inc = true; + case 'submitReset': + if (Tools::isSubmit('submitReset'.$adminTab->table)) + $ok_inc = true; + default: + if (isset($_GET[$action.$adminTab->table])) + $ok_inc = true; + } + } + $inc = false; + if ((isset($ok_inc) AND $ok_inc) OR !sizeof($actions)) + { + if (!$adminTab->viewAccess()) + { + echo Tools::displayError('Access denied'); + return false; + } + if (!sizeof($actions)) + if (($methodname == 'displayErrors' AND sizeof($adminTab->_errors)) OR $methodname != 'displayErrors') + echo (isset($this->_includeTabTitle[$key]) ? '

    '.$this->_includeTabTitle[$key].'

    ' : ''); + if ($adminTab->_includeVars) + foreach ($adminTab->_includeVars AS $var => $value) + $adminTab->$var = $this->$value; + $adminTab->$methodname(); + $inc = true; + } + $key++; + } + return $inc; + } + + /** + * Manage page display (form, list...) + * + * @param string $className Allow to validate a different class than the current one + */ + public function validateRules($className = false) + { + if (!$className) + $className = $this->className; + + /* Class specific validation rules */ + $rules = call_user_func(array($className, 'getValidationRules'), $className); + + if ((sizeof($rules['requiredLang']) OR sizeof($rules['sizeLang']) OR sizeof($rules['validateLang']))) + { + /* Language() instance determined by default language */ + $defaultLanguage = new Language((int)(Configuration::get('PS_LANG_DEFAULT'))); + + /* All availables languages */ + $languages = Language::getLanguages(false); + } + + /* Checking for required fields */ + foreach ($rules['required'] AS $field) + if (($value = Tools::getValue($field)) == false AND (string)$value != '0') + if (!Tools::getValue($this->identifier) OR ($field != 'passwd' AND $field != 'no-picture')) + $this->_errors[] = $this->l('the field').' '.call_user_func(array($className, 'displayFieldName'), $field, $className).' '.$this->l('is required'); + + /* Checking for multilingual required fields */ + foreach ($rules['requiredLang'] AS $fieldLang) + if (($empty = Tools::getValue($fieldLang.'_'.$defaultLanguage->id)) === false OR $empty !== '0' AND empty($empty)) + $this->_errors[] = $this->l('the field').' '.call_user_func(array($className, 'displayFieldName'), $fieldLang, $className).' '.$this->l('is required at least in').' '.$defaultLanguage->name; + + /* Checking for maximum fields sizes */ + foreach ($rules['size'] AS $field => $maxLength) + if (Tools::getValue($field) !== false AND Tools::strlen(Tools::getValue($field)) > $maxLength) + $this->_errors[] = $this->l('the field').' '.call_user_func(array($className, 'displayFieldName'), $field, $className).' '.$this->l('is too long').' ('.$maxLength.' '.$this->l('chars max').')'; + + /* Checking for maximum multilingual fields size */ + foreach ($rules['sizeLang'] AS $fieldLang => $maxLength) + foreach ($languages AS $language) + if (Tools::getValue($fieldLang.'_'.$language['id_lang']) !== false AND Tools::strlen(Tools::getValue($fieldLang.'_'.$language['id_lang'])) > $maxLength) + $this->_errors[] = $this->l('the field').' '.call_user_func(array($className, 'displayFieldName'), $fieldLang, $className).' ('.$language['name'].') '.$this->l('is too long').' ('.$maxLength.' '.$this->l('chars max, html chars including').')'; + + /* Overload this method for custom checking */ + $this->_childValidation(); + + /* Checking for fields validity */ + foreach ($rules['validate'] AS $field => $function) + if (($value = Tools::getValue($field)) !== false AND ($field != 'passwd')) + if (!Validate::$function($value)) + $this->_errors[] = $this->l('the field').' '.call_user_func(array($className, 'displayFieldName'), $field, $className).' '.$this->l('is invalid'); + + /* Checking for passwd_old validity */ + if (($value = Tools::getValue('passwd')) != false) + { + if ($className == 'Employee' AND !Validate::isPasswdAdmin($value)) + $this->_errors[] = $this->l('the field').' '.call_user_func(array($className, 'displayFieldName'), 'passwd', $className).' '.$this->l('is invalid'); + elseif ($className == 'Customer' AND !Validate::isPasswd($value)) + $this->_errors[] = $this->l('the field').' '.call_user_func(array($className, 'displayFieldName'), 'passwd', $className).' '.$this->l('is invalid'); + } + + /* Checking for multilingual fields validity */ + foreach ($rules['validateLang'] AS $fieldLang => $function) + foreach ($languages AS $language) + if (($value = Tools::getValue($fieldLang.'_'.$language['id_lang'])) !== false AND !empty($value)) + if (!Validate::$function($value)) + $this->_errors[] = $this->l('the field').' '.call_user_func(array($className, 'displayFieldName'), $fieldLang, $className).' ('.$language['name'].') '.$this->l('is invalid'); + } + + /** + * Overload this method for custom checking + */ + protected function _childValidation() { } + + /** + * Overload this method for custom checking + * + * @param integer $id Object id used for deleting images + * TODO This function will soon be deprecated. Use ObjectModel->deleteImage instead. + */ + public function deleteImage($id) + { + $dir = null; + /* Deleting object images and thumbnails (cache) */ + if (key_exists('dir', $this->fieldImageSettings)) + { + $dir = $this->fieldImageSettings['dir'].'/'; + if (file_exists(_PS_IMG_DIR_.$dir.$id.'.'.$this->imageType) AND !unlink(_PS_IMG_DIR_.$dir.$id.'.'.$this->imageType)) + return false; + } + if (file_exists(_PS_TMP_IMG_DIR_.$this->table.'_'.$id.'.'.$this->imageType) AND !unlink(_PS_TMP_IMG_DIR_.$this->table.'_'.$id.'.'.$this->imageType)) + return false; + if (file_exists(_PS_TMP_IMG_DIR_.$this->table.'_mini_'.$id.'.'.$this->imageType) AND !unlink(_PS_TMP_IMG_DIR_.$this->table.'_mini_'.$id.'.'.$this->imageType)) + return false; + $types = ImageType::getImagesTypes(); + foreach ($types AS $imageType) + if (file_exists(_PS_IMG_DIR_.$dir.$id.'-'.stripslashes($imageType['name']).'.'.$this->imageType) AND !unlink(_PS_IMG_DIR_.$dir.$id.'-'.stripslashes($imageType['name']).'.'.$this->imageType)) + return false; + return true; + } + + /** + * ajaxPreProcess is a method called in ajax-tab.php before displayConf(). + * + * @return void + */ + public function ajaxPreProcess() + { + } + + /** + * ajaxProcess is the default handle method for request with ajax-tab.php + * + * @return void + */ + public function ajaxProcess() + { + } + + /** + * Manage page processing + * + * @global string $currentIndex Current URL in order to keep current Tab + */ + public function postProcess() + { + global $currentIndex, $cookie; + if (!isset($this->table)) + return false; + + // set token + $token = Tools::getValue('token') ? Tools::getValue('token') : $this->token; + + // Sub included tab postProcessing + $this->includeSubTab('postProcess', array('status', 'submitAdd1', 'submitDel', 'delete', 'submitFilter', 'submitReset')); + + /* Delete object image */ + if (isset($_GET['deleteImage'])) + { + if (Validate::isLoadedObject($object = $this->loadObject())) + if (($object->deleteImage())) + Tools::redirectAdmin($currentIndex.'&add'.$this->table.'&'.$this->identifier.'='.Tools::getValue($this->identifier).'&conf=7&token='.$token); + $this->_errors[] = Tools::displayError('An error occurred during image deletion (cannot load object).'); + } + + /* Delete object */ + elseif (isset($_GET['delete'.$this->table])) + { + if ($this->tabAccess['delete'] === '1') + { + if (Validate::isLoadedObject($object = $this->loadObject()) AND isset($this->fieldImageSettings)) + { + // check if request at least one object with noZeroObject + if (isset($object->noZeroObject) AND sizeof(call_user_func(array($this->className, $object->noZeroObject))) <= 1) + $this->_errors[] = Tools::displayError('You need at least one object.').' '.$this->table.'
    '.Tools::displayError('You cannot delete all of the items.'); + else + { + if ($this->deleted) + { + $object->deleteImage(); + $object->deleted = 1; + if ($object->update()) + Tools::redirectAdmin($currentIndex.'&conf=1&token='.$token); + } + elseif ($object->delete()) + Tools::redirectAdmin($currentIndex.'&conf=1&token='.$token); + $this->_errors[] = Tools::displayError('An error occurred during deletion.'); + } + } + else + $this->_errors[] = Tools::displayError('An error occurred while deleting object.').' '.$this->table.' '.Tools::displayError('(cannot load object)'); + } + else + $this->_errors[] = Tools::displayError('You do not have permission to delete here.'); + } + + /* Change object statuts (active, inactive) */ + elseif ((isset($_GET['status'.$this->table]) OR isset($_GET['status'])) AND Tools::getValue($this->identifier)) + { + if ($this->tabAccess['edit'] === '1') + { + if (Validate::isLoadedObject($object = $this->loadObject())) + { + if ($object->toggleStatus()) + Tools::redirectAdmin($currentIndex.'&conf=5'.((($id_category = (int)(Tools::getValue('id_category'))) AND Tools::getValue('id_product')) ? '&id_category='.$id_category : '').'&token='.$token); + else + $this->_errors[] = Tools::displayError('An error occurred while updating status.'); + } + else + $this->_errors[] = Tools::displayError('An error occurred while updating status for object.').' '.$this->table.' '.Tools::displayError('(cannot load object)'); + } + else + $this->_errors[] = Tools::displayError('You do not have permission to edit here.'); + } + /* Move an object */ + elseif (isset($_GET['position'])) + { + if ($this->tabAccess['edit'] !== '1') + $this->_errors[] = Tools::displayError('You do not have permission to edit here.'); + elseif (!Validate::isLoadedObject($object = $this->loadObject())) + $this->_errors[] = Tools::displayError('An error occurred while updating status for object.').' '.$this->table.' '.Tools::displayError('(cannot load object)'); + elseif (!$object->updatePosition((int)(Tools::getValue('way')), (int)(Tools::getValue('position')))) + $this->_errors[] = Tools::displayError('Failed to update the position.'); + else + Tools::redirectAdmin($currentIndex.'&'.$this->table.'Orderby=position&'.$this->table.'Orderway=asc&conf=5'.(($id_category = (int)(Tools::getValue($this->identifier))) ? ('&'.$this->identifier.'='.$id_category) : '').'&token='.$token); + Tools::redirectAdmin($currentIndex.'&'.$this->table.'Orderby=position&'.$this->table.'Orderway=asc&conf=5'.((($id_category = (int)(Tools::getValue('id_category'))) AND Tools::getValue('id_product')) ? '&id_category='.$id_category : '').'&token='.$token); + } + /* Delete multiple objects */ + elseif (Tools::getValue('submitDel'.$this->table)) + { + if ($this->tabAccess['delete'] === '1') + { + if (isset($_POST[$this->table.'Box'])) + { + $object = new $this->className(); + if (isset($object->noZeroObject) AND + // Check if all object will be deleted + (sizeof(call_user_func(array($this->className, $object->noZeroObject))) <= 1 OR sizeof($_POST[$this->table.'Box']) == sizeof(call_user_func(array($this->className, $object->noZeroObject))))) + $this->_errors[] = Tools::displayError('You need at least one object.').' '.$this->table.'
    '.Tools::displayError('You cannot delete all of the items.'); + else + { + $result = true; + if ($this->deleted) + { + foreach(Tools::getValue($this->table.'Box') as $id) + { + $toDelete = new $this->className($id); + $toDelete->deleted = 1; + $result = $result AND $toDelete->update(); + } + } + else + $result = $object->deleteSelection(Tools::getValue($this->table.'Box')); + + if ($result) + Tools::redirectAdmin($currentIndex.'&conf=2&token='.$token); + $this->_errors[] = Tools::displayError('An error occurred while deleting selection.'); + } + } + else + $this->_errors[] = Tools::displayError('You must select at least one element to delete.'); + } + else + $this->_errors[] = Tools::displayError('You do not have permission to delete here.'); + } + + /* Create or update an object */ + elseif (Tools::getValue('submitAdd'.$this->table)) + { + /* Checking fields validity */ + $this->validateRules(); + if (!sizeof($this->_errors)) + { + $id = (int)(Tools::getValue($this->identifier)); + + /* Object update */ + if (isset($id) AND !empty($id)) + { + if ($this->tabAccess['edit'] === '1' OR ($this->table == 'employee' AND $cookie->id_employee == Tools::getValue('id_employee') AND Tools::isSubmit('updateemployee'))) + { + $object = new $this->className($id); + if (Validate::isLoadedObject($object)) + { + /* Specific to objects which must not be deleted */ + if ($this->deleted AND $this->beforeDelete($object)) + { + // Create new one with old objet values + $objectNew = new $this->className($object->id); + $objectNew->id = NULL; + $objectNew->date_add = ''; + $objectNew->date_upd = ''; + + // Update old object to deleted + $object->deleted = 1; + $object->update(); + + // Update new object with post values + $this->copyFromPost($objectNew, $this->table); + $result = $objectNew->add(); + if (Validate::isLoadedObject($objectNew)) + $this->afterDelete($objectNew, $object->id); + } + else + { + $this->copyFromPost($object, $this->table); + $result = $object->update(); + $this->afterUpdate($object); + } + if (!$result) + $this->_errors[] = Tools::displayError('An error occurred while updating object.').' '.$this->table.' ('.Db::getInstance()->getMsgError().')'; + elseif ($this->postImage($object->id) AND !sizeof($this->_errors)) + { + $parent_id = (int)(Tools::getValue('id_parent', 1)); + // Specific back redirect + if ($back = Tools::getValue('back')) + Tools::redirectAdmin(urldecode($back).'&conf=4'); + // Specific scene feature + if (Tools::getValue('stay_here') == 'on' || Tools::getValue('stay_here') == 'true' || Tools::getValue('stay_here') == '1') + Tools::redirectAdmin($currentIndex.'&'.$this->identifier.'='.$object->id.'&conf=4&updatescene&token='.$token); + // Save and stay on same form + if (Tools::isSubmit('submitAdd'.$this->table.'AndStay')) + Tools::redirectAdmin($currentIndex.'&'.$this->identifier.'='.$object->id.'&conf=4&update'.$this->table.'&token='.$token); + // Save and back to parent + if (Tools::isSubmit('submitAdd'.$this->table.'AndBackToParent')) + Tools::redirectAdmin($currentIndex.'&'.$this->identifier.'='.$parent_id.'&conf=4&token='.$token); + // Default behavior (save and back) + Tools::redirectAdmin($currentIndex.($parent_id ? '&'.$this->identifier.'='.$object->id : '').'&conf=4&token='.$token); + } + } + else + $this->_errors[] = Tools::displayError('An error occurred while updating object.').' '.$this->table.' '.Tools::displayError('(cannot load object)'); + } + else + $this->_errors[] = Tools::displayError('You do not have permission to edit here.'); + } + + /* Object creation */ + else + { + if ($this->tabAccess['add'] === '1') + { + $object = new $this->className(); + $this->copyFromPost($object, $this->table); + if (!$object->add()) + $this->_errors[] = Tools::displayError('An error occurred while creating object.').' '.$this->table.' ('.mysql_error().')'; + elseif (($_POST[$this->identifier] = $object->id /* voluntary */) AND $this->postImage($object->id) AND !sizeof($this->_errors) AND $this->_redirect) + { + $parent_id = (int)(Tools::getValue('id_parent', 1)); + $this->afterAdd($object); + // Save and stay on same form + if (Tools::isSubmit('submitAdd'.$this->table.'AndStay')) + Tools::redirectAdmin($currentIndex.'&'.$this->identifier.'='.$object->id.'&conf=3&update'.$this->table.'&token='.$token); + // Save and back to parent + if (Tools::isSubmit('submitAdd'.$this->table.'AndBackToParent')) + Tools::redirectAdmin($currentIndex.'&'.$this->identifier.'='.$parent_id.'&conf=3&token='.$token); + // Default behavior (save and back) + Tools::redirectAdmin($currentIndex.($parent_id ? '&'.$this->identifier.'='.$object->id : '').'&conf=3&token='.$token); + } + } + else + $this->_errors[] = Tools::displayError('You do not have permission to add here.'); + } + } + $this->_errors = array_unique($this->_errors); + } + + /* Cancel all filters for this tab */ + elseif (isset($_POST['submitReset'.$this->table])) + { + $filters = $cookie->getFamily($this->table.'Filter_'); + foreach ($filters AS $cookieKey => $filter) + if (strncmp($cookieKey, $this->table.'Filter_', 7 + Tools::strlen($this->table)) == 0) + { + $key = substr($cookieKey, 7 + Tools::strlen($this->table)); + /* Table alias could be specified using a ! eg. alias!field */ + $tmpTab = explode('!', $key); + $key = (count($tmpTab) > 1 ? $tmpTab[1] : $tmpTab[0]); + if (array_key_exists($key, $this->fieldsDisplay)) + unset($cookie->$cookieKey); + } + if (isset($cookie->{'submitFilter'.$this->table})) + unset($cookie->{'submitFilter'.$this->table}); + if (isset($cookie->{$this->table.'Orderby'})) + unset($cookie->{$this->table.'Orderby'}); + if (isset($cookie->{$this->table.'Orderway'})) + unset($cookie->{$this->table.'Orderway'}); + unset($_POST); + } + + /* Submit options list */ + elseif (Tools::getValue('submitOptions'.$this->table)) + { + $this->updateOptions($token); + } + + /* Manage list filtering */ + elseif (Tools::isSubmit('submitFilter'.$this->table) OR $cookie->{'submitFilter'.$this->table} !== false) + { + $_POST = array_merge($cookie->getFamily($this->table.'Filter_'), (isset($_POST) ? $_POST : array())); + foreach ($_POST AS $key => $value) + { + /* Extracting filters from $_POST on key filter_ */ + if ($value != NULL AND !strncmp($key, $this->table.'Filter_', 7 + Tools::strlen($this->table))) + { + $key = Tools::substr($key, 7 + Tools::strlen($this->table)); + /* Table alias could be specified using a ! eg. alias!field */ + $tmpTab = explode('!', $key); + $filter = count($tmpTab) > 1 ? $tmpTab[1] : $tmpTab[0]; + if ($field = $this->filterToField($key, $filter)) + { + $type = (array_key_exists('filter_type', $field) ? $field['filter_type'] : (array_key_exists('type', $field) ? $field['type'] : false)); + if (($type == 'date' OR $type == 'datetime') AND is_string($value)) + $value = unserialize($value); + $key = isset($tmpTab[1]) ? $tmpTab[0].'.`'.$tmpTab[1].'`' : '`'.$tmpTab[0].'`'; + if (array_key_exists('tmpTableFilter', $field)) + $sqlFilter = & $this->_tmpTableFilter; + elseif (array_key_exists('havingFilter', $field)) + $sqlFilter = & $this->_filterHaving; + else + $sqlFilter = & $this->_filter; + + /* Only for date filtering (from, to) */ + if (is_array($value)) + { + if (isset($value[0]) AND !empty($value[0])) + { + if (!Validate::isDate($value[0])) + $this->_errors[] = Tools::displayError('\'from:\' date format is invalid (YYYY-MM-DD)'); + else + $sqlFilter .= ' AND '.pSQL($key).' >= \''.pSQL(Tools::dateFrom($value[0])).'\''; + } + + if (isset($value[1]) AND !empty($value[1])) + { + if (!Validate::isDate($value[1])) + $this->_errors[] = Tools::displayError('\'to:\' date format is invalid (YYYY-MM-DD)'); + else + $sqlFilter .= ' AND '.pSQL($key).' <= \''.pSQL(Tools::dateTo($value[1])).'\''; + } + } + else + { + $sqlFilter .= ' AND '; + if ($type == 'int' OR $type == 'bool') + $sqlFilter .= (($key == $this->identifier OR $key == '`'.$this->identifier.'`' OR $key == '`active`') ? 'a.' : '').pSQL($key).' = '.(int)($value).' '; + elseif ($type == 'decimal') + $sqlFilter .= (($key == $this->identifier OR $key == '`'.$this->identifier.'`') ? 'a.' : '').pSQL($key).' = '.(float)($value).' '; + elseif ($type == 'select') + $sqlFilter .= (($key == $this->identifier OR $key == '`'.$this->identifier.'`') ? 'a.' : '').pSQL($key).' = \''.pSQL($value).'\' '; + else + $sqlFilter .= (($key == $this->identifier OR $key == '`'.$this->identifier.'`') ? 'a.' : '').pSQL($key).' LIKE \'%'.pSQL($value).'%\' '; + } + } + } + } + } + elseif (Tools::isSubmit('submitFields') AND $this->requiredDatabase AND $this->tabAccess['add'] === '1' AND $this->tabAccess['delete'] === '1') + { + if (!is_array($fields = Tools::getValue('fieldsBox'))) + $fields = array(); + + $object = new $this->className(); + if (!$object->addFieldsRequiredDatabase($fields)) + $this->_errors[] = Tools::displayError('Error in updating required fields'); + else + Tools::redirectAdmin($currentIndex.'&conf=4&token='.$token); + } + } + + protected function updateOptions($token) + { + global $currentIndex; + + if ($this->tabAccess['edit'] === '1') + { + foreach ($this->_fieldsOptions as $key => $field) + { + + if ($this->validateField(Tools::getValue($key), $field)) + { + // check if a method updateOptionFieldName is available + $method_name = 'updateOption'.Tools::toCamelCase($key, true); + if (method_exists($this, $method_name)) + $this->$method_name(Tools::getValue($key)); + elseif ($field['type'] == 'textLang' OR $field['type'] == 'textareaLang') + { + $languages = Language::getLanguages(false); + $list = array(); + foreach ($languages as $language) + { + $val = (isset($field['cast']) ? $field['cast'](Tools::getValue($key.'_'.$language['id_lang'])) : Tools::getValue($key.'_'.$language['id_lang'])); + if (Validate::isCleanHtml($val)) + $list[$language['id_lang']] = $val; + else + $this->_errors[] = Tools::displayError('Can not add configuration '.$key.' for lang '.Language::getIsoById((int)$language['id_lang'])); + } + Configuration::updateValue($key, $list); + } + else + { + $val = (isset($field['cast']) ? $field['cast'](Tools::getValue($key)) : Tools::getValue($key)); + if (Validate::isCleanHtml($val)) + Configuration::updateValue($key, $val); + else + $this->_errors[] = Tools::displayError('Can not add configuration '.$key); + + } + } + } + + if (count($this->_errors) <= 0) + Tools::redirectAdmin($currentIndex.'&conf=6&token='.$token); + } + else + $this->_errors[] = Tools::displayError('You do not have permission to edit here.'); + } + + protected function validateField($value, $field) + { + if (isset($field['validation'])) + { + $validate = new Validate(); + if (method_exists($validate, $field['validation'])) + { + if (!Validate::$field['validation']($value)) + { + $this->_errors[] = Tools::displayError($field['title'].' : Incorrect value'); + return false; + } + } + } + + return true; + } + + protected function uploadImage($id, $name, $dir, $ext = false) + { + if (isset($_FILES[$name]['tmp_name']) AND !empty($_FILES[$name]['tmp_name'])) + { + // Delete old image + if (Validate::isLoadedObject($object = $this->loadObject())) + $object->deleteImage(); + else + return false; + + // Check image validity + if ($error = checkImage($_FILES[$name], $this->maxImageSize)) + $this->_errors[] = $error; + elseif (!$tmpName = tempnam(_PS_TMP_IMG_DIR_, 'PS') OR !move_uploaded_file($_FILES[$name]['tmp_name'], $tmpName)) + return false; + else + { + $_FILES[$name]['tmp_name'] = $tmpName; + // Copy new image + if (!imageResize($tmpName, _PS_IMG_DIR_.$dir.$id.'.'.$this->imageType, NULL, NULL, ($ext ? $ext : $this->imageType))) + $this->_errors[] = Tools::displayError('An error occurred while uploading image.'); + if (sizeof($this->_errors)) + return false; + if ($this->afterImageUpload()) + { + unlink($tmpName); + return true; + } + return false; + } + } + return true; + } + + + + protected function uploadIco($name, $dest) + { + + if (isset($_FILES[$name]['tmp_name']) AND !empty($_FILES[$name]['tmp_name'])) + { + /* Check ico validity */ + if ($error = checkIco($_FILES[$name], $this->maxImageSize)) + $this->_errors[] = $error; + + /* Copy new ico */ + elseif (!copy($_FILES[$name]['tmp_name'], $dest)) + $this->_errors[] = Tools::displayError('an error occurred while uploading favicon: '.$_FILES[$name]['tmp_name'].' to '.$dest); + } + return !sizeof($this->_errors) ? true : false; + } + + /** + * Overload this method for custom checking + * + * @param integer $id Object id used for deleting images + * @return boolean + */ + protected function postImage($id) + { + if (isset($this->fieldImageSettings['name']) AND isset($this->fieldImageSettings['dir'])) + return $this->uploadImage($id, $this->fieldImageSettings['name'], $this->fieldImageSettings['dir'].'/'); + elseif (!empty($this->fieldImageSettings)) + foreach ($this->fieldImageSettings AS $image) + if (isset($image['name']) AND isset($image['dir'])) + $this->uploadImage($id, $image['name'], $image['dir'].'/'); + return !sizeof($this->_errors) ? true : false; + } + + /** + * Copy datas from $_POST to object + * + * @param object &$object Object + * @param string $table Object table + */ + protected function copyFromPost(&$object, $table) + { + /* Classical fields */ + foreach ($_POST AS $key => $value) + if (key_exists($key, $object) AND $key != 'id_'.$table) + { + /* Do not take care of password field if empty */ + if ($key == 'passwd' AND Tools::getValue('id_'.$table) AND empty($value)) + continue; + /* Automatically encrypt password in MD5 */ + if ($key == 'passwd' AND !empty($value)) + $value = Tools::encrypt($value); + $object->{$key} = $value; + } + + /* Multilingual fields */ + $rules = call_user_func(array(get_class($object), 'getValidationRules'), get_class($object)); + if (sizeof($rules['validateLang'])) + { + $languages = Language::getLanguages(false); + foreach ($languages AS $language) + foreach (array_keys($rules['validateLang']) AS $field) + if (isset($_POST[$field.'_'.(int)($language['id_lang'])])) + $object->{$field}[(int)($language['id_lang'])] = $_POST[$field.'_'.(int)($language['id_lang'])]; + } + } + + /** + * Display errors + */ + public function displayErrors() + { + if ($nbErrors = count($this->_errors) AND $this->_includeContainer) + { + echo ' +
    X'; + if (count($this->_errors) == 1) + echo $this->_errors[0]; + else + { + echo $nbErrors.' '.$this->l('errors').'
      '; + foreach ($this->_errors AS $error) + echo '
    1. '.$error.'
    2. '; + echo '
    '; + } + echo '
    '; + } + $this->includeSubTab('displayErrors'); + } + + /** + * Display a warning message + * + * @param string $warn Warning message to display + */ + public function displayWarning($warn) + { + $str_output = ''; + if (!empty($warn)) + { + $str_output .= ' +
    '; + if (!is_array($warn)) + $str_output .= ''.$warn; + else + { $str_output .= 'X'. + (count($warn) > 1 ? $this->l('There are') : $this->l('There is')).' '.count($warn).' '.(count($warn) > 1 ? $this->l('warnings') : $this->l('warning')) + .' + '.$this->l('Click here to see more').' + '; + } + $str_output .= '
    '; + } + echo $str_output; + } + + /** + * Display confirmations + */ + public function displayConf() + { + if ($conf = Tools::getValue('conf')) + echo ' +
    + '.$this->_conf[(int)($conf)].' +
    '; + } + + /** + * Get the current objects' list form the database + * + * @param integer $id_lang Language used for display + * @param string $orderBy ORDER BY clause + * @param string $_orderWay Order way (ASC, DESC) + * @param integer $start Offset in LIMIT clause + * @param integer $limit Row count in LIMIT clause + */ + public function getList($id_lang, $orderBy = NULL, $orderWay = NULL, $start = 0, $limit = NULL) + { + global $cookie; + + /* Manage default params values */ + if (empty($limit)) + $limit = ((!isset($cookie->{$this->table.'_pagination'})) ? $this->_pagination[1] : $limit = $cookie->{$this->table.'_pagination'}); + + if (!Validate::isTableOrIdentifier($this->table)) + die (Tools::displayError('Table name is invalid:').' "'.$this->table.'"'); + + if (empty($orderBy)) + $orderBy = $cookie->__get($this->table.'Orderby') ? $cookie->__get($this->table.'Orderby') : $this->_defaultOrderBy; + if (empty($orderWay)) + $orderWay = $cookie->__get($this->table.'Orderway') ? $cookie->__get($this->table.'Orderway') : 'ASC'; + + $limit = (int)(Tools::getValue('pagination', $limit)); + $cookie->{$this->table.'_pagination'} = $limit; + + /* Check params validity */ + if (!Validate::isOrderBy($orderBy) OR !Validate::isOrderWay($orderWay) + OR !is_numeric($start) OR !is_numeric($limit) + OR !Validate::isUnsignedId($id_lang)) + die(Tools::displayError('get list params is not valid')); + + /* Determine offset from current page */ + if ((isset($_POST['submitFilter'.$this->table]) OR + isset($_POST['submitFilter'.$this->table.'_x']) OR + isset($_POST['submitFilter'.$this->table.'_y'])) AND + !empty($_POST['submitFilter'.$this->table]) AND + is_numeric($_POST['submitFilter'.$this->table])) + $start = (int)($_POST['submitFilter'.$this->table] - 1) * $limit; + + /* Cache */ + $this->_lang = (int)($id_lang); + $this->_orderBy = $orderBy; + $this->_orderWay = Tools::strtoupper($orderWay); + + /* SQL table : orders, but class name is Order */ + $sqlTable = $this->table == 'order' ? 'orders' : $this->table; + + /* Query in order to get results with all fields */ + $sql = 'SELECT SQL_CALC_FOUND_ROWS + '.($this->_tmpTableFilter ? ' * FROM (SELECT ' : '').' + '.($this->lang ? 'b.*, ' : '').'a.*'.(isset($this->_select) ? ', '.$this->_select.' ' : '').' + FROM `'._DB_PREFIX_.$sqlTable.'` a + '.($this->lang ? 'LEFT JOIN `'._DB_PREFIX_.$this->table.'_lang` b ON (b.`'.$this->identifier.'` = a.`'.$this->identifier.'` AND b.`id_lang` = '.(int)($id_lang).')' : '').' + '.(isset($this->_join) ? $this->_join.' ' : '').' + WHERE 1 '.(isset($this->_where) ? $this->_where.' ' : '').($this->deleted ? 'AND a.`deleted` = 0 ' : '').(isset($this->_filter) ? $this->_filter : '').' + '.(isset($this->_group) ? $this->_group.' ' : '').' + '.((isset($this->_filterHaving) || isset($this->_having)) ? 'HAVING ' : '').(isset($this->_filterHaving) ? ltrim($this->_filterHaving, ' AND ') : '').(isset($this->_having) ? $this->_having.' ' : '').' + ORDER BY '.(($orderBy == $this->identifier) ? 'a.' : '').'`'.pSQL($orderBy).'` '.pSQL($orderWay). + ($this->_tmpTableFilter ? ') tmpTable WHERE 1'.$this->_tmpTableFilter : '').' + LIMIT '.(int)($start).','.(int)($limit); + + $this->_list = Db::getInstance()->ExecuteS($sql); + $this->_listTotal = Db::getInstance()->getValue('SELECT FOUND_ROWS() AS `'._DB_PREFIX_.$this->table.'`'); + + } + + /** + * Display image aside object form + * + * @param integer $id Object id + * @param string $image Local image filepath + * @param integer $size Image width + * @param integer $id_image Image id (for products with several images) + * @param string $token Employee token used in the image deletion link + * @param boolean $disableCache When turned on a timestamp will be added to the image URI to disable the HTTP cache + * + * @global string $currentIndex Current URL in order to keep current Tab + */ + public function displayImage($id, $image, $size, $id_image = NULL, $token = NULL, $disableCache = false) + { + global $currentIndex; + + if (!isset($token) OR empty($token)) + $token = $this->token; + if ($id AND file_exists($image)) + echo ' +
    + '.cacheImage($image, $this->table.'_'.(int)($id).'.'.$this->imageType, $size, $this->imageType, $disableCache).' +

    '.$this->l('File size').' '.(filesize($image) / 1000).'kb

    + + '.$this->l('Delete').' '.$this->l('Delete').' +
    '; + } + + /** + * Display list header (filtering, pagination and column names) + * + * @global string $currentIndex Current URL in order to keep current Tab + */ + public function displayListHeader($token = NULL) + { + global $currentIndex, $cookie; + $isCms = false; + if (preg_match('/cms/Ui', $this->identifier)) + $isCms = true; + $id_cat = Tools::getValue('id_'.($isCms ? 'cms_' : '').'category'); + + if (!isset($token) OR empty($token)) + $token = $this->token; + + /* Determine total page number */ + $totalPages = ceil($this->_listTotal / Tools::getValue('pagination', (isset($cookie->{$this->table.'_pagination'}) ? $cookie->{$this->table.'_pagination'} : $this->_pagination[0]))); + if (!$totalPages) $totalPages = 1; + + echo ' '; + echo ' + + + + + + + + + '; + foreach ($this->fieldsDisplay AS $key => $params) + { + echo ' '; + } + + /* Check if object can be modified, deleted or detailed */ + if ($this->edit OR $this->delete OR ($this->view AND $this->view !== 'noActionColumn')) + echo ' '; + echo ' + + '; + + /* Javascript hack in order to catch ENTER keypress event */ + $keyPress = 'onkeypress="formSubmit(event, \'submitFilterButton_'.$this->table.'\');"'; + + /* Filters (input, select, date or bool) */ + foreach ($this->fieldsDisplay AS $key => $params) + { + $width = (isset($params['width']) ? ' style="width: '.(int)($params['width']).'px;"' : ''); + echo ''; + if (!isset($params['type'])) + $params['type'] = 'text'; + + $value = Tools::getValue($this->table.'Filter_'.(array_key_exists('filter_key', $params) ? $params['filter_key'] : $key)); + if (isset($params['search']) AND !$params['search']) + { + echo '--'; + continue; + } + switch ($params['type']) + { + case 'bool': + echo ' + '; + break; + + case 'date': + case 'datetime': + if (is_string($value)) + $value = unserialize($value); + if (!Validate::isCleanHtml($value[0]) OR !Validate::isCleanHtml($value[1])) + $value = ''; + $name = $this->table.'Filter_'.(isset($params['filter_key']) ? $params['filter_key'] : $key); + $nameId = str_replace('!', '__', $name); + includeDatepicker(array($nameId.'_0', $nameId.'_1')); + echo $this->l('From').'
    + '.$this->l('To').' '; + break; + + case 'select': + + if (isset($params['filter_key'])) + { + echo ''; + break; + } + + case 'text': + default: + if (!Validate::isCleanHtml($value)) + $value = ''; + echo ''; + } + echo ''; + } + + if ($this->edit OR $this->delete OR ($this->view AND $this->view !== 'noActionColumn')) + echo '
    '; + + echo ' + '; + } + + public function displayTop() + { + } + + /** + * Display list + * + * @global string $currentIndex Current URL in order to keep current Tab + */ + public function displayList() + { + global $currentIndex; + + $this->displayTop(); + + if ($this->edit AND (!isset($this->noAdd) OR !$this->noAdd)) + echo '
    '.$this->l('Add new').'

    '; + /* Append when we get a syntax error in SQL query */ + if ($this->_list === false) + { + $this->displayWarning($this->l('Bad SQL query')); + return false; + } + + /* Display list header (filtering, pagination and column names) */ + $this->displayListHeader(); + if (!sizeof($this->_list)) + echo ''; + + /* Show the content of the table */ + $this->displayListContent(); + + /* Close list table and submit button */ + $this->displayListFooter(); + } + + public function displayListContent($token = NULL) + { + /* Display results in a table + * + * align : determine value alignment + * prefix : displayed before value + * suffix : displayed after value + * image : object image + * icon : icon determined by values + * active : allow to toggle status + */ + + global $currentIndex, $cookie; + $currency = new Currency(Configuration::get('PS_CURRENCY_DEFAULT')); + + $id_category = 1; // default categ + + $irow = 0; + if ($this->_list AND isset($this->fieldsDisplay['position'])) + { + $positions = array_map(create_function('$elem', 'return (int)($elem[\'position\']);'), $this->_list); + sort($positions); + } + if ($this->_list) + { + $isCms = false; + if (preg_match('/cms/Ui', $this->identifier)) + $isCms = true; + $keyToGet = 'id_'.($isCms ? 'cms_' : '').'category'.(in_array($this->identifier, array('id_category', 'id_cms_category')) ? '_parent' : ''); + foreach ($this->_list AS $tr) + { + $id = $tr[$this->identifier]; + echo 'identifier,$this->identifiersDnd) ? ' id="tr_'.(($id_category = (int)(Tools::getValue('id_'.($isCms ? 'cms_' : '').'category', '1'))) ? $id_category : '').'_'.$id.'_'.$tr['position'].'"' : '').($irow++ % 2 ? ' class="alt_row"' : '').' '.((isset($tr['color']) AND $this->colorOnBackground) ? 'style="background-color: '.$tr['color'].'"' : '').'> + '; + foreach ($this->fieldsDisplay AS $key => $params) + { + $tmp = explode('!', $key); + $key = isset($tmp[1]) ? $tmp[1] : $tmp[0]; + echo ' + '; + } + + if ($this->edit OR $this->delete OR ($this->view AND $this->view !== 'noActionColumn')) + { + echo ''; + } + echo ''; + } + } + } + + + protected function _displayEnableLink($token, $id, $value, $active, $id_category = NULL, $id_product = NULL) + { + global $currentIndex; + + echo ' + '.($value ? $this->l('Enabled') : $this->l('Disabled')).''; + } + + protected function _displayDuplicate($token = NULL, $id) + { + global $currentIndex; + + $_cacheLang['Duplicate'] = $this->l('Duplicate'); + $_cacheLang['Copy images too?'] = $this->l('Copy images too?', __CLASS__, TRUE, FALSE); + + $duplicate = $currentIndex.'&'.$this->identifier.'='.$id.'&duplicate'.$this->table; + + echo ' + + '.$_cacheLang['Duplicate'].''; + } + + protected function _displayViewLink($token = NULL, $id) + { + global $currentIndex; + + $_cacheLang['View'] = $this->l('View'); + + echo ' + + '.$_cacheLang['View'].''; + } + + protected function _displayEditLink($token = NULL, $id) + { + global $currentIndex; + + $_cacheLang['Edit'] = $this->l('Edit'); + + echo ' + + '; + } + + protected function _displayDeleteLink($token = NULL, $id) + { + global $currentIndex; + + $_cacheLang['Delete'] = $this->l('Delete'); + $_cacheLang['DeleteItem'] = $this->l('Delete item #', __CLASS__, TRUE, FALSE); + + echo ' + + '.$_cacheLang['Delete'].''; + } + + /** + * Close list table and submit button + */ + public function displayListFooter($token = NULL) + { + echo '
    + '; + + /* Determine current page number */ + $page = (int)(Tools::getValue('submitFilter'.$this->table)); + if (!$page) $page = 1; + if ($page > 1) + echo ' + +   '; + echo $this->l('Page').' '.$page.' / '.$totalPages; + if ($page < $totalPages) + echo ' + +  '; + echo ' | '.$this->l('Display').' + + / '.(int)($this->_listTotal).' '.$this->l('result(s)').' + + + + + + +
    '; + + /* Display column names and arrows for ordering (ASC, DESC) */ + if (array_key_exists($this->identifier,$this->identifiersDnd) AND $this->_orderBy == 'position') + { + echo ' + + + + '; + } + echo 'identifier,$this->identifiersDnd) ? ' id="'.(((int)(Tools::getValue($this->identifiersDnd[$this->identifier], 1))) ? substr($this->identifier,3,strlen($this->identifier)) : '').'"' : '' ).' class="table'.((array_key_exists($this->identifier,$this->identifiersDnd) AND ($this->_orderBy != 'position 'AND $this->_orderWay != 'DESC')) ? ' tableDnD' : '' ).'" cellpadding="0" cellspacing="0"> +
    '; + if ($this->delete) + echo ' '; + echo ' '.$params['title']; + if (!isset($params['orderby']) OR $params['orderby']) + { + // Cleaning links + if (Tools::getValue($this->table.'Orderby') && Tools::getValue($this->table.'Orderway')) + $currentIndex = preg_replace('/&'.$this->table.'Orderby=([a-z _]*)&'.$this->table.'Orderway=([a-z]*)/i', '', $currentIndex); + echo '
    + + '; + } + echo '
    '.$this->l('Actions').'
    '; + if ($this->delete) + echo ' --'; + echo ' --
    '.$this->l('No items found').'
    '; + if ($this->delete AND (!isset($this->_listSkipDelete) OR !in_array($id, $this->_listSkipDelete))) + echo ''; + echo 'noLink) OR !$this->noLink)) + echo ' onclick="document.location = \''.$currentIndex.'&'.$this->identifier.'='.$id.($this->view? '&view' : '&update').$this->table.'&token='.($token!=NULL ? $token : $this->token).'\'">'.(isset($params['prefix']) ? $params['prefix'] : ''); + else + echo '>'; + if (isset($params['active']) AND isset($tr[$key])) + $this->_displayEnableLink($token, $id, $tr[$key], $params['active'], Tools::getValue('id_category'), Tools::getValue('id_product')); + elseif (isset($params['activeVisu']) AND isset($tr[$key])) + echo ''.($tr[$key] ? $this->l('Enabled') : $this->l('Disabled')).''; + elseif (isset($params['position'])) + { + if ($this->_orderBy == 'position' AND $this->_orderWay != 'DESC') + { + echo ' + '.$this->l('Down').''; + + echo ' + '.$this->l('Up').''; } + else + echo (int)($tr[$key] + 1); + } + elseif (isset($params['image'])) + { + // item_id is the product id in a product image context, else it is the image id. + $item_id = isset($params['image_id']) ? $tr[$params['image_id']] : $id; + // If it's a product image + if (isset($tr['id_image'])) + { + $image = new Image((int)$tr['id_image']); + $path_to_image = _PS_IMG_DIR_.$params['image'].'/'.$image->getExistingImgPath().'.'.$this->imageType; + }else + $path_to_image = _PS_IMG_DIR_.$params['image'].'/'.$item_id.(isset($tr['id_image']) ? '-'.(int)($tr['id_image']) : '').'.'.$this->imageType; + + echo cacheImage($path_to_image, $this->table.'_mini_'.$item_id.'.'.$this->imageType, 45, $this->imageType); + } + elseif (isset($params['icon']) AND (isset($params['icon'][$tr[$key]]) OR isset($params['icon']['default']))) + echo ''.$tr[$key]).''; + elseif (isset($params['price'])) + echo Tools::displayPrice($tr[$key], (isset($params['currency']) ? Currency::getCurrencyInstance((int)($tr['id_currency'])) : $currency), false); + elseif (isset($params['float'])) + echo rtrim(rtrim($tr[$key], '0'), '.'); + elseif (isset($params['type']) AND $params['type'] == 'date') + echo Tools::displayDate($tr[$key], (int)$cookie->id_lang); + elseif (isset($params['type']) AND $params['type'] == 'datetime') + echo Tools::displayDate($tr[$key], (int)$cookie->id_lang, true); + elseif (isset($tr[$key])) + { + $echo = ($key == 'price' ? round($tr[$key], 2) : isset($params['maxlength']) ? Tools::substr($tr[$key], 0, $params['maxlength']).'...' : $tr[$key]); + echo isset($params['callback']) ? call_user_func_array(array($this->className, $params['callback']), array($echo, $tr)) : $echo; + } + else + echo '--'; + + echo (isset($params['suffix']) ? $params['suffix'] : ''). + ''; + if ($this->view) + $this->_displayViewLink($token, $id); + if ($this->edit) + $this->_displayEditLink($token, $id); + if ($this->delete AND (!isset($this->_listSkipDelete) OR !in_array($id, $this->_listSkipDelete))) + $this->_displayDeleteLink($token, $id); + if ($this->duplicate) + $this->_displayDuplicate($token, $id); + echo '
    '; + if ($this->delete) + echo '

    '; + echo ' + + + + + '; + if (isset($this->_includeTab) AND sizeof($this->_includeTab)) + echo '

    '; + } + + /** + * Options lists + */ + public function displayOptionsList() + { + global $currentIndex, $cookie, $tab; + + if (!isset($this->_fieldsOptions) OR !sizeof($this->_fieldsOptions)) + return false; + + $defaultLanguage = (int)Configuration::get('PS_LANG_DEFAULT'); + $this->_languages = Language::getLanguages(false); + $tab = Tab::getTab((int)$cookie->id_lang, Tab::getIdFromClassName($tab)); + echo '

    '; + echo (isset($this->optionTitle) ? '

    '.$this->optionTitle.'

    ' : ''); + echo ' + +
    +
    '; + echo (isset($this->optionTitle) ? ' + ' + .$this->optionTitle.'' : ''); + foreach ($this->_fieldsOptions AS $key => $field) + { + $val = Tools::getValue($key, Configuration::get($key)); + if ($field['type'] != 'textLang') + if (!Validate::isCleanHtml($val)) + $val = Configuration::get($key); + + echo ' +
    '; + switch ($field['type']) + { + case 'select': + echo ''; + break; + case 'bool': + echo ' + + + + + '; + break; + case 'textLang': + foreach ($this->_languages as $language) + { + $val = Tools::getValue($key.'_'.$language['id_lang'], Configuration::get($key, $language['id_lang'])); + if (!Validate::isCleanHtml($val)) + $val = Configuration::get($key); + echo ' +
    + +
    '; + } + $this->displayFlags($this->_languages, $defaultLanguage, $key, $key); + echo '
    '; + break; + case 'textareaLang': + foreach ($this->_languages as $language) + { + $val = Configuration::get($key, $language['id_lang']); + echo ' +
    + +
    '; + } + $this->displayFlags($this->_languages, $defaultLanguage, $key, $key); + echo '
    '; + break; + case 'text': + default: + echo ''.(isset($field['suffix']) ? $field['suffix'] : ''); + } + + if (isset($field['required']) AND $field['required']) + echo ' *'; + + echo (isset($field['desc']) ? '

    '.$field['desc'].'

    ' : ''); + echo '
    '; + } + echo '
    + +
    +
    + +
    '; + } + + /** + * Load class object using identifier in $_GET (if possible) + * otherwise return an empty object, or die + * + * @param boolean $opt Return an empty object if load fail + * @return object + */ + protected function loadObject($opt = false) + { + if ($id = (int)(Tools::getValue($this->identifier)) AND Validate::isUnsignedId($id)) + { + if (!$this->_object) + $this->_object = new $this->className($id); + if (Validate::isLoadedObject($this->_object)) + return $this->_object; + $this->_errors[] = Tools::displayError('Object cannot be loaded (not found)'); + } + elseif ($opt) + { + $this->_object = new $this->className(); + return $this->_object; + } + else + $this->_errors[] = Tools::displayError('Object cannot be loaded (identifier missing or invalid)'); + + $this->displayErrors(); + } + + /** + * Return field value if possible (both classical and multilingual fields) + * + * Case 1 : Return value if present in $_POST / $_GET + * Case 2 : Return object value + * + * @param object $obj Object + * @param string $key Field name + * @param integer $id_lang Language id (optional) + * @return string + */ + protected function getFieldValue($obj, $key, $id_lang = NULL) + { + if ($id_lang) + $defaultValue = ($obj->id AND isset($obj->{$key}[$id_lang])) ? $obj->{$key}[$id_lang] : ''; + else + $defaultValue = isset($obj->{$key}) ? $obj->{$key} : ''; + + return Tools::getValue($key.($id_lang ? '_'.$id_lang : ''), $defaultValue); + } + + /** + * Display form + * + * @global string $currentIndex Current URL in order to keep current Tab + */ + public function displayForm($firstCall = true) + { + global $cookie; + + $allowEmployeeFormLang = Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG') ? Configuration::get('PS_BO_ALLOW_EMPLOYEE_FORM_LANG') : 0; + if ($allowEmployeeFormLang && !$cookie->employee_form_lang) + $cookie->employee_form_lang = (int)(Configuration::get('PS_LANG_DEFAULT')); + $useLangFromCookie = false; + $this->_languages = Language::getLanguages(false); + if ($allowEmployeeFormLang) + foreach ($this->_languages AS $lang) + if ($cookie->employee_form_lang == $lang['id_lang']) + $useLangFromCookie = true; + if (!$useLangFromCookie) + $this->_defaultFormLanguage = (int)(Configuration::get('PS_LANG_DEFAULT')); + else + $this->_defaultFormLanguage = (int)($cookie->employee_form_lang); + + // Only if it is the first call to displayForm, otherwise it has already been defined + if ($firstCall) + { + echo ' + '; + } + } + + /** + * Display object details + * + * @global string $currentIndex Current URL in order to keep current Tab + */ + public function viewDetails() { global $currentIndex; } + + /** + * Called before deletion + * + * @param object $object Object + * @return boolean + */ + protected function beforeDelete($object) { return true; } + + /** + * Called before deletion + * + * @param object $object Object + * @return boolean + */ + protected function afterDelete($object, $oldId) { return true; } + + protected function afterAdd($object) { return true; } + + protected function afterUpdate($object) { return true; } + + /** + * Check rights to view the current tab + * + * @return boolean + */ + + protected function afterImageUpload() { + return true; + } + + /** + * Check rights to view the current tab + * + * @return boolean + */ + + public function viewAccess($disable = false) + { + global $cookie; + + if ($disable) + return true; + + $this->tabAccess = Profile::getProfileAccess($cookie->profile, $this->id); + + if ($this->tabAccess['view'] === '1') + return true; + return false; + } + + /** + * Check for security token + */ + public function checkToken() + { + $token = Tools::getValue('token'); + return (!empty($token) AND $token === $this->token); + } + + /** + * Display flags in forms for translations + * + * @param array $languages All languages available + * @param integer $defaultLanguage Default language id + * @param string $ids Multilingual div ids in form + * @param string $id Current div id] + * #param boolean $return define the return way : false for a display, true for a return + */ + public function displayFlags($languages, $defaultLanguage, $ids, $id, $return = false) + { + if (sizeof($languages) == 1) + return false; + $output = ' +
    + +
    +
    + '.$this->l('Choose language:').'

    '; + foreach ($languages as $language) + $output .= ''.$language['name'].' '; + $output .= '
    '; + + if ($return) + return $output; + echo $output; + } + + protected function filterToField($key, $filter) + { + foreach ($this->fieldsDisplay AS $field) + if (array_key_exists('filter_key', $field) AND $field['filter_key'] == $key) + return $field; + if (array_key_exists($filter, $this->fieldsDisplay)) + return $this->fieldsDisplay[$filter]; + return false; + } + + protected function warnDomainName() + { + if ($_SERVER['HTTP_HOST'] != Configuration::get('PS_SHOP_DOMAIN') AND $_SERVER['HTTP_HOST'] != Configuration::get('PS_SHOP_DOMAIN_SSL')) + $this->displayWarning($this->l('Your are currently connected with the following domain name:').' '.$_SERVER['HTTP_HOST'].'
    '. + $this->l('This one is different from the main shop domain name set in "Preferences > SEO & URLs":').' '.Configuration::get('PS_SHOP_DOMAIN').'
    + '. + $this->l('Click here if you want to modify the main shop domain name').''); + } +} + diff --git a/modules/autoupgrade/AdminSelfUpgrade.php b/modules/autoupgrade/AdminSelfUpgrade.php new file mode 100644 index 000000000..fae3739bf --- /dev/null +++ b/modules/autoupgrade/AdminSelfUpgrade.php @@ -0,0 +1,2116 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ +if(!defined('_PS_ADMIN_DIR_')) + define('_PS_ADMIN_DIR_',PS_ADMIN_DIR); +if(!defined('_PS_USE_SQL_SLAVE_')) + define('_PS_USE_SQL_SLAVE_',false); + +if(empty($_POST['action']) OR !in_array($_POST['action'],array('upgradeDb'))) +{ + if(!defined('PS_ORDER_PROCESS_STANDARD')) + define('PS_ORDER_PROCESS_STANDARD',true); + if(!defined('PS_ORDER_PROCESS_OPC')) + define('PS_ORDER_PROCESS_OPC',true); + require_once(dirname(__FILE__).'/ConfigurationTest.php'); + eval('class ConfigurationTest extends ConfigurationTestCore{}'); +} + require_once(dirname(__FILE__).'/AdminSelfTab.php'); + require_once(dirname(__FILE__).'/SelfModule.php'); + if(file_exists(_PS_ROOT_DIR_.'/classes/Upgrader.php')) + { + require_once(_PS_ROOT_DIR_.'/classes/Upgrader.php'); + } + else + require_once(dirname(__FILE__).'/Upgrader.php'); + if(!class_exists('Upgrader',false)) + if(file_exists(_PS_ROOT_DIR_.'/override/classes/Upgrader.php')) + require_once(_PS_ROOT_DIR_.'/override/classes/Upgrader.php'); + else + eval('class Upgrader extends UpgraderCore{}'); + +require_once(dirname(__FILE__).'/Tools14.php'); +if(!class_exists('Tools',false)) + eval('class Tools extends Tools14{}'); +class AdminSelfUpgrade extends AdminSelfTab +{ + public $ajax = false; + public $nextResponseType = 'json'; // json, xml + public $next = 'N/A'; + + /** + * set to false if the current step is a loop + * + * @var boolean + */ + public $stepDone = true; + public $status = true; + public $error ='0'; + public $nextDesc = '.'; + public $nextParams = array(); + public $nextQuickInfo = array(); + public $currentParams = array(); + /** + * @var array theses values will be automatically added in "nextParams" + * if their properties exists + */ + public $ajaxParams = array( + // autoupgrade options + 'dontBackupImages', + 'keepDefaultTheme', + 'keepTrad', + 'manualMode', + 'desactivateCustomModule', + + // + 'backupDbFilename', + 'backupFilesFilename', + + + ); + public $autoupgradePath = null; + /** + * autoupgradeDir + * + * @var string directory relative to admin dir + */ + public $autoupgradeDir = 'autoupgrade'; + public $latestRootDir = ''; + public $prodRootDir = ''; + public $adminDir = ''; + public $rootWritable = false; + public $svnDir = 'svn'; + public $destDownloadFilename = 'prestashop.zip'; + public $toUpgradeFileList = array(); + public $backupFileList = array(); + public $sampleFileList = array(); + private $backupIgnoreFiles = array(); + private $backupIgnoreAbsoluteFiles = array(); + private $excludeFilesFromUpgrade = array(); + private $excludeAbsoluteFilesFromUpgrade = array(); + + private $backupFilesFilename = ''; + private $backupDbFilename = ''; + +/** + * int loopBackupFiles : if your server has a low memory size, lower this value + * @TODO remove the static, add a const, and use it like this : min(AdminUpgrade::DEFAULT_LOOP_ADD_FILE_TO_ZIP,Configuration::get('LOOP_ADD_FILE_TO_ZIP'); + */ + public static $loopBackupFiles = 1000; +/** + * int loopUpgradeFiles : if your server has a low memory size, lower this value + */ + public static $loopUpgradeFiles = 1000; +/** + * intloopRemoveSamples : if your server has a low memory size, lower this value + */ + public static $loopRemoveSamples = 1000; + +// public static $skipAction = array('unzip'=>'listSampleFiles'); + public static $skipAction; + public $useSvn; + + protected $_includeContainer = false; + + public function encrypt($string) + { + return md5(_COOKIE_KEY_.$string); + } + public function checkToken() + { + // simple checkToken in ajax-mode, to be free of Cookie class (and no Tools::encrypt() too ) + if ($this->ajax) + return ($_COOKIE['autoupgrade'] == $this->encrypt($_COOKIE['id_employee'])); + else + return parent::checkToken(); + } + + /** + * create cookies id_employee, id_tab and autoupgrade (token) + */ + public function createCustomToken() + { + // ajax-mode for autoupgrade, we can't use the classic authentication + // so, we'll create a cookie in admin dir, based on cookie key + global $cookie; + $id_employee = $cookie->id_employee; + + $cookiePath = __PS_BASE_URI__.str_replace($this->prodRootDir,'',trim($this->adminDir,'/')); + setcookie('id_employee', $id_employee, time()+3600, $cookiePath); + setcookie('id_tab', $this->id, time()+3600, $cookiePath); + setcookie('autoupgrade', $this->encrypt($id_employee), time()+3600, $cookiePath); + return false; + } + + + + public function viewAccess($disable = false){ + if ($this->ajax) + return true; + else + { + // simple access : we'll allow only admin + global $cookie; + if ($cookie->profile == 1) + return true; + } + return false; + } + + public function __construct() + { + @set_time_limit(0); + @ini_set('max_execution_time', '0'); + + $this->init(); + // retrocompatibility when used in module : Tab can't work, + // but we saved the tab id in a cookie. + if(class_exists('Tab',false)) + parent::__construct(); + else + $this->id = $_COOKIE['id_tab']; + } + + protected function l($string, $class = 'AdminTab', $addslashes = FALSE, $htmlentities = TRUE) + { + if(version_compare(_PS_VERSION_,'1.4.3.0','<')) + { + $currentClass = get_class($this); + // need to be called in order to populate $classInModule + return SelfModule::findTranslation('autoupgrade', $string, 'AdminSelfUpgrade'); + } + else + return parent::l($string, $class, $addslashes, $htmlentities); + } + + /** + * _setFields function to set fields (only when we need it). + * + * @return void + */ + private function _setFields() + { + $this->_fieldsAutoUpgrade['PS_AUTOUP_DONT_SAVE_IMAGES'] = array( + 'title' => $this->l('Don\'t save images'), 'cast' => 'intval', 'validation' => 'isBool', + 'type' => 'bool', 'desc'=>$this->l('You can exclude the image directory from backup if you already saved it by another method (not recommended)'), + ); + + $this->_fieldsAutoUpgrade['PS_AUTOUP_KEEP_DEFAULT_THEME'] = array( + 'title' => $this->l('Keep theme "prestashop"'), 'cast' => 'intval', 'validation' => 'isBool', + 'type' => 'bool', 'desc'=>$this->l('If you have customized PrestaShop default theme, you can protect it from upgrade (not recommended)'), + ); + + $this->_fieldsAutoUpgrade['PS_AUTOUP_KEEP_TRAD'] = array( + 'title' => $this->l('Keep translations'), 'cast' => 'intval', 'validation' => 'isBool', + 'type' => 'bool', 'desc'=>$this->l('If set too yes, you will keep all your translations'), + ); + + $this->_fieldsAutoUpgrade['PS_AUTOUP_CUSTOM_MOD_DESACT'] = array( + 'title' => $this->l('Desactivate custom modules'), 'cast' => 'intval', 'validation' => 'isBool', + 'type' => 'bool', 'desc'=>$this->l('If you don\'t desactivate your modules, you can have some compatibility problem and the Modules page might not load correctly.'), + ); + // allow manual mode only for dev + if (defined('_PS_MODE_DEV_') AND _PS_MODE_DEV_) + $this->_fieldsAutoUpgrade['PS_AUTOUP_MANUAL_MODE'] = array( + 'title' => $this->l('Manual mode'), 'cast' => 'intval', 'validation' => 'isBool', + 'type' => 'bool', 'desc'=>$this->l('Check this if you want to stop after each step'), + ); + + if (defined('_PS_ALLOW_UPGRADE_UNSTABLE_') AND _PS_ALLOW_UPGRADE_UNSTABLE_ AND function_exists('svn_checkout')) + { + $this->_fieldsAutoUpgrade['PS_AUTOUP_USE_SVN'] = array( + 'title' => $this->l('Use Subversion'), 'cast' => 'intval', 'validation' => 'isBool', + 'type' => 'bool', 'desc' => $this->l('check this if you want to use unstable svn instead of official release'), + ); + } + } + + public function configOk() + { + $allowed = (ConfigurationTest::test_fopen() && $this->rootWritable); + $allowed &= !Configuration::get('PS_SHOP_ENABLE'); + + return $allowed; + } + /** + * isUpgradeAllowed checks if all server configuration is valid for upgrade + * + * @return void + */ + public function isUpgradeAllowed() + { + $allowed = (ConfigurationTest::test_fopen() && $this->rootWritable); + + if (!defined('_PS_MODE_DEV_') OR !_PS_MODE_DEV_) + $allowed &= $this->upgrader->autoupgrade; + + return $allowed; + } + + /** + * init to build informations we need + * + * @return void + */ + public function init() + { + // For later use, let's set up prodRootDir and adminDir + // This way it will be easier to upgrade a different path if needed + $this->prodRootDir = _PS_ROOT_DIR_; + $this->adminDir = _PS_ADMIN_DIR_; + + // test writable recursively + if(version_compare(_PS_VERSION_,'1.4.4.0','<')) + { + require_once('ConfigurationTest.php'); + if(!class_exists('ConfigurationTest', false) AND class_exists('ConfigurationTestCore')) + eval('class ConfigurationTest extends ConfigurationTestCore{}'); + } + if (ConfigurationTest::test_dir($this->prodRootDir,true)) + $this->rootWritable = true; + + // checkPSVersion will be not + $this->upgrader = new Upgrader(true); + $this->upgrader->checkPSVersion(); + if (version_compare(_PS_VERSION_,'1.4.4.0','<') OR $this->upgrader->need_standalone) + $this->standalone = true; + else + $this->standalone = false; + // If you have defined this somewhere, you know what you do + if (defined('_PS_ALLOW_UPGRADE_UNSTABLE_') AND _PS_ALLOW_UPGRADE_UNSTABLE_ AND function_exists('svn_checkout')) + { + if(version_compare(_PS_VERSION_,'1.4.4.0','<') OR class_exists('Configuration',false)) + $this->useSvn = Configuration::get('PS_AUTOUP_USE_SVN'); + } + else + $this->useSvn = false; + + // from $_POST or $_GET + $this->action = empty($_REQUEST['action'])?null:$_REQUEST['action']; + $this->currentParams = empty($_REQUEST['params'])?null:$_REQUEST['params']; + + // If not exists in this sessions, "create" + // session handling : from current to next params + if (isset($this->currentParams['removeList'])) + $this->nextParams['removeList'] = $this->currentParams['removeList']; + + if (isset($this->currentParams['filesToUpgrade'])) + $this->nextParams['filesToUpgrade'] = $this->currentParams['filesToUpgrade']; + + if (class_exists('Configuration',false)) + { + $time = time(); + $this->backupDbFilename = Configuration::get('UPGRADER_BACKUPDB_FILENAME'); + if(!file_exists($this->backupDbFilename)) + { + // If not exists, the filename is generated by Backup.php + $this->backupDbFilename = ''; + Configuration::updateValue('UPGRADER_BACKUPDB_FILENAME', $this->backupDbFilename); + } + + $this->backupFilesFilename = Configuration::get('UPGRADER_BACKUPFILES_FILENAME'); + if(!file_exists($this->backupFilesFilename)) + { + $this->backupFilesFilename = $this->autoupgradePath . DIRECTORY_SEPARATOR . 'backupfile-'.date('Y-m-d').'-'.$time.'.zip'; + Configuration::updateValue('UPGRADER_BACKUPFILES_FILENAME', $this->backupFilesFilename); + } + } + else{ + // backupDbFilename should never be empty + $this->backupDbFilename = $this->currentParams['backupDbFilename']; + // backupFilesFilename should never etc. + $this->backupFilesFilename = $this->currentParams['backupFilesFilename']; + } + $this->autoupgradePath = $this->adminDir.DIRECTORY_SEPARATOR.$this->autoupgradeDir; + + if (!file_exists($this->autoupgradePath)) + if (!@mkdir($this->autoupgradePath,0777)) + $this->_errors[] = Tools::displayError(sprintf($this->l('unable to create directory %s'),$this->autoupgradePath)); + + $latest = $this->autoupgradePath.DIRECTORY_SEPARATOR.'latest'; + if (!file_exists($latest)) + if (!@mkdir($latest,0777)) + $this->_errors[] = Tools::displayError(sprintf($this->l('unable to create directory %s'),$latest)); + + $this->latestRootDir = $latest.DIRECTORY_SEPARATOR.'prestashop'; + $this->adminDir = str_replace($this->prodRootDir,'',$this->adminDir); + // @TODO future option + // $this->testRootDir = $this->autoupgradePath.DIRECTORY_SEPARATOR.'test'; + + /* option */ + if (class_exists('Configuration',false)) + { + $this->dontBackupImages = Configuration::get('PS_AUTOUP_DONT_SAVE_IMAGES'); + $this->keepDefaultTheme = Configuration::get('PS_AUTOUP_KEEP_DEFAULT_THEME'); + $this->keepTrad = Configuration::get('PS_AUTOUP_KEEP_TRAD'); + $this->manualMode = Configuration::get('PS_AUTOUP_MANUAL_MODE'); + $this->desactivateCustomModule = Configuration::get('PS_AUTOUP_CUSTOM_MOD_DESACT'); + } + else + { + $this->dontBackupImages = $this->currentParams['dontBackupImages']; + $this->keepDefaultTheme = $this->currentParams['keepDefaultTheme']; + $this->keepTrad = $this->currentParams['keepTrad']; + $this->manualMode = $this->currentParams['manualMode']; + $this->desactivateCustomModule = $this->current['desactivateCustomModule']; + } + // We can add any file or directory in the exclude dir : theses files will be not removed or overwritten + // @TODO cache should be ignored recursively, but we have to reconstruct it after upgrade + // - compiled from smarty + // - .svn + $this->backupIgnoreAbsoluteFiles[] = "/tools/smarty_v2/compile"; + $this->backupIgnoreAbsoluteFiles[] = "/tools/smarty_v2/cache"; + $this->backupIgnoreAbsoluteFiles[] = "/tools/smarty/compile"; + $this->backupIgnoreAbsoluteFiles[] = "/tools/smarty/cache"; + + $this->excludeFilesFromUpgrade[] = '.'; + $this->excludeFilesFromUpgrade[] = '..'; + $this->excludeFilesFromUpgrade[] = '.svn'; + $this->excludeFilesFromUpgrade[] = 'install'; + $this->excludeFilesFromUpgrade[] = 'settings.inc.php'; + $this->excludeFilesFromUpgrade[] = 'autoupgrade'; + $this->backupIgnoreFiles[] = '.'; + $this->backupIgnoreFiles[] = '..'; + $this->backupIgnoreFiles[] = '.svn'; + $this->backupIgnoreFiles[] = 'autoupgrade'; + + if ($this->dontBackupImages) + $this->backupIgnoreAbsoluteFiles[] = "/img"; + + + if ($this->keepDefaultTheme) + $this->excludeAbsoluteFilesFromUpgrade[] = "/themes/prestashop"; + + if ($this->keepTrad) + $this->excludeFilesFromUpgrade[] = "translations"; + } + + /** + * getFilePath return the path to the zipfile containing prestashop. + * + * @return void + */ + private function getFilePath() + { + return $this->autoupgradePath.DIRECTORY_SEPARATOR.$this->destDownloadFilename; + } + + public function postProcess() + { + $this->_setFields(); + + if (!empty($_POST)) + $this->_postConfig($this->_fieldsAutoUpgrade); + } + + public function ajaxProcessUpgradeComplete() + { + $this->nextDesc = $this->l('Upgrade process done. Congratulations ! You can now reactive your shop.'); + $this->next = ''; + } + + public function ajaxProcessUpgradeNow() + { + $this->nextDesc = $this->l('Starting upgrade ...'); + + if ($this->useSvn) + { + $this->next = 'svnCheckout'; + $this->nextDesc = $this->l('switching to svn checkout (useSvn set to true)'); + } + else + { + $this->next = 'download'; + $this->nextDesc = $this->l('Shop desactivated. Now downloading (this can takes some times )...'); + } + } + + public function ajaxProcessSvnExport() + { + if ($this->useSvn) + { + // first of all, delete the content of the latest root dir just in case + if (is_dir($this->latestRootDir)) + Tools::deleteDirectory($this->latestRootDir, false); + + if (!file_exists($this->latestRootDir)) + { + @mkdir($this->latestRootDir); + } + + if (svn_export($this->autoupgradePath . DIRECTORY_SEPARATOR . $this->svnDir, $this->latestRootDir)) + { + + // export means svn means install-dev and admin-dev. + // let's rename admin to the correct admin dir + // and rename install-dev to install + $adminDir = str_replace($this->prodRootDir, '', $this->adminDir); + rename($this->latestRootDir.DIRECTORY_SEPARATOR.'install-dev', $this->latestRootDir.DIRECTORY_SEPARATOR.'install'); + rename($this->latestRootDir.DIRECTORY_SEPARATOR.'admin-dev', $this->latestRootDir.DIRECTORY_SEPARATOR.$adminDir); + + // Unsetting to force listing + unset($this->nextParams['removeList']); + $this->next = "removeSamples"; + $this->nextDesc = $this->l('Export svn complete. removing sample files...'); + return true; + } + else + { + $this->next = 'error'; + $this->nextDesc = $this->l('error when svn export '); + } + } + } + + public function ajaxProcessUnzip(){ + if(version_compare(_PS_VERSION_,'1.4.4.0','<') + AND !class_exists('Tools',false) + ) + require_once('Tools.php'); + + $filepath = $this->getFilePath(); + $destExtract = $this->autoupgradePath.DIRECTORY_SEPARATOR.'latest'; + if (file_exists($destExtract)) + Tools::deletedirectory($destExtract); + + if (Tools::ZipExtract($filepath,$destExtract)) + { + $adminDir = str_replace($this->prodRootDir, '', $this->adminDir); + rename($this->latestRootDir.DIRECTORY_SEPARATOR.'admin', $this->latestRootDir.DIRECTORY_SEPARATOR.$adminDir); + // Unsetting to force listing + unset($this->nextParams['removeList']); + $this->next = "removeSamples"; + $this->nextDesc = $this->l('Extract complete. removing sample files...'); + return true; + } + else{ + $this->next = "error"; + $this->nextDesc = sprintf($this->l('unable to extract %1$s into %2$s ...'),$filepath,$destExtract); + return true; + } + } + + + /** + * _listSampleFiles will make a recursive call to scandir() function + * and list all file which match to the $fileext suffixe (this can be an extension or whole filename) + * + * @TODO maybe $regex instead of $fileext ? + * @param string $dir directory to look in + * @param string $fileext suffixe filename + * @return void + */ + private function _listSampleFiles($dir, $fileext = '.jpg'){ + $res = true; + $dir = rtrim($dir,'/').DIRECTORY_SEPARATOR; + + $toDel = scandir($dir); + // copied (and kind of) adapted from AdminImages.php + foreach ($toDel AS $file) + { + if ($file!='.' AND $file != '..' AND $file != '.svn') + { + + if (preg_match('#'.preg_quote($fileext,'#').'$#i',$file)) + { + $this->sampleFileList[] = $dir.$file; + } + else if (is_dir($dir.$file)) + { + $res &= $this->_listSampleFiles($dir.$file); + } + } + } + return $res; + } + + public function _listBackupFiles($dir) + { + $allFiles = scandir($dir); + foreach ($allFiles as $file) + { + $fullPath = $dir.DIRECTORY_SEPARATOR.$file; + + if (!$this->_skipFile($file, $fullPath,'backup')) + { + if (is_dir($fullPath)) + $this->_listBackupFiles($fullPath); + else + $this->backupFileList[] = $fullPath; + } + else + $this->backupIgnoreFiles[] = $fullPath; + + } + } + + public function _listFilesToUpgrade($dir) + { + $allFiles = scandir($dir); + foreach ($allFiles as $file) + { + $fullPath = $dir.DIRECTORY_SEPARATOR.$file; + + if (!$this->_skipFile($file, $fullPath, "upgrade")) + { + if (is_dir($fullPath)) + { + // if is_dir, we will create it :)e it :) + $this->toUpgradeFileList[] = $fullPath; + if (strpos($dir.DIRECTORY_SEPARATOR.$file, 'install') === false) + { + $this->_listFilesToUpgrade($fullPath); + } + } + else + $this->toUpgradeFileList[] = $fullPath; + } + } + + $this->nextParams['filesToUpgrade'] = $this->toUpgradeFileList; + } + + + public function ajaxProcessUpgradeFiles(){ + // @TODO : + $this->nextParams = $this->currentParams; + if (!isset($this->nextParams['filesToUpgrade'])) + $this->_listFilesToUpgrade($this->latestRootDir); + + // later we could choose between _PS_ROOT_DIR_ or _PS_TEST_DIR_ + $this->destUpgradePath = $this->prodRootDir; + + // upgrade files one by one like for the backup + // with a 1000 loop because it's funny + // @TODO : + // foreach files in latest, copy + $this->next = 'upgradeFiles'; + if (!is_array($this->nextParams['filesToUpgrade'])) + { + error($this->nextParams); + $this->next = 'error'; + $this->nextDesc = $this->l('filesToUpgrade is not an array'); + $this->nextQuickInfo[] = $this->l('filesToUpgrade is not an array'); + return false; + } + + // @TODO : does not upgrade files in modules, translations if they have not a correct md5 (or crc32, or whatever) from previous version + for ($i=0;$inextParams['filesToUpgrade'])<=0) + { + $this->next = 'upgradeDb'; + $this->nextDesc = $this->l('All files upgraded. Now upgrading database'); + $this->nextResponseType = 'xml'; + break; + } + + //$file = array_shift($this->nextParams['filesToUpgrade']); + $file = array_shift($this->nextParams['filesToUpgrade']); + if (!$this->upgradeThisFile($file)) + { + // put the file back to the begin of the list + $totalFiles = array_unshift($this->nextParams['filesToUpgrade'],$file); + $this->next = 'error'; + $this->nextQuickInfo[] = sprintf($this->l('error when trying to upgrade %s'),$file); + break; + } + else{ + // @TODO : maybe put several files at the same times ? + $this->nextDesc = sprintf($this->l('%2$s files left to upgrade.'),$file,sizeof($this->nextParams['filesToUpgrade'])); + } + } + } + + /** + * _modelDoUpgrade prepare the call to doUpgrade.php file (like model.php) + * + * @return void + */ + public function _modelDoUpgrade() + { + // a. set logger + // it will be used later + global $logger; + $logger = new FileLogger(); + if (function_exists('date_default_timezone_set')) + date_default_timezone_set('Europe/Paris'); + // use autoupgrade as log dir + $logger->setFilename($this->latestRootDir.'/'.date('Ymd').'_autoupgrade.log'); + + // init env. + @set_time_limit(0); + @ini_set('max_execution_time', '0'); + // setting the memory limit to 128M only if current is lower + $memory_limit = ini_get('memory_limit'); + if (substr($memory_limit,-1) != 'G' + AND ((substr($memory_limit,-1) == 'M' AND substr($memory_limit,0,-1) < 128) + OR is_numeric($memory_limit) AND (intval($memory_limit) < 131072)) + ){ + @ini_set('memory_limit','128M'); + } + + /* Redefine REQUEST_URI if empty (on some webservers...) */ + if (!isset($_SERVER['REQUEST_URI']) || $_SERVER['REQUEST_URI'] == '') + $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME']; + + if ($tmp = strpos($_SERVER['REQUEST_URI'], '?')) + $_SERVER['REQUEST_URI'] = substr($_SERVER['REQUEST_URI'], 0, $tmp); + + $_SERVER['REQUEST_URI'] = str_replace('//', '/', $_SERVER['REQUEST_URI']); + + /////////////////////// + // Copy from model.php + /////////////////////// + $upgrader = $this->upgrader; + $upgrader->checkPSVersion(); + + define('INSTALL_VERSION', $upgrader->version_num); + // now the install dir to use is in a subdirectory of the admin dir + define('INSTALL_PATH', realpath($this->latestRootDir.DIRECTORY_SEPARATOR.'install')); + + define('PS_INSTALLATION_IN_PROGRESS', true); + // Note : we don't need ToolsInstall.php + // include_once(INSTALL_PATH.'/classes/ToolsInstall.php'); + + define('SETTINGS_FILE', $this->prodRootDir . '/config/settings.inc.php'); + define('DEFINES_FILE', $this->prodRootDir .'/config/defines.inc.php'); + define('INSTALLER__PS_BASE_URI', substr($_SERVER['REQUEST_URI'], 0, -1 * (strlen($_SERVER['REQUEST_URI']) - strrpos($_SERVER['REQUEST_URI'], '/')) - strlen(substr(dirname($_SERVER['REQUEST_URI']), strrpos(dirname($_SERVER['REQUEST_URI']), '/')+1)))); + + // Note : INSTALLER__PS_BASE_URI_ABSOLUTE is not used for upgrade + // define('INSTALLER__PS_BASE_URI_ABSOLUTE', 'http://'.ToolsInstall::getHttpHost(false, true).INSTALLER__PS_BASE_URI); + + // XML Header + header('Content-Type: text/xml'); + require_once(INSTALL_PATH.DIRECTORY_SEPARATOR.'xml'.DIRECTORY_SEPARATOR.'doUpgrade.php'); + + ////////////////////////////// + // End of copy from model.php + ////////////////////////////// + + } + + public function ajaxProcessUpgradeDb(){ + // @TODO : 1/2/3 have to be done at the beginning !!!!!!!!!!!!!!!!!!!!!! + + // use something like actual in install-dev + // Notice : xml used here ... + + // 1) confirm version is correct(DB) + // install/model.php?method=getVersionFromDb&language=0 + // later + + // 2) confirm config is correct (r/w rights) + // install/model.php?method=checkConfig&firsttime=0 + // later + + // 3) save current activated modules in nextParams, or don't desactivate them ? + // @TODO + // 4) upgrade + // install/model.php?_=1309193641470&method=doUpgrade&customModule=desactivate + if (!empty($this->currentParams['desactivateCustomModule'])) + $_GET['customModule'] = 'desactivate'; + + if (!$this->_modelDoUpgrade()) + { + $this->next = 'error'; + $this->nextDesc = $this->l('error during upgrade Db'); + } + + // 5) compare activated modules and reactivate them + // @TODO + + } + + + /** + * upgradeThisFile + * + * @param mixed $file + * @return void + */ + public function upgradeThisFile($file) + { + // @TODO : later, we could handle customization with some kind of diff functions + // for now, just copy $file in str_replace($this->latestRootDir,_PS_ROOT_DIR_) + // $file comes from scandir function, no need to lost time and memory with file_exists() + if ($this->_skipFile('', $file,'upgrade')) + { + $this->nextQuickInfo[] = $this->l('%s ignored'); + return true; + } + else + { + $dest = str_replace($this->latestRootDir, $this->destUpgradePath,$file); + + if (is_dir($file)) + { + if (!file_exists($dest)) + { + if (@mkdir($dest)) + { + $this->nextQuickInfo[] = sprintf($this->l('created dir %2$s. %3$s files left to upgrade.'),$file, $dest, sizeof($this->nextParams['filesToUpgrade'])); + return true; + } + else + { + $this->next = 'error'; + $this->nextQuickInfo[] = sprintf($this->l('error when creating directory %s'),$dest); + $this->nextDesc = sprintf($this->l('error when creating directory %s'),$dest); + return false; + } + } + else + { + // directory already exists + return true; + } + } + else + { + if (copy($file,$dest)) + { + $this->nextQuickInfo[] = sprintf($this->l('copied %1$s in %2$s. %3$s files left to upgrade.'),$file, $dest, sizeof($this->nextParams['filesToUpgrade'])); + return true; + } + else + { + $this->next = 'error'; + $this->nextQuickInfo[] = sprintf($this->l('error for copy %1$s in %2$s'),$file,$dest); + $this->nextDesc = sprintf($this->l('error for copy %1$s in %2$s'),$file,$dest); + return false; + } + } + } + + } + + public function ajaxProcessRollback() + { + // 1st, need to analyse what was wrong. + + $this->nextParams = $this->currentParams; + if (!empty($this->backupFilesFilename) AND file_exists($this->backupFilesFilename)) + { + $this->next = 'restoreFiles'; + $this->status = 'ok'; + $this->nextDesc = $this->l('Files restored, now restoring database.'); + } + else + { + if (!empty($this->backupDbFilename) AND file_exists($this->backupDbFilename)) + { + $this->next = 'restoreDb'; + $this->status = 'ok'; + $this->nextDesc = $this->l('Database restored'); + } + else + { + // 2nd case if upgradeFiles made an error + // 3rd case if no upgrade has been done + // all theses cases are handled by the method ajaxRequestRollback() + $this->next = ''; // next is empty : nothing next :) + $this->status = 'ok'; + $this->nextDesc = $this->l('All your site is restored... '); + } + } + } + + /** + * ajaxProcessRestoreFiles restore the previously saved files. + * + * @return boolean true if succeed + */ + public function ajaxProcessRestoreFiles() + { + // @TODO : workaround max_execution_time / ajax batch unzip + if (!empty($this->backupFilesFilename) AND file_exists($this->backupFilesFilename)) + { + // cleanup current PS tree + $list = $this->_listArchivedFiles(); + if (count($list) > 0) + { + $this->_cleanUp($this->prodRootDir.'/'); + $this->nextQuickInfo[] = $this->l('root directory cleaned.'); + + $filepath = $this->backupFilesFilename; + $destExtract = $this->prodRootDir; + + if (self::ZipExtract($filepath, $destExtract)) + { + // once it's restored, delete the file ! + unlink($this->backupFilesFilename); + Configuration::updateValue('UPGRADER_BACKUPFILES_FILENAME', ''); + if (!empty($this->backupDbFilename)) + { + $this->nextDesc = $this->l('Files restored. No database backup found. Restoration done.'); + $this->next = ''; + } + else + { + $this->nextDesc = $this->l('Files restored.'); + $this->next = 'rollback'; + } + return true; + } + else + { + $this->next = "error"; + $this->nextDesc = sprintf($this->l('unable to extract $1$s into %2$s .'), $filepath, $destExtract); + return false; + } + } + } + else + { + $this->next = 'error'; + $this->nextDesc = $this->l('no known backup. nothing to restore.'); + return false; + } + } + + /** + * try to restore db backup file + * @return type : hey , what you expect ? well mysql errors array ..... + * @TODO : maybe this could be in the Backup class + */ + public function ajaxProcessRestoreDb() + { + $exts = explode('.', $this->backupDbFilename); + $fileext = $exts[count($exts)-1]; + $requests = array(); + $errors = array(); + $content = ''; + switch ($fileext) + { + case 'bz': + case 'bz2': + if ($fp = bzopen($this->backupDbFilename, 'r')) + { + while(!feof($fp)) + $content .= bzread($fp, filesize($this->backupDbFilename)); + bzclose($fp); + } + break; + case 'gz': + if ($fp = gzopen($this->backupDbFilename, 'r')) + { + while(!feof($fp)) + $content = gzread($fp, filesize($this->backupDbFilename)); + gzclose($fp); + } + break; + // default means sql ? + default : + if ($fp = fopen($this->backupDbFilename, 'r')) + { + while(!feof($fp)) + $content = fread($fp, filesize($this->backupDbFilename)); + fclose($fp); + } + } + + if ($content=='') + return false; + + // preg_match_all is better than preg_split (what is used in doUpgrade.php) + // This way we avoid extra blank lines + // option s (PCRE_DOTALL) added + // @TODO need to check if a ";" in description could block that (I suppose it can at the end of a line) + preg_match_all('/.*;[\n]\+/s', $content, $requests); + /* @TODO maybe improve regex pattern ... */ + $db = Db::getInstance(); + if (count($requests)>0) + { + foreach ($requests as $request) + if (!empty($request)) + if (!$db->Execute($request)) + $this->nextQuickInfo[] = $db->getMsgError(); + + // once it's restored, delete the file ! + unlink($this->backupDbFilename); + Configuration::updateValue('UPGRADER_BACKUPDB_FILENAME',''); + } + else + $this->nextQuickInfo[] = $this->l('Nothing to restore (no request found)'); + + $this->next = 'rollback'; + $this->nextDesc = 'Database restore done.'; + } + + public function ajaxProcessBackupDb() + { + if(!class_exists('ObjectModel',false)) + { + require_once(_PS_ROOT_DIR_.'/classes/ObjectModel.php'); + if(!class_exists('ObjectModel',false)) + eval('Class ObjectModel extends ObjectModelCore{}'); + } + + if(!class_exists('Language',false)) + { + require_once(_PS_ROOT_DIR_.'/classes/Language.php'); + if(!class_exists('Language',false)) + eval('Class Language extends LanguageCore{}'); + } + if(!class_exists('Validate',false)) + { + require_once(_PS_ROOT_DIR_.'/classes/Validate.php'); + if(!class_exists('Validate',false)) + eval('Class Validate extends ValidateCore{}'); + } + if(!class_exists('Db',false)) + { + require_once(_PS_ROOT_DIR_.'/classes/Db.php'); + if(!class_exists('Db',false)) + eval('abstract Class Db extends DbCore{}'); + } + if(!class_exists('MySQL',false)) + { + require_once(_PS_ROOT_DIR_.'/classes/MySQL.php'); + if(!class_exists('MySQL',false)) + eval('Class MySQL extends MySQLCore{}'); + } + if(!class_exists('Configuration',false)) + { + require_once(_PS_ROOT_DIR_.'/classes/Configuration.php'); + if(!class_exists('Configuration',false)) + eval('Class Configuration extends ConfigurationCore{}'); + } + if (!class_exists('Backup',false)) + { + if (!class_exists('BackupCore', false)) + require_once('Backup.php'); + if(file_exists(_PS_ROOT_DIR_.'/override/classes/Backup.php')) + require_once(_PS_ROOT_DIR_.'/override/classes/Backup.php'); + else + eval('Class Backup extends BackupCore{}'); + } + + if(!defined('_PS_MAGIC_QUOTES_GPC_')) + define('_PS_MAGIC_QUOTES_GPC_', get_magic_quotes_gpc()); + if(!defined('_PS_MYSQL_REAL_ESCAPE_STRING_')) + define('_PS_MYSQL_REAL_ESCAPE_STRING_', function_exists('mysql_real_escape_string')); + + $backup = new Backup(); + // for backup db, use autoupgrade directory + // @TODO : autoupgrade must not be static + $backup->setCustomBackupPath('autoupgrade'); + // maybe for big tables we should save them in more than one file ? + $res = $backup->add(); + if ($res) + { + $this->nextParams['backupDbFilename'] = $backup->id; + // We need to load configuration to use it ... + Configuration::loadConfiguration(); + Configuration::updateValue('UPGRADER_BACKUPDB_FILENAME', $backup->id); + + $this->next = 'upgradeFiles'; + $this->nextDesc = sprintf($this->l('Database backup done in %s. Now updating files'),$backup->id); + } + // if an error occur, we assume the file is not saved + } + + public function ajaxProcessBackupFiles() + { + $this->nextParams = $this->currentParams; + $this->stepDone = false; + ///////////////////// + + if (!isset($this->nextParams['filesForBackup'])) + { + $list = $this->_listBackupFiles($this->prodRootDir); + $this->nextQuickInfo[] = sprintf($this->l('%s Files to backup.'), sizeof($this->backupFileList)); + $this->nextParams['filesForBackup'] = $this->backupFileList; + + // delete old backup, create new + if (file_exists($this->backupFilesFilename)) + unlink($this->backupFilesFilename); + + $this->nextQuickInfo[] = sprintf($this->l('backup files initialized in %s'), $this->backupFilesFilename); + } + + ///////////////////// + $this->next = 'backupFiles'; + // @TODO : display % instead of this + $this->nextDesc = sprintf($this->l('Backup files in progress. %s files left'), sizeof($this->nextParams['filesForBackup'])); + if (is_array($this->nextParams['filesForBackup'])) + { + // @TODO later + // 1) calculate crc32 of next file + // 2) use the provided xml with crc32 calculated from previous versions ? + // or simply use the latest dir ? + //$current = crc32(file_get_contents($file)); + //$file = $this->nextParams['filesForBackup'][0]; + //$latestFile = str_replace(_PS_ROOT_DIR_,$this->latestRootDir,$file); + + // if (file_exists($latestFile)) + // $latest = crc32($latestFile); + // else + // $latest = ''; + + $zip = new ZipArchive(); + if ($zip->open($this->backupFilesFilename, ZIPARCHIVE::CREATE)) + { + $this->next = 'backupFiles'; + // @TODO all in one time will be probably too long + // 1000 ok during test, but 10 by 10 to be sure + $this->stepok = false; + // @TODO min(self::$loopBackupFiles, sizeof()) + for($i=0;$inextParams['filesForBackup'])<=0) + { + $this->stepok = true; + $this->status = 'ok'; + $this->next = 'backupDb'; + $this->nextDesc = $this->l('All files saved. Now backup Database'); + $this->nextQuickInfo[] = $this->l('all files have been added to archive.'); + break; + } + // filesForBackup already contains all the correct files + $file = array_shift($this->nextParams['filesForBackup']); + $archiveFilename = str_replace($this->prodRootDir,'',$file); + // @TODO : maybe put several files at the same times ? + if ($zip->addFile($file,$archiveFilename)) + $this->nextQuickInfo[] = sprintf($this->l('%1$s added to archive. %2$s left.'),$file, sizeof($this->nextParams['filesForBackup'])); + else + { + // if an error occur, it's more safe to delete the corrupted backup + if (file_exists($this->backupFilesFilename)) + unlink($this->backupFilesFilename); + $this->next = 'error'; + $this->nextDesc = sprintf($this->l('error when trying to add %1$s to archive %2$s.'),$file, $backupFilePath); + break; + } + } + $zip->close(); + return true; + } + else{ + $this->next = 'error'; + $this->nextDesc = $this->l('unable to open archive'); + return false; + } + } + else + { + $this->next = 'backupDb'; + $this->nextDesc = 'All files saved. Now backup Database'; + return true; + } + // 4) save for display. + } + + + private function _removeOneSample($removeList) + { + if (is_array($removeList) AND sizeof($removeList)>0) + { + if (file_exists($removeList[0]) AND unlink($removeList[0])) + { + $item = array_shift($removeList); + $this->next = 'removeSamples'; + $this->nextParams['removeList'] = $removeList; + $this->nextQuickInfo[] = sprintf($this->l('%1$s removed. %2$s items left'), $item, sizeof($removeList)); + } + else + { + $this->next = 'error'; + $this->nextParams['removeList'] = $removeList; + $this->nextQuickInfo[] = sprintf($this->l('error when removing %1$s, %2$s items left'), $removeList[0], sizeof($removeList)); + return false; + } + } + return true; + } + + public function ajaxProcessRemoveSamples(){ + $this->stepDone = false; + // @TODO : list exaustive list of files to remove : + // all images from img dir exept admin ? + // all images like logo, favicon, ?. + // all custom image from modules ? + // all custom image from theme ? + if (!isset($this->currentParams['removeList'])) + { + $this->_listSampleFiles($this->autoupgradePath.'/latest/prestashop/img', 'jpg'); + $this->_listSampleFiles($this->autoupgradePath.'/latest/prestashop/modules/editorial/', 'homepage_logo.jpg'); + // @TODO handle this bad thing + $this->nextQuickInfo[] = sprintf($this->l('Starting to remove %1$s sample files'), sizeof($this->sampleFileList)); + $this->nextParams['removeList'] = $this->sampleFileList; + } + + + // @TODO : removing @, adding if file_exists +// @unlink(_PS_ROOT_DIR_.'modules'.DIRECTORY_SEPARATOR.'editorial'.DIRECTORY_SEPARATOR.'editorial.xml'); +// @unlink(_PS_ROOT_DIR_.'modules'.DIRECTORY_SEPARATOR.'editorial'.DIRECTORY_SEPARATOR.'homepage_logo.jpg'); // homepage custom ? +// @unlink(_PS_ROOT_DIR_.'img'.DIRECTORY_SEPARATOR.'logo.jpg'); +// @unlink(_PS_ROOT_DIR_.'img'.DIRECTORY_SEPARATOR.'favicon.ico'); + $resRemove = true; + for($i=0;$inextParams['removeList']) <= 0 ) + { + $this->stepDone = true; + $this->next = 'backupFiles'; + $this->nextDesc = $this->l('All sample files removed. Now backup files.'); + // break the loop, all sample already removed + return true; + } + $resRemove &= $this->_removeOneSample($this->nextParams['removeList']); + if (!$resRemove) + break; + } + + return $resRemove; + } + + public function ajaxProcessSvnCheckout() + { + $this->nextParams = $this->currentParams; + if ($this->useSvn){ + $svnLink = 'http://svn.prestashop.com/trunk'; + $dest = $this->autoupgradePath . DIRECTORY_SEPARATOR . $this->svnDir; + + $svnStatus = svn_status($dest); + if (is_array($svnStatus)) + { + if (sizeof($svnStatus) == 0) + { + $this->next = 'svnExport'; + $this->nextDesc = sprintf($this->l('working copy already %s up-to-date. now exporting it into latest dir'),$dest); + } + else + { + // we assume no modification has been done + // @TODO a svn revert ? + if ($svnUpdate = svn_update($dest)) + { + $this->next = 'svnExport'; + $this->nextDesc = sprintf($this->l('SVN Update done for working copy %s . now exporting it into latest...'),$dest); + } + } + } + else + { + // no valid status found + // @TODO : is 0777 good idea ? + if (!file_exists($dest)) + if (!@mkdir($dest,0777)) + { + $this->next = 'error'; + $this->nextDesc = sprintf($this->l('unable to create directory %s'),$dest); + return false; + } + + if (svn_checkout($svnLink, $dest)) + { + $this->next = 'svnExport'; + $this->nextDesc = sprintf($this->l('SVN Checkout done from %s . now exporting it into latest...'),$svnLink); + return true; + } + else + { + $this->next = 'error'; + $this->nextDesc = $this->l('SVN Checkout error...'); + } + } + } + else + { + $this->next = 'error'; + $this->nextDesc = $this->l('not allowed to use svn'); + } + } + + public function ajaxProcessDownload() + { + if (@ini_get('allow_url_fopen')) + { + $res = $this->upgrader->downloadLast($this->autoupgradePath,$this->destDownloadFilename); + if ($res){ + $this->next = 'unzip'; + $this->nextDesc = $this->l('Download complete. Now extracting'); + } + else + { + $this->next = 'error'; + $this->nextDesc = $this->l('Error during download'); + } + } + else + { + // @TODO : ftp mode + $this->next = 'error'; + $this->nextDesc = sprintf($this->l('you need allow_url_fopen for automatic download. You can also manually upload it in %s'),$this->autoupgradePath.$this->destDownloadFilename); + } + } + + public function buildAjaxResult() + { + $return['error'] = $this->error; + $return['stepDone'] = $this->stepDone; + $return['next'] = $this->next; + $return['status'] = $this->next == 'error' ? 'error' : 'ok'; + $return['nextDesc'] = $this->nextDesc; + + foreach($this->ajaxParams as $v) + if(property_exists($this,$v)) + $this->nextParams[$v] = $this->$v; + + $return['nextParams'] = $this->nextParams; + + $return['nextParams']['typeResult'] = $this->nextResponseType; + + $return['nextQuickInfo'] = $this->nextQuickInfo; + return Tools14::jsonEncode($return); + } + + /** + * displayConf + * + * @return void + */ + public function displayConf() + { + if(!$this->standalone) + { + if (version_compare(_PS_VERSION_,'1.4.4.0','<') AND false) + $this->_errors[] = Tools::displayError('This class depends of several files modified in 1.4.4.0 version and should not be used in an older version'); + } + parent::displayConf(); + + } + + public function ajaxPreProcess() + { + if (!empty($_POST['responseType']) AND $_POST['responseType'] == 'json') + header('Content-Type: application/json'); + + if(!empty($_POST['action'])) + { + $action = $_POST['action']; + if (isset(self::$skipAction[strtolower($action)])) + { + $this->next = self::$skipAction[$action]; + $this->nextDesc = sprintf($this->l('action %s skipped'),$action); + $this->nextQuickInfo[] = sprintf($this->l('action %s skipped'),$action); + unset($_POST['action']); + } + else if (!method_exists(get_class($this), 'ajaxProcess'.$action)) + { + $this->nextDesc = sprintf($this->l('action "%1$s" not found'), $action); + $this->next = 'error'; + $this->error = '1'; + } + } + + if ($this->apacheModExists('mod_evasive')) + sleep(1); + } + /** + * apacheModExists return true if the apache module $name is loaded + * @TODO move this method in class Information (when it will exist) + * + * @param string $name module name + * @return boolean true if exists + */ + function apacheModExists($name) + { + if(function_exists('apache_get_modules')) + { + static $apacheModuleList = null; + + if (!is_array($apacheModuleList)) + $apacheModuleList = apache_get_modules(); + + // we need strpos (example can be evasive20 + foreach($apacheModuleList as $module) + if (strpos($name, $module)!==false) + return true; + } + else{ + // If apache_get_modules does not exists, + // one solution should be parsing httpd.conf, + // but we could simple parse phpinfo(INFO_MODULES) return string + ob_start(); + phpinfo(INFO_MODULES); + $phpinfo = ob_get_contents(); + ob_end_clean(); + if (strpos($phpinfo, $module) !== false) + return true; + } + + return false; + } + + private function _getJsErrorMsgs() + { + $INSTALL_VERSION = $this->upgrader->version_num; + $ret = ' +var txtError = new Array(); +txtError[0] = "'.$this->l('Required field').'"; +txtError[1] = "'.$this->l('Too long!').'"; +txtError[2] = "'.$this->l('Fields are different!').'"; +txtError[3] = "'.$this->l('This email adress is wrong!').'"; +txtError[4] = "'.$this->l('Impossible to send the email!').'"; +txtError[5] = "'.$this->l('Can\'t create settings file, if /config/settings.inc.php exists, please give the public write permissions to this file, else please create a file named settings.inc.php in config directory.').'"; +txtError[6] = "'.$this->l('Can\'t write settings file, please create a file named settings.inc.php in config directory.').'"; +txtError[7] = "'.$this->l('Impossible to upload the file!').'"; +txtError[8] = "'.$this->l('Data integrity is not valided. Hack attempt?').'"; +txtError[9] = "'.$this->l('Impossible to read the content of a MySQL content file.').'"; +txtError[10] = "'.$this->l('Impossible the access the a MySQL content file.').'"; +txtError[11] = "'.$this->l('Error while inserting data in the database:').'"; +txtError[12] = "'.$this->l('The password is incorrect (alphanumeric string at least 8 characters).').'"; +txtError[14] = "'.$this->l('A Prestashop database already exists, please drop it or change the prefix.').'"; +txtError[15] = "'.$this->l('This is not a valid file name.').'"; +txtError[16] = "'.$this->l('This is not a valid image file.').'"; +txtError[17] = "'.$this->l('Error while creating the /config/settings.inc.php file.').'"; +txtError[18] = "'.$this->l('Error:').'"; +txtError[19] = "'.$this->l('This PrestaShop database already exists. Please revalidate your authentication informations to the database.').'"; +txtError[22] = "'.$this->l('An error occurred while resizing the picture.').'"; +txtError[23] = "'.$this->l('Database connection is available!').'"; +txtError[24] = "'.$this->l('Database Server is available but database is not found').'"; +txtError[25] = "'.$this->l('Database Server is not found. Please verify the login, password and server fields.').'"; +txtError[26] = "'.$this->l('An error occurred while sending email, please verify your parameters.').'"; +txtError[37] = "'.$this->l('Impossible to write the image /img/logo.jpg. If this image already exists, please delete it.').'"; +txtError[38] = "'.$this->l('The uploaded file exceeds the upload_max_filesize directive in php.ini').'"; +txtError[39] = "'.$this->l('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form').'"; +txtError[40] = "'.$this->l('The uploaded file was only partially uploaded').'"; +txtError[41] = "'.$this->l('No file was uploaded.').'"; +txtError[42] = "'.$this->l('Missing a temporary folder').'"; +txtError[43] = "'.$this->l('Failed to write file to disk').'"; +txtError[44] = "'.$this->l('File upload stopped by extension').'"; +txtError[45] = "'.$this->l('Cannot convert your database\'s data to utf-8.').'"; +txtError[46] = "'.$this->l('Invalid shop name').'"; +txtError[47] = "'.$this->l('Your firstname contains some invalid characters').'"; +txtError[48] = "'.$this->l('Your lastname contains some invalid characters').'"; +txtError[49] = "'.$this->l('Your database server does not support the utf-8 charset.').'"; +txtError[50] = "'.$this->l('Your MySQL server doesn\'t support this engine, please use another one like MyISAM').'"; +txtError[51] = "'.$this->l('The file /img/logo.jpg is not writable, please CHMOD 755 this file or CHMOD 777').'"; +txtError[52] = "'.$this->l('Invalid catalog mode').'"; +txtError[999] = "'.$this->l('No error code available').'"; +//upgrader +txtError[27] = "'.$this->l('This installer is too old.').'"; +txtError[28] = "'.sprintf($this->l('You already have the %s version.'),$INSTALL_VERSION).'"; +txtError[29] = "'.$this->l('There is no older version. Did you delete or rename the config/settings.inc.php file?').'"; +txtError[30] = "'.$this->l('The config/settings.inc.php file was not found. Did you delete or rename this file?').'"; +txtError[31] = "'.$this->l('Can\'t find the sql upgrade files. Please verify that the /install/sql/upgrade folder is not empty)').'"; +txtError[32] = "'.$this->l('No upgrade is possible.').'"; +txtError[33] = "'.$this->l('Error while loading sql upgrade file.').'"; +txtError[34] = "'.$this->l('Error while inserting content into the database').'"; +txtError[35] = "'.$this->l('Unfortunately,').'"; +txtError[36] = "'.$this->l('SQL errors have occurred.').'"; +txtError[37] = "'.$this->l('The config/defines.inc.php file was not found. Where did you move it?').'";'; + return $ret; + } + + public function displayAjax(){ + echo $this->buildAjaxResult(); + } + + private function _displayRollbackForm() + { + echo '
    '.$this->l('Rollback').' +
    '; + if (empty($this->backupFilesFilename) AND empty($this->backupDbFilename)) + echo $this->l('No rollback available'); + else if (!empty($this->backupFilesFilename) OR !empty($this->backupDbFilename)) + { + echo '
    '; + } + if (!empty($this->backupFilesFilename) AND file_exists($this->backupFilesFilename)) + echo '
    restoreFiles '.sprintf($this->l('click to restore %s'),$this->backupFilesFilename).'

    '; + if (!empty($this->backupDbFilename) AND file_exists($this->backupDbFilename)) + echo '
    restoreDb '.sprintf($this->l('click to restore %s'), $this->backupDbFilename).'

    '; + + echo '
    '; + } + + private function _displayUpgraderForm() + { + global $cookie; + $pleaseUpdate = $this->upgrader->checkPSVersion(); + + echo '
    '; + echo ''.$this->l('Your current configuration').''; + echo ''.$this->l('Root directory').' : '.$this->prodRootDir.'

    '; + + if ($this->rootWritable) + $srcRootWritable = '../img/admin/enabled.gif'; + else + $srcRootWritable = '../img/admin/disabled.gif'; + echo ''.$this->l('Root directory status').' : '.' '.($this->rootWritable?$this->l('fully writable'):$this->l('not writable recursively')).'

    '; + + if ($this->upgrader->needUpgrade) + { + if ($this->upgrader->autoupgrade) + $srcAutoupgrade = '../img/admin/enabled.gif'; + else + $srcAutoupgrade = '../img/admin/disabled.gif'; + echo ''.$this->l('Autoupgrade allowed').' : '.' '.($this->upgrader->autoupgrade?$this->l('This release allow autoupgrade.'):$this->l('This release does not allow autoupgrade')).'.

    '; + } + + if (Configuration::get('PS_SHOP_ENABLE')) + { + $srcShopStatus = '../img/admin/disabled.gif'; + $label = $this->l('No'); + } + else + { + $srcShopStatus = '../img/admin/enabled.gif'; + $label = $this->l('Yes'); + } + echo ''.$this->l('Shop desactivated').' : '.''.$label.'

    '; + + $max_exec_time = ini_get('max_execution_time'); + if ($max_exec_time == 0) + $srcExecTime = '../img/admin/enabled.gif'; + else + $srcExecTime = '../img/admin/warning.gif'; + echo ''.$this->l('PHP time limit').' : '.''.($max_exec_time == 0?$this->l('disabled'):$max_exec_time.' '.$this->l('seconds')).'

    '; + + if ($this->rootWritable) + $srcRootWritable = '../img/admin/enabled.gif'; + else + $srcRootWritable = '../img/admin/disabled.gif'; + echo ''.$this->l('Root directory').' : '.' '.($this->rootWritable?$this->l('writable recursively'):$this->l('not writable recursively')).'.

    '; + + echo ''.$this->l('Modify your options').''; + echo '
    '; + + echo '
    '; + + echo '
    '.$this->l('Update').''; + + + echo '
    +

    '.sprintf($this->l('Your current prestashop version : %s '),_PS_VERSION_).'

    '; + echo '

    '.sprintf($this->l('Last version is %1$s (%2$s) '), $this->upgrader->version_name, $this->upgrader->version_num).'

    '; + + // @TODO : this should be checked when init() + if ($this->isUpgradeAllowed()) { + if ($pleaseUpdate) { + echo '
  • information '.$this->l('Latest Prestashop version available is:').' '.$pleaseUpdate['name'].'
  • '; + } +// echo ''; +// echo ''; + echo '
    '; + if ($this->upgrader->needUpgrade) + { + if($this->configOk()) + { + echo ''.$this->l('Upgrade PrestaShop now !').''; + echo ''.sprintf($this->l('PrestaShop will be downloaded from %s'), $this->upgrader->link).''; + } + else + echo $this->displayWarning('Your current configuration does not allow upgrade.'); + } + else + { + echo ''.$this->l('Your shop is already up to date.').' '; + } + echo '

    '.sprintf($this->l('last datetime check : %s '),date('Y-m-d H:i:s',Configuration::get('PS_LAST_VERSION_CHECK'))).' + '.$this->l('Please click to refresh').' + '; + + echo'
    + '; + + echo '
    '; + + + echo '
    '; + + if (defined('_PS_MODE_DEV_') AND _PS_MODE_DEV_) + { + echo '
    '.$this->l('Step').''; + echo '

    '.$this->l('Upgrade steps').'

    '; + echo '
    '; + echo 'download'; + echo 'unzip'; // unzip in autoupgrade/latest + echo 'removeSamples'; // remove samples (iWheel images) + echo 'backupFiles'; // backup files + echo 'backupDb'; + echo 'upgradeFiles'; + echo 'upgradeDb'; + echo '
    '; + + if (defined('_PS_ALLOW_UPGRADE_UNSTABLE_') AND _PS_ALLOW_UPGRADE_UNSTABLE_ ) + { + echo '

    Development tools

    '; + } + } + + echo '
    '; + echo'
    quick info
    '; + // for upgradeDb + echo '

    '; + echo '

    '; + } + else + echo '

    '.$this->l('Your current configuration does not allow upgrade.').'

    '; + + echo '
    '; +/* echo '
    + Error +
    no error yet
    +
    '; + * + */ + // information to keep will be in #infoStep + // temporary infoUpdate will be in #tmpInformation + echo ''; + } + + public function display() + { + if(isset($_GET['refreshCurrentVersion'])) + { + $upgrader = new Upgrader(); + $upgrader->checkPSVersion(true); + } + echo ''; + $this->displayWarning($this->l('This function is experimental. It\'s highly recommended to make a backup of your files and database before starting the upgrade process.')); + + global $currentIndex; + // update['name'] = version name + // update['num'] = only the version + // update['link'] = download link + // @TODO + + if ($this->isUpgradeAllowed()) + { + $this->createCustomToken(); + if ($this->useSvn) + echo '

    '.$this->l('Unstable upgrade').'

    +

    '.$this->l('Your current configuration indicate you want to upgrade your system from the unstable development branch, with no version number. If you upgrade, you will not be able to follow the official release process anymore').'.

    +
    '; + $this->_displayUpgraderForm(); + + echo '
    '; + $this->_displayRollbackForm(); + + echo '
    '; + $this->_displayForm('autoUpgradeOptions',$this->_fieldsAutoUpgrade,''.$this->l('Options').'', '','prefs'); + + if ($this->standalone) + { + echo ' + '; + } + echo ''; + } + else + { + echo '
    + '.$this->l('Update').''; + echo '

    '.$this->l('You currently don\'t need to use this feature.').'

    '; + echo '
    '; + } + + } + + private function _getJsInit() + { + global $currentIndex; + $js = ''; + $js .= ' +function ucFirst(str) { + if (str.length > 0) { + return str[0].toUpperCase() + str.substring(1); + } + else { + return str; + } +} + +function cleanInfo(){ + $("#infoStep").html("reset
    "); +} + +function updateInfoStep(msg){ + if (msg) + { + $("#infoStep").html(msg); + $("#infoStep").attr({ scrollTop: $("#infoStep").attr("scrollHeight") }); + } +} + +function addError(msg){ + if (msg) + $("#errorWindow").html(msg); +} + +function addQuickInfo(arrQuickInfo){ + if (arrQuickInfo) + { + $("#quickInfo").show(); + for(i=0;i"); + + $("#quickInfo").attr({ scrollTop: $("#quickInfo").attr("scrollHeight") }); + } +}'; + + if ($this->manualMode) + $js .= 'var manualMode = true;'; + else + $js .= 'var manualMode = false;'; + + $js .= ' +var firstTimeParams = '.$this->buildAjaxResult().'; +firstTimeParams = firstTimeParams.nextParams; + +$(document).ready(function(){ + $(".upgradestep").click(function(e) + { + e.preventDefault(); + // $.scrollTo("#options") + }); + + // more convenient to have that param for handling success and error + var requestParams; + + // set timeout to 5 minutes (download can be long)? + $.ajaxSetup({timeout:300000}); + + + // prepare available button here, without params ? + prepareNextButton("#upgradeNow",firstTimeParams); + prepareNextButton("#rollback",firstTimeParams); + prepareNextButton("#restoreDb",firstTimeParams); + prepareNextButton("#restoreFiles",firstTimeParams); + +}); + +/** + * parseXMLResult is used to handle the return value of the doUpgrade method + * + */ +function parseXMLResult(xmlRet) +{ + ret = $(xmlRet); + ret = ret.find("action")[0]; + if (ret.getAttribute("result") == "ok") + { + $("#dbResultCheck") + .addClass("ok") + .removeClass("fail") + .html("

    '.$this->l('upgrade complete. Please check your front-office (try to make an order, check theme)').'

    ") + .show("slow"); + $("#dbCreateResultCheck") + .hide("slow"); + + // difference with the original function + ret = {next:"upgradeComplete",nextParams:{typeResult:"json"},status:"ok"}; + + } + else + { + $("#dbResultCheck") + .addClass("fail") + .removeClass("ok") + .html(txtError[parseInt(ret.getAttribute("error"))]) + .show("slow"); + $("#dbCreateResultCheck") + .hide("slow"); + + // propose rollback if there is an error + if (confirm(txtError[parseInt(ret.getAttribute("error"))]+"\r\n\r\n'.$this->l('Do you want to rollback ?').'")) + ret = {next:"rollback",nextParams:{typeResult:"json"},status:"error"}; + } + + return ret +}; + +function afterUpgradeComplete() +{ + $("#pleaseWait").hide(); + $("#infoStep").html("

    '.$this->l('Upgrade Complete ! ').'

    "); +} +/** + * afterBackupDb display the button + * + */ +function afterBackupDb() +{ + $("#restoreDbContainer").html("restoreDb '.$this->l('click to restore database').'"); + prepareNextButton("#restoreDb",{}); +} + +function afterRestoreDb() +{ + $("#restoreDbContainer").html(""); +} + +function afterRestoreFiles() +{ + $("#restoreFilesContainer").html(""); +} + +function afterBackupFiles() +{ + $("#restoreFilesContainer").html("
    restoreFiles '.$this->l('click to restore files').'"); + prepareNextButton("#restoreFiles",{}); + +} + +function doAjaxRequest(action, nextParams){ + req = $.ajax({ + type:"POST", + url : "'.($this->standalone? __PS_BASE_URI__ . trim($this->adminDir,'/').'/autoupgrade/ajax-upgradetab.php' : str_replace('index','ajax-tab',$currentIndex)).'", + async: true, + data : { + dir:"'.trim($this->adminDir,'/').'", + ajaxMode : "1", + token : "'.$this->token.'", + tab : "'.get_class($this).'", + action : action, + params : nextParams + }, + success : function(res,textStatus,jqXHR) + { + if(eval("typeof nextParams") == "undefined") + { + nextParams = {typeResult : "json"}; + } + + if (nextParams.typeResult == "xml") + { + xmlRes = parseXMLResult(res); + res = {}; + res.next = xmlRes.next; + // if xml, we keep the next params + nextParams = myNext; + res.status = xmlRes.status; + } + else + { + try{ + res = $.parseJSON(res); + nextParams = res.nextParams; + } + catch(e){ + res = {status : "error"}; + alert("error during "+action); + /* + nextParams = { + error:"0", + next:"cancelUpgrade", + nextDesc:"Error detected during ["+action+"] step, reverting...", + nextQuickInfo:[], + status:"ok", + "stepDone":true + } + */ + } + } + + if (res.status == "ok") + { + $("#"+action).addClass("done"); + if (res.stepDone) + $("#"+action).addClass("stepok"); + + // if a function "after[action name]" exists, it should be called. + // This is used for enabling restore buttons for example + funcName = "after"+ucFirst(action); + if (typeof funcName == "string" && + eval("typeof " + funcName) == "function") { + + eval(funcName+"()"); + } + + handleSuccess(res,nextParams.typeResult); + } + else + { + // display progression + $("#"+action).addClass("done"); + $("#"+action).addClass("steperror"); + handleError(res); + } + }, + error: function(res,textStatus,jqXHR) + { + if (textStatus == "timeout" && action == "download") + { + updateInfoStep("'.$this->l('Your server can\'t download the file. Please upload it first by ftp in your admin/autoupgrade directory').'"); + } + else + { + //console.log(res); + //console.log(jqXHR); + updateInfoStep("[Server Error] Status message : " + textStatus); + } + } + }); + }; + +/** + * prepareNextButton make the button button_selector available, and update the nextParams values + * + * @param button_selector $button_selector + * @param nextParams $nextParams + * @return void + */ +function prepareNextButton(button_selector, nextParams) +{ +// myNext; + myNext = nextParams; + $(button_selector).unbind(); + $(button_selector).click(function(e){ + e.preventDefault(); + $("#currentlyProcessing").show(); +'; + if (defined('_PS_MODE_DEV_') AND _PS_MODE_DEV_) + $js .= 'addQuickInfo(["[DEV] request : "+$(this).attr("id")]);'; + $js .= ' + action = button_selector.substr(1); + res = doAjaxRequest(action, nextParams); + }); +} + +/** + * handleSuccess + * res = {error:, next:, nextDesc:, nextParams:, nextQuickInfo:,status:"ok"} + * @param res $res + * @return void + */ +function handleSuccess(res) +{ + updateInfoStep(res.nextDesc); + if (res.next != "") + { + addQuickInfo(res.nextQuickInfo); + + $("#"+res.next).addClass("nextStep"); + if (manualMode) + { + prepareNextButton("#"+res.next,res.nextParams); + alert("manually go to "+res.next+" button "); + } + else + { + // @TODO : + // 1) instead of click(), call a function. + doAjaxRequest(res.next,res.nextParams); + // 2) remove all step link (or show them only in dev mode) + // 3) when steps link displayed, they should change color when passed + } + } + else + { + // Way To Go, end of upgrade process + addQuickInfo(["End of upgrade process"]); + } +} + +// res = {nextParams, NextDesc} +function handleError(res) +{ + // display error message in the main process thing + updateInfoStep(res.nextDesc); + addQuickInfo(res.nextQuickInfo); + // In case the rollback button has been desactivated, just re-enable it + prepareNextButton("#rollback",res.nextParams); + // ask if you want to rollback + // @TODO !!! + if (confirm(res.NextDesc+"\r\r'.$this->l('Do you want to rollback ?').'")) + { + if (manualMode) + alert("'.$this->l('Please go manually go to rollback button').'"); + else + { + $("#rollback").click(); + } + + } +} +'; + return $js; + } + private function _cleanUp($path) + { + // as we need theses files for restore operation, we can't remove them. + // They will be overwritten + $skipDirs = array('backups', 'pclzip', 'autoupgrade', '.', '..', '.svn'); + $skipFiles = array('autoload.php', 'init.php', 'settings.inc.php', 'config.inc.php', 'Tools.php', 'AdminUpgrade.php'); + $skipFiles[] = $this->standalone?'ajax-upgradetab.php':'ajax-tab.php'; + if (is_dir($path)) + { + $fp = opendir($path); + while ($file = readdir($fp)) + { + if (!in_array($file, $skipDirs) AND !$this->_skipFile('', $path.$file, 'backup')) + { + $fullpath = $path.$file; + if (is_dir($fullpath)) + $this->_cleanUp($fullpath.'/'); + else + { + if (!in_array($file, $skipFiles)) + { + unlink($fullpath); + } + } + } + } + closedir($fp); + /* fortunately not empty dir won't be removed by the following */ + @rmdir($path); + } + else + if (!$this->_skipFile($file, '', 'backup')) unlink($path); + + return true; + } + + /** + * @desc extract a zip file to the given directory + * @return bool success + * we need a copy of it to be able to restore without keeping Tools and Autoload stuff + */ + private static function ZipExtract($fromFile, $toDir) + { + if (!file_exists($toDir)) + if (!@mkdir($toDir,0777)) + { + $this->next = 'error'; + $this->nextDesc = sprintf($this->l('unable to create directory %s'),$toDir); + return false; + } + + if (class_exists('ZipArchive', false)) + { + $zip = new ZipArchive(); + if ($zip->open($fromFile) === true AND $zip->extractTo($toDir) AND $zip->close()) + return true; + return false; + } + else + { + + if (!class_exists('PclZip',false)) + require_once($this->prodRootDir.'/tools/pclzip/pclzip.lib.php'); + + $zip = new PclZip($fromFile); + $list = $zip->extract(PCLZIP_OPT_PATH, $toDir); + foreach ($list as $extractedFile) + if ($extractedFile['status'] != 'ok') + return false; + + return true; + } + } + + private function _listArchivedFiles() + { + if (!empty($this->nextParams['backupFilesFilepath'])) + { + if (class_exists('ZipArchive', false)) + { + $files=array(); + if ($zip = zip_open($this->currentParams['backupFilesFilepath'])) + { + while ($entry=zip_read($zip)) + $files[] = zip_entry_name($entry); + + zip_close($zip); + } + } + else + { + require_once($this->prodRootDir.'/tools/pclzip/pclzip.lib.php'); + if ($zip = new PclZip($this->currentParams['backupFilesFilepath'])) + return $zip->listContent(); + } + } + return false; + } + + /** + * bool _skipFile : check whether a file is in backup or restore skip list + * + * @param type $file : current file or directory name eg:'.svn' , 'settings.inc.php' + * @param type $fullpath : current file or directory fullpath eg:'/home/web/www/prestashop/img' + * @param type $way : 'backup' , 'upgrade' + */ + private function _skipFile($file,$fullpath,$way='backup') + { + $fullpath = str_replace('\\','/', $fullpath); // wamp compliant + $rootpath = str_replace('\\','/', $this->prodRootDir); + switch ($way) + { + case 'backup': + if (in_array($file, $this->backupIgnoreFiles)) + return true; + + foreach($this->backupIgnoreAbsoluteFiles as $path) + if ($file == 'img') + if (strpos($fullpath, $rootpath.$path) !== false) + return true; + break; + + case 'upgrade': + if (in_array($file, $this->excludeFilesFromUpgrade)) + return true; + + foreach ($this->excludeAbsoluteFilesFromUpgrade as $path) + if (strpos($fullpath, $rootpath.$path) !== false) + return true; + break; + // default : if it's not a backup or an upgrade, juste skip the file + default: + return false; + } + } +} + diff --git a/modules/autoupgrade/Backup.php b/modules/autoupgrade/Backup.php new file mode 100644 index 000000000..2254cfeb7 --- /dev/null +++ b/modules/autoupgrade/Backup.php @@ -0,0 +1,305 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +class BackupCore +{ + /** @var integer Object id */ + public $id; + /** @var string Last error messages */ + public $error; + /** @var string default backup directory. */ + public static $backupDir = '/backups/'; + /** @var string custom backup directory. */ + public $customBackupDir = NULL; + + public $psBackupAll = true; + public $psBackupDropTable = true; + + /** + * Creates a new backup object + * + * @param string $filename Filename of the backup file + */ + public function __construct($filename = NULL) + { + if ($filename) + $this->id = $this->getRealBackupPath($filename); + + $psBackupAll = Configuration::get('PS_BACKUP_ALL'); + $psBackupDropTable = Configuration::get('PS_BACKUP_DROP_TABLE'); + $this->psBackupAll = $psBackupAll !== false ? $psBackupAll : true; + $this->psBackupDropTable = $psBackupDropTable !== false ? $psBackupDropTable : true; + } + + /** + * you can set a different path with that function + * + * @TODO include the prefix name + * @param string $dir + * @return boolean bo + */ + public function setCustomBackupPath($dir) + { + $customDir = DIRECTORY_SEPARATOR.trim($dir,'/').DIRECTORY_SEPARATOR; + if(is_dir(_PS_ADMIN_DIR_.DIRECTORY_SEPARATOR.$customDir.DIRECTORY_SEPARATOR)) + $this->customBackupDir = $customDir; + else + return false; + + return true; + + } + + /** + * get the path to use for backup (customBackupDir if specified, or default) + * + * @param string $filename filename to use + * @return string full path + */ + public function getRealBackupPath($filename = NULL) + { + $backupDir = Backup::getBackupPath($filename); + if (!empty($this->customBackupDir)) + { + $backupDir = str_replace(_PS_ADMIN_DIR_.self::$backupDir, _PS_ADMIN_DIR_.$this->customBackupDir, $backupDir); + + if(strrpos($backupDir,DIRECTORY_SEPARATOR)) + $backupDir .= DIRECTORY_SEPARATOR; + } + return $backupDir; + } + /** + * Get the full path of the backup file + * + * @param string $filename prefix of the backup file (datetime will be the second part) + * @return The full path of the backup file, or false if the backup file does not exists + */ + public static function getBackupPath($filename) + { + $backupdir = realpath(_PS_ADMIN_DIR_.self::$backupDir); + + if ($backupdir === false) + die(Tools::displayError('Backups directory does not exist.')); + + // Check the realpath so we can validate the backup file is under the backup directory + if(!empty($filename)) + $backupfile = realpath($backupdir.'/'.$filename); + else + $backupfile = $backupdir.DIRECTORY_SEPARATOR; + + if ($backupfile === false OR strncmp($backupdir, $backupfile, strlen($backupdir)) != 0) + die (Tools::displayError()); + + return $backupfile; + } + + /** + * Get the URL used to retreive this backup file + * + * @return The url used to request the backup file + */ + public function getBackupURL() + { + $adminDir = __PS_BASE_URI__.substr($_SERVER['SCRIPT_NAME'], strlen(__PS_BASE_URI__) ); + $adminDir = substr($adminDir, 0, strrpos($adminDir, '/')); + + return $adminDir.'/backup.php?filename='.basename($this->id); + } + + /** + * Delete the current backup file + * + * @return boolean Deletion result, true on success + */ + public function delete() + { + if (!$this->id || !unlink($this->id)) + { + $this->error = Tools::displayError('Error deleting').' '.($this->id ? '"'.$this->id.'"' : Tools::displayError('Invalid ID')); + return false; + } + return true; + } + + /** + * Deletes a range of backup files + * + * @return boolean True on success + */ + public function deleteSelection($list) + { + foreach ($list as $file) + { + $backup = new Backup($file); + if (!$backup->delete()) + { + $this->error = $backup->error; + return false; + } + } + return true; + } + + /** + * Creates a new backup file + * + * @return boolean true on successful backup + */ + public function add() + { + if ( _DB_TYPE_ !== 'MySQL' ) + { + $this->error = Tools::displayError('Sorry, backup currently only supports MySQL database types. You are using') . ' "' . _DB_TYPE_ . '"'; + return false; + } + + if (!$this->psBackupAll) + $ignore_insert_table = array(_DB_PREFIX_.'connections', _DB_PREFIX_.'connections_page', _DB_PREFIX_.'connections_source', _DB_PREFIX_.'guest', _DB_PREFIX_.'statssearch'); + else + $ignore_insert_table = array(); + + // Generate some random number, to make it extra hard to guess backup file names + $rand = dechex(mt_rand(0, min(0xffffffff, mt_getrandmax()))); + $date = time(); + $backupfile = $this->getRealBackupPath().$date.'-'.$rand.'.sql'; + + // Figure out what compression is available and open the file + if (function_exists('bzopen')) + { + $backupfile .= '.bz2'; + $fp = @bzopen($backupfile, 'w'); + } + elseif (function_exists('gzopen')) + { + $backupfile .= '.gz'; + $fp = @gzopen($backupfile, 'w'); + } + else + $fp = @fopen($backupfile, 'w'); + + if ($fp === false) + { + echo Tools::displayError('Unable to create backup file') . ' "' . addslashes($backupfile) . '"'; + return false; + } + + $this->id = realpath($backupfile); + + fwrite($fp, '/* Backup for ' . Tools::getHttpHost(false, false) . __PS_BASE_URI__ . "\n * at " . date($date) . "\n */\n"); + fwrite($fp, "\n".'SET NAMES \'utf8\';'."\n\n"); + + // Find all tables + $tables = Db::getInstance()->ExecuteS('SHOW TABLES'); + $found = 0; + foreach ($tables AS $table) + { + $table = current($table); + + // Skip tables which do not start with _DB_PREFIX_ + if (strlen($table) < strlen(_DB_PREFIX_) || strncmp($table, _DB_PREFIX_, strlen(_DB_PREFIX_)) != 0) + continue; + + // Export the table schema + $schema = Db::getInstance()->ExecuteS('SHOW CREATE TABLE `' . $table . '`'); + + if (count($schema) != 1 || !isset($schema[0]['Table']) || !isset($schema[0]['Create Table'])) + { + fclose($fp); + $this->delete(); + echo Tools::displayError('An error occurred while backing up. Unable to obtain the schema of').' "'.$table; + return false; + } + + fwrite($fp, '/* Scheme for table ' . $schema[0]['Table'] . " */\n"); + + if ($this->psBackupDropTable) + fwrite($fp, 'DROP TABLE IF EXISTS `'.$schema[0]['Table'].'`;'."\n"); + + fwrite($fp, $schema[0]['Create Table'] . ";\n\n"); + + if (!in_array($schema[0]['Table'], $ignore_insert_table)) + { + $data = Db::getInstance()->ExecuteS('SELECT * FROM `' . $schema[0]['Table'] . '`', false); + $sizeof = DB::getInstance()->NumRows(); + $lines = explode("\n", $schema[0]['Create Table']); + + if ($data AND $sizeof > 0) + { + // Export the table data + fwrite($fp, 'INSERT INTO `' . $schema[0]['Table'] . "` VALUES\n"); + $i = 1; + while ($row = DB::getInstance()->nextRow($data)) + { + $s = '('; + + foreach ($row AS $field => $value) + { + $tmp = "'" . mysql_real_escape_string($value) . "',"; + if ($tmp != "'',") + $s .= $tmp; + else + { + foreach($lines AS $line) + if (strpos($line, '`'.$field.'`') !== false) + { + if (preg_match('/(.*NOT NULL.*)/Ui', $line)) + $s .= "'',"; + else + $s .= 'NULL,'; + break; + } + } + } + $s = rtrim($s, ','); + + if ($i%200 == 0 AND $i < $sizeof) + $s .= ");\nINSERT INTO `".$schema[0]['Table']."` VALUES\n"; + elseif ($i < $sizeof) + $s .= "),\n"; + else + $s .= ");\n"; + + fwrite($fp, $s); + ++$i; + } + } + } + $found++; + } + + fclose($fp); + if ($found == 0) + { + $this->delete(); + echo Tools::displayError('No valid tables were found to backup.' ); + return false; + } + + return true; + } + +} diff --git a/modules/autoupgrade/ConfigurationTest.php b/modules/autoupgrade/ConfigurationTest.php new file mode 100644 index 000000000..f74539405 --- /dev/null +++ b/modules/autoupgrade/ConfigurationTest.php @@ -0,0 +1,230 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +class ConfigurationTestCore +{ + static function check($tests) + { + $res = array(); + foreach ($tests AS $key => $test) + $res[$key] = self::run($key, $test); + return $res; + } + + static function run($ptr, $arg = 0) + { + if (call_user_func(array('ConfigurationTest', 'test_'.$ptr), $arg)) + return ('ok'); + return ('fail'); + } + + // Misc functions + static function test_phpversion() + { + return version_compare(substr(phpversion(), 0, 3), '5.0', '>='); + } + + static function test_mysql_support() + { + return function_exists('mysql_connect'); + } + + static function test_magicquotes() + { + return !ini_get('magic_quotes_gpc'); + } + + static function test_upload() + { + return ini_get('file_uploads'); + } + + static function test_fopen() + { + return ini_get('allow_url_fopen'); + } + + static function test_system($funcs) + { + foreach ($funcs AS $func) + if (!function_exists($func)) + return false; + return true; + } + + static function test_gd() + { + return function_exists('imagecreatetruecolor'); + } + + static function test_register_globals() + { + return !ini_get('register_globals'); + } + + static function test_gz() + { + if (function_exists('gzencode')) + return !(@gzencode('dd') === false); + return false; + } + + // is_writable dirs + static function test_dir($dir, $recursive = false) + { + if (!file_exists($dir) OR !$dh = opendir($dir)) + return false; + $dummy = rtrim($dir, '/').'/'.uniqid(); + if (@file_put_contents($dummy, 'test')) + { + @unlink($dummy); + if (!$recursive) + return true; + } + elseif (!is_writable($dir)) + return false; + if ($recursive) + { + while (($file = readdir($dh)) !== false) + if (@filetype($dir.$file) == 'dir' AND $file != '.' AND $file != '..') + if (!self::test_dir($dir.$file, true)) + return false; + } + closedir($dh); + return true; + } + + // is_writable files + static function test_file($file) + { + return (file_exists($file) AND is_writable($file)); + } + + static function test_config_dir($dir) + { + return self::test_dir($dir); + } + + static function test_sitemap($dir) + { + return self::test_file($dir); + } + + static function test_root_dir($dir) + { + return self::test_dir($dir); + } + + static function test_log_dir($dir) + { + return self::test_dir($dir); + } + + static function test_admin_dir($dir) + { + return self::test_dir($dir); + } + + static function test_img_dir($dir) + { + return self::test_dir($dir, true); + } + + static function test_module_dir($dir) + { + return self::test_dir($dir, true); + } + + static function test_tools_dir($dir) + { + return self::test_dir($dir); + } + + static function test_cache_dir($dir) + { + return self::test_dir($dir); + } + + static function test_tools_v2_dir($dir) + { + return self::test_dir($dir); + } + + static function test_cache_v2_dir($dir) + { + return self::test_dir($dir); + } + + static function test_download_dir($dir) + { + return self::test_dir($dir); + } + + static function test_mails_dir($dir) + { + return self::test_dir($dir, true); + } + + static function test_translations_dir($dir) + { + return self::test_dir($dir, true); + } + + static function test_theme_lang_dir($dir) + { + if (!file_exists($dir)) + return true; + return self::test_dir($dir, true); + } + + static function test_theme_cache_dir($dir) + { + if (!file_exists($dir)) + return true; + return self::test_dir($dir, true); + } + + static function test_customizable_products_dir($dir) + { + return self::test_dir($dir); + } + + static function test_virtual_products_dir($dir) + { + return self::test_dir($dir); + } + + static function test_mcrypt() + { + return function_exists('mcrypt_encrypt'); + } + + static function test_dom() + { + return extension_loaded('Dom'); + } +} diff --git a/modules/autoupgrade/SelfModule.php b/modules/autoupgrade/SelfModule.php new file mode 100644 index 000000000..e0da70cbe --- /dev/null +++ b/modules/autoupgrade/SelfModule.php @@ -0,0 +1,1102 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +abstract class SelfModule +{ + /** @var integer Module ID */ + public $id = NULL; + + /** @var float Version */ + public $version; + + /** @var string Unique name */ + public $name; + + /** @var string Human name */ + public $displayName; + + /** @var string A little description of the module */ + public $description; + + /** @var string author of the module */ + public $author; + + /** @var int need_instance */ + public $need_instance = 1; + + /** @var string Admin tab correponding to the module */ + public $tab = NULL; + + /** @var boolean Status */ + public $active = false; + + /** @var array current language translations */ + protected $_lang = array(); + + /** @var string Module web path (eg. '/shop/modules/modulename/') */ + protected $_path = NULL; + + /** @var string Fill it if the module is installed but not yet set up */ + public $warning; + + /** @var string Message display before uninstall a module */ + public $beforeUninstall = NULL; + + public $_errors = false; + + protected $table = 'module'; + + protected $identifier = 'id_module'; + + public static $_db; + + /** @var array to store the limited country */ + public $limited_countries = array(); + + /** + * Constructor + * + * @param string $name Module unique name + */ + protected static $modulesCache; + protected static $_hookModulesCache; + + protected static $_INSTANCE = array(); + + protected static $_generateConfigXmlMode = false; + + protected static $l_cache = array(); + + /** + * @var array used by AdminTab to determine which lang file to use (admin.php or module lang file) + */ + public static $classInModule = array(); + + public function __construct($name = NULL) + { + if ($this->name == NULL) + $this->name = $this->id; + if ($this->name != NULL) + { + if (self::$modulesCache == NULL AND !is_array(self::$modulesCache)) + { + self::$modulesCache = array(); + $result = Db::getInstance()->ExecuteS('SELECT * FROM `'.pSQL(_DB_PREFIX_.$this->table).'`'); + foreach ($result as $row) + self::$modulesCache[$row['name']] = $row; + } + if (isset(self::$modulesCache[$this->name])) + { + $this->active = true; + $this->id = self::$modulesCache[$this->name]['id_module']; + foreach (self::$modulesCache[$this->name] AS $key => $value) + if (key_exists($key, $this)) + $this->{$key} = $value; + $this->_path = __PS_BASE_URI__.'modules/'.$this->name.'/'; + } + } + } + + /** + * Insert module into datable + */ + public function install() + { + if (!Validate::isModuleName($this->name)) + die(Tools::displayError()); + $result = Db::getInstance()->getRow(' + SELECT `id_module` + FROM `'._DB_PREFIX_.'module` + WHERE `name` = \''.pSQL($this->name).'\''); + if ($result) + return false; + $result = Db::getInstance()->AutoExecute(_DB_PREFIX_.$this->table, array('name' => $this->name, 'active' => 1), 'INSERT'); + if (!$result) + return false; + $this->id = Db::getInstance()->Insert_ID(); + return true; + } + + /** + * Delete module from datable + * + * @return boolean result + */ + public function uninstall() + { + if (!Validate::isUnsignedId($this->id)) + return false; + $result = Db::getInstance()->ExecuteS(' + SELECT `id_hook` + FROM `'._DB_PREFIX_.'hook_module` hm + WHERE `id_module` = '.(int)($this->id)); + foreach ($result AS $row) + { + Db::getInstance()->Execute(' + DELETE FROM `'._DB_PREFIX_.'hook_module` + WHERE `id_module` = '.(int)($this->id).' + AND `id_hook` = '.(int)($row['id_hook'])); + $this->cleanPositions($row['id_hook']); + } + return Db::getInstance()->Execute(' + DELETE FROM `'._DB_PREFIX_.'module` + WHERE `id_module` = '.(int)($this->id)); + } + + /** + * This function enable module $name. If an $name is an array, + * this will enable all of them + * + * @param array|string $name + * @return true if succeed + * @since 1.4.1 + */ + public static function enableByName($name) + { + if (!is_array($name)) + $name = array($name); + + foreach ($name as $k=>$v) + $name[$k] = '"'.pSQL($v).'"'; + + return Db::getInstance()->Execute(' + UPDATE `'._DB_PREFIX_.'module` + SET `active`= 1 + WHERE `name` IN ('.implode(',',$name).')'); + } + /** + * Called when module is set to active + */ + public function enable() + { + return Db::getInstance()->Execute(' + UPDATE `'._DB_PREFIX_.'module` + SET `active`= 1 + WHERE `name` = \''.pSQL($this->name).'\''); + } + + /** + * This function disable module $name. If an $name is an array, + * this will disable all of them + * + * @param array|string $name + * @return true if succeed + * @since 1.4.1 + */ + public static function disableByName($name) + { + if (!is_array($name)) + $name = array($name); + + foreach ($name as $k=>$v) + $name[$k] = '"'.pSQL($v).'"'; + + return Db::getInstance()->Execute(' + UPDATE `'._DB_PREFIX_.'module` + SET `active`= 0 + WHERE `name` IN ('.implode(',',$name).')'); + } + + /** + * Called when module is set to deactive + */ + public function disable() + { + return Module::disableByName($this->name); + } + + /** + * Connect module to a hook + * + * @param string $hook_name Hook name + * @return boolean result + */ + public function registerHook($hook_name) + { + if (!Validate::isHookName($hook_name)) + die(Tools::displayError()); + if (!isset($this->id) OR !is_numeric($this->id)) + return false; + + // Check if already register + $result = Db::getInstance()->getRow(' + SELECT hm.`id_module` FROM `'._DB_PREFIX_.'hook_module` hm, `'._DB_PREFIX_.'hook` h + WHERE hm.`id_module` = '.(int)($this->id).' + AND h.`name` = \''.pSQL($hook_name).'\' + AND h.`id_hook` = hm.`id_hook`'); + if ($result) + return true; + + // Get hook id + $result = Db::getInstance()->getRow(' + SELECT `id_hook` + FROM `'._DB_PREFIX_.'hook` + WHERE `name` = \''.pSQL($hook_name).'\''); + if (!isset($result['id_hook'])) + return false; + + // Get module position in hook + $result2 = Db::getInstance()->getRow(' + SELECT MAX(`position`) AS position + FROM `'._DB_PREFIX_.'hook_module` + WHERE `id_hook` = '.(int)($result['id_hook'])); + if (!$result2) + return false; + + // Register module in hook + $return = Db::getInstance()->Execute(' + INSERT INTO `'._DB_PREFIX_.'hook_module` (`id_module`, `id_hook`, `position`) + VALUES ('.(int)($this->id).', '.(int)($result['id_hook']).', '.(int)($result2['position'] + 1).')'); + + $this->cleanPositions((int)($result['id_hook'])); + + return $return; + } + + /** + * Display flags in forms for translations + * + * @param array $languages All languages available + * @param integer $defaultLanguage Default language id + * @param string $ids Multilingual div ids in form + * @param string $id Current div id] + * #param boolean $return define the return way : false for a display, true for a return + */ + public function displayFlags($languages, $defaultLanguage, $ids, $id, $return = false) + { + if (sizeof($languages) == 1) + return false; + $output = ' +
    + +
    +
    + '.$this->l('Choose language:').'

    '; + foreach ($languages as $language) + $output .= ''.$language['name'].' '; + $output .= '
    '; + + if ($return) + return $output; + echo $output; + } + + /** + * Unregister module from hook + * + * @param int $id_hook Hook id + * @return boolean result + */ + public function unregisterHook($hook_id) + { + return Db::getInstance()->Execute(' + DELETE + FROM `'._DB_PREFIX_.'hook_module` + WHERE `id_module` = '.(int)($this->id).' + AND `id_hook` = '.(int)($hook_id)); + } + + /** + * Unregister exceptions linked to module + * + * @param int $id_hook Hook id + * @return boolean result + */ + public function unregisterExceptions($hook_id) + { + return Db::getInstance()->Execute(' + DELETE + FROM `'._DB_PREFIX_.'hook_module_exceptions` + WHERE `id_module` = '.(int)($this->id).' + AND `id_hook` = '.(int)($hook_id)); + } + + /** + * Add exceptions for module->Hook + * + * @param int $id_hook Hook id + * @param array $excepts List of file name + * @return boolean result + */ + public function registerExceptions($id_hook, $excepts) + { + foreach ($excepts AS $except) + { + if (!empty($except)) + { + $result = Db::getInstance()->Execute(' + INSERT INTO `'._DB_PREFIX_.'hook_module_exceptions` (`id_module`, `id_hook`, `file_name`) + VALUES ('.(int)($this->id).', '.(int)($id_hook).', \''.pSQL(strval($except)).'\')'); + if (!$result) + return false; + } + } + return true; + } + + public function editExceptions($id_hook, $excepts) + { + // Cleaning... + Db::getInstance()->Execute(' + DELETE FROM `'._DB_PREFIX_.'hook_module_exceptions` + WHERE `id_module` = '.(int)($this->id).' AND `id_hook` ='.(int)($id_hook)); + return $this->registerExceptions($id_hook, $excepts); + } + + + /** + * This function is used to determine the module name + * of an AdminTab which belongs to a module, in order to keep translation + * related to a module in its directory (instead of $_LANGADM) + * + * @param mixed $currentClass the + * @return boolean|string if the class belongs to a module, will return the module name. Otherwise, return false. + */ + public static function getModuleNameFromClass($currentClass) + { + global $cookie; + + // Module can now define AdminTab keeping the module translations method, + // i.e. in modules/[module name]/[iso_code].php + if (!isset(self::$classInModule[$currentClass])) + { + global $_MODULES; + $_MODULE = array(); + $reflectionClass = new ReflectionClass($currentClass); + $filePath = realpath($reflectionClass->getFileName()); + $realpathModuleDir = realpath(_PS_MODULE_DIR_); + if (substr(realpath($filePath), 0, strlen($realpathModuleDir)) == $realpathModuleDir) + { + self::$classInModule[$currentClass] = substr(dirname($filePath), strlen($realpathModuleDir)+1); + + $id_lang = (int)($cookie->id_lang); + $file = _PS_MODULE_DIR_.self::$classInModule[$currentClass].'/'.Language::getIsoById($id_lang).'.php'; + if (file_exists($file) AND include_once($file)) + $_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE; + } + else + self::$classInModule[$currentClass] = false; + } + // return name of the module, or false + return self::$classInModule[$currentClass]; + } + + /** + * Return an instance of the specified module + * + * @param string $moduleName Module name + * @return Module instance + */ + public static function getInstanceByName($moduleName) + { + if (!Tools::file_exists_cache(_PS_MODULE_DIR_.$moduleName.'/'.$moduleName.'.php')) + return false; + include_once(_PS_MODULE_DIR_.$moduleName.'/'.$moduleName.'.php'); + if (!class_exists($moduleName, false)) + return false; + + if (!isset(self::$_INSTANCE[$moduleName])) + self::$_INSTANCE[$moduleName] = new $moduleName; + return self::$_INSTANCE[$moduleName]; + } + + /** + * Load modules Ids from Ids + * + * @param array|int $ids Modules ID + * @return Array of module name + */ + public static function preloadModuleNameFromId($ids) + { + static $preloadedModuleNameFromId; + if (!isset($preloadedModuleNameFromId)) { + $preloadedModuleNameFromId = array(); + } + + if (is_array($ids)) + { + foreach($ids as $id) + $preloadedModuleNameFromId[$id] = false; + + $results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' + SELECT `name`,`id_module` + FROM `'._DB_PREFIX_.'module` + WHERE `id_module` IN ('.join(',',$ids) .');'); + foreach($results as $result) + $preloadedModuleNameFromId[$result['id_module']] = $result['name']; + } + elseif (!isset($preloadedModuleNameFromId[$ids])) + { + $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(' + SELECT `name` + FROM `'._DB_PREFIX_.'module` + WHERE `id_module` = '.(int)($ids)); + if ($result) + $preloadedModuleNameFromId[$ids] = $result['name']; + else + $preloadedModuleNameFromId[$ids] = false; + } + + + if (is_array($ids)) { + return $preloadedModuleNameFromId; + } else { + if (!isset($preloadedModuleNameFromId[$ids])) + return false; + return $preloadedModuleNameFromId[$ids]; + } + } + + /** + * Return an instance of the specified module + * + * @param integer $id_module Module ID + * @return Module instance + */ + public static function getInstanceById($id_module) + { + $moduleName = Module::preloadModuleNameFromId($id_module); + return ($moduleName ? Module::getInstanceByName($moduleName) : false); + } + + public static function configXmlStringFormat($string) + { + return str_replace('\'', '\\\'', Tools::htmlentitiesDecodeUTF8($string)); + } + + /** + * Return available modules + * + * @param boolean $useConfig in order to use config.xml file in module dir + * @return array Modules + */ + public static function getModulesOnDisk($useConfig = false) + { + global $cookie, $_MODULES; + + $moduleList = array(); + $moduleListCursor = 0; + $moduleNameList = array(); + $modulesNameToCursor = array(); + $errors = array(); + $modules_dir = self::getModulesDirOnDisk(); + + $memory_limit = Tools::getMemoryLimit(); + + foreach ($modules_dir AS $module) + { + // Memory usage checking + if (function_exists('memory_get_usage') && $memory_limit !== -1) + { + $current_memory = memory_get_usage(true); + // memory_threshold in MB + $memory_threshold = (Tools::isX86_64arch() ? 3 : 1.5); + if (($memory_limit - $current_memory) <= ($memory_threshold * 1024 * 1024)) + { + $errors[] = Tools::displayError('All modules cannot be loaded due to memory limit restriction reason, please increase your memory_limit value on your server configuration'); + break; + } + } + + $configFile = _PS_MODULE_DIR_.$module.'/config.xml'; + $xml_exist = file_exists($configFile); + if ($xml_exist) + $needNewConfigFile = (filemtime($configFile) < filemtime(_PS_MODULE_DIR_.$module.'/'.$module.'.php')); + else + $needNewConfigFile = true; + if ($useConfig AND $xml_exist) + { + libxml_use_internal_errors(true); + $xml_module = simplexml_load_file($configFile); + foreach (libxml_get_errors() as $error) + $errors[] = '['.$module.'] '.Tools::displayError('Error found in config file:').' '.htmlentities($error->message); + libxml_clear_errors(); + + if (!count($errors) AND (int)$xml_module->need_instance == 0 AND !$needNewConfigFile) + { + $file = _PS_MODULE_DIR_.$module.'/'.Language::getIsoById($cookie->id_lang).'.php'; + if (Tools::file_exists_cache($file) AND include_once($file)) + if (isset($_MODULE) AND is_array($_MODULE)) + $_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE; + + $xml_module->displayName = Module::findTranslation($xml_module->name, self::configXmlStringFormat($xml_module->displayName), (string)$xml_module->name); + $xml_module->description = Module::findTranslation($xml_module->name, self::configXmlStringFormat($xml_module->description), (string)$xml_module->name); + $xml_module->author = Module::findTranslation($xml_module->name, self::configXmlStringFormat($xml_module->author), (string)$xml_module->name); + + if (isset($xml_module->confirmUninstall)) + $xml_module->confirmUninstall = Module::findTranslation($xml_module->name, self::configXmlStringFormat($xml_module->confirmUninstall), (string)$xml_module->name); + + + $moduleList[$moduleListCursor] = $xml_module; + $moduleNameList[$moduleListCursor] = '\''.strval($xml_module->name).'\''; + $modulesNameToCursor[strval($xml_module->name)] = $moduleListCursor; + $moduleListCursor++; + } + } + if (!$useConfig OR !$xml_exist OR (isset($xml_module->need_instance) AND (int)$xml_module->need_instance == 1) OR $needNewConfigFile) + { + $file = trim(file_get_contents(_PS_MODULE_DIR_.$module.'/'.$module.'.php')); + if (substr($file, 0, 5) == '') + $file = substr($file, 0, -2); + if (class_exists($module, false) OR eval($file) !== false) + { + $moduleList[$moduleListCursor++] = new $module; + if (!$xml_exist OR $needNewConfigFile) + { + self::$_generateConfigXmlMode = true; + $tmpModule = new $module; + $tmpModule->_generateConfigXml(); + self::$_generateConfigXmlMode = false; + } + } + else + $errors[] = $module; + } + } + + // Get modules information from database + if (!empty($moduleNameList)) + { + $results = Db::getInstance()->executeS('SELECT `id_module`, `active`, `name` FROM `'._DB_PREFIX_.'module` WHERE `name` IN ('.join(',',$moduleNameList).')'); + foreach($results as $result) + { + $moduleCursor = $modulesNameToCursor[$result['name']]; + if (isset($result['active']) AND $result['active']) + $moduleList[$moduleCursor]->active = $result['active']; + if (isset($result['id_module']) AND $result['id_module']) + $moduleList[$moduleCursor]->id = $result['id_module']; + } + } + + if (sizeof($errors)) + { + echo '

    '.Tools::displayError('Parse error(s) in module(s)').'

      '; + foreach ($errors AS $error) + echo '
    1. '.$error.'
    2. '; + echo '
    '; + } + + return $moduleList; + } + + public static function getModulesDirOnDisk() + { + $moduleList = array(); + $modules = scandir(_PS_MODULE_DIR_); + foreach ($modules AS $name) + { + if (is_dir(_PS_MODULE_DIR_.$name) && Tools::file_exists_cache(_PS_MODULE_DIR_.$name.'/'.$name.'.php')) + { + if (!Validate::isModuleName($name)) + die(Tools::displayError().' (Module '.$name.')'); + $moduleList[] = $name; + } + } + return $moduleList; + } + + /** + * Return non native module + * + * @param int $position Take only positionnables modules + * @return array Modules + */ + public static function getNonNativeModuleList() + { + $db = Db::getInstance(); + + $module_list_xml = _PS_ROOT_DIR_.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'modules_list.xml'; + $nativeModules = simplexml_load_file($module_list_xml); + $nativeModules = $nativeModules->modules; + foreach ($nativeModules as $nativeModulesType) + if (in_array($nativeModulesType['type'],array('native','partner'))) + { + $arrNativeModules[] = '""'; + foreach ($nativeModulesType->module as $module) + $arrNativeModules[] = '"'.pSQL($module['name']).'"'; + } + + return $db->ExecuteS(' + SELECT * + FROM `'._DB_PREFIX_.'module` m + WHERE name NOT IN ('.implode(',',$arrNativeModules).') '); + } + + /** + * Return installed modules + * + * @param int $position Take only positionnables modules + * @return array Modules + */ + public static function getModulesInstalled($position = 0) + { + return Db::getInstance()->ExecuteS(' + SELECT * + FROM `'._DB_PREFIX_.'module` m + '.($position ? ' + LEFT JOIN `'._DB_PREFIX_.'hook_module` hm ON m.`id_module` = hm.`id_module` + LEFT JOIN `'._DB_PREFIX_.'hook` k ON hm.`id_hook` = k.`id_hook` + WHERE k.`position` = 1' : '')); + } + + /* + * Execute modules for specified hook + * + * @param string $hook_name Hook Name + * @param array $hookArgs Parameters for the functions + * @return string modules output + */ + public static function hookExec($hook_name, $hookArgs = array(), $id_module = NULL) + { + global $cookie; + if ((!empty($id_module) AND !Validate::isUnsignedId($id_module)) OR !Validate::isHookName($hook_name)) + die(Tools::displayError()); + + global $cart, $cookie; + $live_edit = false; + if (!isset($hookArgs['cookie']) OR !$hookArgs['cookie']) + $hookArgs['cookie'] = $cookie; + if (!isset($hookArgs['cart']) OR !$hookArgs['cart']) + $hookArgs['cart'] = $cart; + $hook_name = strtolower($hook_name); + + if (!isset(self::$_hookModulesCache)) + { + $db = Db::getInstance(_PS_USE_SQL_SLAVE_); + $result = $db->ExecuteS(' + SELECT h.`name` as hook, m.`id_module`, h.`id_hook`, m.`name` as module, h.`live_edit` + FROM `'._DB_PREFIX_.'module` m + LEFT JOIN `'._DB_PREFIX_.'hook_module` hm ON hm.`id_module` = m.`id_module` + LEFT JOIN `'._DB_PREFIX_.'hook` h ON hm.`id_hook` = h.`id_hook` + AND m.`active` = 1 + ORDER BY hm.`position`', false); + self::$_hookModulesCache = array(); + + if ($result) + while ($row = $db->nextRow()) + { + $row['hook'] = strtolower($row['hook']); + if (!isset(self::$_hookModulesCache[$row['hook']])) + self::$_hookModulesCache[$row['hook']] = array(); + self::$_hookModulesCache[$row['hook']][] = array('id_hook' => $row['id_hook'], 'module' => $row['module'], 'id_module' => $row['id_module'], 'live_edit' => $row['live_edit']); + } + } + + if (!isset(self::$_hookModulesCache[$hook_name])) + return; + + $altern = 0; + $output = ''; + foreach (self::$_hookModulesCache[$hook_name] AS $array) + { + if ($id_module AND $id_module != $array['id_module']) + continue; + if (!($moduleInstance = Module::getInstanceByName($array['module']))) + continue; + + $exceptions = $moduleInstance->getExceptions((int)$array['id_hook'], (int)$array['id_module']); + foreach ($exceptions AS $exception) + if (strstr(basename($_SERVER['PHP_SELF']).'?'.$_SERVER['QUERY_STRING'], $exception['file_name']) && !strstr($_SERVER['QUERY_STRING'], $exception['file_name'])) + continue 2; + + if (is_callable(array($moduleInstance, 'hook'.$hook_name))) + { + $hookArgs['altern'] = ++$altern; + + $display = call_user_func(array($moduleInstance, 'hook'.$hook_name), $hookArgs); + if ($array['live_edit'] && ((Tools::isSubmit('live_edit') AND Tools::getValue('ad') AND (Tools::getValue('liveToken') == sha1(Tools::getValue('ad')._COOKIE_KEY_))))) + { + $live_edit = true; + $output .= ' +
    + ' + .$moduleInstance->displayName.' + + + + + '.$display.'
    '; + } + else + $output .= $display; + } + } + return ($live_edit ? '
    ' : '').$output.($live_edit ? '
    ' : ''); + } + + public static function hookExecPayment() + { + global $cart, $cookie; + $hookArgs = array('cookie' => $cookie, 'cart' => $cart); + $output = ''; + + $result = self::getPaymentModules(); + + if ($result) + foreach ($result AS $module) + if (($moduleInstance = Module::getInstanceByName($module['name'])) AND is_callable(array($moduleInstance, 'hookpayment'))) + if (!$moduleInstance->currencies OR ($moduleInstance->currencies AND sizeof(Currency::checkPaymentCurrencies($moduleInstance->id)))) + $output .= call_user_func(array($moduleInstance, 'hookpayment'), $hookArgs); + return $output; + } + + public static function getPaymentModules() + { + global $cart, $cookie; + $id_customer = (int)($cookie->id_customer); + $billing = new Address((int)($cart->id_address_invoice)); + + $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' + SELECT DISTINCT h.`id_hook`, m.`name`, hm.`position` + FROM `'._DB_PREFIX_.'module_country` mc + LEFT JOIN `'._DB_PREFIX_.'module` m ON m.`id_module` = mc.`id_module` + INNER JOIN `'._DB_PREFIX_.'module_group` mg ON (m.`id_module` = mg.`id_module`) + INNER JOIN `'._DB_PREFIX_.'customer_group` cg on (cg.`id_group` = mg.`id_group` AND cg.`id_customer` = '.(int)($id_customer).') + LEFT JOIN `'._DB_PREFIX_.'hook_module` hm ON hm.`id_module` = m.`id_module` + LEFT JOIN `'._DB_PREFIX_.'hook` h ON hm.`id_hook` = h.`id_hook` + WHERE h.`name` = \'payment\' + AND mc.id_country = '.(int)($billing->id_country).' + AND m.`active` = 1 + ORDER BY hm.`position`, m.`name` DESC'); + + return $result; + } + + /** + * find translation from $_MODULES and put it in self::$l_cache if not already exist + * and return it. + * + * @param string $name name of the module + * @param string $string term to find + * @param string $source additional param for building translation key + * @return string + */ + public static function findTranslation($name, $string, $source) + { + global $_MODULES; + + $cache_key = $name . '|' . $string . '|' . $source; + + if (!isset(self::$l_cache[$cache_key])) + { + if (!is_array($_MODULES)) + return str_replace('"', '"', $string); + // set array key to lowercase for 1.3 compatibility + $_MODULES = array_change_key_case($_MODULES); + $currentKey = '<{'.strtolower($name).'}'.strtolower(_THEME_NAME_).'>'.strtolower($source).'_'.md5($string); + $defaultKey = '<{'.strtolower($name).'}prestashop>'.strtolower($source).'_'.md5($string); + + if (isset($_MODULES[$currentKey])) + $ret = stripslashes($_MODULES[$currentKey]); + elseif (isset($_MODULES[Tools::strtolower($currentKey)])) + $ret = stripslashes($_MODULES[Tools::strtolower($currentKey)]); + elseif (isset($_MODULES[$defaultKey])) + $ret = stripslashes($_MODULES[$defaultKey]); + elseif (isset($_MODULES[Tools::strtolower($defaultKey)])) + $ret = stripslashes($_MODULES[Tools::strtolower($defaultKey)]); + else + $ret = stripslashes($string); + + self::$l_cache[$cache_key] = str_replace('"', '"', $ret); + } + return self::$l_cache[$cache_key]; + } + /** + * Get translation for a given module text + * + * Note: $specific parameter is mandatory for library files. + * Otherwise, translation key will not match for Module library + * when module is loaded with eval() Module::getModulesOnDisk() + * + * @param string $string String to translate + * @param boolean|string $specific filename to use in translation key + * @return string Translation + */ + public function l($string, $specific = false, $id_lang = null) + { + if (self::$_generateConfigXmlMode) + return $string; + + global $_MODULES, $_MODULE, $cookie; + + if ($id_lang == null) + $id_lang = (!isset($cookie) OR !is_object($cookie)) ? (int)(Configuration::get('PS_LANG_DEFAULT')) : (int)($cookie->id_lang); + $file = _PS_MODULE_DIR_.$this->name.'/'.Language::getIsoById($id_lang).'.php'; + if (Tools::file_exists_cache($file) AND include_once($file)) + $_MODULES = !empty($_MODULES) ? array_merge($_MODULES, $_MODULE) : $_MODULE; + + $source = $specific ? $specific : $this->name; + $string = str_replace('\'', '\\\'', $string); + $ret = $this->findTranslation($this->name, $string, $source); + return $ret; + } + + /* + * Reposition module + * + * @param boolean $id_hook Hook ID + * @param boolean $way Up (1) or Down (0) + * @param intger $position + */ + public function updatePosition($id_hook, $way, $position = NULL) + { + if (!$res = Db::getInstance()->ExecuteS(' + SELECT hm.`id_module`, hm.`position`, hm.`id_hook` + FROM `'._DB_PREFIX_.'hook_module` hm + WHERE hm.`id_hook` = '.(int)($id_hook).' + ORDER BY hm.`position` '.((int)($way) ? 'ASC' : 'DESC'))) + return false; + foreach ($res AS $key => $values) + if ((int)($values[$this->identifier]) == (int)($this->id)) + { + $k = $key ; + break ; + } + if (!isset($k) OR !isset($res[$k]) OR !isset($res[$k + 1])) + return false; + $from = $res[$k]; + $to = $res[$k + 1]; + + if (isset($position) and !empty($position)) + $to['position'] = (int)($position); + + return (Db::getInstance()->Execute(' + UPDATE `'._DB_PREFIX_.'hook_module` + SET `position`= position '.($way ? '-1' : '+1').' + WHERE position between '.(int)(min(array($from['position'], $to['position']))) .' AND '.(int)(max(array($from['position'], $to['position']))).' + AND `id_hook`='.(int)($from['id_hook'])) + AND + Db::getInstance()->Execute(' + UPDATE `'._DB_PREFIX_.'hook_module` + SET `position`='.(int)($to['position']).' + WHERE `'.pSQL($this->identifier).'` = '.(int)($from[$this->identifier]).' AND `id_hook`='.(int)($to['id_hook'])) + ); + } + + /* + * Reorder modules position + * + * @param boolean $id_hook Hook ID + */ + public function cleanPositions($id_hook) + { + $result = Db::getInstance()->ExecuteS(' + SELECT `id_module` + FROM `'._DB_PREFIX_.'hook_module` + WHERE `id_hook` = '.(int)($id_hook).' + ORDER BY `position`'); + $sizeof = sizeof($result); + for ($i = 0; $i < $sizeof; ++$i) + Db::getInstance()->Execute(' + UPDATE `'._DB_PREFIX_.'hook_module` + SET `position` = '.(int)($i + 1).' + WHERE `id_hook` = '.(int)($id_hook).' + AND `id_module` = '.(int)($result[$i]['id_module'])); + return true; + } + + /* + * Return module position for a given hook + * + * @param boolean $id_hook Hook ID + * @return integer position + */ + public function getPosition($id_hook) + { + if (isset(Hook::$preloadModulesFromHooks)) + if (isset(Hook::$preloadModulesFromHooks[$id_hook])) + if (isset(Hook::$preloadModulesFromHooks[$id_hook]['module_position'][$this->id])) + return Hook::$preloadModulesFromHooks[$id_hook]['module_position'][$this->id]; + else + return 0; + $result = Db::getInstance()->getRow(' + SELECT `position` + FROM `'._DB_PREFIX_.'hook_module` + WHERE `id_hook` = '.(int)($id_hook).' + AND `id_module` = '.(int)($this->id)); + return $result['position']; + } + + public function displayError($error) + { + $output = ' +
    + '.$error.' +
    '; + $this->error = true; + return $output; + } + + public function displayConfirmation($string) + { + $output = ' +
    + '.$string.' +
    '; + return $output; + } + + /* + * Return exceptions for module in hook + * + * @param int $id_hook Hook ID + * @return array Exceptions + */ + protected static $exceptionsCache = NULL; + public function getExceptions($id_hook) + { + if (self::$exceptionsCache == NULL AND !is_array(self::$exceptionsCache)) + { + self::$exceptionsCache = array(); + $result = Db::getInstance()->ExecuteS(' + SELECT CONCAT(id_hook, \'-\', id_module) as `key`, `file_name` as value + FROM `'._DB_PREFIX_.'hook_module_exceptions`'); + foreach ($result as $row) + { + if (empty($row['value'])) + continue; + if (!array_key_exists($row['key'], self::$exceptionsCache)) + self::$exceptionsCache[$row['key']] = array(); + self::$exceptionsCache[$row['key']][] = array('file_name' => $row['value']); + } + } + return (array_key_exists((int)($id_hook).'-'.(int)($this->id), self::$exceptionsCache) ? self::$exceptionsCache[(int)($id_hook).'-'.(int)($this->id)] : array()); + } + + public static function isInstalled($moduleName) + { + Db::getInstance()->ExecuteS('SELECT `id_module` FROM `'._DB_PREFIX_.'module` WHERE `name` = \''.pSQL($moduleName).'\''); + return (bool)Db::getInstance()->NumRows(); + } + + public function isRegisteredInHook($hook) + { + if (!$this->id) + return false; + + return Db::getInstance()->getValue(' + SELECT COUNT(*) + FROM `'._DB_PREFIX_.'hook_module` hm + LEFT JOIN `'._DB_PREFIX_.'hook` h ON (h.`id_hook` = hm.`id_hook`) + WHERE h.`name` = \''.pSQL($hook).'\' + AND hm.`id_module` = '.(int)($this->id) + ); + } + + /* + ** Template management (display, overload, cache) + */ + protected static function _isTemplateOverloadedStatic($moduleName, $template) + { + if (Tools::file_exists_cache(_PS_THEME_DIR_.'modules/'.$moduleName.'/'.$template)) + return true; + elseif (Tools::file_exists_cache(_PS_MODULE_DIR_.$moduleName.'/'.$template)) + return false; + return NULL; + } + + protected function _isTemplateOverloaded($template) + { + return self::_isTemplateOverloadedStatic($this->name, $template); + } + + public static function display($file, $template, $cacheId = NULL, $compileId = NULL) + { + global $smarty; + + if (Configuration::get('PS_FORCE_SMARTY_2')) /* Keep a backward compatibility for Smarty v2 */ + { + $previousTemplate = $smarty->currentTemplate; + $smarty->currentTemplate = substr(basename($template), 0, -4); + } + $smarty->assign('module_dir', __PS_BASE_URI__.'modules/'.basename($file, '.php').'/'); + if (($overloaded = self::_isTemplateOverloadedStatic(basename($file, '.php'), $template)) === NULL) + $result = Tools::displayError('No template found for module').' '.basename($file,'.php'); + else + { + $smarty->assign('module_template_dir', ($overloaded ? _THEME_DIR_ : __PS_BASE_URI__).'modules/'.basename($file, '.php').'/'); + $result = $smarty->fetch(($overloaded ? _PS_THEME_DIR_.'modules/'.basename($file, '.php') : _PS_MODULE_DIR_.basename($file, '.php')).'/'.$template, $cacheId, $compileId); + } + if (Configuration::get('PS_FORCE_SMARTY_2')) /* Keep a backward compatibility for Smarty v2 */ + $smarty->currentTemplate = $previousTemplate; + return $result; + } + + protected function _getApplicableTemplateDir($template) + { + return $this->_isTemplateOverloaded($template) ? _PS_THEME_DIR_ : _PS_MODULE_DIR_.$this->name.'/'; + } + + public function isCached($template, $cacheId = NULL, $compileId = NULL) + { + global $smarty; + + /* Use Smarty 3 API calls */ + if (!Configuration::get('PS_FORCE_SMARTY_2')) /* PHP version > 5.1.2 */ + return $smarty->isCached($this->_getApplicableTemplateDir($template).$template, $cacheId, $compileId); + /* or keep a backward compatibility if PHP version < 5.1.2 */ + else + return $smarty->is_cached($this->_getApplicableTemplateDir($template).$template, $cacheId, $compileId); + } + + protected function _clearCache($template, $cacheId = NULL, $compileId = NULL) + { + global $smarty; + Tools::clearCache($smarty); + } + + protected function _generateConfigXml() + { + $xml = ' + + '.$this->name.' + displayName).']]> + version.']]> + description).']]> + author).']]> + tab).']]>'.(isset($this->confirmUninstall) ? "\n\t".''.$this->confirmUninstall.'' : '').' + '.(int)method_exists($this, 'getContent').' + '.(int)$this->need_instance.''.(isset($this->limited_countries) ? "\n\t".''.(sizeof($this->limited_countries) == 1 ? $this->limited_countries[0] : '').'' : '').' +'; + if (is_writable(_PS_MODULE_DIR_.$this->name.'/')) + file_put_contents(_PS_MODULE_DIR_.$this->name.'/config.xml', $xml); + } + + /** + * @param string $hook_name + * @return bool if module can be transplanted on hook + */ + public function isHookableOn($hook_name) + { + return is_callable(array($this, 'hook'.ucfirst($hook_name))); + } +} + diff --git a/modules/autoupgrade/Tools14.php b/modules/autoupgrade/Tools14.php new file mode 100644 index 000000000..0822e78d5 --- /dev/null +++ b/modules/autoupgrade/Tools14.php @@ -0,0 +1,2449 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ +if(!class_exists('Tools14',false)) +{ +class Tools14Core +{ + protected static $file_exists_cache = array(); + protected static $_forceCompile; + protected static $_caching; + + /** + * Random password generator + * + * @param integer $length Desired length (optional) + * @return string Password + */ + public static function passwdGen($length = 8) + { + $str = 'abcdefghijkmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + for ($i = 0, $passwd = ''; $i < $length; $i++) + $passwd .= self::substr($str, mt_rand(0, self::strlen($str) - 1), 1); + return $passwd; + } + + /** + * Redirect user to another page + * + * @param string $url Desired URL + * @param string $baseUri Base URI (optional) + */ + public static function redirect($url, $baseUri = __PS_BASE_URI__) + { + if (strpos($url, 'http://') === FALSE && strpos($url, 'https://') === FALSE) + { + global $link; + if (strpos($url, $baseUri) !== FALSE && strpos($url, $baseUri) == 0) + $url = substr($url, strlen($baseUri)); + $explode = explode('?', $url, 2); + $url = $link->getPageLink($explode[0], true); + if (isset($explode[1])) + $url .= '?'.$explode[1]; + $baseUri = ''; + } + + if (isset($_SERVER['HTTP_REFERER']) AND ($url == $_SERVER['HTTP_REFERER'])) + header('Location: '.$_SERVER['HTTP_REFERER']); + else + header('Location: '.$baseUri.$url); + exit; + } + + /** + * Redirect url wich allready PS_BASE_URI + * + * @param string $url Desired URL + */ + public static function redirectLink($url) + { + if (!preg_match('@^https?://@i', $url)) + { + global $link; + if (strpos($url, __PS_BASE_URI__) !== FALSE && strpos($url, __PS_BASE_URI__) == 0) + $url = substr($url, strlen(__PS_BASE_URI__)); + $explode = explode('?', $url, 2); + $url = $link->getPageLink($explode[0]); + if (isset($explode[1])) + $url .= '?'.$explode[1]; + } + + header('Location: '.$url); + exit; + } + + /** + * Redirect user to another admin page + * + * @param string $url Desired URL + */ + public static function redirectAdmin($url) + { + header('Location: '.$url); + exit; + } + + /** + * getProtocol return the set protocol according to configuration (http[s]) + * @param Boolean true if require ssl + * @return String (http|https) + */ + public static function getProtocol($use_ssl = null) + { + return (!is_null($use_ssl) && $use_ssl ? 'https://' : 'http://'); + } + + /** + * getHttpHost return the current host used, with the protocol (http or https) if $http is true + * This function should not be used to choose http or https domain name. + * Use Tools::getShopDomain() or Tools::getShopDomainSsl instead + * + * @param boolean $http + * @param boolean $entities + * @return string host + */ + public static function getHttpHost($http = false, $entities = false) + { + $host = (isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : $_SERVER['HTTP_HOST']); + if ($entities) + $host = htmlspecialchars($host, ENT_COMPAT, 'UTF-8'); + if ($http) + $host = (Configuration::get('PS_SSL_ENABLED') ? 'https://' : 'http://').$host; + return $host; + } + + /** + * getShopDomain returns domain name according to configuration and ignoring ssl + * + * @param boolean $http if true, return domain name with protocol + * @param boolean $entities if true, + * @return string domain + */ + public static function getShopDomain($http = false, $entities = false) + { + if (!($domain = Configuration::get('PS_SHOP_DOMAIN'))) + $domain = self::getHttpHost(); + if ($entities) + $domain = htmlspecialchars($domain, ENT_COMPAT, 'UTF-8'); + if ($http) + $domain = 'http://'.$domain; + return $domain; + } + + /** + * getShopDomainSsl returns domain name according to configuration and depending on ssl activation + * + * @param boolean $http if true, return domain name with protocol + * @param boolean $entities if true, + * @return string domain + */ + public static function getShopDomainSsl($http = false, $entities = false) + { + if (!($domain = Configuration::get('PS_SHOP_DOMAIN_SSL'))) + $domain = self::getHttpHost(); + if ($entities) + $domain = htmlspecialchars($domain, ENT_COMPAT, 'UTF-8'); + if ($http) + $domain = (Configuration::get('PS_SSL_ENABLED') ? 'https://' : 'http://').$domain; + return $domain; + } + + /** + * Get the server variable SERVER_NAME + * + * @return string server name + */ + static function getServerName() + { + if (isset($_SERVER['HTTP_X_FORWARDED_SERVER']) AND $_SERVER['HTTP_X_FORWARDED_SERVER']) + return $_SERVER['HTTP_X_FORWARDED_SERVER']; + return $_SERVER['SERVER_NAME']; + } + + /** + * Get the server variable REMOTE_ADDR, or the first ip of HTTP_X_FORWARDED_FOR (when using proxy) + * + * @return string $remote_addr ip of client + */ + static function getRemoteAddr() + { + // This condition is necessary when using CDN, don't remove it. + if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) AND $_SERVER['HTTP_X_FORWARDED_FOR'] AND (!isset($_SERVER['REMOTE_ADDR']) OR preg_match('/^127\..*/i', trim($_SERVER['REMOTE_ADDR'])) OR preg_match('/^172\.16.*/i', trim($_SERVER['REMOTE_ADDR'])) OR preg_match('/^192\.168\.*/i', trim($_SERVER['REMOTE_ADDR'])) OR preg_match('/^10\..*/i', trim($_SERVER['REMOTE_ADDR'])))) + { + if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',')) + { + $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + return $ips[0]; + } + else + return $_SERVER['HTTP_X_FORWARDED_FOR']; + } + return $_SERVER['REMOTE_ADDR']; + } + + /** + * Check if the current page use SSL connection on not + * + * @return bool uses SSL + */ + public static function usingSecureMode() + { + return !(empty($_SERVER['HTTPS']) OR strtolower($_SERVER['HTTPS']) == 'off'); + } + + /** + * Get the current url prefix protocol (https/http) + * + * @return string protocol + */ + public static function getCurrentUrlProtocolPrefix() + { + if (self::usingSecureMode()) + return 'https://'; + else + return 'http://'; + } + + /** + * Secure an URL referrer + * + * @param string $referrer URL referrer + * @return secured referrer + */ + public static function secureReferrer($referrer) + { + if (preg_match('/^http[s]?:\/\/'.self::getServerName().'(:'._PS_SSL_PORT_.')?\/.*$/Ui', $referrer)) + return $referrer; + return __PS_BASE_URI__; + } + + /** + * Get a value from $_POST / $_GET + * if unavailable, take a default value + * + * @param string $key Value key + * @param mixed $defaultValue (optional) + * @return mixed Value + */ + public static function getValue($key, $defaultValue = false) + { + if (!isset($key) OR empty($key) OR !is_string($key)) + return false; + $ret = (isset($_POST[$key]) ? $_POST[$key] : (isset($_GET[$key]) ? $_GET[$key] : $defaultValue)); + + if (is_string($ret) === true) + $ret = urldecode(preg_replace('/((\%5C0+)|(\%00+))/i', '', urlencode($ret))); + return !is_string($ret)? $ret : stripslashes($ret); + } + + public static function getIsset($key) + { + if (!isset($key) OR empty($key) OR !is_string($key)) + return false; + return isset($_POST[$key]) ? true : (isset($_GET[$key]) ? true : false); + } + + /** + * Change language in cookie while clicking on a flag + * + * @return string iso code + */ + public static function setCookieLanguage() + { + global $cookie; + + /* If language does not exist or is disabled, erase it */ + if ($cookie->id_lang) + { + $lang = new Language((int)$cookie->id_lang); + if (!Validate::isLoadedObject($lang) OR !$lang->active) + $cookie->id_lang = NULL; + } + + /* Automatically detect language if not already defined */ + if (!$cookie->id_lang AND isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) + { + $array = explode(',', self::strtolower($_SERVER['HTTP_ACCEPT_LANGUAGE'])); + if (self::strlen($array[0]) > 2) + { + $tab = explode('-', $array[0]); + $string = $tab[0]; + } + else + $string = $array[0]; + if (Validate::isLanguageIsoCode($string)) + { + $lang = new Language((int)(Language::getIdByIso($string))); + if (Validate::isLoadedObject($lang) AND $lang->active) + $cookie->id_lang = (int)($lang->id); + } + } + + /* If language file not present, you must use default language file */ + if (!$cookie->id_lang OR !Validate::isUnsignedId($cookie->id_lang)) + $cookie->id_lang = (int)(Configuration::get('PS_LANG_DEFAULT')); + + $iso = Language::getIsoById((int)$cookie->id_lang); + @include_once(_PS_THEME_DIR_.'lang/'.$iso.'.php'); + + return $iso; + } + + /** + * Set cookie id_lang + */ + public static function switchLanguage() + { + global $cookie; + + if ($id_lang = (int)(self::getValue('id_lang')) AND Validate::isUnsignedId($id_lang)) + $cookie->id_lang = $id_lang; + } + + /** + * Set cookie currency from POST or default currency + * + * @return Currency object + */ + public static function setCurrency() + { + global $cookie; + + if (self::isSubmit('SubmitCurrency')) + if (isset($_POST['id_currency']) AND is_numeric($_POST['id_currency'])) + { + $currency = Currency::getCurrencyInstance((int)($_POST['id_currency'])); + if (is_object($currency) AND $currency->id AND !$currency->deleted) + $cookie->id_currency = (int)($currency->id); + } + + if ((int)$cookie->id_currency) + { + $currency = Currency::getCurrencyInstance((int)$cookie->id_currency); + if (is_object($currency) AND (int)$currency->id AND (int)$currency->deleted != 1 AND $currency->active) + return $currency; + } + $currency = Currency::getCurrencyInstance((int)(Configuration::get('PS_CURRENCY_DEFAULT'))); + if (is_object($currency) AND $currency->id) + $cookie->id_currency = (int)($currency->id); + return $currency; + } + + /** + * Return price with currency sign for a given product + * + * @param float $price Product price + * @param object $currency Current currency (object, id_currency, NULL => getCurrent()) + * @return string Price correctly formated (sign, decimal separator...) + */ + public static function displayPrice($price, $currency = NULL, $no_utf8 = false) + { + if ($currency === NULL) + $currency = Currency::getCurrent(); + /* if you modified this function, don't forget to modify the Javascript function formatCurrency (in tools.js) */ + if (is_int($currency)) + $currency = Currency::getCurrencyInstance((int)($currency)); + $c_char = (is_array($currency) ? $currency['sign'] : $currency->sign); + $c_format = (is_array($currency) ? $currency['format'] : $currency->format); + $c_decimals = (is_array($currency) ? (int)($currency['decimals']) : (int)($currency->decimals)) * _PS_PRICE_DISPLAY_PRECISION_; + $c_blank = (is_array($currency) ? $currency['blank'] : $currency->blank); + $blank = ($c_blank ? ' ' : ''); + $ret = 0; + if (($isNegative = ($price < 0))) + $price *= -1; + $price = self::ps_round($price, $c_decimals); + switch ($c_format) + { + /* X 0,000.00 */ + case 1: + $ret = $c_char.$blank.number_format($price, $c_decimals, '.', ','); + break; + /* 0 000,00 X*/ + case 2: + $ret = number_format($price, $c_decimals, ',', ' ').$blank.$c_char; + break; + /* X 0.000,00 */ + case 3: + $ret = $c_char.$blank.number_format($price, $c_decimals, ',', '.'); + break; + /* 0,000.00 X */ + case 4: + $ret = number_format($price, $c_decimals, '.', ',').$blank.$c_char; + break; + } + if ($isNegative) + $ret = '-'.$ret; + if ($no_utf8) + return str_replace('€', chr(128), $ret); + return $ret; + } + + public static function displayPriceSmarty($params, &$smarty) + { + if (array_key_exists('currency', $params)) + { + $currency = Currency::getCurrencyInstance((int)($params['currency'])); + if (Validate::isLoadedObject($currency)) + return self::displayPrice($params['price'], $currency, false); + } + return self::displayPrice($params['price']); + } + + /** + * Return price converted + * + * @param float $price Product price + * @param object $currency Current currency object + * @param boolean $to_currency convert to currency or from currency to default currency + */ + public static function convertPrice($price, $currency = NULL, $to_currency = true) + { + if ($currency === NULL) + $currency = Currency::getCurrent(); + elseif (is_numeric($currency)) + $currency = Currency::getCurrencyInstance($currency); + + $c_id = (is_array($currency) ? $currency['id_currency'] : $currency->id); + $c_rate = (is_array($currency) ? $currency['conversion_rate'] : $currency->conversion_rate); + + if ($c_id != (int)(Configuration::get('PS_CURRENCY_DEFAULT'))) + { + if ($to_currency) + $price *= $c_rate; + else + $price /= $c_rate; + } + + return $price; + } + + + + /** + * Display date regarding to language preferences + * + * @param array $params Date, format... + * @param object $smarty Smarty object for language preferences + * @return string Date + */ + public static function dateFormat($params, &$smarty) + { + return self::displayDate($params['date'], $smarty->ps_language->id, (isset($params['full']) ? $params['full'] : false)); + } + + /** + * Display date regarding to language preferences + * + * @param string $date Date to display format UNIX + * @param integer $id_lang Language id + * @param boolean $full With time or not (optional) + * @return string Date + */ + public static function displayDate($date, $id_lang, $full = false, $separator = '-') + { + if (!$date OR !strtotime($date)) + return $date; + if (!Validate::isDate($date) OR !Validate::isBool($full)) + die (self::displayError('Invalid date')); + $tmpTab = explode($separator, substr($date, 0, 10)); + $hour = ' '.substr($date, -8); + + $language = Language::getLanguage((int)($id_lang)); + if ($language AND strtolower($language['iso_code']) == 'fr') + return ($tmpTab[2].'-'.$tmpTab[1].'-'.$tmpTab[0].($full ? $hour : '')); + else + return ($tmpTab[0].'-'.$tmpTab[1].'-'.$tmpTab[2].($full ? $hour : '')); + } + + /** + * Sanitize a string + * + * @param string $string String to sanitize + * @param boolean $full String contains HTML or not (optional) + * @return string Sanitized string + */ + public static function safeOutput($string, $html = false) + { + if (!$html) + $string = @htmlentities(strip_tags($string), ENT_QUOTES, 'utf-8'); + return $string; + } + + public static function htmlentitiesUTF8($string, $type = ENT_QUOTES) + { + if (is_array($string)) + return array_map(array('Tools', 'htmlentitiesUTF8'), $string); + return htmlentities($string, $type, 'utf-8'); + } + + public static function htmlentitiesDecodeUTF8($string) + { + if (is_array($string)) + return array_map(array('Tools', 'htmlentitiesDecodeUTF8'), $string); + return html_entity_decode($string, ENT_QUOTES, 'utf-8'); + } + + public static function safePostVars() + { + $_POST = array_map(array('Tools', 'htmlentitiesUTF8'), $_POST); + } + + /** + * Delete directory and subdirectories + * + * @param string $dirname Directory name + */ + public static function deleteDirectory($dirname, $delete_self = true) + { + $dirname = rtrim($dirname, '/').'/'; + $files = scandir($dirname); + foreach ($files as $file) + if ($file != '.' AND $file != '..') + { + if (is_dir($dirname.$file)) + self::deleteDirectory($dirname.$file, true); + elseif (file_exists($dirname.$file)) + unlink($dirname.$file); + } + if ($delete_self) + rmdir($dirname); + } + + /** + * Display an error according to an error code + * + * @param integer $code Error code + */ + public static function displayError($string = 'Fatal error', $htmlentities = true) + { + global $_ERRORS, $cookie; + + $iso = strtolower(Language::getIsoById((is_object($cookie) AND $cookie->id_lang) ? (int)$cookie->id_lang : (int)Configuration::get('PS_LANG_DEFAULT'))); + @include_once(_PS_TRANSLATIONS_DIR_.$iso.'/errors.php'); + + if (defined('_PS_MODE_DEV_') AND _PS_MODE_DEV_ AND $string == 'Fatal error') + return ('
    '.print_r(debug_backtrace(), true).'
    '); + if (!is_array($_ERRORS)) + return str_replace('"', '"', $string); + $key = md5(str_replace('\'', '\\\'', $string)); + $str = (isset($_ERRORS) AND is_array($_ERRORS) AND key_exists($key, $_ERRORS)) ? ($htmlentities ? htmlentities($_ERRORS[$key], ENT_COMPAT, 'UTF-8') : $_ERRORS[$key]) : $string; + return str_replace('"', '"', stripslashes($str)); + } + + /** + * Display an error with detailed object + * + * @param mixed $object + * @param boolean $kill + * @return $object if $kill = false; + */ + public static function dieObject($object, $kill = true) + { + echo '
    ';
    +		print_r($object);
    +		echo '

    '; + if ($kill) + die('END'); + return $object; + } + + /** + * ALIAS OF dieObject() - Display an error with detailed object + * + * @param object $object Object to display + */ + public static function d($object, $kill = true) + { + return (self::dieObject($object, $kill = true)); + } + + /** + * ALIAS OF dieObject() - Display an error with detailed object but don't stop the execution + * + * @param object $object Object to display + */ + public static function p($object) + { + return (self::dieObject($object, false)); + } + + /** + * Check if submit has been posted + * + * @param string $submit submit name + */ + public static function isSubmit($submit) + { + return ( + isset($_POST[$submit]) OR isset($_POST[$submit.'_x']) OR isset($_POST[$submit.'_y']) + OR isset($_GET[$submit]) OR isset($_GET[$submit.'_x']) OR isset($_GET[$submit.'_y']) + ); + } + + /** + * Get meta tages for a given page + * + * @param integer $id_lang Language id + * @return array Meta tags + */ + public static function getMetaTags($id_lang, $page_name) + { + global $maintenance; + + if (!(isset($maintenance) AND (!in_array(self::getRemoteAddr(), explode(',', Configuration::get('PS_MAINTENANCE_IP')))))) + { + /* Products specifics meta tags */ + if ($id_product = self::getValue('id_product')) + { + $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(' + SELECT `name`, `meta_title`, `meta_description`, `meta_keywords`, `description_short` + FROM `'._DB_PREFIX_.'product` p + LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (pl.`id_product` = p.`id_product`) + WHERE pl.id_lang = '.(int)($id_lang).' AND pl.id_product = '.(int)($id_product).' AND p.active = 1'); + if ($row) + { + if (empty($row['meta_description'])) + $row['meta_description'] = strip_tags($row['description_short']); + return self::completeMetaTags($row, $row['name']); + } + } + + /* Categories specifics meta tags */ + elseif ($id_category = self::getValue('id_category')) + { + $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(' + SELECT `name`, `meta_title`, `meta_description`, `meta_keywords`, `description` + FROM `'._DB_PREFIX_.'category_lang` + WHERE id_lang = '.(int)($id_lang).' AND id_category = '.(int)($id_category)); + if ($row) + { + if (empty($row['meta_description'])) + $row['meta_description'] = strip_tags($row['description']); + return self::completeMetaTags($row, $row['name']); + } + } + + /* Manufacturers specifics meta tags */ + elseif ($id_manufacturer = self::getValue('id_manufacturer')) + { + $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(' + SELECT `name`, `meta_title`, `meta_description`, `meta_keywords` + FROM `'._DB_PREFIX_.'manufacturer_lang` ml + LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (ml.`id_manufacturer` = m.`id_manufacturer`) + WHERE ml.id_lang = '.(int)($id_lang).' AND ml.id_manufacturer = '.(int)($id_manufacturer)); + if ($row) + { + if (empty($row['meta_description'])) + $row['meta_description'] = strip_tags($row['meta_description']); + if (!empty($row['meta_title'])) + $row['meta_title'] = $row['meta_title'].' - '.Configuration::get('PS_SHOP_NAME'); + return self::completeMetaTags($row, $row['name']); + } + } + + /* Suppliers specifics meta tags */ + elseif ($id_supplier = self::getValue('id_supplier')) + { + $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(' + SELECT `name`, `meta_title`, `meta_description`, `meta_keywords` + FROM `'._DB_PREFIX_.'supplier_lang` sl + LEFT JOIN `'._DB_PREFIX_.'supplier` s ON (sl.`id_supplier` = s.`id_supplier`) + WHERE sl.id_lang = '.(int)($id_lang).' AND sl.id_supplier = '.(int)($id_supplier)); + + if ($row) + { + if (empty($row['meta_description'])) + $row['meta_description'] = strip_tags($row['meta_description']); + if (!empty($row['meta_title'])) + $row['meta_title'] = $row['meta_title'].' - '.Configuration::get('PS_SHOP_NAME'); + return self::completeMetaTags($row, $row['name']); + } + } + + /* CMS specifics meta tags */ + elseif ($id_cms = self::getValue('id_cms')) + { + $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(' + SELECT `meta_title`, `meta_description`, `meta_keywords` + FROM `'._DB_PREFIX_.'cms_lang` + WHERE id_lang = '.(int)($id_lang).' AND id_cms = '.(int)($id_cms)); + if ($row) + { + $row['meta_title'] = $row['meta_title'].' - '.Configuration::get('PS_SHOP_NAME'); + return self::completeMetaTags($row, $row['meta_title']); + } + } + + /* CMS category specifics meta tags */ + elseif ($id_cms = self::getValue('id_cms_category')) + { + $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow(' + SELECT `meta_title`, `meta_description`, `meta_keywords` + FROM `'._DB_PREFIX_.'cms_category_lang` + WHERE id_lang = '.(int)($id_lang).' AND id_cms_category = '.(int)($id_cms)); + if ($row) + { + $row['meta_title'] = $row['meta_title'].' - '.Configuration::get('PS_SHOP_NAME'); + return self::completeMetaTags($row, $row['meta_title']); + } + } + } + + /* Default meta tags */ + return self::getHomeMetaTags($id_lang, $page_name); + } + + /** + * Get meta tags for a given page + * + * @param integer $id_lang Language id + * @return array Meta tags + */ + public static function getHomeMetaTags($id_lang, $page_name) + { + /* Metas-tags */ + $metas = Meta::getMetaByPage($page_name, $id_lang); + $ret['meta_title'] = (isset($metas['title']) AND $metas['title']) ? $metas['title'].' - '.Configuration::get('PS_SHOP_NAME') : Configuration::get('PS_SHOP_NAME'); + $ret['meta_description'] = (isset($metas['description']) AND $metas['description']) ? $metas['description'] : ''; + $ret['meta_keywords'] = (isset($metas['keywords']) AND $metas['keywords']) ? $metas['keywords'] : ''; + return $ret; + } + + + public static function completeMetaTags($metaTags, $defaultValue) + { + global $cookie; + + if ($metaTags['meta_title'] == NULL) + $metaTags['meta_title'] = $defaultValue.' - '.Configuration::get('PS_SHOP_NAME'); + if ($metaTags['meta_description'] == NULL) + $metaTags['meta_description'] = Configuration::get('PS_META_DESCRIPTION', (int)($cookie->id_lang)) ? Configuration::get('PS_META_DESCRIPTION', (int)($cookie->id_lang)) : ''; + if ($metaTags['meta_keywords'] == NULL) + $metaTags['meta_keywords'] = Configuration::get('PS_META_KEYWORDS', (int)($cookie->id_lang)) ? Configuration::get('PS_META_KEYWORDS', (int)($cookie->id_lang)) : ''; + return $metaTags; + } + + /** + * Encrypt password + * + * @param object $object Object to display + */ + public static function encrypt($passwd) + { + return md5(pSQL(_COOKIE_KEY_.$passwd)); + } + + /** + * Get token to prevent CSRF + * + * @param string $token token to encrypt + */ + public static function getToken($page = true) + { + global $cookie; + if ($page === true) + return (self::encrypt($cookie->id_customer.$cookie->passwd.$_SERVER['SCRIPT_NAME'])); + else + return (self::encrypt($cookie->id_customer.$cookie->passwd.$page)); + } + + /** + * Encrypt password + * + * @param object $object Object to display + */ + public static function getAdminToken($string) + { + return !empty($string) ? self::encrypt($string) : false; + } + public static function getAdminTokenLite($tab) + { + global $cookie; + return self::getAdminToken($tab.(int)Tab::getIdFromClassName($tab).(int)$cookie->id_employee); + } + + /** + * Get the user's journey + * + * @param integer $id_category Category ID + * @param string $path Path end + * @param boolean $linkOntheLastItem Put or not a link on the current category + * @param string [optionnal] $categoryType defined what type of categories is used (products or cms) + */ + public static function getPath($id_category, $path = '', $linkOntheLastItem = false, $categoryType = 'products') + { + global $link, $cookie; + + if ($id_category == 1) + return ''.$path.''; + + $pipe = Configuration::get('PS_NAVIGATION_PIPE'); + if (empty($pipe)) + $pipe = '>'; + + $fullPath = ''; + + if ($categoryType === 'products') + { + $category = Db::getInstance()->getRow(' + SELECT id_category, level_depth, nleft, nright + FROM '._DB_PREFIX_.'category + WHERE id_category = '.(int)$id_category); + + if (isset($category['id_category'])) + { + $categories = Db::getInstance()->ExecuteS(' + SELECT c.id_category, cl.name, cl.link_rewrite + FROM '._DB_PREFIX_.'category c + LEFT JOIN '._DB_PREFIX_.'category_lang cl ON (cl.id_category = c.id_category) + WHERE c.nleft <= '.(int)$category['nleft'].' AND c.nright >= '.(int)$category['nright'].' AND cl.id_lang = '.(int)($cookie->id_lang).' AND c.id_category != 1 + ORDER BY c.level_depth ASC + LIMIT '.(int)$category['level_depth']); + + $n = 1; + $nCategories = (int)sizeof($categories); + foreach ($categories AS $category) + { + $fullPath .= + (($n < $nCategories OR $linkOntheLastItem) ? '' : ''). + htmlentities($category['name'], ENT_NOQUOTES, 'UTF-8'). + (($n < $nCategories OR $linkOntheLastItem) ? '' : ''). + (($n++ != $nCategories OR !empty($path)) ? ''.$pipe.'' : ''); + } + + return $fullPath.$path; + } + } + elseif ($categoryType === 'CMS') + { + $category = new CMSCategory((int)($id_category), (int)($cookie->id_lang)); + if (!Validate::isLoadedObject($category)) + die(self::displayError()); + $categoryLink = $link->getCMSCategoryLink($category); + + if ($path != $category->name) + $fullPath .= ''.htmlentities($category->name, ENT_NOQUOTES, 'UTF-8').''.$pipe.''.$path; + else + $fullPath = ($linkOntheLastItem ? '' : '').htmlentities($path, ENT_NOQUOTES, 'UTF-8').($linkOntheLastItem ? '' : ''); + + return self::getPath((int)($category->id_parent), $fullPath, $linkOntheLastItem, $categoryType); + } + } + + /** + * @param string [optionnal] $type_cat defined what type of categories is used (products or cms) + */ + public static function getFullPath($id_category, $end, $type_cat = 'products') + { + global $cookie; + + $pipe = (Configuration::get('PS_NAVIGATION_PIPE') ? Configuration::get('PS_NAVIGATION_PIPE') : '>'); + + if ($type_cat === 'products') + $category = new Category((int)($id_category), (int)($cookie->id_lang)); + elseif ($type_cat === 'CMS') + $category = new CMSCategory((int)($id_category), (int)($cookie->id_lang)); + + if (!Validate::isLoadedObject($category)) + $id_category = 1; + if ($id_category == 1) + return htmlentities($end, ENT_NOQUOTES, 'UTF-8'); + + return self::getPath($id_category, $category->name, true, $type_cat).''.$pipe.' '.htmlentities($end, ENT_NOQUOTES, 'UTF-8').''; + } + + /** + * @deprecated + */ + public static function getCategoriesTotal() + { + Tools::displayAsDeprecated(); + $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('SELECT COUNT(`id_category`) AS total FROM `'._DB_PREFIX_.'category`'); + return (int)($row['total']); + } + + /** + * @deprecated + */ + public static function getProductsTotal() + { + Tools::displayAsDeprecated(); + $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('SELECT COUNT(`id_product`) AS total FROM `'._DB_PREFIX_.'product`'); + return (int)($row['total']); + } + + /** + * @deprecated + */ + public static function getCustomersTotal() + { + Tools::displayAsDeprecated(); + $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('SELECT COUNT(`id_customer`) AS total FROM `'._DB_PREFIX_.'customer`'); + return (int)($row['total']); + } + + /** + * @deprecated + */ + public static function getOrdersTotal() + { + Tools::displayAsDeprecated(); + $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('SELECT COUNT(`id_order`) AS total FROM `'._DB_PREFIX_.'orders`'); + return (int)($row['total']); + } + + /* + ** Historyc translation function kept for compatibility + ** Removing soon + */ + public static function historyc_l($key, $translations) + { + global $cookie; + if (!$translations OR !is_array($translations)) + die(self::displayError()); + $iso = strtoupper(Language::getIsoById($cookie->id_lang)); + $lang = key_exists($iso, $translations) ? $translations[$iso] : false; + return (($lang AND is_array($lang) AND key_exists($key, $lang)) ? stripslashes($lang[$key]) : $key); + } + + + /** + * Return the friendly url from the provided string + * + * @param string $str + * @param bool $utf8_decode => needs to be marked as deprecated + * @return string + */ + public static function link_rewrite($str, $utf8_decode = false) + { + return self::str2url($str); + } + + /** + * Return a friendly url made from the provided string + * If the mbstring library is available, the output is the same as the js function of the same name + * + * @param string $str + * @return string + */ + public static function str2url($str) + { + if (function_exists('mb_strtolower')) + $str = mb_strtolower($str, 'utf-8'); + + $str = trim($str); + $str = preg_replace('/[\x{0105}\x{0104}\x{00E0}\x{00E1}\x{00E2}\x{00E3}\x{00E4}\x{00E5}]/u','a', $str); + $str = preg_replace('/[\x{00E7}\x{010D}\x{0107}\x{0106}]/u','c', $str); + $str = preg_replace('/[\x{010F}]/u','d', $str); + $str = preg_replace('/[\x{00E8}\x{00E9}\x{00EA}\x{00EB}\x{011B}\x{0119}\x{0118}]/u','e', $str); + $str = preg_replace('/[\x{00EC}\x{00ED}\x{00EE}\x{00EF}]/u','i', $str); + $str = preg_replace('/[\x{0142}\x{0141}\x{013E}\x{013A}]/u','l', $str); + $str = preg_replace('/[\x{00F1}\x{0148}]/u','n', $str); + $str = preg_replace('/[\x{00F2}\x{00F3}\x{00F4}\x{00F5}\x{00F6}\x{00F8}\x{00D3}]/u','o', $str); + $str = preg_replace('/[\x{0159}\x{0155}]/u','r', $str); + $str = preg_replace('/[\x{015B}\x{015A}\x{0161}]/u','s', $str); + $str = preg_replace('/[\x{00DF}]/u','ss', $str); + $str = preg_replace('/[\x{0165}]/u','t', $str); + $str = preg_replace('/[\x{00F9}\x{00FA}\x{00FB}\x{00FC}\x{016F}]/u','u', $str); + $str = preg_replace('/[\x{00FD}\x{00FF}]/u','y', $str); + $str = preg_replace('/[\x{017C}\x{017A}\x{017B}\x{0179}\x{017E}]/u','z', $str); + $str = preg_replace('/[\x{00E6}]/u','ae', $str); + $str = preg_replace('/[\x{0153}]/u','oe', $str); + + // Remove all non-whitelist chars. + $str = preg_replace('/[^a-zA-Z0-9\s\'\:\/\[\]-]/','', $str); + $str = preg_replace('/[\s\'\:\/\[\]-]+/',' ', $str); + $str = preg_replace('/[ ]/','-', $str); + $str = preg_replace('/[\/]/','-', $str); + + // If it was not possible to lowercase the string with mb_strtolower, we do it after the transformations. + // This way we lose fewer special chars. + $str = strtolower($str); + + return $str; + } + + /** + * Truncate strings + * + * @param string $str + * @param integer $maxLen Max length + * @param string $suffix Suffix optional + * @return string $str truncated + */ + /* CAUTION : Use it only on module hookEvents. + ** For other purposes use the smarty function instead */ + public static function truncate($str, $maxLen, $suffix = '...') + { + if (self::strlen($str) <= $maxLen) + return $str; + $str = utf8_decode($str); + return (utf8_encode(substr($str, 0, $maxLen - self::strlen($suffix)).$suffix)); + } + + /** + * Generate date form + * + * @param integer $year Year to select + * @param integer $month Month to select + * @param integer $day Day to select + * @return array $tab html data with 3 cells :['days'], ['months'], ['years'] + * + */ + public static function dateYears() + { + for ($i = date('Y') - 10; $i >= 1900; $i--) + $tab[] = $i; + return $tab; + } + + public static function dateDays() + { + for ($i = 1; $i != 32; $i++) + $tab[] = $i; + return $tab; + } + + public static function dateMonths() + { + for ($i = 1; $i != 13; $i++) + $tab[$i] = date('F', mktime(0, 0, 0, $i, date('m'), date('Y'))); + return $tab; + } + + public static function hourGenerate($hours, $minutes, $seconds) + { + return implode(':', array($hours, $minutes, $seconds)); + } + + public static function dateFrom($date) + { + $tab = explode(' ', $date); + if (!isset($tab[1])) + $date .= ' ' . self::hourGenerate(0, 0, 0); + return $date; + } + + public static function dateTo($date) + { + $tab = explode(' ', $date); + if (!isset($tab[1])) + $date .= ' ' . self::hourGenerate(23, 59, 59); + return $date; + } + + /** + * @deprecated + */ + public static function getExactTime() + { + Tools::displayAsDeprecated(); + return time()+microtime(); + } + + static function strtolower($str) + { + if (is_array($str)) + return false; + if (function_exists('mb_strtolower')) + return mb_strtolower($str, 'utf-8'); + return strtolower($str); + } + + static function strlen($str, $encoding = 'UTF-8') + { + if (is_array($str)) + return false; + $str = html_entity_decode($str, ENT_COMPAT, 'UTF-8'); + if (function_exists('mb_strlen')) + return mb_strlen($str, $encoding); + return strlen($str); + } + + static function stripslashes($string) + { + if (_PS_MAGIC_QUOTES_GPC_) + $string = stripslashes($string); + return $string; + } + + static function strtoupper($str) + { + if (is_array($str)) + return false; + if (function_exists('mb_strtoupper')) + return mb_strtoupper($str, 'utf-8'); + return strtoupper($str); + } + + static function substr($str, $start, $length = false, $encoding = 'utf-8') + { + if (is_array($str)) + return false; + if (function_exists('mb_substr')) + return mb_substr($str, (int)($start), ($length === false ? self::strlen($str) : (int)($length)), $encoding); + return substr($str, $start, ($length === false ? self::strlen($str) : (int)($length))); + } + + static function ucfirst($str) + { + return self::strtoupper(self::substr($str, 0, 1)).self::substr($str, 1); + } + + public static function orderbyPrice(&$array, $orderWay) + { + foreach ($array as &$row) + $row['price_tmp'] = Product::getPriceStatic($row['id_product'], true, ((isset($row['id_product_attribute']) AND !empty($row['id_product_attribute'])) ? (int)($row['id_product_attribute']) : NULL), 2); + if (strtolower($orderWay) == 'desc') + uasort($array, 'cmpPriceDesc'); + else + uasort($array, 'cmpPriceAsc'); + foreach ($array as &$row) + unset($row['price_tmp']); + } + + public static function iconv($from, $to, $string) + { + if (function_exists('iconv')) + return iconv($from, $to.'//TRANSLIT', str_replace('¥', '¥', str_replace('£', '£', str_replace('€', '€', $string)))); + return html_entity_decode(htmlentities($string, ENT_NOQUOTES, $from), ENT_NOQUOTES, $to); + } + + public static function isEmpty($field) + { + return ($field === '' OR $field === NULL); + } + + /** + * @deprecated + **/ + public static function getTimezones($select = false) + { + Tools::displayAsDeprecated(); + + static $_cache = 0; + + // One select + if ($select) + { + // No cache + if (!$_cache) + { + $tmz = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('SELECT `name` FROM '._DB_PREFIX_.'timezone WHERE id_timezone = '.(int)($select)); + $_cache = $tmz['name']; + } + return $_cache; + } + + // Multiple select + $tmz = Db::getInstance(_PS_USE_SQL_SLAVE_)->s('SELECT * FROM '._DB_PREFIX_.'timezone'); + $tab = array(); + foreach ($tmz as $timezone) + $tab[$timezone['id_timezone']] = str_replace('_', ' ', $timezone['name']); + return $tab; + } + + /** + * @deprecated + **/ + public static function ps_set_magic_quotes_runtime($var) + { + Tools::displayAsDeprecated(); + + if (function_exists('set_magic_quotes_runtime')) + set_magic_quotes_runtime($var); + } + + public static function ps_round($value, $precision = 0) + { + $method = (int)(Configuration::get('PS_PRICE_ROUND_MODE')); + if ($method == PS_ROUND_UP) + return self::ceilf($value, $precision); + elseif ($method == PS_ROUND_DOWN) + return self::floorf($value, $precision); + return round($value, $precision); + } + + public static function ceilf($value, $precision = 0) + { + $precisionFactor = $precision == 0 ? 1 : pow(10, $precision); + $tmp = $value * $precisionFactor; + $tmp2 = (string)$tmp; + // If the current value has already the desired precision + if (strpos($tmp2, '.') === false) + return ($value); + if ($tmp2[strlen($tmp2) - 1] == 0) + return $value; + return ceil($tmp) / $precisionFactor; + } + + public static function floorf($value, $precision = 0) + { + $precisionFactor = $precision == 0 ? 1 : pow(10, $precision); + $tmp = $value * $precisionFactor; + $tmp2 = (string)$tmp; + // If the current value has already the desired precision + if (strpos($tmp2, '.') === false) + return ($value); + if ($tmp2[strlen($tmp2) - 1] == 0) + return $value; + return floor($tmp) / $precisionFactor; + } + + /** + * file_exists() wrapper with cache to speedup performance + * + * @param string $filename File name + * @return boolean Cached result of file_exists($filename) + */ + public static function file_exists_cache($filename) + { + if (!isset(self::$file_exists_cache[$filename])) + self::$file_exists_cache[$filename] = file_exists($filename); + return self::$file_exists_cache[$filename]; + } + + public static function file_get_contents($url, $useIncludePath = false, $streamContext = NULL) + { + if (in_array(ini_get('allow_url_fopen'), array('On', 'on', '1'))) + return file_get_contents($url, $useIncludePath, $streamContext); + elseif (function_exists('curl_init')) + { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_URL, $url); + $content = curl_exec($curl); + curl_close($curl); + return $content; + } + else + return false; + } + + public static function simplexml_load_file($url, $class_name = null) + { + if (in_array(ini_get('allow_url_fopen'), array('On', 'on', '1'))) + return simplexml_load_file($url, $class_name); + elseif (function_exists('curl_init')) + { + return simplexml_load_string(Tools::file_get_contents($url), $class_name); + } + else + return false; + } + + public static function minifyHTML($html_content) + { + if (strlen($html_content) > 0) + { + //set an alphabetical order for args + $html_content = preg_replace_callback( + '/(<[a-zA-Z0-9]+)((\s?[a-zA-Z0-9]+=[\"\\\'][^\"\\\']*[\"\\\']\s?)*)>/' + ,array('Tools', 'minifyHTMLpregCallback') + ,$html_content); + + require_once(_PS_TOOL_DIR_.'minify_html/minify_html.class.php'); + $html_content = str_replace(chr(194) . chr(160), ' ', $html_content); + $html_content = Minify_HTML::minify($html_content, array('xhtml', 'cssMinifier', 'jsMinifier')); + + if (Configuration::get('PS_HIGH_HTML_THEME_COMPRESSION')) + { + //$html_content = preg_replace('/"([^\>\s"]*)"/i', '$1', $html_content);//FIXME create a js bug + $html_content = preg_replace('/]*dtd\">/is', '', $html_content); + $html_content = preg_replace('/\s\>/is', '>', $html_content); + $html_content = str_replace('', '', $html_content); + $html_content = str_replace('', '', $html_content); + $html_content = str_replace('', '', $html_content); + $html_content = str_replace('', '', $html_content); + $html_content = str_replace('', '', $html_content); + $html_content = str_replace('', '', $html_content); + $html_content = str_replace('', '', $html_content); + //$html_content = str_replace('

    ', '', $html_content);//FIXME doesnt work... + $html_content = str_replace("\n", '', $html_content);//TODO with bellow + $html_content = str_replace('', '', $html_content); + $html_content = str_replace(''; + $iso = Db::getInstance()->getValue('SELECT iso_code FROM '._DB_PREFIX_.'lang WHERE `id_lang` = '.(int)($cookie->id_lang)); + if ($iso != 'en') + echo ''; + echo ''; +} + +/** + * Generate a new settings file, only transmitted parameters are updated + * + * @param string $baseUri Base URI + * @param string $theme Theme name (eg. default) + * @param array $arrayDB Parameters in order to connect to database + */ +function rewriteSettingsFile($baseUrls = NULL, $theme = NULL, $arrayDB = NULL) +{ + $defines = array(); + $defines['__PS_BASE_URI__'] = ($baseUrls AND $baseUrls['__PS_BASE_URI__']) ? $baseUrls['__PS_BASE_URI__'] : __PS_BASE_URI__; + $defines['_MEDIA_SERVER_1_'] = ($baseUrls AND isset($baseUrls['_MEDIA_SERVER_1_'])) ? $baseUrls['_MEDIA_SERVER_1_'] : _MEDIA_SERVER_1_; + $defines['_MEDIA_SERVER_2_'] = ($baseUrls AND isset($baseUrls['_MEDIA_SERVER_2_'])) ? $baseUrls['_MEDIA_SERVER_2_'] : _MEDIA_SERVER_2_; + $defines['_MEDIA_SERVER_3_'] = ($baseUrls AND isset($baseUrls['_MEDIA_SERVER_3_'])) ? $baseUrls['_MEDIA_SERVER_3_'] : _MEDIA_SERVER_3_; + $defines['_PS_CACHING_SYSTEM_'] = _PS_CACHING_SYSTEM_; + $defines['_PS_CACHE_ENABLED_'] = _PS_CACHE_ENABLED_; + $defines['_THEME_NAME_'] = $theme ? $theme : _THEME_NAME_; + $defines['_DB_NAME_'] = (($arrayDB AND isset($arrayDB['_DB_NAME_'])) ? $arrayDB['_DB_NAME_'] : _DB_NAME_); + $defines['_MYSQL_ENGINE_'] = (($arrayDB AND isset($arrayDB['_MYSQL_ENGINE_'])) ? $arrayDB['_MYSQL_ENGINE_'] : _MYSQL_ENGINE_); + $defines['_DB_SERVER_'] = (($arrayDB AND isset($arrayDB['_DB_SERVER_'])) ? $arrayDB['_DB_SERVER_'] : _DB_SERVER_); + $defines['_DB_USER_'] = (($arrayDB AND isset($arrayDB['_DB_USER_'])) ? $arrayDB['_DB_USER_'] : _DB_USER_); + $defines['_DB_PREFIX_'] = (($arrayDB AND isset($arrayDB['_DB_PREFIX_'])) ? $arrayDB['_DB_PREFIX_'] : _DB_PREFIX_); + $defines['_DB_PASSWD_'] = (($arrayDB AND isset($arrayDB['_DB_PASSWD_'])) ? $arrayDB['_DB_PASSWD_'] : _DB_PASSWD_); + $defines['_DB_TYPE_'] = (($arrayDB AND isset($arrayDB['_DB_TYPE_'])) ? $arrayDB['_DB_TYPE_'] : _DB_TYPE_); + $defines['_COOKIE_KEY_'] = addslashes(_COOKIE_KEY_); + $defines['_COOKIE_IV_'] = addslashes(_COOKIE_IV_); + if (defined('_RIJNDAEL_KEY_')) + $defines['_RIJNDAEL_KEY_'] = addslashes(_RIJNDAEL_KEY_); + if (defined('_RIJNDAEL_IV_')) + $defines['_RIJNDAEL_IV_'] = addslashes(_RIJNDAEL_IV_); + $defines['_PS_VERSION_'] = addslashes(_PS_VERSION_); + $content = " $value) + $content .= 'define(\''.$k.'\', \''.addslashes($value).'\');'."\n"; + $content .= "\n?>"; + if ($fd = @fopen(PS_ADMIN_DIR.'/../config/settings.inc.php', 'w')) + { + fwrite($fd, $content); + fclose($fd); + return true; + } + return false; +} + +/** + * Display SQL date in friendly format + * + * @param string $sqlDate Date in SQL format (YYYY-MM-DD HH:mm:ss) + * @param boolean $withTime Display both date and time + * @todo Several formats (french : DD-MM-YYYY) + */ +function displayDate($sqlDate, $withTime = false) +{ + return strftime('%Y-%m-%d'.($withTime ? ' %H:%M:%S' : ''), strtotime($sqlDate)); +} + +/** + * Return path to a product category + * + * @param string $urlBase Start URL + * @param integer $id_category Start category + * @param string $path Current path + * @param string $highlight String to highlight (in XHTML/CSS) + * @param string $type Category type (products/cms) + */ +function getPath($urlBase, $id_category, $path = '', $highlight = '', $categoryType = 'catalog') +{ + global $cookie; + + if ($categoryType == 'catalog') + { + $category = Db::getInstance()->getRow(' + SELECT id_category, level_depth, nleft, nright + FROM '._DB_PREFIX_.'category + WHERE id_category = '.(int)$id_category); + + if (isset($category['id_category'])) + { + $categories = Db::getInstance()->ExecuteS(' + SELECT c.id_category, cl.name, cl.link_rewrite + FROM '._DB_PREFIX_.'category c + LEFT JOIN '._DB_PREFIX_.'category_lang cl ON (cl.id_category = c.id_category) + WHERE c.nleft <= '.(int)$category['nleft'].' AND c.nright >= '.(int)$category['nright'].' AND cl.id_lang = '.(int)($cookie->id_lang).' + ORDER BY c.level_depth ASC + LIMIT '.(int)($category['level_depth'] + 1)); + + $fullPath = ''; + $n = 1; + $nCategories = (int)sizeof($categories); + foreach ($categories AS $category) + { + $edit = ' '; + $fullPath .= $edit. + ($n < $nCategories ? '' : ''). + (!empty($highlight) ? str_ireplace($highlight, ''.htmlentities($highlight, ENT_NOQUOTES, 'UTF-8').'', $category['name']) : $category['name']). + ($n < $nCategories ? '' : ''). + (($n++ != $nCategories OR !empty($path)) ? ' > ' : ''); + } + + return $fullPath.$path; + } + } + elseif ($categoryType == 'cms') + { + $category = new CMSCategory($id_category, (int)($cookie->id_lang)); + if (!$category->id) + return $path; + + $name = ($highlight != NULL) ? str_ireplace($highlight, ''.$highlight.'', CMSCategory::hideCMSCategoryPosition($category->name)) : CMSCategory::hideCMSCategoryPosition($category->name); + $edit = ' + Modify '; + if ($category->id == 1) + $edit = ' + Home '; + $path = $edit.' + '.$name.' > '.$path; + if ($category->id == 1) + return substr($path, 0, strlen($path) - 3); + return getPath($urlBase, $category->id_parent, $path, '', 'cms'); + } +} + +function getDirContent($path) +{ + $content = array(); + if (is_dir($path)) + { + $d = dir($path); + while (false !== ($entry = $d->read())) + if ($entry{0} != '.') + $content[] = $entry; + $d->close(); + } + return $content; +} + +function createDir($path, $rights) +{ + if (file_exists($path)) + return true; + return @mkdir($path, $rights); +} + +function checkPSVersion() +{ + $upgrader = new Upgrader(); + + return $upgrader->checkPSVersion(); +} + +function translate($string) +{ + global $_LANGADM; + if (!is_array($_LANGADM)) + return str_replace('"', '"', $string); + $key = md5(str_replace('\'', '\\\'', $string)); + $str = (key_exists('index'.$key, $_LANGADM)) ? $_LANGADM['index'.$key] : ((key_exists('index'.$key, $_LANGADM)) ? $_LANGADM['index'.$key] : $string); + return str_replace('"', '"', stripslashes($str)); +} + +function recursiveTab($id_tab) +{ + global $cookie, $tabs; + + $adminTab = Tab::getTab((int)$cookie->id_lang, $id_tab); + $tabs[]= $adminTab; + if ($adminTab['id_parent'] > 0) + recursiveTab($adminTab['id_parent']); +} + +function checkingTab($tab) +{ + global $adminObj, $cookie; + + $tab = trim($tab); + if (!Validate::isTabName($tab)) + return false; + + $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql = 'SELECT id_tab, module FROM `'._DB_PREFIX_.'tab` WHERE class_name = \''.pSQL($tab).'\''); + if (!$row['id_tab']) + { + if (isset(AdminTab::$tabParenting[$tab])) + Tools::redirectAdmin('?tab='.AdminTab::$tabParenting[$tab].'&token='.Tools::getAdminTokenLite(AdminTab::$tabParenting[$tab])); + echo Tools::displayError('Tab cannot be found.'); + return false; + } + if ($row['module'] AND file_exists(_PS_MODULE_DIR_.'/'.$row['module'].'/'.$tab.'.php')) + include_once(_PS_MODULE_DIR_.'/'.$row['module'].'/'.$tab.'.php'); + elseif (file_exists(PS_ADMIN_DIR.'/tabs/'.$tab.'.php')) + include_once(PS_ADMIN_DIR.'/tabs/'.$tab.'.php'); + + if (!class_exists($tab, false) OR !$row['id_tab']) + { + echo Tools::displayError('Tab file cannot be found.'); + return false; + } + $adminObj = new $tab; + if (!$adminObj->viewAccess()) + { + $adminObj->_errors = array(Tools::displayError('Access denied')); + echo $adminObj->displayErrors(); + return false; + } + return $row['id_tab']; +} + +function checkTabRights($id_tab) +{ + global $cookie; + static $tabAccesses = NULL; + + if ($tabAccesses === NULL) + $tabAccesses = Profile::getProfileAccesses($cookie->profile); + + if (isset($tabAccesses[(int)($id_tab)]['view'])) + return ($tabAccesses[(int)($id_tab)]['view'] === '1'); + return false; +} + + +/** + * Converts a simpleXML element into an array. Preserves attributes and everything. + * You can choose to get your elements either flattened, or stored in a custom index that + * you define. + * For example, for a given element + * + * if you choose to flatten attributes, you would get: + * $array['field']['name'] = 'someName'; + * $array['field']['type'] = 'someType'; + * If you choose not to flatten, you get: + * $array['field']['@attributes']['name'] = 'someName'; + * _____________________________________ + * Repeating fields are stored in indexed arrays. so for a markup such as: + * + * a + * b + * c + * + * you array would be: + * $array['parent']['child'][0] = 'a'; + * $array['parent']['child'][1] = 'b'; + * ...And so on. + * _____________________________________ + * @param simpleXMLElement $xml the XML to convert + * @param boolean $flattenValues Choose wether to flatten values + * or to set them under a particular index. + * defaults to true; + * @param boolean $flattenAttributes Choose wether to flatten attributes + * or to set them under a particular index. + * Defaults to true; + * @param boolean $flattenChildren Choose wether to flatten children + * or to set them under a particular index. + * Defaults to true; + * @param string $valueKey index for values, in case $flattenValues was set to + * false. Defaults to "@value" + * @param string $attributesKey index for attributes, in case $flattenAttributes was set to + * false. Defaults to "@attributes" + * @param string $childrenKey index for children, in case $flattenChildren was set to + * false. Defaults to "@children" + * @return array the resulting array. + */ +function simpleXMLToArray ($xml, $flattenValues = true, $flattenAttributes = true, $flattenChildren = true, $valueKey = '@value', $attributesKey = '@attributes', $childrenKey = '@children') +{ + $return = array(); + if (!($xml instanceof SimpleXMLElement)) + return $return; + + $name = $xml->getName(); + $_value = trim((string)$xml); + if (strlen($_value) == 0) + $_value = null; + + if ($_value !== null) + { + if (!$flattenValues) + $return[$valueKey] = $_value; + else + $return = $_value; + } + + $children = array(); + $first = true; + foreach($xml->children() as $elementName => $child) + { + $value = simpleXMLToArray($child, $flattenValues, $flattenAttributes, $flattenChildren, $valueKey, $attributesKey, $childrenKey); + if (isset($children[$elementName])) + { + if ($first) + { + $temp = $children[$elementName]; + unset($children[$elementName]); + $children[$elementName][] = $temp; + $first=false; + } + $children[$elementName][] = $value; + } + else + $children[$elementName] = $value; + } + + if (count($children) > 0 ) + { + if (!$flattenChildren) + $return[$childrenKey] = $children; + else + $return = array_merge($return, $children); + } + + $attributes = array(); + foreach($xml->attributes() as $name => $value) + $attributes[$name] = trim($value); + + if (count($attributes) > 0) + { + if (!$flattenAttributes) + $return[$attributesKey] = $attributes; + else + $return = array_merge($return, $attributes); + } + + return $return; +} diff --git a/modules/autoupgrade/init.php b/modules/autoupgrade/init.php new file mode 100644 index 000000000..aff0e2cf9 --- /dev/null +++ b/modules/autoupgrade/init.php @@ -0,0 +1,84 @@ + +* @copyright 2007-2011 PrestaShop SA +* @version Release: $Revision$ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + + +// autoloader 1.3 / 1.4 +/* +if(!function_exists('__autoload')) +{ + die('fct already exists -_-'); + function __autoload($className) + { + info("try $className"); + if(!class_exists($className,false) AND class_exists($className.'Core',false)) + { + echo 'pouet'; + eval('class '.$className.' extends '.$className.'Core {}'); + } + + } +} +else +{ + die('fct already exists -_-'); +} +*/ +ob_start(); +$timerStart = microtime(true); + +$currentFileName = array_reverse(explode("/", $_SERVER['SCRIPT_NAME'])); +// $cookieLifetime = (time() + (((int)Configuration::get('PS_COOKIE_LIFETIME_BO') > 0 ? (int)Configuration::get('PS_COOKIE_LIFETIME_BO') : 1)* 3600)); +$cookieLifetime = time() + 84600; +$adminFilename = trim($_POST['dir'],'/').'/'; +// die(info($adminFilename)); +require_once(AUTOUPGRADE_MODULE_DIR.'Tools14.php'); +require_once(AUTOUPGRADE_MODULE_DIR.'AdminSelfTab.php'); +require_once(AUTOUPGRADE_MODULE_DIR.'AdminSelfUpgrade.php'); +// $needClass = array('Cookie'); //, 'ObjectModel', 'Db', 'MySQL', 'SubDomain', 'Tools'); +$needClass = array(); +foreach ($needClass as $class) +{ + if (!class_exists($class,false)) + { + if(file_exists(_PS_ADMIN_DIR_.'/autoupgrade/'.$class.'.php')) + { + require_once(_PS_ADMIN_DIR_.'/autoupgrade/'.$class.'.php'); + info($class,'from autoupgrade'); + } + else + require_once(_PS_ROOT_DIR_.'/classes/'.$class.'.php'); + if (version_compare(_PS_VERSION_, '1.4','<')) + if (!class_exists($class,false) AND class_exists($class.'Core',false)) + eval ('class '.$class.' extends '.$class.'Core{}'); + } +} + + + $currentIndex = $_SERVER['SCRIPT_NAME'].(($tab = Tools14::getValue('tab')) ? '?tab='.$tab : ''); + + + diff --git a/modules/autoupgrade/it.php b/modules/autoupgrade/it.php new file mode 100644 index 000000000..0eb7028fc --- /dev/null +++ b/modules/autoupgrade/it.php @@ -0,0 +1,56 @@ +AdminSelfUpgrade_19f823c6453c2b1ffd09cb715214813d'] = 'Campi richiesti'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_760c4026bc5a0bd5378e6efc3f1370b4'] = 'Troppo lungo!'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_4f2e28904946a09d8c7f36dd3ee72457'] = 'I campi non corrispondono!'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_43b01d1a6c5065545c65f42003b0ab5c'] = 'Indirizzo errato!'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_2e170dfd78c2171a25605ececc0950a4'] = 'Impossibile inviare l\'e-mail!'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_6deee80e4bdb8894331994116818558e'] = 'Impossibile creare file di impostazione, se /config/settings.inc.php esiste, dai un\'autorizzazione scritta a questo file, altrimenti crea un file chiamato settings.inc.php nella directory config (/config/)'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_aa946cb00b1c23ff6553b6f9e05da151'] = 'Il file dei parametri non è stato scritto, crea un file denominato settings.inc.php nella tua directory di configurazione.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_e930e2474c664a3a7e89ecfb8793694b'] = 'Impossibile inviare il file!'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_774fc7a0f56391abc5d8856f2436ca07'] = 'L\'integrità dei dati non è convalidata.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_8fd007bf08e0717537825a3e91cb4fcc'] = 'Impossibile leggere il contenuto di uno dei file *.sql.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_c71e825f6873f64b91efc26313614eab'] = 'Impossibile accedere al contenuto di uno dei file *.sql.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_3d5c8f1d29b1a1dc4ea0673122e0d277'] = 'Errore al momento dell\'inserimento nel database:'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_17e1581d01152347bfaacd153b961379'] = 'La password non è corretta (stringa alfanumerica di almeno 8 caratteri)'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_9b9f3b2f8a4dffcad9188c8fc4b468c8'] = 'Esiste già un database Prestashop con questo prefisso; eliminalo oppure cambia prefisso.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_bc5e1163a15106f9e941a7603124709d'] = 'Non è un nome valido'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_a9d82945b8603f0c8807958d7db9a24d'] = 'Non è un\'immagine valida'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_b37eef07b764ea58eec9afe624e20a40'] = 'Errore al momento della creazione del file /config/settings.inc.php.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_3d9f514d46849760ef1b1412e314fd99'] = 'Errore:'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_504053ab4c6d648edf11624f1bea4bb4'] = 'Questo database PrestaShop esiste già. Inserisci di nuovo i tuoi dati di login nel database.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_d7a99f61bb284d096ea4f221784af85a'] = 'Si è verificato un errore durante il ridimensionamento dell\'immagine'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_428c933bafbf262d693fbf1c22c03e5d'] = 'Il database è stato trovato!'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_76bd190129b7aefb90fdf42f09ec3ec7'] = 'Il server del database è disponibile ma il database non è stato trovato.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_045f4b5c3f990e5a26e2837495d68259'] = 'Il server del database non è stato trovato, verifica i tuoi dati o il nome del server.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_757fc72e8d69106dd2cf9da22cc7adb1'] = 'Si è verificato un errore durante l\'invio della e-mail, verifica i tuoi parametri.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_d18ad19290b3bfc3cd0d7badbb6cb6a3'] = 'Impossibile scrivere l\'immagine /img/logo.jpg. Se esiste già, cancellala manualmente.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_2368e33e3e01d53abb9b60061ab83903'] = 'Il file inviato supera la dimensione massima autorizzata.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_a10ef376d9f4c877ac86d8e4350116bf'] = 'Il file inviato supera la dimensione massima autorizzata.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_f8fe8cba1625eaf8e5c253b041d57dbd'] = 'Il file è stato parzialmente inviato.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_8c9067e52e4440d8a20e74fdc745b0c6'] = 'Non è stato inviato alcun file'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_2384d693d9af53b4727c092af7570a19'] = 'Manca la cartella temporanea di ricezione dell\'invio di file. Consulta il tuo amministratore.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_f5985b2c059d5cc36968baab7585baba'] = 'Impossibile scrivere il file sul disco'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_9e54dfe54e03b0010c1fe70bd65cd5e6'] = 'Invio file interrotto a causa di estensione errata'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_32af3a59b50e98d254d6c03c5b320a94'] = 'Impossibile convertire i dati del tuo database in utf-8.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_989a45a4ca01ee222f4370172bf8850d'] = 'nome negozio non valido'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_68499acecfba9d3bf0ca8711f300d3ed'] = 'Il tuo nome contiene dei caratteri non validi'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_2399cf4ca7b49f2706f6e147a32efa78'] = 'Il tuo cognome contiene dei caratteri non validi'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_7d72600fcff52fb3a2d2f73572117311'] = 'Il tuo server di database non supporta il charset utf-8.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_5b9bbadcf96f15e7f112c13a9e5f076e'] = 'Il supporto di questo motore di database non è supportato, scegline un altro come MyISAM'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_24b481455c1b72b0c2539e7d516b9582'] = 'Il file /img/logo.jpg non ?scrivibile, ti preghiamo di effettuare CHMOD 755 oppure CHMOD 777'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_5746b74663148afffd1350c97d4fcdfe'] = 'Campo modalità catalogo non valido'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_dafe44b99256a7783bc37f4f949da373'] = 'Questo installatore è troppo vecchio.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_37ce0f29c7377c827e7247fe5645a782'] = 'Sei già in possesso della versione %s'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_89928a14f2090aec4fd7aff9ea983f95'] = 'Nessuna versione precedente. Hai cancellato o rinominato il file settings.inc.php della cartella config ?'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_5276a8cbd9e3e467396089b267564f51'] = 'Il file config/settings.inc.php non è stato trovato. Il file potrebbe essere stato cancellato o rinominato'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_24784d9e80638781b74c017b33d8ca0c'] = 'Impossibile trovare i file di upgrade SQL. Verifica che la cartella /install/sql/upgrade non sia vuota.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_580e4b216e324f675f0237cdb34b6c2d'] = 'Nessun aggiornamento disponibile'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_f15e7e7292b0c72894cf45a893e0d497'] = 'Errore al momento dell\'apertura del file SQL'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_b8286438fbb6c7df9129fadc5316c19f'] = 'Errore al momento dell\'inserimento nel database'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_229ee8046cafc09ddaf46fb9db710e91'] = 'Purtroppo,'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_e805a556799b7cef40e9760c81d99218'] = 'sono apparsi errori SQL.'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_5627353fd6ac678497af3ece05087068'] = 'Il file config/defines.inc.php non è stato trovato. Cosa è successo?'; +$_MODULE['<{autoupgrade}prestashop>AdminSelfUpgrade_783e8e29e6a8c3e22baa58a19420eb4f'] = 'secondi'; diff --git a/modules/autoupgrade/jquery-1.6.2.min.js b/modules/autoupgrade/jquery-1.6.2.min.js new file mode 100644 index 000000000..48590ecb9 --- /dev/null +++ b/modules/autoupgrade/jquery-1.6.2.min.js @@ -0,0 +1,18 @@ +/*! + * jQuery JavaScript Library v1.6.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu Jun 30 14:16:56 2011 -0400 + */ +(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
    a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
    ",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
    t
    ",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]||i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=w:v&&c!=="className"&&(f.nodeName(a,"form")||u.test(c))&&(i=v)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}},value:{get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return f.prop(a,c)?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.attrHooks.title=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i. +shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function(c){var d=c.target,e,g;if(!!y.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=I(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

    ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
    ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(h=g;h0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/",""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]};bf.optgroup=bf.option,bf.tbody=bf.tfoot=bf.colgroup=bf.caption=bf.thead,bf.th=bf.td,f.support.htmlSerialize||(bf._default=[1,"div
    ","
    "]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!bf[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j +)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bi(a,d),e=bj(a),g=bj(d);for(h=0;e[h];++h)bi(e[h],g[h])}if(b){bh(a,d);if(c){e=bj(a),g=bj(d);for(h=0;e[h];++h)bh(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!ba.test(k))k=b.createTextNode(k);else{k=k.replace(Z,"<$1>");var l=($.exec(k)||["",""])[1].toLowerCase(),m=bf[l]||bf._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=_.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Y.test(k)&&o.insertBefore(b.createTextNode(Y.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bo.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bn.test(g)?g.replace(bn,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bB=/%20/g,bC=/\[\]$/,bD=/\r?\n/g,bE=/#.*$/,bF=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bG=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bH=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bI=/^(?:GET|HEAD)$/,bJ=/^\/\//,bK=/\?/,bL=/)<[^<]*)*<\/script>/gi,bM=/^(?:select|textarea)/i,bN=/\s+/,bO=/([?&])_=[^&]*/,bP=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bQ=f.fn.load,bR={},bS={},bT,bU;try{bT=e.href}catch(bV){bT=c.createElement("a"),bT.href="",bT=bT.href}bU=bP.exec(bT.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bQ)return bQ.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
    ").append(c.replace(bL,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bM.test(this.nodeName)||bG.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bD,"\r\n")}}):{name:b.name,value:c.replace(bD,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bT,isLocal:bH.test(bU[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bW(bR),ajaxTransport:bW(bS),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?bZ(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b$(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bF.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bE,"").replace(bJ,bU[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bN),d.crossDomain==null&&(r=bP.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bU[1]&&r[2]==bU[2]&&(r[3]||(r[1]==="http:"?80:443))==(bU[3]||(bU[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bX(bR,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bI.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bK.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bO,"$1_="+x);d.url=y+(y===d.url?(bK.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bX(bS,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bB,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn,co=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cr("show",3),a,b,c);for(var g=0,h=this.length;g=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b
    ";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cu.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cu.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cv(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cv(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); \ No newline at end of file diff --git a/modules/autoupgrade/logo.gif b/modules/autoupgrade/logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..98135690867612ab16055ff813afeb7e16dafa20 GIT binary patch literal 652 zcmZ?wbhEHb6krfwI9A3G7#J8F92^l55gi?^QqHMZ$t+jRtXj#gSj&)@n5bIMkdl(3 z-OQMsoo(9Ao}Zs@-7VnSC*jgBR#{o;HBlyTx>93fV^dR8_zaEa=H}?xn(giFNplT4 zJ3Hg%TP4poj9FllI^VFTrzd`)bNWK#j0ILni{1MB`?D9D=Pou+Tk4Uq)U9BNL;h0R zlGXlm=FF*CAG%<{f`tngHg1k;*qpd{@#3bf=}VU`ZQYi(Y}vBbZCT5gFYnxu-M1@$ z&6+h6_LOYcuwmoIjhi-Yns%^Z`k|&7hnlx<-#+hX$HHS>yLRnbc)V-Ro;^!X^zPle zci+B!D^E?>zkmOV(-T&mo_OHEfrAGR9y)Yr?b#`Z4RS@ zwP{OkX-=qLX1ar2La|#*WNvzP&f@H}gjnI0sPx1*Pkp1%=nyyArr5~ftR!#oCiU>p zBqeFd$db&sWb+msMbGFw-hhI%=p@G$Rbz?ZG)_}>!<6VmT}>4kgCIRYCdMYasF0|D zgpEq7r}$O1Wq3|Cx^d6o53Blc!11t~pa2((Zsla<<~DVo2{IQQU5++7iplBx2ySd* h;gD;aq442Ff2*1kn}I~&f@KPf4F41i95e(NtO5C9LstL* literal 0 HcmV?d00001 diff --git a/modules/blocklayered/blocklayered.php b/modules/blocklayered/blocklayered.php index 7fa8d173c..14e67219e 100644 --- a/modules/blocklayered/blocklayered.php +++ b/modules/blocklayered/blocklayered.php @@ -20,7 +20,7 @@ * * @author PrestaShop SA * @copyright 2007-2011 PrestaShop SA -* @version Release: $Revision: 7310 $ +* @version Release: $Revision$ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) * International Registred Trademark & Property of PrestaShop SA */ @@ -30,7 +30,7 @@ if (!defined('_PS_VERSION_')) class BlockLayered extends Module { - private $products; + private $products; private $nbr_products; public function __construct() @@ -46,9 +46,9 @@ class BlockLayered extends Module $this->displayName = $this->l('Layered navigation block'); $this->description = $this->l('Displays a block with layered navigation filters.'); } - + public function install() - { + { if ($result = parent::install() AND $this->registerHook('leftColumn') AND $this->registerHook('header') AND $this->registerHook('footer') AND $this->registerHook('categoryAddition') AND $this->registerHook('categoryUpdate') AND $this->registerHook('categoryDeletion')) { @@ -58,10 +58,12 @@ class BlockLayered extends Module $this->rebuildLayeredStructure(); $this->rebuildLayeredCache(); } + + self::_installPriceIndexTable(); return $result; } - + public function uninstall() { /* Delete all configurations */ @@ -71,28 +73,218 @@ class BlockLayered extends Module return parent::uninstall(); } + private function _installPriceIndexTable() + { + Db::getInstance()->execute(' + DROP TABLE IF EXISTS `'._DB_PREFIX_.'price_static_index`; + '); + Db::getInstance()->execute(' + CREATE TABLE `'._DB_PREFIX_.'price_static_index` ( + `id_product` INT NOT NULL, + `id_currency` INT NOT NULL, + `price_min` INT NOT NULL, + `price_max` INT NOT NULL, + PRIMARY KEY (`id_product`, `id_currency`), + INDEX `id_currency` (`id_currency`), + INDEX `price_min` (`price_min`), + INDEX `price_max` (`price_max`) + ) + ENGINE = '._MYSQL_ENGINE_.'; + '); + } + + /* + * $cursor $cursor in order to restart indexing from the last state + */ + public static function fullIndexProcess($cursor = 0, $ajax = false, $smart = false) + { + if ($cursor == 0 && !$smart) + self::_installPriceIndexTable(); + + return self::_indexer($cursor, true, $ajax, $smart); + } + + /* + * $cursor $cursor in order to restart indexing from the last state + */ + public static function indexProcess($cursor = 0, $ajax = false) + { + return self::_indexer($cursor, false, $ajax); + } + + private static function _indexer($cursor = null, $full = false, $ajax = false, $smart = false) + { + if ($full) + $nbProducts = (int)Db::getInstance()->getValue('SELECT count(*) FROM '._DB_PREFIX_.'product WHERE `active` = 1'); + else + $nbProducts = (int)Db::getInstance()->getValue( + 'SELECT COUNT(*) FROM `'._DB_PREFIX_.'product` p + LEFT JOIN `'._DB_PREFIX_.'price_static_index` psi ON (psi.id_product = p .id_product) + WHERE `active` = 1 AND psi.id_product IS NULL'); + + $maxExecutionTime = ini_get('max_execution_time') * 0.9; // 90% of safety margin + if ($maxExecutionTime > 5) + $maxExecutionTime = 5; + + $startTime = microtime(true); + + do + { + $cursor = (int)self::_index((int)$cursor, $full, $smart); + $timeElapsed = microtime(true) - $startTime; + } + while($cursor < $nbProducts AND (Tools::getMemoryLimit() * 0.9) > memory_get_peak_usage() AND $timeElapsed < $maxExecutionTime); + + if (($nbProducts > 0 AND !$full OR $cursor < $nbProducts AND $full) AND !$ajax) + { + if (!Tools::file_get_contents(Tools::getProtocol().Tools::getHttpHost().'/modules/blocklayered/blocklayered-indexer.php'.'?token='.substr(Tools::encrypt('blocklayered/index'), 0, 10).'&cursor='.(int)$cursor.'&full='.(int)$full)) + self::_indexer((int)$cursor, (int)$full); + return $cursor; + } + if ($ajax AND $nbProducts > 0 AND $cursor < $nbProducts AND $full) + return '{"cursor": '.$cursor.', "count": '.($nbProducts - $cursor).'}'; + elseif ($ajax AND $nbProducts > 0 AND !$full) + return '{"cursor": '.$cursor.', "count": '.($nbProducts).'}'; + else + { + Configuration::updateValue('PS_LAYERED_INDEXED', 1); + if ($ajax) + return '{"result": "ok"}'; + else + return -1; + } + } + + /* + * $cursor $cursor in order to restart indexing from the last state + */ + private static function _index($cursor, $full = false, $smart = false) + { + static $length = 100; // Nb of products to index + + if (is_null($cursor)) + $cursor = 0; + + if ($full) + $query = ' + SELECT id_product + FROM `'._DB_PREFIX_.'product` + WHERE `active` = 1 + ORDER by id_product LIMIT '.(int)$cursor.','.(int)$length; + else + $query = ' + SELECT p.id_product + FROM `'._DB_PREFIX_.'product` as p + LEFT JOIN `'._DB_PREFIX_.'price_static_index` psi ON (psi.id_product = p.id_product) + WHERE `active` = 1 AND psi.id_product is null + ORDER by id_product LIMIT 0,'.(int)$length; + + foreach (Db::getInstance()->executeS($query) as $product) + self::indexProduct((int)$product['id_product'], ($smart AND $full)); + + return (int)($cursor + $length); + } + + public static function indexProduct($idProduct, $smart = true) + { + static $groups = null; + + if (is_null($groups)) + $groups = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('SELECT id_group FROM `'._DB_PREFIX_.'group_reduction`'); + + static $currencyList = null; + if (is_null($currencyList)) + $currencyList = Currency::getCurrencies(); + + $minPrice = array(); + $maxPrice = array(); + + + if ($smart) + Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'price_static_index` WHERE `id_product` = '.(int)$idProduct); + + $maxTaxRate = Db::getInstance()->getValue(' + SELECT max(t.rate) as max_rate + FROM `'._DB_PREFIX_.'product` as p + LEFT JOIN `'._DB_PREFIX_.'tax_rules_group` trg ON (trg.id_tax_rules_group = p.id_tax_rules_group) + LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (tr.id_tax_rules_group = trg.id_tax_rules_group) + LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.id_tax = tr.id_tax AND t.active = 1) + WHERE id_product = '.(int)$idProduct.' + GROUP BY id_product;'); + + $productMinPrices = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' + SELECT id_shop, id_currency, id_country, id_group, from_quantity + FROM `'._DB_PREFIX_.'specific_price` + WHERE id_product = '.(int)$idProduct); + + // Get min price + foreach ($productMinPrices as $specificPrice) + foreach ($currencyList as $currency) + { + if ($specificPrice['id_currency'] AND $specificPrice['id_currency'] != $currency['id_currency']) + continue; + $price = Product::priceCalculation((($specificPrice['id_shop'] == 0) ? null : (int)$specificPrice['id_shop']), (int)$idProduct, + null, (($specificPrice['id_country'] == 0) ? null : $specificPrice['id_country']), null, null, + $currency['id_currency'], (($specificPrice['id_group'] == 0) ? null : $specificPrice['id_group']), + $specificPrice['from_quantity'], false, true, false, true, true, $specificPriceOutput, true); + + if (!isset($maxPrice[$currency['id_currency']])) + $maxPrice[$currency['id_currency']] = 0; + if (!isset($minPrice[$currency['id_currency']])) + $minPrice[$currency['id_currency']] = null; + if ($price > $maxPrice[$currency['id_currency']]) + $maxPrice[$currency['id_currency']] = $price; + if ($price == 0) + continue; + if (is_null($minPrice[$currency['id_currency']]) || $price < $minPrice[$currency['id_currency']]) + $minPrice[$currency['id_currency']] = $price; + } + + foreach ($groups as $group) + foreach ($currencyList as $currency) + { + $price = Product::priceCalculation(null, (int)$idProduct, null, null, null, null, (int)$currency['id_currency'], (int)$group['id_group'], + null, false, true, false, true, true, $specificPriceOutput, true); + + if (!isset($maxPrice[$currency['id_currency']])) + $maxPrice[$currency['id_currency']] = 0; + if (!isset($minPrice[$currency['id_currency']])) + $minPrice[$currency['id_currency']] = null; + if ($price > $maxPrice[$currency['id_currency']]) + $maxPrice[$currency['id_currency']] = $price; + if ($price == 0) + continue; + if (is_null($minPrice[$currency['id_currency']]) || $price < $minPrice[$currency['id_currency']]) + $minPrice[$currency['id_currency']] = $price; + } + + foreach ($currencyList as $currency) + Db::getInstance()->Execute(' + INSERT INTO `'._DB_PREFIX_.'price_static_index` (id_product, id_currency, price_min, price_max) + VALUES ('.(int)$idProduct.', '.(int)$currency['id_currency'].', '.(int)$minPrice[$currency['id_currency']].', '.(int)$maxPrice[$currency['id_currency']].')'); + } + public function hookLeftColumn($params) { return $this->generateFiltersBlock($this->getSelectedFilters()); } - + public function hookRightColumn($params) { return $this->hookLeftColumn($params); } - + public function hookHeader($params) { $id_parent = (int)Tools::getValue('id_category', Tools::getValue('id_category_layered', 1)); if ($id_parent == 1) return; - - $this->context->controller->addJS($this->_path.'blocklayered.js'); - $this->context->controller->addJS(_PS_JS_DIR_.'jquery/jquery-ui-1.8.10.custom.min.js'); - $this->context->controller->addCSS(_PS_CSS_DIR_.'jquery-ui-1.8.10.custom.css', 'all'); - $this->context->controller->addCSS(($this->_path).'blocklayered.css', 'all'); + Tools::addJS(($this->_path).'blocklayered.js'); + Tools::addJS(_PS_JS_DIR_.'jquery/jquery-ui-1.8.10.custom.min.js'); + Tools::addCSS(_PS_CSS_DIR_.'jquery-ui-1.8.10.custom.css', 'all'); + Tools::addCSS(($this->_path).'blocklayered.css', 'all'); } - + public function hookFooter($params) { if (basename($_SERVER['PHP_SELF']) == 'category.php') @@ -107,29 +299,30 @@ class BlockLayered extends Module }) }); //]]> - '; + '; } - + public function hookCategoryAddition($params) { $this->rebuildLayeredCache(array(), array((int)$params['category']->id)); } - + public function hookCategoryUpdate($params) { /* The category status might (active, inactive) have changed, we have to update the layered cache table structure */ if (!$params['category']->active) $this->hookCategoryDeletion($params); - } - + } + public function hookCategoryDeletion($params) { Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_category WHERE id_category = '.(int)$params['category']->id); } - + public function getContent() { - + global $cookie; + $errors = array(); $html = ''; @@ -154,7 +347,7 @@ class BlockLayered extends Module if (Tools::getValue('scope') == 1) { Db::getInstance()->Execute('TRUNCATE TABLE '._DB_PREFIX_.'layered_filter'); - $categories = Db::getInstance()->ExecuteS('SELECT id_category FROM '._DB_PREFIX_.'category'); + $categories = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS('SELECT id_category FROM '._DB_PREFIX_.'category'); foreach ($categories AS $category) $_POST['categoryBox'][] = (int)$category['id_category']; } @@ -188,6 +381,8 @@ class BlockLayered extends Module $sqlToInsert .= '('.(int)$id_category_layered.',NULL,\'condition\','.(int)$n.'),'; elseif ($key == 'layered_selection_weight_slider') $sqlToInsert .= '('.(int)$id_category_layered.',NULL,\'weight\','.(int)$n.'),'; + elseif ($key == 'layered_selection_price_slider') + $sqlToInsert .= '('.(int)$id_category_layered.',NULL,\'price\','.(int)$n.'),'; elseif ($key == 'layered_selection_manufacturer') $sqlToInsert .= '('.(int)$id_category_layered.',NULL,\'manufacturer\','.(int)$n.'),'; elseif (substr($key, 0, 21) == 'layered_selection_ag_') @@ -225,12 +420,12 @@ class BlockLayered extends Module $html .= '
    '.$this->l('Settings not saved :').'
      '; - foreach($errors AS $error) + foreach ($errors AS $error) $html .= '
    • '.$error.'
    • '; $html .= '
    '; } - + } elseif (isset($_GET['deleteFilterTemplate'])) { @@ -238,7 +433,7 @@ class BlockLayered extends Module SELECT filters FROM '._DB_PREFIX_.'layered_filter WHERE id_layered_filter = '.(int)$_GET['id_layered_filter']); - + if ($layeredValues) { Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_filter WHERE id_layered_filter = '.(int)$_GET['id_layered_filter'].' LIMIT 1'); @@ -258,7 +453,79 @@ class BlockLayered extends Module } $html .= ' -

    '.$this->l('Layered navigation').'

    +

    '.$this->l('Layered navigation').'

    '; + $html .= ' +
    + '.$this->l('Indexes and caches').' + + '; + if(!Configuration::get('PS_LAYERED_INDEXED')) + $html .= ' + '; + $html .= ' + - '.$this->l('Index all missing products.').' +
    + - '.$this->l('Re-build entire index.').' +
    +
    + '.$this->l('You can set a cron job that will re-build your index using the following URL: ').Tools::getProtocol().Tools::getHttpHost().__PS_BASE_URI__.'modules/blocklayered/blocklayered-indexer.php'.'?token='.substr(Tools::encrypt('blocklayered/index'),0,10).'&full=1 +
    + '.$this->l('A full re-index process must be done each time products are modified. A nightly rebuild is recomanded.').' + +
    +
    '; + + $html .= '
    '.$this->l('Existing filters templates').''; @@ -268,11 +535,11 @@ class BlockLayered extends Module $html .= '

    '.sizeof($filtersTemplates).' '.$this->l('filters templates are configured:').'

    - - - - - + + + + + '; foreach ($filtersTemplates AS $filtersTemplate) @@ -285,7 +552,7 @@ class BlockLayered extends Module - +
    IDNameCategoriesCreated onActions'.$this->l('ID').''.$this->l('Name').''.$this->l('Categories').''.$this->l('Created on').''.$this->l('Actions').'
    '.(int)$filtersTemplate['id_layered_filter'].' '.$filtersTemplate['name'].' '.(int)$filtersTemplate['n_categories'].''.Tools::displayDate($filtersTemplate['date_add'], (int)$this->context->language->id, true).''.Tools::displayDate($filtersTemplate['date_add'], (int)$cookie->id_lang, true).' @@ -341,7 +608,7 @@ class BlockLayered extends Module $trads = array(); $selectedCat = array(); - foreach(Helper::$translationsKeysForAdminCategorieTree AS $key) + foreach (Helper::$translationsKeysForAdminCategorieTree AS $key) $trads[$key] = $this->l($key); $html .= Helper::renderAdminCategorieTree($trads, $selectedCat, 'categoryBox'); @@ -359,7 +626,7 @@ class BlockLayered extends Module

    '.$this->l('No filters selected yet.').'

      -
      +
      '.$this->ajaxCallBackOffice().'
      @@ -368,7 +635,7 @@ class BlockLayered extends Module -