From fac1566a5fcbc2763d67ea33cd07fa40f679d97d Mon Sep 17 00:00:00 2001 From: jObregon Date: Thu, 26 Apr 2012 16:39:43 +0000 Subject: [PATCH] [+] MO : CloudCache module git-svn-id: http://dev.prestashop.com/svn/v1/branches/1.5.x@14929 b9a71923-0436-4b27-9f14-aed3839534dd --- .../backward_compatibility/Context.php | 221 + .../backward_compatibility/backward.php | 24 + .../backward_compatibility/index.php | 18 + modules/cloudcache/cloudcache.php | 860 ++++ modules/cloudcache/cloudcache_logo.png | Bin 0 -> 9783 bytes modules/cloudcache/cloudcache_ps_logo.png | Bin 0 -> 26694 bytes modules/cloudcache/config.xml | 12 + modules/cloudcache/coupon.png | Bin 0 -> 19547 bytes modules/cloudcache/es.php | 108 + modules/cloudcache/fr.php | 108 + modules/cloudcache/index.php | 36 + modules/cloudcache/jquery-plugins/index.php | 36 + .../jquery.easy-confirm-dialog.js | 155 + modules/cloudcache/lib/CloudCacheApi.php | 388 ++ modules/cloudcache/lib/index.php | 36 + modules/cloudcache/lib/xmlrpc.inc.php | 3716 +++++++++++++++++ modules/cloudcache/loading.gif | Bin 0 -> 16810 bytes modules/cloudcache/logo.gif | Bin 0 -> 845 bytes modules/cloudcache/override/classes/Tools.php | 180 + .../classes/controller/FrontController.php | 55 + .../override/classes/controller/index.php | 36 + modules/cloudcache/override/classes/index.php | 36 + modules/cloudcache/override/index.php | 36 + modules/cloudcache/script.js | 94 + modules/cloudcache/sql/index.php | 36 + modules/cloudcache/sql/sql-install.php | 21 + modules/cloudcache/sql/sql-uninstall.php | 5 + modules/cloudcache/style.css | 128 + modules/cloudcache/views/backOfficeTop.tpl | 35 + modules/cloudcache/views/content2.tpl | 360 ++ modules/cloudcache/views/index.php | 36 + 31 files changed, 6776 insertions(+) create mode 100644 modules/cloudcache/backward_compatibility/Context.php create mode 100644 modules/cloudcache/backward_compatibility/backward.php create mode 100644 modules/cloudcache/backward_compatibility/index.php create mode 100644 modules/cloudcache/cloudcache.php create mode 100644 modules/cloudcache/cloudcache_logo.png create mode 100644 modules/cloudcache/cloudcache_ps_logo.png create mode 100644 modules/cloudcache/config.xml create mode 100644 modules/cloudcache/coupon.png create mode 100644 modules/cloudcache/es.php create mode 100644 modules/cloudcache/fr.php create mode 100644 modules/cloudcache/index.php create mode 100644 modules/cloudcache/jquery-plugins/index.php create mode 100644 modules/cloudcache/jquery-plugins/jquery.easy-confirm-dialog.js create mode 100644 modules/cloudcache/lib/CloudCacheApi.php create mode 100644 modules/cloudcache/lib/index.php create mode 100644 modules/cloudcache/lib/xmlrpc.inc.php create mode 100644 modules/cloudcache/loading.gif create mode 100644 modules/cloudcache/logo.gif create mode 100644 modules/cloudcache/override/classes/Tools.php create mode 100644 modules/cloudcache/override/classes/controller/FrontController.php create mode 100644 modules/cloudcache/override/classes/controller/index.php create mode 100644 modules/cloudcache/override/classes/index.php create mode 100644 modules/cloudcache/override/index.php create mode 100644 modules/cloudcache/script.js create mode 100644 modules/cloudcache/sql/index.php create mode 100644 modules/cloudcache/sql/sql-install.php create mode 100644 modules/cloudcache/sql/sql-uninstall.php create mode 100644 modules/cloudcache/style.css create mode 100644 modules/cloudcache/views/backOfficeTop.tpl create mode 100644 modules/cloudcache/views/content2.tpl create mode 100644 modules/cloudcache/views/index.php diff --git a/modules/cloudcache/backward_compatibility/Context.php b/modules/cloudcache/backward_compatibility/Context.php new file mode 100644 index 000000000..22cc54b27 --- /dev/null +++ b/modules/cloudcache/backward_compatibility/Context.php @@ -0,0 +1,221 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 1.4 $ +* +* International Registered Trademark & Property of PrestaShop SA +*/ + +// Retro 1.3, 'class_exists' cause problem with autoload... +if (version_compare(_PS_VERSION_, '1.4', '<')) +{ + // Not exist for 1.3 + class Shop extends ObjectModel + { + public $id = 1; + public $id_group_shop = 1; + + public function __construct() + { + + } + + public static function getShops() + { + return array( + array('id_shop' => 1, 'name' => 'Default shop') + ); + } + + public static function getCurrentShop() + { + return 1; + } + } + + class Logger + { + public static function AddLog($message, $severity = 2) + { + $fp = fopen(dirname(__FILE__).'/../logs.txt', 'a+'); + fwrite($fp, '['.(int)$severity.'] '.Tools::safeOutput($message)); + fclose($fp); + } + } + +} + +// Not exist for 1.3 and 1.4 +class Context +{ + /** + * @var Context + */ + protected static $instance; + + /** + * @var Cart + */ + public $cart; + + /** + * @var Customer + */ + public $customer; + + /** + * @var Cookie + */ + public $cookie; + + /** + * @var Link + */ + public $link; + + /** + * @var Country + */ + public $country; + + /** + * @var Employee + */ + public $employee; + + /** + * @var Controller + */ + public $controller; + + /** + * @var Language + */ + public $language; + + /** + * @var Currency + */ + public $currency; + + /** + * @var AdminTab + */ + public $tab; + + /** + * @var Shop + */ + public $shop; + + /** + * @var Smarty + */ + public $smarty; + + public function __construct() + { + global $cookie, $cart, $smarty, $link; + + $this->tab = null; + + if ($cookie) + $this->cookie = $cookie; + + $this->cart = $cart; + $this->smarty = $smarty; + $this->link = $link; + + $this->controller = new ControllerBackwardModule(); + if ($cookie) + { + $this->currency = new Currency((int)$cookie->id_currency); + $this->language = new Language((int)$cookie->id_lang); + $this->country = new Country((int)$cookie->id_country); + $this->customer = new Customer((int)$cookie->id_customer); + $this->employee = new Employee((int)$cookie->id_employee); + } + $this->shop = new ShopBackwardModule(); + } + + /** + * Get a singleton context + * + * @return Context + */ + public static function getContext() + { + if (!isset(self::$instance)) + self::$instance = new Context(); + return self::$instance; + } + + /** + * Clone current context + * + * @return Context + */ + public function cloneContext() + { + return clone($this); + } + + /** + * @return int Shop context type (Shop::CONTEXT_ALL, etc.) + */ + public static function shop() + { + if (!self::$instance->shop->getContextType()) + return ShopBackwardModule::CONTEXT_ALL; + return self::$instance->shop->getContextType(); + } +} + +/** + * Class Shop for Backward compatibility + */ +class ShopBackwardModule extends Shop +{ + const CONTEXT_ALL = 1; + public $id = 1; + public $id_group_shop = 1; + + + public function getContextType() + { + return ShopBackwardModule::CONTEXT_ALL; + } + + // Simulate shop for 1.3 / 1.4 + public function getID() + { + return 1; + } +} + +/** + * Class Controller for a Backward compatibility + * Allow to use method declared in 1.5 + */ +class ControllerBackwardModule +{ + /** + * @param $js_uri + * @return void + */ + public function addJS($js_uri) + { + Tools::addJS($js_uri); + } + + /** + * @param $css_uri + * @param string $css_media_type + * @return void + */ + public function addCSS($css_uri, $css_media_type = 'all') + { + Tools::addCSS($css_uri, $css_media_type); + } +} diff --git a/modules/cloudcache/backward_compatibility/backward.php b/modules/cloudcache/backward_compatibility/backward.php new file mode 100644 index 000000000..46d9a0c48 --- /dev/null +++ b/modules/cloudcache/backward_compatibility/backward.php @@ -0,0 +1,24 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 1.4 $ +* +* International Registered Trademark & Property of PrestaShop SA +*/ + +/** + * Backward function compatibility + * Need to be called for each module in 1.4 + */ + +// Get out if the context is already defined +if (!in_array('Context', get_declared_classes())) + require_once(dirname(__FILE__).'/Context.php'); + +if (!isset($this) || isset($this->context)) + return; + +$this->context = Context::getContext(); +$this->smarty = $this->context->smarty; + diff --git a/modules/cloudcache/backward_compatibility/index.php b/modules/cloudcache/backward_compatibility/index.php new file mode 100644 index 000000000..caac81969 --- /dev/null +++ b/modules/cloudcache/backward_compatibility/index.php @@ -0,0 +1,18 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 1.4 $ +* +* International Registered Trademark & Property of PrestaShop SA +*/ + +header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); +header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); + +header("Cache-Control: no-store, no-cache, must-revalidate"); +header("Cache-Control: post-check=0, pre-check=0", false); +header("Pragma: no-cache"); + +header("Location: ../"); +exit; \ No newline at end of file diff --git a/modules/cloudcache/cloudcache.php b/modules/cloudcache/cloudcache.php new file mode 100644 index 000000000..76148b956 --- /dev/null +++ b/modules/cloudcache/cloudcache.php @@ -0,0 +1,860 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 1.4 $ +* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +// Security +if (!defined('_PS_VERSION_')) + exit; + +if (file_exists(dirname(__FILE__).'/lib/CloudCacheApi.php')) + require(dirname(__FILE__).'/lib/CloudCacheApi.php'); +elseif (file_exists(dirname(__FILE__).'/../modules/cloudcache/lib/CloudCacheApi.php')) + require(dirname(__FILE__).'/../modules/cloudcache/lib/CloudCacheApi.php'); + +define('CLOUDCACHE_API_PORT', 80); +define('CLOUDCACHE_API_HTTP_METHOD', 'http11'); +define('CLOUDCACHE_API_URI', '/xmlrpc/'); +define('CLOUDCACHE_API_URL', 'api.netdna.com'); +define('CLOUDCACHE_API_ZONE_URL', 'netdna-cdn.com'); +define('CLOUDCACHE_API_HASH_TYPE', 'sha256'); +define('CLOUDCACHE_API_PULL_ZONE_TYPE', 1); + +class CloudCache extends Module +{ + /** @var _cipherTool Helper Object to encrypt API KEY */ + private $_cipherTool; + + /** @var _api Cloudcache Api Object */ + private $_api; + + /******************************************************************/ + /** Construct Method **********************************************/ + /******************************************************************/ + public function __construct() + { + $this->name = 'cloudcache'; + $this->tab = 'administration'; + $this->version = '1.2'; + $this->author = 'PrestaShop'; + + parent::__construct(); + + $this->displayName = $this->l('CloudCache'); + $this->description = $this->l('Supercharge your Shop with the CloudCache.com Content Delivery Network (CDN).'); + + + /* Backward compatibility */ + require(_PS_MODULE_DIR_.$this->name.'/backward_compatibility/backward.php'); + $this->context->smarty->assign('base_dir', __PS_BASE_URI__); + + if (Configuration::get('PS_CIPHER_ALGORITHM')) + $this->_cipherTool = new Rijndael(_RIJNDAEL_KEY_, _RIJNDAEL_IV_); + else + $this->_cipherTool = new Blowfish(_COOKIE_KEY_, _COOKIE_IV_); + + $this->_api = new CloudcacheApi(); + } + + /** + * @brief Install/Uninstall Configuration variables + * + * @param install True for installation, false for uninstall + * + * @return Success or failure + */ + private function _setupConfigVariables($install = true) + { + $configVars = array( + 'CLOUDCACHE_API_USER' => '', + 'CLOUDCACHE_API_KEY' => '', + 'CLOUDCACHE_API_COMPANY_ID' => '', + 'CLOUDCACHE_API_ACTIVE' => 0, + ); + + $error = 0; + foreach ($configVars as $varName => $value) + if ($install) + $error += Configuration::updateValue($varName, $value) ? 0 : 1; + else + $error += Configuration::deleteByName($varName) ? 0 : 1; + + return $error > 0 ? false : true; + } + + private function _installOverride() + { + // Hash of the empty file in 1.5 => file name + $files = array('810f3fa83a88b5019be31d7b80db460d' => 'classes/Tools.php'); + if (_PS_VERSION_ > '1.5') + $files['5b917f57038acb75714cf144c9043bb4'] = 'classes/controller/FrontController.php'; + + // Make sure the environment is OK + if (!is_dir(dirname(__FILE__).'/../../override/classes/')) + mkdir(dirname(__FILE__).'/../../override/classes/', 0777, true); + if (_PS_VERSION_ > '1.5' && !is_dir(dirname(__FILE__).'/../../override/classes/controller/')) + mkdir(dirname(__FILE__).'/../../override/classes/controller/', 0777); + + $errors = array(); + foreach ($files as $hash => $path) + { + if (file_exists(dirname(__FILE__).'/../../override/'.$path)) + { + if (md5_file(dirname(__FILE__).'/../../override/'.$path) == $hash) + rename(dirname(__FILE__).'/../../override/'.$path, dirname(__FILE__).'/../../override/'.$path.'.origin.php'); + elseif (md5_file(dirname(__FILE__).'/../../override/'.$path) == md5_file(dirname(__FILE__).'/override/'.$path)) + continue ; + else + { + $errors[] = '/override/'.$path; + continue ; + } + } + copy(dirname(__FILE__).'/override/'.$path, dirname(__FILE__).'/../../override/'.$path); + } + + if (count($errors)) + die('
+ '. + $this->l('The module was successfully installed ('). + ''.$this->l('configure').''. + $this->l(') but the following file already exist. Please, merge the file manually.').'
'. + implode('
', $errors). + '
'); + return true; + } + + /******************************************************************/ + /** Install / Uninstall Methods ***********************************/ + /******************************************************************/ + public function install() + { + // Setup config variable with 'install' flag on + if (!$this->_setupConfigVariables(true)) + return false; + + if (!parent::install() || !$this->registerHook('backOfficeTop')) + return false; + + // Perform the sql install + include(dirname(__FILE__).'/sql/sql-install.php'); + foreach ($sql as $s) + if (!Db::getInstance()->execute($s)) + return false; + + return $this->_installOverride();; + } + + /** + * @brief Uninstall function + * + * @return Success or failure + */ + public function uninstall() + { + // Uninstall parent and unregister Configuration + if (!parent::uninstall()) + return false; + + // Unregister hook + if (!$this->unregisterHook('backOfficeTop')) + return false; + + // Remove configuration variable with 'install' flag off + if (!$this->_setupConfigVariables(false)) + return false; + + // Uninstall SQL + include(dirname(__FILE__).'/sql/sql-uninstall.php'); + foreach ($sql as $s) + if (!Db::getInstance()->execute($s)) + return false; + + return true; + } + + /** + * @brief Check that everything is alright for Cloudcache usage. + * + * @return Array empty on success, filled with error messages on failure. + */ + private function _compatibilityCheck() + { + // Compatibility check + $messages = array(); + if (Configuration::get('PS_CSS_THEME_CACHE') || + Configuration::get('PS_JS_THEME_CACHE') || + Configuration::get('PS_HTML_THEME_COMPRESSION') || + Configuration::get('PS_JS_HTML_THEME_COMPRESSION') || + Configuration::get('PS_HIGH_HTML_THEME_COMPRESSION')) + $messages[] = $this->l('In order to succesfully use Cloudcache, please fix the following:'); + + if (Configuration::get('PS_CSS_THEME_CACHE')) + $messages[] = $this->l('Make sure you check "Keep CSS as original"'); + if (Configuration::get('PS_JS_THEME_CACHE')) + $messages[] = $this->l('Make sure you check "Keep JavaScript as original"'); + if (Configuration::get('PS_HTML_THEME_COMPRESSION')) + $messages[] = $this->l('Make sure you check "Keep HTML as original"'); + if (Configuration::get('PS_JS_HTML_THEME_COMPRESSION')) + $messages[] = $this->l('Make sure you check "Keep inline JavaScript in HTML as original"'); + if (Configuration::get('PS_HIGH_HTML_THEME_COMPRESSION')) + $messages[] = $this->l('Make sure you check "Keep W3C validation"'); + if (!extension_loaded('curl')) + $messages[] = $this->l('You should ask your hosting provider to enable CURL extension in PHP (php.ini) for Cloudcache module to work.'); + + // If there is any compatibility issue, just deactivate everything + if (count($messages)) + Configuration::updateValue('CLOUDCACHE_API_ACTIVE', 0); + + return $messages; + } + + /** + * @brief hookBackOfficeTop Implementation. + * + * Hook that allow to add script anywhere in the backoffice. + * + * @return Render to display + */ + public function hookBackOfficeTop() + { + $this->context->smarty->assign('isModuleActive', $this->active); + $this->context->smarty->assign('adminToken', Tools::getAdminTokenLite('AdminModules')); + + $messages = $this->_compatibilityCheck(); + if (count($messages)) + $this->context->smarty->assign('compatibilityIssues', $messages); + + return $this->display(__FILE__, 'views/backOfficeTop.tpl'); + } + + /** + * @brief Empty all tables of the module. + */ + private function _clearTables() + { + Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cloudcache_zone` WHERE `id_shop` = '.(int)$this->context->shop->id); + } + + private function _getCouponUrl() + { + $curLang = $this->context->cookie->id_lang; + +// $prestaBaseUrl = 'http://www.prestashop.com/modules/cloudcache.png?source='.urlencode($_SERVER['HTTP_HOST']); + $prestaBaseUrl = __PS_BASE_URI__.'modules/cloudcache/coupon.php?lang='.$curLang.'&source='.urlencode($_SERVER['HTTP_HOST']); + if (Configuration::get('CLOUDCACHE_API_ACTIVE')) + return $prestaBaseUrl.'&userId='.((int)Configuration::get('CLOUDCACHE_API_USER')). + '&companyId='.urlencode(Configuration::get('CLOUDCACHE_API_COMPANY_ID')); + return $prestaBaseUrl; + } + + /** + * @brief Main Form Method + * + * @return Rendered form + */ + public function getContent() + { + if (Tools::isSubmit('SubmitCloudcacheSettings')) + { + // If we change the credentials, we deactivate the module + Configuration::updateValue('CLOUDCACHE_API_ACTIVE', 0); + // And clear the local cache for the zones + $this->_clearTables(); + + Configuration::updateValue('CLOUDCACHE_API_USER', + Tools::getValue('cloudcache_api_user')); + Configuration::updateValue('CLOUDCACHE_API_COMPANY_ID', + Tools::getValue('cloudcache_api_company_id')); + Configuration::updateValue('CLOUDCACHE_API_KEY', + $this->_cipherTool->encrypt(Tools::getValue('cloudcache_api_key'))); + + $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation()); + } + elseif (Tools::isSubmit('SubmitCloudcacheTestConnection')) + $connectionTestResult = $this->_testConnection(); + elseif (Tools::isSubmit('SubmitCloudcacheAdd_zone')) + { + // Check http[s] + if (substr(Tools::getValue('origin'), 0, 7) != 'http://' && + substr(Tools::getValue('origin'), 0, 8) != 'https://') + $origin = 'http://'.Tools::getValue('origin'); + else + $origin = Tools::getValue('origin'); + + if (substr(Tools::getValue('vanity_domain'), 0, 7) == 'http://' || + substr(Tools::getValue('vanity_domain'), 0, 8) == 'https://') + $vanity = substr(Tools::getValue('vanity_domain'), strpos(':') + 3); + else + $vanity = Tools::getValue('vanity_domain'); + + $zone_info = array( + 'name' => Tools::getValue('name'), + 'origin' => $origin, + 'vanity_domain' => $vanity, + 'label' => Tools::getValue('label'), + 'compress' => Tools::getValue('compress'), + ); + + $action = $this->createZone(Tools::getValue('type'), $zone_info); + + if (is_array($action)) + { + Db::getInstance()->Execute('UPDATE `'._DB_PREFIX_.'cloudcache_zone` SET + `name` = \''.pSQL($_POST['name']).'\', + `origin` = \''.pSQL($_POST['origin']).'\', + `compress` = \''.(isset($_POST['compress']) ? 1: 0).'\', + `label` = \''.pSQL($_POST['label']).'\', + `file_type` = \''.pSQL(CLOUDCACHE_FILE_TYPE_ALL).'\', + `cdn_url` = \''.($_POST['vanity_domain'] ? pSQL($_POST['vanity_domain']) : pSQL($_POST['name'].'.'.Configuration::get('CLOUDCACHE_API_COMPANY_ID').'.'.CLOUDCACHE_API_ZONE_URL)).'\' + WHERE `id_zone` = '.(int)$action['id']); + + $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation($this->l('Zone added.'))); + } + else + $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation($this->l('Error adding the zone: ').' '.pSQL($action), 'error')); + } + elseif (Tools::isSubmit('SubmitCloudcacheSync')) + { + $action = $this->_syncZonesWithServer('all'); + if (is_array($action)) + $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation($this->l('All zones were synced.'))); + else + $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation($this->l('Error syncing zones: ').' '.pSQL($action), 'error')); + } + elseif (Tools::isSubmit('SubmitCloudcacheClearAllCache')) + { + $error = false; + foreach (Db::getInstance()->ExecuteS('SELECT `id_zone`, `zone_type`, `name` FROM `'._DB_PREFIX_.'cloudcache_zone` WHERE `id_shop` = '.(int)$this->context->shop->id.' AND `id_shop` = '.(int)$this->context->shop->id) as $zone) + if (!$this->_api->cachePurgeAll('cache', $zone['id_zone'])) + { + $error = true; + break; + } + + if (!$error) + $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation($this->l('The cache was purged for all zones.'))); + else + $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation($this->l('Error purging cache for all zones.'), 'error')); + } + elseif (Tools::isSubmit('SubmitCloudcacheClearZoneCache')) + { + $zoneName = Db::getInstance()->ExecuteS('SELECT `name` FROM '._DB_PREFIX_.'cloudcache_zone + WHERE `id_zone` = '.(int)Tools::getValue('id_zone').' AND `id_shop` = '.(int)$this->context->shop->id); + + if ($this->_api->cachePurgeAll('cache', Tools::getValue('id_zone'))) + $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation($this->l('The cache was purged for zone:').' '.Tools::safeOutput($zoneName[0]['name']))); + else + $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation($this->l('Error purging cache for zone:').' '.Tools::safeOutput($zoneName[0]['name']), 'error')); + } + elseif (Tools::isSubmit('SubmitCloudcacheEditZoneAction')) // display the form to edit the zone + { + // Get info for current zone + $zone_info = Db::getInstance()->getRow('SELECT `id_zone`, `name`, `origin`, `compress`, `label`, + `cdn_url`, `bw_yesterday`, `bw_last_week`, `bw_last_month`, + `file_type`, `zone_type` + FROM `'._DB_PREFIX_.'cloudcache_zone` + WHERE `id_zone` = '.(int)Tools::getValue('id_zone').' AND `id_shop` = '.(int)$this->context->shop->id); + + // Clean $zone_info before sending to smarty + $zone_info_clean = array(); + foreach ($zone_info as $key => $z) + $zone_info_clean[$key] = pSQL($z); + + $this->context->smarty->assign('edit_zone_info', $zone_info_clean); + } + elseif (Tools::isSubmit('SubmitCloudcacheEdit_zone')) // save the changes on the edited zone + { + Db::getInstance()->Execute('UPDATE `'._DB_PREFIX_.'cloudcache_zone` SET + `name` = \''.pSQL($_POST['name']).'\', + `origin` = \''.pSQL($_POST['origin']).'\', + `compress` = \''.(isset($_POST['compress']) ? 1: 0).'\', + `label` = \''.pSQL($_POST['label']).'\', + `cdn_url` = \''.pSQL($_POST['vanity_domain']).'\', + `file_type` = \''.pSQL($_POST['file_type']).'\' + WHERE `id_zone` = '.(int)Tools::getValue('id_zone')); + + $zone_info = array('id_zone' => (int)Tools::getValue('id_zone'), + 'name' => Tools::getValue('name'), + 'origin' => Tools::getValue('origin'), + 'vanity_domain' => Tools::getValue('vanity_domain'), + 'label' => Tools::getValue('label'), + 'compress' => (bool)Tools::getValue('compress'), + 'file_type' => Tools::getValue('file_type')); + + if (!$this->updateZone(Tools::getValue('type'), $zone_info)) // 0 : good | 1 : bad + $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation($this->l('The following zone was updated:').' '.pSQL(Tools::getValue('name')))); + else + $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation($this->l('Error updating zone:').' '.pSQL(Tools::getValue('name')), 'error')); + } + + $confValues = Configuration::getMultiple(array( + 'CLOUDCACHE_API_USER', + 'CLOUDCACHE_API_KEY', + 'CLOUDCACHE_API_COMPANY_ID')); + + // Set the smarty env + $this->context->smarty->assign('serverRequestUri', + Tools::safeOutput($_SERVER['REQUEST_URI'])); + $this->context->smarty->assign('displayName', + Tools::safeOutput($this->displayName)); + + if (isset($connectionTestResult)) + $this->context->smarty->assign('connectionTestResult', + $connectionTestResult); + + if (isset($confValues['CLOUDCACHE_API_COMPANY_ID'])) + $this->context->smarty->assign('companyId', + Tools::safeOutput($confValues['CLOUDCACHE_API_COMPANY_ID'])); + if (isset($confValues['CLOUDCACHE_API_USER'])) + $this->context->smarty->assign('apiUser', + Tools::safeOutput($confValues['CLOUDCACHE_API_USER'])); + + $this->context->smarty->assign('apiKey', + Tools::safeOutput($this->_cipherTool->decrypt($confValues['CLOUDCACHE_API_KEY']))); + + $messages = $this->_compatibilityCheck(); + + if (count($messages)) + $this->context->smarty->assign('compatibilityIssues', $messages); + + $this->context->smarty->assign('allAvailableZones', $this->_api->getAvailableNamespaces(true)); + $this->context->smarty->assign('prepaidBandwith', $this->getPrepaidBandwidth()); + + // Get the zones + //$zones = array('zone2' => array('name' => '','type' => 'css')); + + $zones = array(); + if (Configuration::get('CLOUDCACHE_API_USER')) + $zones = $this->getZones('pullzone'); + + // display the form + $this->context->smarty->assign('apiActive', Configuration::get('CLOUDCACHE_API_ACTIVE')); + $this->context->smarty->assign('zones', $zones); + + $this->context->smarty->assign('couponUrl', $this->_getCouponUrl()); + $this->context->smarty->assign('defaultOriginServerURL', (Configuration::get('PS_SSL_ENABLED') ? 'https://' : 'http://').Configuration::get('PS_SHOP_DOMAIN')); + if (isset($_GET['id_tab'])) + $this->context->smarty->assign('cloudcache_id_tab', (int)$_GET['id_tab']); + $this->context->smarty->assign('cloudcache_tracking', 'http://www.prestashop.com/modules/'.$this->name.'.png?url_site='.Tools::safeOutput($_SERVER['SERVER_NAME']).'&id_lang='.$this->context->cookie->id_lang); + return $this->display(__FILE__, 'views/content2.tpl'); + } + + /** + * @brief Test the conenction to Cloudcache and the credentials. + * + * In order to test that, we just try to get the pullzones amd we check if + * the server reply the errorCode 0. If can't connect or other errorCode, then + * there is something wrong. + * + * @return True if the connection is OK, false otherwise + */ + private function _testConnection() + { + set_time_limit(0); + + if (count($this->_compatibilityCheck())) + return array(''. + $this->l('You have compatibility issues, please fix them before using the module.').'', + '#FFD8D8'); + + $zones = $this->_api->listZones('pullzone'); + + if ($this->_api->getLastFaultCode()) + { + $ret = array(' + '.$this->l('Connection Test Failed.').'', + '#FFD8D8', false); + Configuration::updateValue('CLOUDCACHE_API_ACTIVE', 0); + return $ret; + } + + $defaultName = pSQL($this->l('prestashop')); + // Check if default zone exists + for ($i = 0; $i < count($zones); $i++) + if ($zones[$i]['name'] == $defaultName) + { + $defaultName .= rand(1, 999); + $i = 0; + } + + $newZone = false; + + // If there is no zones, then create the default one + if (!count($zones) || !Configuration::get('CLOUDCACHE_API_COMPANY_ID')) + { + $origin = pSQL((Configuration::get('PS_SSL_ENABLED') ? 'https://' : 'http://').Configuration::get('PS_SHOP_DOMAIN')); + $r = $this->createZone('pullzone', array( + 'name' => $defaultName, + 'origin' => $origin, + 'compress' => 1, + )); + + if (is_array($r)) + { + Db::getInstance()->Execute('UPDATE `'._DB_PREFIX_.'cloudcache_zone` SET `name` = \''.pSQL($defaultName).'\', `origin` = \''.$origin.'\', `compress` = \'1\', `file_type` = \''.pSQL(CLOUDCACHE_FILE_TYPE_ALL).'\', `cdn_url` = \''.pSQL($r['cdn_url']).'\' WHERE `id_zone` = '.(int)$r['id']); + + $tmp = substr($r['cdn_url'], strlen($defaultName) + 1); + $companyId = substr($tmp, 0, strlen('netdna-cdn.com') * -1 - 1); + Configuration::updateValue('CLOUDCACHE_API_COMPANY_ID', pSQL($companyId)); + } + else // If failure, the zonename have probably been taken + { + $defaultName .= rand(1, 999); + $r = $this->createZone('pullzone', array( + 'name' => $defaultName, + 'origin' => $origin, + 'compress' => 1, + )); + if (is_array($r)) + { + Db::getInstance()->Execute('UPDATE `'._DB_PREFIX_.'cloudcache_zone` SET `name` = \''.pSQL($defaultName).'\', `origin` = \''.$origin.'\', `compress` = \'1\', `file_type` = \''.pSQL(CLOUDCACHE_FILE_TYPE_ALL).'\', `cdn_url` = \''.pSQL($r['cdn_url']).'\' WHERE `id_zone` = '.(int)$r['id']); + $tmp = substr($r['cdn_url'], strlen($defaultName) + 1); + $companyId = substr($tmp, 0, strlen('netdna-cdn.com') * -1 - 1); + Configuration::updateValue('CLOUDCACHE_API_COMPANY_ID', pSQL($companyId)); + } + else + return array(''.$this->l('An error occured, impossible to create a default zone.').'', '#FFD8D8', true); + } + $newZone = $tmp; + } + + Configuration::updateValue('CLOUDCACHE_API_ACTIVE', 1); + $ret = array(' + '.$this->l('Register').' '. + Configuration::get('PS_SHOP_DOMAIN').' '. + $this->l('on Cloudcache').'
+ + ', '#D6F5D6', true); + if ($newZone) + $ret['newZone'] = $origin; + return $ret; + } + + /* + ** Display a custom message for settings update + ** $text string Text to be displayed in the message + ** $type string (confirm|warn|error) Decides what color will the + ** message have (green|yellow) + */ + private function _displayConfirmation($text = '', $type = 'confirm') + { + switch ($type) + { + case 'confirm': + $img = 'ok.gif'; + break ; + case 'warn': + $img = 'warn2.png'; + break ; + case 'error': + $img = 'disabled.gif'; + break ; + default: + die('Invalid type.'); + } + + return array( + 'class' => Tools::safeOutput($type), + 'img' => Tools::safeOutput($img), + 'text' => (empty($text) ? $this->l('Settings updated') : $text) + ); + } + + /******************************************************************/ + /** Tools methods *************************************************/ + /******************************************************************/ + function getCurrentURL($htmlEntities = false) + { + $url = $_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI']; + return (!empty($_SERVER['HTTPS']) ? 'https' : 'http'). + '://'.($htmlEntities ? preg_replace('/&/', '&', $url): $url); + } + + /** + * @brief Retrieve the bandwith from the selected zone and date range. + * + * @param zoneId Zone Id from which we want to retrieve data + * @param range Range to retrieve (daily, weekly, monthly) + * + * @return Bandwith spent by the selecte zone between the wanted date range. + */ + private function _getZoneTransfer($zoneId, $range) + { + // Associate a range with real date + $allowedRange = array( + 'daily' => date('Y-m-d', + mktime(0, 0, 0, date('n'), date('j') - 1, date('Y'))), + 'weekly' => date('Y-m-d', + mktime(0, 0, 0, date('n'), date('j') - 7, date('Y'))), + 'monthly' => date('Y-m-d', + mktime(0, 0, 0, date('n') - 1, date('j'), date('Y'))), + ); + // Retrieve today's date + $today = date('Y-m-d'); + + // We check the range is correct + if (!array_key_exists($range, $allowedRange)) + return -1; + + + $companyId = Configuration::get('CLOUDCACHE_API_COMPANY_ID'); + + // Retrieve the data from the server + $r = $this->_api->getTotalTransferStats('report', $companyId, $zoneId, + $allowedRange[$range], $today); + + // Check if the transaction went well + if ($this->_api->getLastFaultCode()) + return -1; + + return 0; + } + + /** + * @brief Retrieves Zones from Cloudcache Server and sync with local data + * + * @param type Namespace of the zones to retrieve (pullzone, pushzone, etc) + * + * @return Array describing the zones. + */ + private function _syncZonesWithServer($type) + { + // Send the request to the API server + $cdnZones = array(); + if ($type == 'all') + foreach ($this->_api->getAvailableNamespaces() as $namespace) + $cdnZones[$namespace] = $this->_api->listZones($namespace); + else + $cdnZones[$type] = $this->_api->listZones($type); + + + $zones = array(); + // Check if the transaction went well + if (!$this->_api->getLastFaultCode()) + { + // Build our custom array from the retieved data + foreach ($cdnZones as $namespace => $cdnZone) + { + foreach ($cdnZone as $zone) + { + $exists = false; + + $row = Db::getInstance()->getRow('SELECT `id_zone`, `id_shop`, `origin`, `cdn_url`, `file_type` FROM `'._DB_PREFIX_.'cloudcache_zone` WHERE `id_zone` = '.(int)$zone['id']); + + if ($row['id_zone']) + $exists = true; + + if ($exists && $row['id_shop'] != $this->context->shop->id) + continue ; + + $zones[(int)$zone['id']] = array( + 'id_zone' => (int)$zone['id'], + 'name' => pSQL($zone['name']), + 'origin' => ($exists ? pSQL($row['origin']) : $this->l('no data')), + 'cdn_url' => ($exists ? pSQL($row['cdn_url']) : $this->l('no data')), + 'bw_yesterday' => (int)$this->_getZoneTransfer($zone['id'], 'daily'), + 'bw_last_week' => (int)$this->_getZoneTransfer($zone['id'], 'weekly'), + 'bw_last_month' => (int)$this->_getZoneTransfer($zone['id'], 'monthly'), + 'file_type' => ($exists ? pSQL($row['file_type']) : 'none'), + 'zone_type' => pSQL($namespace), + 'id_shop' => $this->context->shop->id, + 'id_group_shop' => $this->context->shop->id_group_shop, + ); + } + } + + // For each zone, update or insert the new data in the database + foreach ($zones as $id_zone => $zone_data) + if ($zone_data['zone_type'] != 'all') + Db::getInstance()->Execute('REPLACE INTO `'._DB_PREFIX_.'cloudcache_zone` + (`'.implode('`,`', array_keys($zone_data)).'`) + VALUES (\''.implode('\', \'', $zone_data).'\')'); + + return $zones; + } + + return false; + } + + /** + * @brief Get Zones from the selected zone type. + * + * If the sync flag is setted to true, then retrieve data from Cloudcache servers. + * + * @note Function call thru ajax + * + * @param type Type of zone (Cloudcache namespace) + * @param sync Flag to know if we should ask the Cloudcache servers. + * + * @return Array describing the zones + */ + public function getZones($type, $sync = false) + { + // Check that the $type is correct an harmless for the database + if (!in_array($type, $this->_api->getAvailableNamespaces())) + return $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation($this->l('Invalid zone type.'), 'error')); + + // Check on the database if $sync is false (cache) + $zones = array(); + if (!$sync) + { + $d = Db::getInstance()->ExecuteS(' + SELECT `id_zone`, `id_shop`, `name`, `origin`, `compress`, `label`, `cdn_url`, + `bw_yesterday`, `bw_last_week`, `bw_last_month`, `file_type`, `zone_type` + FROM `'._DB_PREFIX_.'cloudcache_zone` + WHERE `zone_type` = \''.pSQL($type).'\' AND `id_shop` = '.(int)$this->context->shop->id); + foreach ($d as $line) + $zones[$line['id_zone']] = $line; + } + + // if no result or if $sync, load data from API server + if (($sync || !count($zones)) && Configuration::get('CLOUDCACHE_API_ACTIVE')) + $zones = $this->_syncZonesWithServer($type); + + // Return the data array + return $zones; + } + + /** + * @brief Create a Zone on the Cloudcache Server. + * + * First create the zone thru the API then call the sync function in order to + * update the local data. + * + * @param type Type of the zone (pullzone, pushzone, etc) + * @param values Array describing the zone. + * + * @return True if everything went well, False otherwise + */ + public function createZone($type, $values) + { + // Check that the $type is correct an harmless for the database + if (!in_array($type, $this->_api->getAvailableNamespaces())) + return $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation($this->l('Invalid zone type.'), 'error')); + + // Create the basic zone data + $zone = array( + 'name' => pSQL($values['name']), + 'origin' => pSQL($values['origin']), + ); + + // If an optional field is set, add it to the zone + $optionalFields = array('vanity_domain', 'vhost', + 'ip', 'compress', 'label'); + foreach ($optionalFields as $field) + if (isset($values[$field]) && !empty($values[$field])) + $zone[$field] = pSQL($values[$field]); + + // Then send the request to the server + // The server return an array with the new id, vanity_ip and cdn_url + $r = $this->_api->createZone($type, $zone); + + // Check if the transaction went well and return result + if ($this->_api->getLastFaultCode()) + return $this->_api->getLastFaultString(); + + /* // Insert the new zone in database */ + /* if ($this->_updateZoneSql($r, 'CRE)) */ + /* return $this->context->smarty->assign('confirmMessage', */ + /* $this->_displayConfirmation($this->l('Unknown internal error.'), 'error')); */ + + // Sync + $this->_syncZonesWithServer($type); + + return $r; + } + + /** + * @brief Update the selected zone. + * + * @param type Type of the zone (namespace) + * @param values Values of the updated zone + * + * @return True if OK, false otherwise. + */ + public function updateZone($type, $values) + { + // Check that the $type is correct an harmless for the database + if (!in_array($type, $this->_api->getAvailableNamespaces())) + return $this->context->smarty->assign('confirmMessage', + $this->_displayConfirmation($this->l('Invalid zone type.'), 'error')); + + // Create the basic zone data + $zone = array( + 'id' => (int)$values['id_zone'], + ); + + $optionalFields = array('name', 'origin', 'vhost', 'ip', 'compress', 'label'); + foreach ($optionalFields as $field) + if (isset($values[$field]) && !empty($values[$field])) + $zone[$field] = pSQL($values[$field]); + + // The updateZone return a bool success/error + $r = $this->_api->updateZone($type, (int)$values['id_zone'], $zone); + + // Check if the transaction went well + if ($this->_api->getLastFaultCode()) + return ; //die('KO '.pSQL($this->_api->getLastFaultString())); // pSQL for XSS + + return $r; + } + + /** + * @brief Retrieve the paid Bandwith left + * + * @return The bandwith left. + */ + public function getPrepaidBandwidth() + { + // The namespace need to retrieve the bandwith is 'account' + // The server reply the amount of bandwith left + $r = $this->_api->getBandwidth('account'); + + // Check if the transaction went well + if ($this->_api->getLastFaultCode()) + return $this->l('N/A'); + +// $r /= (1024 * 1024 * 1024 * 1024); + $r /= (1000 * 1000 * 1000 * 1000); + return round($r, 2); + } + + +} diff --git a/modules/cloudcache/cloudcache_logo.png b/modules/cloudcache/cloudcache_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..137c7c14800517e89eb3a9f1240c7c323fa63945 GIT binary patch literal 9783 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

Rhoc#hT^hzP5zSFZ6h7*+^9}=^= zRa_xzbZ(RMw^>SH39MU2JxE= zz)X7+8SbUsV+-!7q%trh(mmr_B-bN4{XK?29)2#t|Bz97AQ6U(0LhQJ6b^cH0!LJ) z4%7LKS$G)Y>9%&W`vVj+8JMt_TG|dC(KeHHT#O|ugmvCqCGs;?xqx3@^c-rEr`6(! zrEmzl_G!;}lP@b`uAL<5Ah3se_SB6-yw0-?-X1i%E3cLo@yIjz?kTtcBS{KyXfB5#sR<=e-u88_PqZLO7-j-Owi1RB5Mu3^lV6KM{>Bf59tHPLMO>$ zMhJ+OqZ4GnrW`4|o4_wZ;FYdGCC=MCaXxJ;By<-owS$tRqRSF@<=hc}G~SOw)73~m z9O%Y!t%^R|J*&ewUz)CStK{sv6bA3VxZ!bhkG)RN zu#7*#Zb>8kk+!fww%Yx6J~^^2RJ4aeYR1krA+d&(4_Lse*dyt%ZZ*W%OAVhtb4+L% zw@qo8lZ%^6P)&O$>YU_b@R8q?B0YhW0CT|@t;(qB?+V6DY)1QBmUyahaV>YT16%MY zpyF(PS?qy<{YMKPJF=(l)V@Lms%+kM!%*QPiDJ!}Gj2WZxwnIksI>I&x0(K zv|b15Ed7qpZ@AQvYrW;WoY?DeHT|H30U3WP+?Y?@bErGS!OgPog06oU=oc9R0&uFs zFiMIvYn2uCGe~4jglliA=}CQf-GUX`5iu;#_rr)E&(7ste%hK*-O={g`^eU7jiy&( z&IVD=m&H)EPLeC?IUPBlyHZ+ zm<(AYQW$ca6;&jf!h^Y3(D2(U_kU2F1LRHVaVa3MWx@0#5e#L&%Feb#%7(LM^C*GsT~?r_PaZe1zM=CRJr?G?XjwdcfUwg zS$uQY<@jk3O2CSSjx?Advw-@5jwNpT@Z=%D><1cVo^T}evIY`Q^8<7_3BJ5t+=!4Q zRX>5=E@lz>IXZFS90d&$5{6vv12UCVxJUmxv^hoev83v$tz3=2vB6dnDfIL8I^(N| z!f9+Q{ep+D)ogTw630rF2Eyk+kDM3XbSuEyN`cK#>M*+}ijoJDkjIIVp?dH-4$=me z%)m&p0f|s}3I#491CyjbeKuW~>(S>4P*3Kb~=uJq~B!eB>aMn=qWr#Z3T73{w zye6ex1^|R+u#$mALm7??&;`0>1S?Ky)*&XWHy44e76H&^C0bo~e2S8HvsxKF3x@`F F{x1oWuU!BD literal 0 HcmV?d00001 diff --git a/modules/cloudcache/cloudcache_ps_logo.png b/modules/cloudcache/cloudcache_ps_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f63ad7eaf208f2cec7d5b43184a5b7b3a50b9081 GIT binary patch literal 26694 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

;7SyIN0ghb=o+Hgzh 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 literal 0 HcmV?d00001 diff --git a/modules/cloudcache/config.xml b/modules/cloudcache/config.xml new file mode 100644 index 000000000..dd061f2dc --- /dev/null +++ b/modules/cloudcache/config.xml @@ -0,0 +1,12 @@ + + + cloudcache + + + + + + 1 + 1 + + \ No newline at end of file diff --git a/modules/cloudcache/coupon.png b/modules/cloudcache/coupon.png new file mode 100644 index 0000000000000000000000000000000000000000..c5195185a8c784777496f1f669d542d63628a256 GIT binary patch literal 19547 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 literal 0 HcmV?d00001 diff --git a/modules/cloudcache/es.php b/modules/cloudcache/es.php new file mode 100644 index 000000000..25b5f9cb4 --- /dev/null +++ b/modules/cloudcache/es.php @@ -0,0 +1,108 @@ +cloudcache_f86d8910fee395bdb3d9e0165763235d'] = 'CloudCache'; +$_MODULE['<{cloudcache}prestashop>cloudcache_04a5752fc2ed63bac60e8e819fb37a8a'] = 'Acelera tu tienda con la red de distribución de contenido de CloudCache.com'; +$_MODULE['<{cloudcache}prestashop>cloudcache_3d31c37e7b674b0882fd6eb636b9f883'] = 'El módulo se ha instalado correctamente ('; +$_MODULE['<{cloudcache}prestashop>cloudcache_e2d5a00791bce9a01f99bc6fd613a39d'] = 'configurar'; +$_MODULE['<{cloudcache}prestashop>cloudcache_7171d1fc720355ddfb40537f566d8775'] = ') pero el siguiente archivo ya existe. Por favor, combine los archivos manualmente.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_21d079ded53ed9bc6e6fef18a7deff87'] = 'Para poder usar Cloudcache correctamente, por favor corrija los siguientes errores:'; +$_MODULE['<{cloudcache}prestashop>cloudcache_c3c06280e8f7a758b5b3ba55bc31aa54'] = 'Asegúrese de marcar \"Mantener CSS como el original\"'; +$_MODULE['<{cloudcache}prestashop>cloudcache_610c04b1a22a45228804fbe850e5035c'] = 'Asegúrese de marcar \"Mantener JavaScript como el original\"'; +$_MODULE['<{cloudcache}prestashop>cloudcache_adf0bfd77f102fb4b793f62efdb52ad8'] = 'Asegúrese de marcar \"Mantener HTML como el original\"'; +$_MODULE['<{cloudcache}prestashop>cloudcache_936aea1c0611cae3cd3c8b6299d22c28'] = 'Asegúrese de marcar \"Mantener JavaScript en HTML como el original\"'; +$_MODULE['<{cloudcache}prestashop>cloudcache_3da4c8edd49a7c69fcd74b31955ec538'] = 'Asegúrese de marcar \"Mantener validación W3C como el original\"'; +$_MODULE['<{cloudcache}prestashop>cloudcache_f0c746da3e0a8585f9431752a47f4471'] = 'Usted debe pedir a su proveedor de hosting que active la extensión CURL de PHP (php.ini) para que el módulo Cloudcache pueda funcionar correctamente.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_108118f45620e45322c3d5862591c418'] = 'Zona añadida.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_a63636b538337a40fcdd3363c6707f15'] = 'Error añadiendo zona.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_58f9c3960361509cf1d68bf643941ca1'] = 'Todas las zonas fueron sincronizadas.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_c72fd9c59ceba8b6724e2fd47823a371'] = 'Error sincronizando las zonas.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_3d015f6519d6a4266d58735cbb0646aa'] = 'Se limpió el cache para todas las zonas.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_927a80bfb795058dd0d56b910ce75182'] = 'Error limpiando el cache para todas las zonas.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_0784546200bf0e0b692ce92fca030051'] = 'Se limpió el cache de la zona: '; +$_MODULE['<{cloudcache}prestashop>cloudcache_677b9ce0ce1753890cfeb9d88b8b6a2d'] = 'Error limpiando el cache de la zona: '; +$_MODULE['<{cloudcache}prestashop>cloudcache_5ea453e070eec6c0d81206b66e7b47bb'] = 'La siguiente zona fue actualizada:'; +$_MODULE['<{cloudcache}prestashop>cloudcache_7e185a7cd33a7ec22abaff3f1faa8d28'] = 'Error actualizando la zona: '; +$_MODULE['<{cloudcache}prestashop>cloudcache_d5a735d3395d8928332af64d25479273'] = 'Se han encontrado problemas de compatibilidad, por favor corrija los errores antes de usar el modulo.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_63a77eaf9f310dc5def6709c2887dcc4'] = 'Conexión de prueba fallida.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_4dee17e319890f92a2595a308752a1c3'] = 'prestashop'; +$_MODULE['<{cloudcache}prestashop>cloudcache_2631b272000ffdc568ad04d7982d1f7c'] = 'Ha ocurrido un error. Imposible crear una zona predeterminada.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_0ba7583639a274c434bbe6ef797115a4'] = 'Incribirse'; +$_MODULE['<{cloudcache}prestashop>cloudcache_43bff7159a5c1a845ce6bd00f90b692f'] = 'en Cloudcache'; +$_MODULE['<{cloudcache}prestashop>cloudcache_c888438d14855d7d96a2724ee9c306bd'] = 'Opciones actualizadas'; +$_MODULE['<{cloudcache}prestashop>cloudcache_42c4d8746ae597c8db2724998063b0f5'] = 'no hay datos'; +$_MODULE['<{cloudcache}prestashop>cloudcache_fd7695c3fa7df2aeb0957554d5007378'] = 'Tipo de zona inválida.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_fb27ef14699bf81370e0c7384aa9e118'] = 'Error interno desconocido'; +$_MODULE['<{cloudcache}prestashop>cloudcache_382b0f5185773fa0f67a8ed8056c7759'] = 'N/A'; +$_MODULE['<{cloudcache}prestashop>backofficetop_3f3391f413c692b76b90034e19074ea4'] = 'Actualmente usted está teniendo mejor rendimiento'; +$_MODULE['<{cloudcache}prestashop>backofficetop_baccc7910ea31b5cf2fedc346eee0fe6'] = 'usando el módulo CloudCache'; +$_MODULE['<{cloudcache}prestashop>backofficetop_bdb079cd385ce7a5227f1d56d91200c0'] = 'el mejor servicio CDN recomendado por usuarios de PrestaShop!'; +$_MODULE['<{cloudcache}prestashop>backofficetop_69595fc8764ca77da1ca3cab85ba38b1'] = 'Tenga aun mejor rendimiento'; +$_MODULE['<{cloudcache}prestashop>backofficetop_938d6948b598780c6d713b7cd350e963'] = 'al activar el módulo CloudCache'; +$_MODULE['<{cloudcache}prestashop>backofficetop_fa65e0f3acb9c0b1d8243c3edeebe058'] = 'Todo luce bien para CloudCache'; +$_MODULE['<{cloudcache}prestashop>content2_41902f7e9bb357cbada1d47b0c252f23'] = 'Abra su cuenta en CloudCache.com'; +$_MODULE['<{cloudcache}prestashop>content2_d500acbdf6da11b99ba163036743bd67'] = 'CloudCache provee a los usuarios de PrestaShop un descuento exclusivo del 25% al mes en cada paquete disponible. Por favor, haga click en logo abajo para subscribirse:'; +$_MODULE['<{cloudcache}prestashop>content2_38d9b91c515b7d3a97f2bf6e72cd24ec'] = 'Este módulo le permite acelerar su tienda PrestaShop a través de la red de distribución de contenido de CloudCache.'; +$_MODULE['<{cloudcache}prestashop>content2_03894b6f1d0447b352ae5093b25beb3d'] = 'Una red de distribución de contenido (CDN) es para cada dueño de tiendas en línea que demanda de un alto rendimiento, y un servicio exclusivo al visitante.'; +$_MODULE['<{cloudcache}prestashop>content2_0bcf4cd42f3d29472055668091d3ebc6'] = 'Chequeo de compatibilidad de CloudCache'; +$_MODULE['<{cloudcache}prestashop>content2_d83cbcd4f991b9b43098437b58a7d1f3'] = 'Por qué CloudCache'; +$_MODULE['<{cloudcache}prestashop>content2_6a26f548831e6a8c26bfbbd9f6ec61e0'] = 'Ayuda'; +$_MODULE['<{cloudcache}prestashop>content2_f4f70727dc34561dfde1a3c529b6205c'] = 'Opciones'; +$_MODULE['<{cloudcache}prestashop>content2_dad1f8d794ee0dd7753fe75e73b78f31'] = 'Zonas'; +$_MODULE['<{cloudcache}prestashop>content2_52b730e469f179c52956110ff7d1920f'] = 'CloudCache está enfocado en entregar el contenido de la web con una eficiencia a la velocidad de la luz. Nosotros mantenemos una copia de los elementos \"pesados\" de su tienda PrestaShop, como imágenes, CSS y JavaScript en nuestros centros de datos alrededor del mundo y entregamos el contenido a sus visitantes desde la localización mas cercana. Estos son algunos de los numerosos beneficios de tener un sitio que carga mas rápido:'; +$_MODULE['<{cloudcache}prestashop>content2_3ce64c688b949bec0dcec57bbdf2e48f'] = 'Crezca durante los picos de tráfico'; +$_MODULE['<{cloudcache}prestashop>content2_be2501ec16464402d9069fabeae68cd0'] = 'En caso de que llegará a ser famoso durante la noche (es decir, su tienda se menciona en la televisión), sus ventas se dispararían, pero su servidor puede bloquearse debido a la sobrecarga, nosotros te ayudamos a equilibrar la sobrecarga de tráfico.'; +$_MODULE['<{cloudcache}prestashop>content2_867681a0d2b527c81f35f397e26d96dc'] = 'Certificados SSL fáciles'; +$_MODULE['<{cloudcache}prestashop>content2_aeebd43d3152b36de94e03808ad25957'] = 'Nuestra aceleración SSL disminuye la sobrecarga de energía de la CPU del servidor hasta un 70%. Además, hemos hecho que sea muy fácil y asequible para ejecutar SSL en la nube. Solo cobramos $ 24.95 por certificados SSL por Zona, por mes, incluyendo el certificado SSL.'; +$_MODULE['<{cloudcache}prestashop>content2_2ea70b7571baf4de04631283af3a3a4b'] = 'Mayor puntuación SEO'; +$_MODULE['<{cloudcache}prestashop>content2_6ad31549b6404d19697b28d718e2aa77'] = 'Google utiliza la velocidad de la página como un factor clave en su algoritmo de clasificación. Páginas que cargan rapido ganan posiciones más altas en los resultados de búsqueda, lo que significa más tráfico y más dinero para usted.'; +$_MODULE['<{cloudcache}prestashop>content2_5b40a55b46342c709ef61fe48a11572f'] = 'Aumentar sus conversiones'; +$_MODULE['<{cloudcache}prestashop>content2_8a8d5433fef3fe8b8940e32698d97e9d'] = 'Amazon halló que si su sitio se carga 100 milésimas de segundo más lento, pierden un 1% mas de sus ingresos. Creemos que esta sola razón es suficiente para preocuparse por el tiempo de carga de página.'; +$_MODULE['<{cloudcache}prestashop>content2_412504f79aaab3a708c24f2b5f6af4b6'] = 'Cómo configurar CloudCache'; +$_MODULE['<{cloudcache}prestashop>content2_2cb43a908b4bc11d16e651063a9c4027'] = 'Suscríbete a CloudCache haciendo clic en el logo a la derecha'; +$_MODULE['<{cloudcache}prestashop>content2_f85e413f1f55286db4da358df3053062'] = 'Crear un usuario de la API y obtenga el ID de usuario y la clave de la API'; +$_MODULE['<{cloudcache}prestashop>content2_13ad6491d90c3d8b08f072c13c34a797'] = 'ver vídeo tutorial'; +$_MODULE['<{cloudcache}prestashop>content2_44641e218ebb6c07d0cb756a79673069'] = 'Cómo configurar el módulo:'; +$_MODULE['<{cloudcache}prestashop>content2_784f83c332ae60b4d453cb63dc8c49b1'] = 'Llene el ID de usuario CloudCache y el campos de la clave de la API con los proporcionados por CloudCache.'; +$_MODULE['<{cloudcache}prestashop>content2_2a5075cb5b1148436ee37a1ef46e51a6'] = 'Haga clic en \"Guardar configuración\" y luego \"Probar su conexión\".'; +$_MODULE['<{cloudcache}prestashop>content2_59c8906c9f68c992003be17308173bd9'] = 'Si usted no tiene las zonas ya existentes, una zona predeterminada se creará.'; +$_MODULE['<{cloudcache}prestashop>content2_fbd942973e8b17f065e587a2512a8f7f'] = 'Con el fin de aumentar el rendimiento, se puede ir a su cuenta CloudCache haciendo clic en el enlace de la derecha, vaya a la lista Pullzones, elija su zona, editar, ir a las Opciones avanzadas y desactive la opción de \"Query String\". Con el fin de guardar los cambios, haga clic en \"Actualizar\".'; +$_MODULE['<{cloudcache}prestashop>content2_727283f37d3e70d3faceb71761d0fb51'] = 'Propósito del modulo:'; +$_MODULE['<{cloudcache}prestashop>content2_0bae64ec079443c47750df1bdbf35871'] = 'Un Content Delivery Network (CDN) es para cada propietario de un sitio que exige un alto rendimiento, y proveer una experiencia excelente al visitante. ¿Por qué? Debido a que nuestro CDN acelera al máximo tus archivos descargables - imágenes, vídeo, scripts, css-con tiempos de carga super-rápido.'; +$_MODULE['<{cloudcache}prestashop>content2_343618e9fcce53224cc7a47c2145c5e5'] = '¿Qué modificaciones hace el módulo de hacer en mi tienda?'; +$_MODULE['<{cloudcache}prestashop>content2_082be8b13b40bfbf9c9cc70316bc1fec'] = 'Tools.php se sobrescribirá.'; +$_MODULE['<{cloudcache}prestashop>content2_21ce50e4e30845efcb5005cdaa912bfd'] = '[Ficha Preferencias -> Rendimiento -> Servidores de Medios de Comunicación] mostrará un enlace a las configuraciones de módulos CloudCache y una notificación cuando el módulo está activo.'; +$_MODULE['<{cloudcache}prestashop>content2_b480863a2096d2a5fca0cb3460b79c1e'] = 'Ha ocurrido un error mientras se prueba la conexión.'; +$_MODULE['<{cloudcache}prestashop>content2_e72dafd095a9ec228dfd9c054f0530b0'] = 'Éxito! Eso es todo lo que tienes que hacer!'; +$_MODULE['<{cloudcache}prestashop>content2_633d7bd9ecfcb1b67fa03b8a281aa8dd'] = 'ahora es acelerado por cloudcache.com'; +$_MODULE['<{cloudcache}prestashop>content2_7e1678acc78078f22f6196a9737e3431'] = 'Éxito! La conexión se ha establecido!'; +$_MODULE['<{cloudcache}prestashop>content2_bd93cd0fb528a1a2cb9cee3ecea69906'] = 'Haga clic aquí para acceder a su cuenta CloudCache'; +$_MODULE['<{cloudcache}prestashop>content2_c949d7fe576e586e47a39b41752bd12c'] = 'El ancho de banda disponible es:'; +$_MODULE['<{cloudcache}prestashop>content2_c6bce2a0c02ed4dc352779f8ecfdb3f5'] = 'ID de la compañia'; +$_MODULE['<{cloudcache}prestashop>content2_e9db460a0154b96521063e4054997cdc'] = 'Usuario API'; +$_MODULE['<{cloudcache}prestashop>content2_d876ff8da67c3731ae25d8335a4168b4'] = 'Clave API'; +$_MODULE['<{cloudcache}prestashop>content2_9daf1fb753b42c3cdc8f1d01669cd6d8'] = 'Guardar configuración'; +$_MODULE['<{cloudcache}prestashop>content2_79ddd6cfbf01f63986f7b114ecc2ca95'] = 'Haga clic aquí para Conexión de prueba'; +$_MODULE['<{cloudcache}prestashop>content2_16bfbf9c462762cf1cba4134ec53c504'] = 'Cargando'; +$_MODULE['<{cloudcache}prestashop>content2_382b0f5185773fa0f67a8ed8056c7759'] = 'N/A'; +$_MODULE['<{cloudcache}prestashop>content2_7469a286259799e5b37e5db9296f00b3'] = 'SI'; +$_MODULE['<{cloudcache}prestashop>content2_c2f3f489a00553e7a01d369c103c7251'] = 'NO'; +$_MODULE['<{cloudcache}prestashop>content2_2e1911a1922b9c56488e7e3d37bf9cb5'] = 'Purgar caché'; +$_MODULE['<{cloudcache}prestashop>content2_50100a6fa3a40543749d43b69210d473'] = 'Sincronizar las zonas!'; +$_MODULE['<{cloudcache}prestashop>content2_1cd6038e51e247649e3046c3d963658d'] = 'Añadir zonas'; +$_MODULE['<{cloudcache}prestashop>content2_3114114e2cdf0892739ee4ca557e8da5'] = 'Borrar todo el caché'; +$_MODULE['<{cloudcache}prestashop>content2_93604834836f885a26ad3c780a490841'] = 'ID de la zona'; +$_MODULE['<{cloudcache}prestashop>content2_fa018acf009f5435d50578357f449ed3'] = 'Nombre del Pull Zone'; +$_MODULE['<{cloudcache}prestashop>content2_5e50936ee3316f28363a8de993cb07e8'] = 'URL del servidor de origen'; +$_MODULE['<{cloudcache}prestashop>content2_ae9980aee84bd689e09532a927049f2c'] = 'Dominio CDN personalizado '; +$_MODULE['<{cloudcache}prestashop>content2_b021df6aac4654c454f46c77646e745f'] = 'Etiqueta'; +$_MODULE['<{cloudcache}prestashop>content2_82af841589057aa8922b1ac3bb4a28a4'] = 'Compresión'; +$_MODULE['<{cloudcache}prestashop>content2_e539fd665e202f536325140d87d7bf72'] = 'Tipo de Archivo'; +$_MODULE['<{cloudcache}prestashop>content2_b1c94ca2fbc3e78fc30069c8d0f01680'] = 'Todo'; +$_MODULE['<{cloudcache}prestashop>content2_9b001c69726efc0b7ef4876759a01874'] = 'Tipo de Zona'; +$_MODULE['<{cloudcache}prestashop>content2_ea4788705e6873b424c65e91c2846b19'] = 'Cancelar'; +$_MODULE['<{cloudcache}prestashop>content2_c9cc8cce247e49bae79f15173ce97354'] = 'Guardar'; +$_MODULE['<{cloudcache}prestashop>content2_962a4c03ca790e4682304039c71e79d4'] = 'Crear Zona'; +$_MODULE['<{cloudcache}prestashop>content2_96f583efefa0ce369ffeb5c3a7b3bd2a'] = 'Cloudcache y PrestaShop'; +$_MODULE['<{cloudcache}prestashop>content2_fb66c688ec4cf810d1d60a11a67458c4'] = 'Aprenda mas acerca de Cloudcache en PrestaShop.com'; diff --git a/modules/cloudcache/fr.php b/modules/cloudcache/fr.php new file mode 100644 index 000000000..98dfb2d3c --- /dev/null +++ b/modules/cloudcache/fr.php @@ -0,0 +1,108 @@ +cloudcache_f86d8910fee395bdb3d9e0165763235d'] = 'CloudCache'; +$_MODULE['<{cloudcache}prestashop>cloudcache_04a5752fc2ed63bac60e8e819fb37a8a'] = 'Accélérez votre boutique avec le Content Delivery Network (CDN) de CloudCache.com'; +$_MODULE['<{cloudcache}prestashop>cloudcache_3d31c37e7b674b0882fd6eb636b9f883'] = 'Le module a été correctement intallé ('; +$_MODULE['<{cloudcache}prestashop>cloudcache_e2d5a00791bce9a01f99bc6fd613a39d'] = 'configurer'; +$_MODULE['<{cloudcache}prestashop>cloudcache_7171d1fc720355ddfb40537f566d8775'] = ') mais le fichier suivant existe. Fusionnez le manuellement.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_21d079ded53ed9bc6e6fef18a7deff87'] = 'Pour utiliser correctement CloudCache, réglez les problèmes suivant:'; +$_MODULE['<{cloudcache}prestashop>cloudcache_c3c06280e8f7a758b5b3ba55bc31aa54'] = 'Assurez vous que \'Gardez les CSS originaux\''; +$_MODULE['<{cloudcache}prestashop>cloudcache_610c04b1a22a45228804fbe850e5035c'] = 'Assurez vous que \'Gardez les JavaScript originaux\''; +$_MODULE['<{cloudcache}prestashop>cloudcache_adf0bfd77f102fb4b793f62efdb52ad8'] = 'Assurez vous que \'Gardez les HTML originaux\''; +$_MODULE['<{cloudcache}prestashop>cloudcache_936aea1c0611cae3cd3c8b6299d22c28'] = 'Assurez vous que \"Gardez les Javascript dans les HTML originaux\"'; +$_MODULE['<{cloudcache}prestashop>cloudcache_3da4c8edd49a7c69fcd74b31955ec538'] = 'Assurez vous que \'Garder la validation W3C\' soit séléctionné'; +$_MODULE['<{cloudcache}prestashop>cloudcache_f0c746da3e0a8585f9431752a47f4471'] = 'Vous devez demander à votre hébergeur d\'activer l\'extension PHP CURL (dans php.ini) afin de pouvoir faire fonctionner le module CloudCache.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_108118f45620e45322c3d5862591c418'] = 'Zone ajouté.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_a63636b538337a40fcdd3363c6707f15'] = 'Erreur pendant l\'ajout de la zone:'; +$_MODULE['<{cloudcache}prestashop>cloudcache_58f9c3960361509cf1d68bf643941ca1'] = 'Toutes les zones ont été synchronisé'; +$_MODULE['<{cloudcache}prestashop>cloudcache_c72fd9c59ceba8b6724e2fd47823a371'] = 'Erreur pendant la synchronisation des zones:'; +$_MODULE['<{cloudcache}prestashop>cloudcache_3d015f6519d6a4266d58735cbb0646aa'] = 'Le cache a été purgé pour toutes les zones.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_927a80bfb795058dd0d56b910ce75182'] = 'Erreur pendant la purge du cache pour toutes els zones.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_0784546200bf0e0b692ce92fca030051'] = 'Le cache a été purgé pour la zone:'; +$_MODULE['<{cloudcache}prestashop>cloudcache_677b9ce0ce1753890cfeb9d88b8b6a2d'] = 'Erreur pendant la purge du cache pour la zone:'; +$_MODULE['<{cloudcache}prestashop>cloudcache_5ea453e070eec6c0d81206b66e7b47bb'] = 'Les zones suivantes ont été mise à jour:'; +$_MODULE['<{cloudcache}prestashop>cloudcache_7e185a7cd33a7ec22abaff3f1faa8d28'] = 'Erreur pendant la mise à jour de la zone:'; +$_MODULE['<{cloudcache}prestashop>cloudcache_d5a735d3395d8928332af64d25479273'] = 'Vous avez des erreurs de compatibilité, résolvez les avant d\'utiliser le module.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_63a77eaf9f310dc5def6709c2887dcc4'] = 'Le test de connexion a échoué.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_4dee17e319890f92a2595a308752a1c3'] = 'PrestaShop'; +$_MODULE['<{cloudcache}prestashop>cloudcache_2631b272000ffdc568ad04d7982d1f7c'] = 'Une erreur est survenue, impossible de créer la zone par défault.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_0ba7583639a274c434bbe6ef797115a4'] = 'S\'inscrire'; +$_MODULE['<{cloudcache}prestashop>cloudcache_43bff7159a5c1a845ce6bd00f90b692f'] = 'sur CloudCache'; +$_MODULE['<{cloudcache}prestashop>cloudcache_c888438d14855d7d96a2724ee9c306bd'] = 'Paramètres mis à jour'; +$_MODULE['<{cloudcache}prestashop>cloudcache_42c4d8746ae597c8db2724998063b0f5'] = 'Aucune données'; +$_MODULE['<{cloudcache}prestashop>cloudcache_fd7695c3fa7df2aeb0957554d5007378'] = 'Type de zone invalide.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_fb27ef14699bf81370e0c7384aa9e118'] = 'Erreur interne.'; +$_MODULE['<{cloudcache}prestashop>cloudcache_382b0f5185773fa0f67a8ed8056c7759'] = 'N/A'; +$_MODULE['<{cloudcache}prestashop>backofficetop_3f3391f413c692b76b90034e19074ea4'] = 'Vous économisez encore plus de performances en'; +$_MODULE['<{cloudcache}prestashop>backofficetop_baccc7910ea31b5cf2fedc346eee0fe6'] = 'utilisant le module CloudCache'; +$_MODULE['<{cloudcache}prestashop>backofficetop_bdb079cd385ce7a5227f1d56d91200c0'] = 'le meilleur server CDN recommandé par les utilisateurs de PrestaShop!'; +$_MODULE['<{cloudcache}prestashop>backofficetop_69595fc8764ca77da1ca3cab85ba38b1'] = 'Économisez encore plus de performances en'; +$_MODULE['<{cloudcache}prestashop>backofficetop_938d6948b598780c6d713b7cd350e963'] = 'activant le module CloudCache'; +$_MODULE['<{cloudcache}prestashop>backofficetop_fa65e0f3acb9c0b1d8243c3edeebe058'] = 'Tout les paramètres sont corrects pour CloudCache'; +$_MODULE['<{cloudcache}prestashop>content2_41902f7e9bb357cbada1d47b0c252f23'] = 'Ouvrir un compte chez CloudCache.com'; +$_MODULE['<{cloudcache}prestashop>content2_d500acbdf6da11b99ba163036743bd67'] = 'CloudCache fournis aux utilsiateurs de PrestaShop une réduction exclusive de 25% par mois sur tous les pack disponible. Cliquez sur le logo ci-dessous pour s\'inscrire:'; +$_MODULE['<{cloudcache}prestashop>content2_38d9b91c515b7d3a97f2bf6e72cd24ec'] = 'Ce module permet d\'accélérer votre PrestaShop grâce au Content Delivery Network de CloudCache'; +$_MODULE['<{cloudcache}prestashop>content2_03894b6f1d0447b352ae5093b25beb3d'] = 'Un Content Delivery Network (CDN) est pour un webmaster qui cherche de hautes performances et qui préviligie l\'expérience utilisateur.'; +$_MODULE['<{cloudcache}prestashop>content2_0bcf4cd42f3d29472055668091d3ebc6'] = 'Vérification de compatiblité pour CloudCache'; +$_MODULE['<{cloudcache}prestashop>content2_d83cbcd4f991b9b43098437b58a7d1f3'] = 'Pourquoi CloudCache'; +$_MODULE['<{cloudcache}prestashop>content2_6a26f548831e6a8c26bfbbd9f6ec61e0'] = 'Aide'; +$_MODULE['<{cloudcache}prestashop>content2_f4f70727dc34561dfde1a3c529b6205c'] = 'Paramètres'; +$_MODULE['<{cloudcache}prestashop>content2_dad1f8d794ee0dd7753fe75e73b78f31'] = 'Zones'; +$_MODULE['<{cloudcache}prestashop>content2_52b730e469f179c52956110ff7d1920f'] = 'CloudCache est centré sur l\'envoie de votre contenu à très haute vitesse. Nous convervons une copie de des éléments \'lourd\' de PrestaShop comme les images, CSS et les JavaScript dans nos datacenters répartie partout dans le monde et nous envoyons ces fichier à vos visiteurs depuis l\'endroit le plus proche de chez eux. Ci-dessous vous trouvez quelques avantage d\'avoir un site qui charge plus rapidement'; +$_MODULE['<{cloudcache}prestashop>content2_3ce64c688b949bec0dcec57bbdf2e48f'] = 'Fonctionne très bien lors des période de fort traffic'; +$_MODULE['<{cloudcache}prestashop>content2_be2501ec16464402d9069fabeae68cd0'] = 'Si vous devenez célébre dans la nuit (e.g. votre boutique aparrait à la télé), vos ventes vont augmenter mais votre server risque de tomber; nous vous aidons à gérer la balance de votre traffic.'; +$_MODULE['<{cloudcache}prestashop>content2_867681a0d2b527c81f35f397e26d96dc'] = 'SSL personnel facilité'; +$_MODULE['<{cloudcache}prestashop>content2_aeebd43d3152b36de94e03808ad25957'] = 'Notre accélération SSL alège la charge CPU de votre server de 70%. En plus, on a rendu très simple et bon marché la mise en place du SSL sur la \'cloud\'. Le coût est de 24.95$ par Zone personnalisée par mois incluant le certificat SSL.'; +$_MODULE['<{cloudcache}prestashop>content2_2ea70b7571baf4de04631283af3a3a4b'] = 'Meilleur classement SEO'; +$_MODULE['<{cloudcache}prestashop>content2_6ad31549b6404d19697b28d718e2aa77'] = 'Google utilise la vitesse de chargement des page comme facteur clef dans leur algorithme de classement. Les pages les plus rapide ont le meilleurs classement, ce qui signifie plus de traffic et donc plus de revenu pour vous.'; +$_MODULE['<{cloudcache}prestashop>content2_5b40a55b46342c709ef61fe48a11572f'] = 'Augement le taux de conversions'; +$_MODULE['<{cloudcache}prestashop>content2_8a8d5433fef3fe8b8940e32698d97e9d'] = 'Amazon a trouvé que si votre site charge 100 millisecondes plus lentement, vous perdez 1% de vos revenue. Nous penseons que pour cette simple raison, il est important de s\'inquiéter du temps de chargement des pages.'; +$_MODULE['<{cloudcache}prestashop>content2_412504f79aaab3a708c24f2b5f6af4b6'] = 'Comment configurer CloudCache'; +$_MODULE['<{cloudcache}prestashop>content2_2cb43a908b4bc11d16e651063a9c4027'] = 'S\'inscrire sur CloudCache en cliquant sur le logo à droite'; +$_MODULE['<{cloudcache}prestashop>content2_f85e413f1f55286db4da358df3053062'] = 'Créer des identifiants récupérer le \'User Id\' et le \'API key\''; +$_MODULE['<{cloudcache}prestashop>content2_13ad6491d90c3d8b08f072c13c34a797'] = 'jouer la video tutoriel (en anglais)'; +$_MODULE['<{cloudcache}prestashop>content2_44641e218ebb6c07d0cb756a79673069'] = 'Comment configurer le module:'; +$_MODULE['<{cloudcache}prestashop>content2_784f83c332ae60b4d453cb63dc8c49b1'] = 'Remplir les champs \'User Id\' et \'Api keu\' avec les informations founis par CloudCache'; +$_MODULE['<{cloudcache}prestashop>content2_2a5075cb5b1148436ee37a1ef46e51a6'] = 'Cliquer sur le bouton \'Sauvegarder les paramètres\' puis sur \'Tester la connexion\''; +$_MODULE['<{cloudcache}prestashop>content2_59c8906c9f68c992003be17308173bd9'] = 'Si vous n\'avez pas de zones, une zone par défaut sera créée'; +$_MODULE['<{cloudcache}prestashop>content2_fbd942973e8b17f065e587a2512a8f7f'] = 'Pour améliorer les performances, vous pouvez aller sur votre compte CloudCache en cliquant sur le lien à droite, allez sur la liste des \'Pullzones\', choisissez votre zone, éditez la, allez à \'Advanced Settings\' et décochez l\'option \'Query String\'. Pour sauvegar, cliquez sur \'Update\'.'; +$_MODULE['<{cloudcache}prestashop>content2_727283f37d3e70d3faceb71761d0fb51'] = 'But du module:'; +$_MODULE['<{cloudcache}prestashop>content2_0bae64ec079443c47750df1bdbf35871'] = 'Un Content Delivery Network (CDN) est pour tout webmaster qui veut des hautes performances. Pourquoi? Parce que notre CDN hébérge vos fichiers téléchargeables (images, vidéos, scripts, css) avec des temps de chargement extrêmement rapide.'; +$_MODULE['<{cloudcache}prestashop>content2_343618e9fcce53224cc7a47c2145c5e5'] = 'Quelles modifications le module apporte à mon magasin?'; +$_MODULE['<{cloudcache}prestashop>content2_082be8b13b40bfbf9c9cc70316bc1fec'] = 'Le fichier Tools.php va être surchargé.'; +$_MODULE['<{cloudcache}prestashop>content2_21ce50e4e30845efcb5005cdaa912bfd'] = '[Onglet Préférences -> Performance -> Servers de média] va afficher un lien vers la page de configuration du module CloudCache que celui ci est actif.'; +$_MODULE['<{cloudcache}prestashop>content2_b480863a2096d2a5fca0cb3460b79c1e'] = 'Un erreur est survenue pendant le test de la connexion.'; +$_MODULE['<{cloudcache}prestashop>content2_e72dafd095a9ec228dfd9c054f0530b0'] = 'Succès! C\'est out ce que vous aviez à faire!'; +$_MODULE['<{cloudcache}prestashop>content2_633d7bd9ecfcb1b67fa03b8a281aa8dd'] = 'est maintenant accéléré par CloudCache.com'; +$_MODULE['<{cloudcache}prestashop>content2_7e1678acc78078f22f6196a9737e3431'] = 'Succès! La connexion a été correctement établie!'; +$_MODULE['<{cloudcache}prestashop>content2_bd93cd0fb528a1a2cb9cee3ecea69906'] = 'Cliquez ici pour accéder à votre compte CloudCache'; +$_MODULE['<{cloudcache}prestashop>content2_c949d7fe576e586e47a39b41752bd12c'] = 'Votre bande passante disponnible est:'; +$_MODULE['<{cloudcache}prestashop>content2_c6bce2a0c02ed4dc352779f8ecfdb3f5'] = 'Company ID'; +$_MODULE['<{cloudcache}prestashop>content2_e9db460a0154b96521063e4054997cdc'] = 'API User'; +$_MODULE['<{cloudcache}prestashop>content2_d876ff8da67c3731ae25d8335a4168b4'] = 'API Key'; +$_MODULE['<{cloudcache}prestashop>content2_9daf1fb753b42c3cdc8f1d01669cd6d8'] = 'Sauvegarder les paramètres'; +$_MODULE['<{cloudcache}prestashop>content2_79ddd6cfbf01f63986f7b114ecc2ca95'] = 'Cliquer ici pour tester la connexion'; +$_MODULE['<{cloudcache}prestashop>content2_16bfbf9c462762cf1cba4134ec53c504'] = 'Chargement'; +$_MODULE['<{cloudcache}prestashop>content2_382b0f5185773fa0f67a8ed8056c7759'] = 'N/A'; +$_MODULE['<{cloudcache}prestashop>content2_7469a286259799e5b37e5db9296f00b3'] = 'OUI'; +$_MODULE['<{cloudcache}prestashop>content2_c2f3f489a00553e7a01d369c103c7251'] = 'NON'; +$_MODULE['<{cloudcache}prestashop>content2_2e1911a1922b9c56488e7e3d37bf9cb5'] = 'Purger le cache'; +$_MODULE['<{cloudcache}prestashop>content2_50100a6fa3a40543749d43b69210d473'] = 'Synchroniser les Zones!'; +$_MODULE['<{cloudcache}prestashop>content2_1cd6038e51e247649e3046c3d963658d'] = 'Ajouter des zones'; +$_MODULE['<{cloudcache}prestashop>content2_3114114e2cdf0892739ee4ca557e8da5'] = 'Effacer tout le cache'; +$_MODULE['<{cloudcache}prestashop>content2_93604834836f885a26ad3c780a490841'] = 'Zone ID'; +$_MODULE['<{cloudcache}prestashop>content2_fa018acf009f5435d50578357f449ed3'] = 'Nom de la Pull Zone'; +$_MODULE['<{cloudcache}prestashop>content2_5e50936ee3316f28363a8de993cb07e8'] = 'URL du server d\'origine'; +$_MODULE['<{cloudcache}prestashop>content2_ae9980aee84bd689e09532a927049f2c'] = 'Domaine CDN personalisé'; +$_MODULE['<{cloudcache}prestashop>content2_b021df6aac4654c454f46c77646e745f'] = 'Label'; +$_MODULE['<{cloudcache}prestashop>content2_82af841589057aa8922b1ac3bb4a28a4'] = 'Compression'; +$_MODULE['<{cloudcache}prestashop>content2_e539fd665e202f536325140d87d7bf72'] = 'Type de fichier'; +$_MODULE['<{cloudcache}prestashop>content2_b1c94ca2fbc3e78fc30069c8d0f01680'] = 'Tous'; +$_MODULE['<{cloudcache}prestashop>content2_9b001c69726efc0b7ef4876759a01874'] = 'Type de Zone'; +$_MODULE['<{cloudcache}prestashop>content2_ea4788705e6873b424c65e91c2846b19'] = 'Annuler'; +$_MODULE['<{cloudcache}prestashop>content2_c9cc8cce247e49bae79f15173ce97354'] = 'Sauvegarder'; +$_MODULE['<{cloudcache}prestashop>content2_962a4c03ca790e4682304039c71e79d4'] = 'Créer la zone'; +$_MODULE['<{cloudcache}prestashop>content2_96f583efefa0ce369ffeb5c3a7b3bd2a'] = 'CloudCache et PrestaShop'; +$_MODULE['<{cloudcache}prestashop>content2_fb66c688ec4cf810d1d60a11a67458c4'] = 'En savoir plus sur CloudCache sur PrestaShop.com'; diff --git a/modules/cloudcache/index.php b/modules/cloudcache/index.php new file mode 100644 index 000000000..a85fb997d --- /dev/null +++ b/modules/cloudcache/index.php @@ -0,0 +1,36 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 14011 $ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); +header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); + +header("Cache-Control: no-store, no-cache, must-revalidate"); +header("Cache-Control: post-check=0, pre-check=0", false); +header("Pragma: no-cache"); + +header("Location: ../"); +exit; \ No newline at end of file diff --git a/modules/cloudcache/jquery-plugins/index.php b/modules/cloudcache/jquery-plugins/index.php new file mode 100644 index 000000000..a85fb997d --- /dev/null +++ b/modules/cloudcache/jquery-plugins/index.php @@ -0,0 +1,36 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 14011 $ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); +header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); + +header("Cache-Control: no-store, no-cache, must-revalidate"); +header("Cache-Control: post-check=0, pre-check=0", false); +header("Pragma: no-cache"); + +header("Location: ../"); +exit; \ No newline at end of file diff --git a/modules/cloudcache/jquery-plugins/jquery.easy-confirm-dialog.js b/modules/cloudcache/jquery-plugins/jquery.easy-confirm-dialog.js new file mode 100644 index 000000000..4e8e7ac8e --- /dev/null +++ b/modules/cloudcache/jquery-plugins/jquery.easy-confirm-dialog.js @@ -0,0 +1,155 @@ +/** + * jQuery Easy Confirm Dialog plugin 1.2 + * + * Copyright (c) 2010 Emil Janitzek (http://projectshadowlight.org) + * Based on Confirm 1.3 by Nadia Alramli (http://nadiana.com/) + * + * Samples and instructions at: + * http://projectshadowlight.org/jquery-easy-confirm-dialog/ + * + * This script is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + */ +(function($) { + $.easyconfirm = {}; + $.easyconfirm.locales = {}; + $.easyconfirm.locales.enUS = { + title: 'Are you sure?', + text: 'Are you sure that you want to perform this action?', + button: ['Cancel', 'Confirm'], + closeText: 'close' + }; + $.easyconfirm.locales.svSE = { + title: 'Är du säker?', + text: 'Är du säker på att du vill genomföra denna åtgärden?', + button: ['Avbryt', 'Bekräfta'], + closeText: 'stäng' + }; + + $.fn.easyconfirm = function(options) { + + var _attr = $.fn.attr; + + $.fn.attr = function(attr, value) { + // Let the original attr() do its work. + var returned = _attr.apply(this, arguments); + + // Fix for jQuery 1.6+ + if (attr == 'title' && returned === undefined) + returned = ''; + + return returned; + }; + + var options = jQuery.extend({ + eventType: 'click', + icon: 'help' + }, options); + + var locale = jQuery.extend({}, $.easyconfirm.locales.enUS, options.locale); + + // Shortcut to eventType. + var type = options.eventType; + + return this.each(function() { + var target = this; + var $target = jQuery(target); + + // If no events present then and if there is a valid url, then trigger url change + var urlClick = function() { + if (target.href) { + var length = String(target.href).length; + if (target.href.substring(length - 1, length) != '#') + document.location = target.href; + } + }; + + // If any handlers where bind before triggering, lets save them and add them later + var saveHandlers = function() { + var events = jQuery.data(target, 'events'); + if (events) { + target._handlers = new Array(); + for (var i in events[type]) { + target._handlers.push(events[type][i]); + } + + $target.unbind(type); + } + }; + // Re-bind old events + var rebindHandlers = function() { + if (target._handlers !== undefined) { + jQuery.each(target._handlers, function() { + $target.bind(type, this); + }); + } + }; + + if ($target.attr('title') !== null && $target.attr('title').length > 0) + locale.text = $target.attr('title'); + + var dialog = (options.dialog === undefined || typeof(options.dialog) != 'object') ? + $('

' + locale.text + '
') : + options.dialog; + + var buttons = {}; + buttons[locale.button[1]] = function() { + // Unbind overriding handler and let default actions pass through + $target.unbind(type, handler); + + // Close dialog + $(dialog).dialog("close"); + + // Check if there is any events on the target + if (jQuery.data(target, 'events')) { + // Trigger click event. + $target.click(); + } + else { + // No event trigger new url + urlClick(); + } + + init(); + + }; + buttons[locale.button[0]] = function() { + $(dialog).dialog("close"); + }; + + $(dialog).dialog({ + autoOpen: false, + resizable: false, + draggable: false, + closeOnEscape: true, + width: 'auto', + minHeight: 220, + maxHeight: 200, + buttons: buttons, + title: locale.title, + closeText: locale.closeText, + modal: true + }); + + // Handler that will override all other actions + var handler = function(event) { + $(dialog).dialog('open'); + event.stopImmediatePropagation(); + event.preventDefault(); + return false; + }; + + var init = function() { + saveHandlers(); + $target.bind(type, handler); + rebindHandlers(); + }; + + init(); + + }); + + }; +})(jQuery); diff --git a/modules/cloudcache/lib/CloudCacheApi.php b/modules/cloudcache/lib/CloudCacheApi.php new file mode 100644 index 000000000..8e92770dd --- /dev/null +++ b/modules/cloudcache/lib/CloudCacheApi.php @@ -0,0 +1,388 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 1.4 $ +* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +// Load the XML-RPC abstraction +require(dirname(__FILE__).'/xmlrpc.inc.php'); + +// Defines for the report.gettotaltransfert Method rangeType values +if (!defined('CLOUDCACHE_RANGE_TYPE_TODAY')) +{ + define('CLOUDCACHE_RANGE_TYPE_TODAY', '1'); + define('CLOUDCACHE_RANGE_TYPE_HOUR', '2'); + define('CLOUDCACHE_RANGE_TYPE_DATE', '3'); + + define('CLOUDCACHE_FILE_TYPE_UNASSOCIATED', 'none'); + define('CLOUDCACHE_FILE_TYPE_ALL', 'all'); + define('CLOUDCACHE_FILE_TYPE_IMG', 'img'); + define('CLOUDCACHE_FILE_TYPE_JS', 'js'); + define('CLOUDCACHE_FILE_TYPE_CSS', 'css'); + define('CLOUDCACHE_FILE_TYPE_OTHER', 'other'); +} + +class CloudcacheApi +{ + private $apiKey; + private $apiUserId; + private $companyId; + private $curDate; + private $hashType; + private $pullzoneType; + private $availableNamespaces = array('pullzone' => 'Pull Zone'); + + private $lastRpcRequest; + private $lastRpcResponse; + + /** @var port Port of the cloudcache XML-RPC Api server */ + private $port; + + /** @var httpMethod Http method to be used when using the XML-RPC API */ + private $httpMethod; + + public function __construct() + { + // Declare which method is avaible for which namespace + $tab = array( + 'user' => array('listUsers', 'update'), + 'account' => array('getBandwidth'), + 'report' => array( + 'getTotalTransfer', + 'getTotalHits', + 'getTotalTransferStats', + 'getCacheHitStats', + 'getPopularFiles', + 'getUsagePerDay', + 'getNodeHits', + 'getConnectionStats', + 'getHourlyConnectionStats', + ), + 'cache' => array('purge', 'purgeAllCache'), + 'pullzone' => array('listZones', 'create', 'update'), + 'pushzone' => array('listZones', 'create', 'update'), + 'vodzone' => array('listZones', 'create', 'update'), +// 'livezone' => array('listZones', 'create', 'update', 'delete'), // Not yet implemented + ); + + // Connection settings + $this->port = CLOUDCACHE_API_PORT; + $this->httpMethod = CLOUDCACHE_API_HTTP_METHOD; + $this->apiURI = CLOUDCACHE_API_URI; + $this->apiURL = CLOUDCACHE_API_URL; + + // Api credentials + if (Configuration::get('PS_CIPHER_ALGORITHM')) + $this->_cipherTool = new Rijndael(_RIJNDAEL_KEY_, _RIJNDAEL_IV_); + else + $this->_cipherTool = new Blowfish(_COOKIE_KEY_, _COOKIE_IV_); + + $this->apiKey = $this->_cipherTool->decrypt(Configuration::get('CLOUDCACHE_API_KEY')); + $this->apiUserId = Configuration::get('CLOUDCACHE_API_USER'); + $this->companyId = Configuration::get('CLOUDCACHE_API_COMPANY_ID'); + + // Random stuff needed by the API + // Save the current timezone + $currentTimezone = date_default_timezone_get(); + // Set the timezone to the Cloudcache one + date_default_timezone_set('America/Los_Angeles'); + // Retreive the RFC 8601 With Cloudcache timezone + $this->curDate = date('c'); + // Put back user timezone + date_default_timezone_set($currentTimezone); + + $this->hashType = CLOUDCACHE_API_HASH_TYPE; + $this->pullzoneType = CLOUDCACHE_API_PULL_ZONE_TYPE; + } + + + /** + * @brief Create a new empty XML-RPC message ready for cloudcache API. + * + * @param namespace Namespace of the request + * @param method Method used + * + * @return The new xmlrpcmsg Object instance. + */ + private function _getEmptyRpcMessage($namespace, $method) + { + // reset last command + $this->lastRpcRequest = $this->lastRpcResponse = null; + + return new xmlrpcmsg($namespace.'.'.$method, array( + php_xmlrpc_encode($this->apiUserId), + php_xmlrpc_encode(hash($this->hashType, + $this->curDate.':'.$this->apiKey.':'.$method)), + php_xmlrpc_encode($this->curDate), + )); + } + + /** + * @brief List zones. + * + * @param namespace Namespace wanted (pullzone, pushzone, vodzone) + * + * @return List of the zones (replied by the cloudcache server) + */ + public function listZones($namespace) + { + $method = 'listZones'; + + // Initialize the XML-RPC message + $rpcMsg = $this->_getEmptyRpcMessage($namespace, $method); + + switch ($namespace) + { + case 'pullzone': + // For the pullzone, we add a parameter type + $rpcMsg->addParam(php_xmlrpc_encode($this->pullzoneType)); + break; + case 'pushzone': + case 'vodzone': + case 'livezone': + default: + break; + } + + return $this->_sendRequest($namespace, $rpcMsg); + } + + /** + * @brief Create a zone. + * + * @param namespace Namespace wanted (pullzone, pushzone, vodzone) + * @param values Array describing the zone to create. + * + * @return The anwser from the server. + */ + public function createZone($namespace, $values) + { + $method = 'create'; + + // $values['name'] = 'zonename'; + // $values['origin'] = 'zone origin url'; + // $values['vanity_domain'] = 'domain to use for the zone'; + + $rpcMsg = $this->_getEmptyRpcMessage($namespace, $method); + $rpcMsg->addParam(php_xmlrpc_encode($values)); + + return $this->_sendRequest($namespace, $rpcMsg); + } + + /** + * @brief Update a zone + * + * @param namespace Namespace wanted (pullzone, pushzone, vodzone) + * @param zoneId Id of the zone to update + * @param values Array describing the new infos of the zone. + * + * @return Reply from the server. + */ + public function updateZone($namespace, $zoneId, $values) + { + $method = 'update'; + + $rpcMsg = $this->_getEmptyRpcMessage($namespace, $method); + $rpcMsg->addParam(php_xmlrpc_encode($zoneId)); + $rpcMsg->addParam(php_xmlrpc_encode($values)); + + return $this->_sendRequest($namespace, $rpcMsg); + } + + /** + * @brief Retrieve the Bandwith of the wanted time area + * + * @param namespace Namespace wanted (should be 'account') + * @param from From when to check + * @param to Until when to check + * + * @return Bandwith used (Replied from the server) + */ + public function getBandwidth($namespace, $from = null, $to = null) + { + $method = 'getBandwidth'; + + $rpcMsg = $this->_getEmptyRpcMessage($namespace, $method); + if ($from) + $rpcMsg->addParam(php_xmlrpc_encode($from)); + if ($to) + $rpcMsg->addParam(php_xmlrpc_encode($to)); + + return $this->_sendRequest($namespace, $rpcMsg); + } + + /** + * @brief Purge the cache of the given URL. + * + * @param namespace Namespace wanted (should be 'cache') + * @param url Url to purge + * + * @return Reply from the server. + */ + public function cachePurge($namespace, $url) + { + $method = 'purge'; + + $rpcMsg = $this->_getEmptyRpcMessage($namespace, $method); + $rpcMsg->addParam(php_xmlrpc_encode($url)); + + return $this->_sendRequest($namespace, $rpcMsg); + } + + /** + * @brief Purge all cache of the specified zone. + * + * @param namespace Namespace wanted (should be 'cache') + * @param zoneId Zone to purge. + * + * @return Reply from the server. + */ + public function cachePurgeAll($namespace, $zoneId) + { + $method = 'purgeAllCache'; + + $rpcMsg = $this->_getEmptyRpcMessage($namespace, $method); + $rpcMsg->addParam(php_xmlrpc_encode($zoneId)); + + return $this->_sendRequest($namespace, $rpcMsg); + } + + /** + * @brief Retrieve the data transfert from the given zone and dates + * + * @param namespace Namespace wanted (should be 'report') + * @param zoneId Zone to check + * @param rangeType Type of date range wanted (1: today, 2:cur day, + * 3: date range) + * @param from Date from where to retrieve the data (format Y-m-d) + * @param to Date from where to retrieve the data (format Y-m-d) + * + * @return Reply from the server. + */ + public function getTotalTransfer($namespace, $zoneId, $rangeType, + $from = null, $to = null) + { + $method = 'getTotalTransferStats'; + + + $rpcMsg = $this->_getEmptyRpcMessage($namespace, $method); + $rpcMsg->addParam(php_xmlrpc_encode(Configuration::get('CLOUDCACHE_API_COMPANY_ID'))); + + $rpcMsg->addParam(php_xmlrpc_encode($zoneId)); + //$rpcMsg->addParam(php_xmlrpc_encode($rangeType)); + + if ($rangeType == CLOUDCACHE_RANGE_TYPE_DATE) + { + if ($from) + $rpcMsg->addParam(php_xmlrpc_encode($from)); + if ($to) + $rpcMsg->addParam(php_xmlrpc_encode($to)); + } + return $this->_sendRequest($namespace, $rpcMsg); + } + + /** + * @brief Retrieve the data transfert from the given zone and dates + * + * @param namespace Namespace wanted (should be 'report') + * @param companyId Company to check + * @param zoneId Zone to check + * @param from Date from where to retrieve the data (format Y-m-d) + * @param to Date from where to retrieve the data (format Y-m-d) + * + * @return Reply from the server. + */ + public function getTotalTransferStats($namespace, $companyId, $zoneId, + $from, $to) + { + $method = 'getTotalTransferStats'; + + $rpcMsg = $this->_getEmptyRpcMessage($namespace, $method); + $rpcMsg->addParam(php_xmlrpc_encode($companyId)); + $rpcMsg->addParam(php_xmlrpc_encode($from)); + $rpcMsg->addParam(php_xmlrpc_encode($to)); + $rpcMsg->addParam(php_xmlrpc_encode($zoneId)); + + return $this->_sendRequest($namespace, $rpcMsg); + } + + + /** + * @brief Actually send the request to the cloudcache API server. + * + * @param namespace Namespace of the request + * @param rpcMsg XML-RPC message Object containing the actual request + * + * @return Reply from the server + */ + private function _sendRequest($namespace, $rpcMsg) + { + $this->lastRpcRequest = $rpcMsg; + + // Initialize the XML-RPL client + $rpcClient = new xmlrpc_client($this->apiURI.$namespace, + $this->apiURL, $this->port, $this->httpMethod); + + if (file_exists(dirname(__FILE__).'/proxy.inc.php')) + { + include(dirname(__FILE__).'/proxy.inc.php'); + $rpcClient->setProxy($proxy->host, $proxy->port, + $proxy->username, $proxy->password); + } + + // Send the message + $this->lastRpcResponse = $rpcClient->send($rpcMsg); + + return !$this->getLastFaultCode() ? php_xmlrpc_decode($this->lastRpcResponse->value()) : false; + } + + public function getLastFaultCode() + { + if ($this->lastRpcResponse) + return $this->lastRpcResponse->faultCode(); + return false; + } + + public function getLastFaultString() + { + if ($this->lastRpcResponse) + return $this->lastRpcResponse->faultString(); + return false; + } + + public function getLastRpcResponse() + { + return $this->lastRpcResponse; + } + + public function getLastRpcRequest() + { + return $this->lastRpcRequest; + } + + public function getAvailableNamespaces($name = false) + { + return !$name ? array_keys($this->availableNamespaces) : $this->availableNamespaces; + } +} diff --git a/modules/cloudcache/lib/index.php b/modules/cloudcache/lib/index.php new file mode 100644 index 000000000..a85fb997d --- /dev/null +++ b/modules/cloudcache/lib/index.php @@ -0,0 +1,36 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 14011 $ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); +header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); + +header("Cache-Control: no-store, no-cache, must-revalidate"); +header("Cache-Control: post-check=0, pre-check=0", false); +header("Pragma: no-cache"); + +header("Location: ../"); +exit; \ No newline at end of file diff --git a/modules/cloudcache/lib/xmlrpc.inc.php b/modules/cloudcache/lib/xmlrpc.inc.php new file mode 100644 index 000000000..6013c38f0 --- /dev/null +++ b/modules/cloudcache/lib/xmlrpc.inc.php @@ -0,0 +1,3716 @@ + +// $Id: xmlrpc.inc,v 1.174 2009/03/16 19:36:38 ggiunta Exp $ + +// Copyright (c) 1999,2000,2002 Edd Dumbill. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of the "XML-RPC for PHP" nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + + if(!function_exists('xml_parser_create')) + { + // For PHP 4 onward, XML functionality is always compiled-in on windows: + // no more need to dl-open it. It might have been compiled out on *nix... + if(strtoupper(substr(PHP_OS, 0, 3) != 'WIN')) + { + dl('xml.so'); + } + } + + // Try to be backward compat with php < 4.2 (are we not being nice ?) + $phpversion = phpversion(); + if($phpversion[0] == '4' && $phpversion[2] < 2) + { + // give an opportunity to user to specify where to include other files from + if(!defined('PHP_XMLRPC_COMPAT_DIR')) + { + define('PHP_XMLRPC_COMPAT_DIR',dirname(__FILE__).'/compat/'); + } + if($phpversion[2] == '0') + { + if($phpversion[4] < 6) + { + include(PHP_XMLRPC_COMPAT_DIR.'is_callable.php'); + } + include(PHP_XMLRPC_COMPAT_DIR.'is_scalar.php'); + include(PHP_XMLRPC_COMPAT_DIR.'array_key_exists.php'); + include(PHP_XMLRPC_COMPAT_DIR.'version_compare.php'); + } + include(PHP_XMLRPC_COMPAT_DIR.'var_export.php'); + include(PHP_XMLRPC_COMPAT_DIR.'is_a.php'); + } + + // G. Giunta 2005/01/29: declare global these variables, + // so that xmlrpc.inc will work even if included from within a function + // Milosch: 2005/08/07 - explicitly request these via $GLOBALS where used. + $GLOBALS['xmlrpcI4']='i4'; + $GLOBALS['xmlrpcInt']='int'; + $GLOBALS['xmlrpcBoolean']='boolean'; + $GLOBALS['xmlrpcDouble']='double'; + $GLOBALS['xmlrpcString']='string'; + $GLOBALS['xmlrpcDateTime']='dateTime.iso8601'; + $GLOBALS['xmlrpcBase64']='base64'; + $GLOBALS['xmlrpcArray']='array'; + $GLOBALS['xmlrpcStruct']='struct'; + $GLOBALS['xmlrpcValue']='undefined'; + + $GLOBALS['xmlrpcTypes']=array( + $GLOBALS['xmlrpcI4'] => 1, + $GLOBALS['xmlrpcInt'] => 1, + $GLOBALS['xmlrpcBoolean'] => 1, + $GLOBALS['xmlrpcString'] => 1, + $GLOBALS['xmlrpcDouble'] => 1, + $GLOBALS['xmlrpcDateTime'] => 1, + $GLOBALS['xmlrpcBase64'] => 1, + $GLOBALS['xmlrpcArray'] => 2, + $GLOBALS['xmlrpcStruct'] => 3 + ); + + $GLOBALS['xmlrpc_valid_parents'] = array( + 'VALUE' => array('MEMBER', 'DATA', 'PARAM', 'FAULT'), + 'BOOLEAN' => array('VALUE'), + 'I4' => array('VALUE'), + 'INT' => array('VALUE'), + 'STRING' => array('VALUE'), + 'DOUBLE' => array('VALUE'), + 'DATETIME.ISO8601' => array('VALUE'), + 'BASE64' => array('VALUE'), + 'MEMBER' => array('STRUCT'), + 'NAME' => array('MEMBER'), + 'DATA' => array('ARRAY'), + 'ARRAY' => array('VALUE'), + 'STRUCT' => array('VALUE'), + 'PARAM' => array('PARAMS'), + 'METHODNAME' => array('METHODCALL'), + 'PARAMS' => array('METHODCALL', 'METHODRESPONSE'), + 'FAULT' => array('METHODRESPONSE'), + 'NIL' => array('VALUE') // only used when extension activated + ); + + // define extra types for supporting NULL (useful for json or ) + $GLOBALS['xmlrpcNull']='null'; + $GLOBALS['xmlrpcTypes']['null']=1; + + // Not in use anymore since 2.0. Shall we remove it? + /// @deprecated + $GLOBALS['xmlEntities']=array( + 'amp' => '&', + 'quot' => '"', + 'lt' => '<', + 'gt' => '>', + 'apos' => "'" + ); + + // tables used for transcoding different charsets into us-ascii xml + + $GLOBALS['xml_iso88591_Entities']=array(); + $GLOBALS['xml_iso88591_Entities']['in'] = array(); + $GLOBALS['xml_iso88591_Entities']['out'] = array(); + for ($i = 0; $i < 32; $i++) + { + $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i); + $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';'; + } + for ($i = 160; $i < 256; $i++) + { + $GLOBALS['xml_iso88591_Entities']['in'][] = chr($i); + $GLOBALS['xml_iso88591_Entities']['out'][] = '&#'.$i.';'; + } + + /// @todo add to iso table the characters from cp_1252 range, i.e. 128 to 159? + /// These will NOT be present in true ISO-8859-1, but will save the unwary + /// windows user from sending junk (though no luck when reciving them...) + /* + $GLOBALS['xml_cp1252_Entities']=array(); + for ($i = 128; $i < 160; $i++) + { + $GLOBALS['xml_cp1252_Entities']['in'][] = chr($i); + } + $GLOBALS['xml_cp1252_Entities']['out'] = array( + '€', '?', '‚', 'ƒ', + '„', '…', '†', '‡', + 'ˆ', '‰', 'Š', '‹', + 'Œ', '?', 'Ž', '?', + '?', '‘', '’', '“', + '”', '•', '–', '—', + '˜', '™', 'š', '›', + 'œ', '?', 'ž', 'Ÿ' + ); + */ + + $GLOBALS['xmlrpcerr'] = array( + 'unknown_method'=>1, + 'invalid_return'=>2, + 'incorrect_params'=>3, + 'introspect_unknown'=>4, + 'http_error'=>5, + 'no_data'=>6, + 'no_ssl'=>7, + 'curl_fail'=>8, + 'invalid_request'=>15, + 'no_curl'=>16, + 'server_error'=>17, + 'multicall_error'=>18, + 'multicall_notstruct'=>9, + 'multicall_nomethod'=>10, + 'multicall_notstring'=>11, + 'multicall_recursion'=>12, + 'multicall_noparams'=>13, + 'multicall_notarray'=>14, + + 'cannot_decompress'=>103, + 'decompress_fail'=>104, + 'dechunk_fail'=>105, + 'server_cannot_decompress'=>106, + 'server_decompress_fail'=>107 + ); + + $GLOBALS['xmlrpcstr'] = array( + 'unknown_method'=>'Unknown method', + 'invalid_return'=>'Invalid return payload: enable debugging to examine incoming payload', + 'incorrect_params'=>'Incorrect parameters passed to method', + 'introspect_unknown'=>"Can't introspect: method unknown", + 'http_error'=>"Didn't receive 200 OK from remote server.", + 'no_data'=>'No data received from server.', + 'no_ssl'=>'No SSL support compiled in.', + 'curl_fail'=>'CURL error', + 'invalid_request'=>'Invalid request payload', + 'no_curl'=>'No CURL support compiled in.', + 'server_error'=>'Internal server error', + 'multicall_error'=>'Received from server invalid multicall response', + 'multicall_notstruct'=>'system.multicall expected struct', + 'multicall_nomethod'=>'missing methodName', + 'multicall_notstring'=>'methodName is not a string', + 'multicall_recursion'=>'recursive system.multicall forbidden', + 'multicall_noparams'=>'missing params', + 'multicall_notarray'=>'params is not an array', + + 'cannot_decompress'=>'Received from server compressed HTTP and cannot decompress', + 'decompress_fail'=>'Received from server invalid compressed HTTP', + 'dechunk_fail'=>'Received from server invalid chunked HTTP', + 'server_cannot_decompress'=>'Received from client compressed HTTP request and cannot decompress', + 'server_decompress_fail'=>'Received from client invalid compressed HTTP request' + ); + + // The charset encoding used by the server for received messages and + // by the client for received responses when received charset cannot be determined + // or is not supported + $GLOBALS['xmlrpc_defencoding']='UTF-8'; + + // The encoding used internally by PHP. + // String values received as xml will be converted to this, and php strings will be converted to xml + // as if having been coded with this + $GLOBALS['xmlrpc_internalencoding']='ISO-8859-1'; + + $GLOBALS['xmlrpcName']='XML-RPC for PHP'; + $GLOBALS['xmlrpcVersion']='2.2.2'; + + // let user errors start at 800 + $GLOBALS['xmlrpcerruser']=800; + // let XML parse errors start at 100 + $GLOBALS['xmlrpcerrxml']=100; + + // formulate backslashes for escaping regexp + // Not in use anymore since 2.0. Shall we remove it? + /// @deprecated + $GLOBALS['xmlrpc_backslash']=chr(92).chr(92); + + // set to TRUE to enable correct decoding of values + $GLOBALS['xmlrpc_null_extension']=false; + + // used to store state during parsing + // quick explanation of components: + // ac - used to accumulate values + // isf - used to indicate a parsing fault (2) or xmlrpcresp fault (1) + // isf_reason - used for storing xmlrpcresp fault string + // lv - used to indicate "looking for a value": implements + // the logic to allow values with no types to be strings + // params - used to store parameters in method calls + // method - used to store method name + // stack - array with genealogy of xml elements names: + // used to validate nesting of xmlrpc elements + $GLOBALS['_xh']=null; + + /** + * Convert a string to the correct XML representation in a target charset + * To help correct communication of non-ascii chars inside strings, regardless + * of the charset used when sending requests, parsing them, sending responses + * and parsing responses, an option is to convert all non-ascii chars present in the message + * into their equivalent 'charset entity'. Charset entities enumerated this way + * are independent of the charset encoding used to transmit them, and all XML + * parsers are bound to understand them. + * Note that in the std case we are not sending a charset encoding mime type + * along with http headers, so we are bound by RFC 3023 to emit strict us-ascii. + * + * @todo do a bit of basic benchmarking (strtr vs. str_replace) + * @todo make usage of iconv() or recode_string() or mb_string() where available + */ + function xmlrpc_encode_entitites($data, $src_encoding='', $dest_encoding='') + { + if ($src_encoding == '') + { + // lame, but we know no better... + $src_encoding = $GLOBALS['xmlrpc_internalencoding']; + } + + switch(strtoupper($src_encoding.'_'.$dest_encoding)) + { + case 'ISO-8859-1_': + case 'ISO-8859-1_US-ASCII': + $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); + $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data); + break; + case 'ISO-8859-1_UTF-8': + $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); + $escaped_data = utf8_encode($escaped_data); + break; + case 'ISO-8859-1_ISO-8859-1': + case 'US-ASCII_US-ASCII': + case 'US-ASCII_UTF-8': + case 'US-ASCII_': + case 'US-ASCII_ISO-8859-1': + case 'UTF-8_UTF-8': + //case 'CP1252_CP1252': + $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); + break; + case 'UTF-8_': + case 'UTF-8_US-ASCII': + case 'UTF-8_ISO-8859-1': + // NB: this will choke on invalid UTF-8, going most likely beyond EOF + $escaped_data = ''; + // be kind to users creating string xmlrpcvals out of different php types + $data = (string) $data; + $ns = strlen ($data); + for ($nn = 0; $nn < $ns; $nn++) + { + $ch = $data[$nn]; + $ii = ord($ch); + //1 7 0bbbbbbb (127) + if ($ii < 128) + { + /// @todo shall we replace this with a (supposedly) faster str_replace? + switch($ii){ + case 34: + $escaped_data .= '"'; + break; + case 38: + $escaped_data .= '&'; + break; + case 39: + $escaped_data .= '''; + break; + case 60: + $escaped_data .= '<'; + break; + case 62: + $escaped_data .= '>'; + break; + default: + $escaped_data .= $ch; + } // switch + } + //2 11 110bbbbb 10bbbbbb (2047) + else if ($ii>>5 == 6) + { + $b1 = ($ii & 31); + $ii = ord($data[$nn+1]); + $b2 = ($ii & 63); + $ii = ($b1 * 64) + $b2; + $ent = sprintf ('&#%d;', $ii); + $escaped_data .= $ent; + $nn += 1; + } + //3 16 1110bbbb 10bbbbbb 10bbbbbb + else if ($ii>>4 == 14) + { + $b1 = ($ii & 15); + $ii = ord($data[$nn+1]); + $b2 = ($ii & 63); + $ii = ord($data[$nn+2]); + $b3 = ($ii & 63); + $ii = ((($b1 * 64) + $b2) * 64) + $b3; + $ent = sprintf ('&#%d;', $ii); + $escaped_data .= $ent; + $nn += 2; + } + //4 21 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + else if ($ii>>3 == 30) + { + $b1 = ($ii & 7); + $ii = ord($data[$nn+1]); + $b2 = ($ii & 63); + $ii = ord($data[$nn+2]); + $b3 = ($ii & 63); + $ii = ord($data[$nn+3]); + $b4 = ($ii & 63); + $ii = ((((($b1 * 64) + $b2) * 64) + $b3) * 64) + $b4; + $ent = sprintf ('&#%d;', $ii); + $escaped_data .= $ent; + $nn += 3; + } + } + break; +/* + case 'CP1252_': + case 'CP1252_US-ASCII': + $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); + $escaped_data = str_replace($GLOBALS['xml_iso88591_Entities']['in'], $GLOBALS['xml_iso88591_Entities']['out'], $escaped_data); + $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data); + break; + case 'CP1252_UTF-8': + $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); + /// @todo we could use real UTF8 chars here instead of xml entities... (note that utf_8 encode all allone will NOT convert them) + $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data); + $escaped_data = utf8_encode($escaped_data); + break; + case 'CP1252_ISO-8859-1': + $escaped_data = str_replace(array('&', '"', "'", '<', '>'), array('&', '"', ''', '<', '>'), $data); + // we might as well replave all funky chars with a '?' here, but we are kind and leave it to the receiving application layer to decide what to do with these weird entities... + $escaped_data = str_replace($GLOBALS['xml_cp1252_Entities']['in'], $GLOBALS['xml_cp1252_Entities']['out'], $escaped_data); + break; +*/ + default: + $escaped_data = ''; + error_log("Converting from $src_encoding to $dest_encoding: not supported..."); + } + return $escaped_data; + } + + /// xml parser handler function for opening element tags + function xmlrpc_se($parser, $name, $attrs, $accept_single_vals=false) + { + // if invalid xmlrpc already detected, skip all processing + if ($GLOBALS['_xh']['isf'] < 2) + { + // check for correct element nesting + // top level element can only be of 2 types + /// @todo optimization creep: save this check into a bool variable, instead of using count() every time: + /// there is only a single top level element in xml anyway + if (count($GLOBALS['_xh']['stack']) == 0) + { + if ($name != 'METHODRESPONSE' && $name != 'METHODCALL' && ( + $name != 'VALUE' && !$accept_single_vals)) + { + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = 'missing top level xmlrpc element'; + return; + } + else + { + $GLOBALS['_xh']['rt'] = strtolower($name); + } + } + else + { + // not top level element: see if parent is OK + $parent = end($GLOBALS['_xh']['stack']); + if (!array_key_exists($name, $GLOBALS['xmlrpc_valid_parents']) || !in_array($parent, $GLOBALS['xmlrpc_valid_parents'][$name])) + { + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = "xmlrpc element $name cannot be child of $parent"; + return; + } + } + + switch($name) + { + // optimize for speed switch cases: most common cases first + case 'VALUE': + /// @todo we could check for 2 VALUE elements inside a MEMBER or PARAM element + $GLOBALS['_xh']['vt']='value'; // indicator: no value found yet + $GLOBALS['_xh']['ac']=''; + $GLOBALS['_xh']['lv']=1; + $GLOBALS['_xh']['php_class']=null; + break; + case 'I4': + case 'INT': + case 'STRING': + case 'BOOLEAN': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + if ($GLOBALS['_xh']['vt']!='value') + { + //two data elements inside a value: an error occurred! + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value"; + return; + } + $GLOBALS['_xh']['ac']=''; // reset the accumulator + break; + case 'STRUCT': + case 'ARRAY': + if ($GLOBALS['_xh']['vt']!='value') + { + //two data elements inside a value: an error occurred! + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value"; + return; + } + // create an empty array to hold child values, and push it onto appropriate stack + $cur_val = array(); + $cur_val['values'] = array(); + $cur_val['type'] = $name; + // check for out-of-band information to rebuild php objs + // and in case it is found, save it + if (@isset($attrs['PHP_CLASS'])) + { + $cur_val['php_class'] = $attrs['PHP_CLASS']; + } + $GLOBALS['_xh']['valuestack'][] = $cur_val; + $GLOBALS['_xh']['vt']='data'; // be prepared for a data element next + break; + case 'DATA': + if ($GLOBALS['_xh']['vt']!='data') + { + //two data elements inside a value: an error occurred! + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = "found two data elements inside an array element"; + return; + } + case 'METHODCALL': + case 'METHODRESPONSE': + case 'PARAMS': + // valid elements that add little to processing + break; + case 'METHODNAME': + case 'NAME': + /// @todo we could check for 2 NAME elements inside a MEMBER element + $GLOBALS['_xh']['ac']=''; + break; + case 'FAULT': + $GLOBALS['_xh']['isf']=1; + break; + case 'MEMBER': + $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name']=''; // set member name to null, in case we do not find in the xml later on + //$GLOBALS['_xh']['ac']=''; + // Drop trough intentionally + case 'PARAM': + // clear value type, so we can check later if no value has been passed for this param/member + $GLOBALS['_xh']['vt']=null; + break; + case 'NIL': + if ($GLOBALS['xmlrpc_null_extension']) + { + if ($GLOBALS['_xh']['vt']!='value') + { + //two data elements inside a value: an error occurred! + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = "$name element following a {$GLOBALS['_xh']['vt']} element inside a single value"; + return; + } + $GLOBALS['_xh']['ac']=''; // reset the accumulator + break; + } + // we do not support the extension, so + // drop through intentionally + default: + /// INVALID ELEMENT: RAISE ISF so that it is later recognized!!! + $GLOBALS['_xh']['isf'] = 2; + $GLOBALS['_xh']['isf_reason'] = "found not-xmlrpc xml element $name"; + break; + } + + // Save current element name to stack, to validate nesting + $GLOBALS['_xh']['stack'][] = $name; + + /// @todo optimization creep: move this inside the big switch() above + if($name!='VALUE') + { + $GLOBALS['_xh']['lv']=0; + } + } + } + + /// Used in decoding xml chunks that might represent single xmlrpc values + function xmlrpc_se_any($parser, $name, $attrs) + { + xmlrpc_se($parser, $name, $attrs, true); + } + + /// xml parser handler function for close element tags + function xmlrpc_ee($parser, $name, $rebuild_xmlrpcvals = true) + { + if ($GLOBALS['_xh']['isf'] < 2) + { + // push this element name from stack + // NB: if XML validates, correct opening/closing is guaranteed and + // we do not have to check for $name == $curr_elem. + // we also checked for proper nesting at start of elements... + $curr_elem = array_pop($GLOBALS['_xh']['stack']); + + switch($name) + { + case 'VALUE': + // This if() detects if no scalar was inside + if ($GLOBALS['_xh']['vt']=='value') + { + $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac']; + $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcString']; + } + + if ($rebuild_xmlrpcvals) + { + // build the xmlrpc val out of the data received, and substitute it + $temp = new xmlrpcval($GLOBALS['_xh']['value'], $GLOBALS['_xh']['vt']); + // in case we got info about underlying php class, save it + // in the object we're rebuilding + if (isset($GLOBALS['_xh']['php_class'])) + $temp->_php_class = $GLOBALS['_xh']['php_class']; + // check if we are inside an array or struct: + // if value just built is inside an array, let's move it into array on the stack + $vscount = count($GLOBALS['_xh']['valuestack']); + if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY') + { + $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $temp; + } + else + { + $GLOBALS['_xh']['value'] = $temp; + } + } + else + { + /// @todo this needs to treat correctly php-serialized objects, + /// since std deserializing is done by php_xmlrpc_decode, + /// which we will not be calling... + if (isset($GLOBALS['_xh']['php_class'])) + { + } + + // check if we are inside an array or struct: + // if value just built is inside an array, let's move it into array on the stack + $vscount = count($GLOBALS['_xh']['valuestack']); + if ($vscount && $GLOBALS['_xh']['valuestack'][$vscount-1]['type']=='ARRAY') + { + $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][] = $GLOBALS['_xh']['value']; + } + } + break; + case 'BOOLEAN': + case 'I4': + case 'INT': + case 'STRING': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + $GLOBALS['_xh']['vt']=strtolower($name); + /// @todo: optimization creep - remove the if/elseif cycle below + /// since the case() in which we are already did that + if ($name=='STRING') + { + $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac']; + } + elseif ($name=='DATETIME.ISO8601') + { + if (!preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $GLOBALS['_xh']['ac'])) + { + error_log('XML-RPC: invalid value received in DATETIME: '.$GLOBALS['_xh']['ac']); + } + $GLOBALS['_xh']['vt']=$GLOBALS['xmlrpcDateTime']; + $GLOBALS['_xh']['value']=$GLOBALS['_xh']['ac']; + } + elseif ($name=='BASE64') + { + /// @todo check for failure of base64 decoding / catch warnings + $GLOBALS['_xh']['value']=base64_decode($GLOBALS['_xh']['ac']); + } + elseif ($name=='BOOLEAN') + { + // special case here: we translate boolean 1 or 0 into PHP + // constants true or false. + // Strings 'true' and 'false' are accepted, even though the + // spec never mentions them (see eg. Blogger api docs) + // NB: this simple checks helps a lot sanitizing input, ie no + // security problems around here + if ($GLOBALS['_xh']['ac']=='1' || strcasecmp($GLOBALS['_xh']['ac'], 'true') == 0) + { + $GLOBALS['_xh']['value']=true; + } + else + { + // log if receiveing something strange, even though we set the value to false anyway + if ($GLOBALS['_xh']['ac']!='0' && strcasecmp($GLOBALS['_xh']['ac'], 'false') != 0) + error_log('XML-RPC: invalid value received in BOOLEAN: '.$GLOBALS['_xh']['ac']); + $GLOBALS['_xh']['value']=false; + } + } + elseif ($name=='DOUBLE') + { + // we have a DOUBLE + // we must check that only 0123456789-. are characters here + // NOTE: regexp could be much stricter than this... + if (!preg_match('/^[+-eE0123456789 \t.]+$/', $GLOBALS['_xh']['ac'])) + { + /// @todo: find a better way of throwing an error than this! + error_log('XML-RPC: non numeric value received in DOUBLE: '.$GLOBALS['_xh']['ac']); + $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND'; + } + else + { + // it's ok, add it on + $GLOBALS['_xh']['value']=(double)$GLOBALS['_xh']['ac']; + } + } + else + { + // we have an I4/INT + // we must check that only 0123456789- are characters here + if (!preg_match('/^[+-]?[0123456789 \t]+$/', $GLOBALS['_xh']['ac'])) + { + /// @todo find a better way of throwing an error than this! + error_log('XML-RPC: non numeric value received in INT: '.$GLOBALS['_xh']['ac']); + $GLOBALS['_xh']['value']='ERROR_NON_NUMERIC_FOUND'; + } + else + { + // it's ok, add it on + $GLOBALS['_xh']['value']=(int)$GLOBALS['_xh']['ac']; + } + } + //$GLOBALS['_xh']['ac']=''; // is this necessary? + $GLOBALS['_xh']['lv']=3; // indicate we've found a value + break; + case 'NAME': + $GLOBALS['_xh']['valuestack'][count($GLOBALS['_xh']['valuestack'])-1]['name'] = $GLOBALS['_xh']['ac']; + break; + case 'MEMBER': + //$GLOBALS['_xh']['ac']=''; // is this necessary? + // add to array in the stack the last element built, + // unless no VALUE was found + if ($GLOBALS['_xh']['vt']) + { + $vscount = count($GLOBALS['_xh']['valuestack']); + $GLOBALS['_xh']['valuestack'][$vscount-1]['values'][$GLOBALS['_xh']['valuestack'][$vscount-1]['name']] = $GLOBALS['_xh']['value']; + } else + error_log('XML-RPC: missing VALUE inside STRUCT in received xml'); + break; + case 'DATA': + //$GLOBALS['_xh']['ac']=''; // is this necessary? + $GLOBALS['_xh']['vt']=null; // reset this to check for 2 data elements in a row - even if they're empty + break; + case 'STRUCT': + case 'ARRAY': + // fetch out of stack array of values, and promote it to current value + $curr_val = array_pop($GLOBALS['_xh']['valuestack']); + $GLOBALS['_xh']['value'] = $curr_val['values']; + $GLOBALS['_xh']['vt']=strtolower($name); + if (isset($curr_val['php_class'])) + { + $GLOBALS['_xh']['php_class'] = $curr_val['php_class']; + } + break; + case 'PARAM': + // add to array of params the current value, + // unless no VALUE was found + if ($GLOBALS['_xh']['vt']) + { + $GLOBALS['_xh']['params'][]=$GLOBALS['_xh']['value']; + $GLOBALS['_xh']['pt'][]=$GLOBALS['_xh']['vt']; + } + else + error_log('XML-RPC: missing VALUE inside PARAM in received xml'); + break; + case 'METHODNAME': + $GLOBALS['_xh']['method']=preg_replace('/^[\n\r\t ]+/', '', $GLOBALS['_xh']['ac']); + break; + case 'NIL': + if ($GLOBALS['xmlrpc_null_extension']) + { + $GLOBALS['_xh']['vt']='null'; + $GLOBALS['_xh']['value']=null; + $GLOBALS['_xh']['lv']=3; + break; + } + // drop through intentionally if nil extension not enabled + case 'PARAMS': + case 'FAULT': + case 'METHODCALL': + case 'METHORESPONSE': + break; + default: + // End of INVALID ELEMENT! + // shall we add an assert here for unreachable code??? + break; + } + } + } + + /// Used in decoding xmlrpc requests/responses without rebuilding xmlrpc values + function xmlrpc_ee_fast($parser, $name) + { + xmlrpc_ee($parser, $name, false); + } + + /// xml parser handler function for character data + function xmlrpc_cd($parser, $data) + { + // skip processing if xml fault already detected + if ($GLOBALS['_xh']['isf'] < 2) + { + // "lookforvalue==3" means that we've found an entire value + // and should discard any further character data + if($GLOBALS['_xh']['lv']!=3) + { + // G. Giunta 2006-08-23: useless change of 'lv' from 1 to 2 + //if($GLOBALS['_xh']['lv']==1) + //{ + // if we've found text and we're just in a then + // say we've found a value + //$GLOBALS['_xh']['lv']=2; + //} + // we always initialize the accumulator before starting parsing, anyway... + //if(!@isset($GLOBALS['_xh']['ac'])) + //{ + // $GLOBALS['_xh']['ac'] = ''; + //} + $GLOBALS['_xh']['ac'].=$data; + } + } + } + + /// xml parser handler function for 'other stuff', ie. not char data or + /// element start/end tag. In fact it only gets called on unknown entities... + function xmlrpc_dh($parser, $data) + { + // skip processing if xml fault already detected + if ($GLOBALS['_xh']['isf'] < 2) + { + if(substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';') + { + // G. Giunta 2006-08-25: useless change of 'lv' from 1 to 2 + //if($GLOBALS['_xh']['lv']==1) + //{ + // $GLOBALS['_xh']['lv']=2; + //} + $GLOBALS['_xh']['ac'].=$data; + } + } + return true; + } + + class xmlrpc_client + { + var $path; + var $server; + var $port=0; + var $method='http'; + var $errno; + var $errstr; + var $debug=0; + var $username=''; + var $password=''; + var $authtype=1; + var $cert=''; + var $certpass=''; + var $cacert=''; + var $cacertdir=''; + var $key=''; + var $keypass=''; + var $verifypeer=true; + var $verifyhost=1; + var $no_multicall=false; + var $proxy=''; + var $proxyport=0; + var $proxy_user=''; + var $proxy_pass=''; + var $proxy_authtype=1; + var $cookies=array(); + /** + * List of http compression methods accepted by the client for responses. + * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib + * + * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since + * in those cases it will be up to CURL to decide the compression methods + * it supports. You might check for the presence of 'zlib' in the output of + * curl_version() to determine wheter compression is supported or not + */ + var $accepted_compression = array(); + /** + * Name of compression scheme to be used for sending requests. + * Either null, gzip or deflate + */ + var $request_compression = ''; + /** + * CURL handle: used for keep-alive connections (PHP 4.3.8 up, see: + * http://curl.haxx.se/docs/faq.html#7.3) + */ + var $xmlrpc_curl_handle = null; + /// Wheter to use persistent connections for http 1.1 and https + var $keepalive = false; + /// Charset encodings that can be decoded without problems by the client + var $accepted_charset_encodings = array(); + /// Charset encoding to be used in serializing request. NULL = use ASCII + var $request_charset_encoding = ''; + /** + * Decides the content of xmlrpcresp objects returned by calls to send() + * valid strings are 'xmlrpcvals', 'phpvals' or 'xml' + */ + var $return_type = 'xmlrpcvals'; + + /** + * @param string $path either the complete server URL or the PATH part of the xmlrc server URL, e.g. /xmlrpc/server.php + * @param string $server the server name / ip address + * @param integer $port the port the server is listening on, defaults to 80 or 443 depending on protocol used + * @param string $method the http protocol variant: defaults to 'http', 'https' and 'http11' can be used if CURL is installed + */ + function xmlrpc_client($path, $server='', $port='', $method='') + { + // allow user to specify all params in $path + if($server == '' and $port == '' and $method == '') + { + $parts = parse_url($path); + $server = $parts['host']; + $path = isset($parts['path']) ? $parts['path'] : ''; + if(isset($parts['query'])) + { + $path .= '?'.$parts['query']; + } + if(isset($parts['fragment'])) + { + $path .= '#'.$parts['fragment']; + } + if(isset($parts['port'])) + { + $port = $parts['port']; + } + if(isset($parts['scheme'])) + { + $method = $parts['scheme']; + } + if(isset($parts['user'])) + { + $this->username = $parts['user']; + } + if(isset($parts['pass'])) + { + $this->password = $parts['pass']; + } + } + if($path == '' || $path[0] != '/') + { + $this->path='/'.$path; + } + else + { + $this->path=$path; + } + $this->server=$server; + if($port != '') + { + $this->port=$port; + } + if($method != '') + { + $this->method=$method; + } + + // if ZLIB is enabled, let the client by default accept compressed responses + if(function_exists('gzinflate') || ( + function_exists('curl_init') && (($info = curl_version()) && + ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version']))) + )) + { + $this->accepted_compression = array('gzip', 'deflate'); + } + + // keepalives: enabled by default ONLY for PHP >= 4.3.8 + // (see http://curl.haxx.se/docs/faq.html#7.3) + if(version_compare(phpversion(), '4.3.8') >= 0) + { + $this->keepalive = true; + } + + // by default the xml parser can support these 3 charset encodings + $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII'); + } + + /** + * Enables/disables the echoing to screen of the xmlrpc responses received + * @param integer $debug values 0, 1 and 2 are supported (2 = echo sent msg too, before received response) + * @access public + */ + function setDebug($in) + { + $this->debug=$in; + } + + /** + * Add some http BASIC AUTH credentials, used by the client to authenticate + * @param string $u username + * @param string $p password + * @param integer $t auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC (basic auth) + * @access public + */ + function setCredentials($u, $p, $t=1) + { + $this->username=$u; + $this->password=$p; + $this->authtype=$t; + } + + /** + * Add a client-side https certificate + * @param string $cert + * @param string $certpass + * @access public + */ + function setCertificate($cert, $certpass) + { + $this->cert = $cert; + $this->certpass = $certpass; + } + + /** + * Add a CA certificate to verify server with (see man page about + * CURLOPT_CAINFO for more details + * @param string $cacert certificate file name (or dir holding certificates) + * @param bool $is_dir set to true to indicate cacert is a dir. defaults to false + * @access public + */ + function setCaCertificate($cacert, $is_dir=false) + { + if ($is_dir) + { + $this->cacertdir = $cacert; + } + else + { + $this->cacert = $cacert; + } + } + + /** + * Set attributes for SSL communication: private SSL key + * NB: does not work in older php/curl installs + * Thanks to Daniel Convissor + * @param string $key The name of a file containing a private SSL key + * @param string $keypass The secret password needed to use the private SSL key + * @access public + */ + function setKey($key, $keypass) + { + $this->key = $key; + $this->keypass = $keypass; + } + + /** + * Set attributes for SSL communication: verify server certificate + * @param bool $i enable/disable verification of peer certificate + * @access public + */ + function setSSLVerifyPeer($i) + { + $this->verifypeer = $i; + } + + /** + * Set attributes for SSL communication: verify match of server cert w. hostname + * @param int $i + * @access public + */ + function setSSLVerifyHost($i) + { + $this->verifyhost = $i; + } + + /** + * Set proxy info + * @param string $proxyhost + * @param string $proxyport Defaults to 8080 for HTTP and 443 for HTTPS + * @param string $proxyusername Leave blank if proxy has public access + * @param string $proxypassword Leave blank if proxy has public access + * @param int $proxyauthtype set to constant CURLAUTH_NTLM to use NTLM auth with proxy + * @access public + */ + function setProxy($proxyhost, $proxyport, $proxyusername = '', $proxypassword = '', $proxyauthtype = 1) + { + $this->proxy = $proxyhost; + $this->proxyport = $proxyport; + $this->proxy_user = $proxyusername; + $this->proxy_pass = $proxypassword; + $this->proxy_authtype = $proxyauthtype; + } + + /** + * Enables/disables reception of compressed xmlrpc responses. + * Note that enabling reception of compressed responses merely adds some standard + * http headers to xmlrpc requests. It is up to the xmlrpc server to return + * compressed responses when receiving such requests. + * @param string $compmethod either 'gzip', 'deflate', 'any' or '' + * @access public + */ + function setAcceptedCompression($compmethod) + { + if ($compmethod == 'any') + $this->accepted_compression = array('gzip', 'deflate'); + else + $this->accepted_compression = array($compmethod); + } + + /** + * Enables/disables http compression of xmlrpc request. + * Take care when sending compressed requests: servers might not support them + * (and automatic fallback to uncompressed requests is not yet implemented) + * @param string $compmethod either 'gzip', 'deflate' or '' + * @access public + */ + function setRequestCompression($compmethod) + { + $this->request_compression = $compmethod; + } + + /** + * Adds a cookie to list of cookies that will be sent to server. + * NB: setting any param but name and value will turn the cookie into a 'version 1' cookie: + * do not do it unless you know what you are doing + * @param string $name + * @param string $value + * @param string $path + * @param string $domain + * @param int $port + * @access public + * + * @todo check correctness of urlencoding cookie value (copied from php way of doing it...) + */ + function setCookie($name, $value='', $path='', $domain='', $port=null) + { + $this->cookies[$name]['value'] = urlencode($value); + if ($path || $domain || $port) + { + $this->cookies[$name]['path'] = $path; + $this->cookies[$name]['domain'] = $domain; + $this->cookies[$name]['port'] = $port; + $this->cookies[$name]['version'] = 1; + } + else + { + $this->cookies[$name]['version'] = 0; + } + } + + /** + * Send an xmlrpc request + * @param mixed $msg The message object, or an array of messages for using multicall, or the complete xml representation of a request + * @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply + * @param string $method if left unspecified, the http protocol chosen during creation of the object will be used + * @return xmlrpcresp + * @access public + */ + function send($msg, $timeout=0, $method='') + { + // if user does not specify http protocol, use native method of this client + // (i.e. method set during call to constructor) + if($method == '') + { + $method = $this->method; + } + + if(is_array($msg)) + { + // $msg is an array of xmlrpcmsg's + $r = $this->multicall($msg, $timeout, $method); + return $r; + } + elseif(is_string($msg)) + { + $n = new xmlrpcmsg(''); + $n->payload = $msg; + $msg = $n; + } + + // where msg is an xmlrpcmsg + $msg->debug=$this->debug; + + if($method == 'https') + { + $r =& $this->sendPayloadHTTPS( + $msg, + $this->server, + $this->port, + $timeout, + $this->username, + $this->password, + $this->authtype, + $this->cert, + $this->certpass, + $this->cacert, + $this->cacertdir, + $this->proxy, + $this->proxyport, + $this->proxy_user, + $this->proxy_pass, + $this->proxy_authtype, + $this->keepalive, + $this->key, + $this->keypass + ); + } + elseif($method == 'http11') + { + $r = $this->sendPayloadCURL( + $msg, + $this->server, + $this->port, + $timeout, + $this->username, + $this->password, + $this->authtype, + null, + null, + null, + null, + $this->proxy, + $this->proxyport, + $this->proxy_user, + $this->proxy_pass, + $this->proxy_authtype, + 'http', + $this->keepalive + ); + } + else + { + $r =& $this->sendPayloadHTTP10( + $msg, + $this->server, + $this->port, + $timeout, + $this->username, + $this->password, + $this->authtype, + $this->proxy, + $this->proxyport, + $this->proxy_user, + $this->proxy_pass, + $this->proxy_authtype + ); + } + + return $r; + } + + /** + * @access private + */ + function &sendPayloadHTTP10($msg, $server, $port, $timeout=0, + $username='', $password='', $authtype=1, $proxyhost='', + $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1) + { + if($port==0) + { + $port=80; + } + + // Only create the payload if it was not created previously + if(empty($msg->payload)) + { + $msg->createPayload($this->request_charset_encoding); + } + + $payload = $msg->payload; + // Deflate request body and set appropriate request headers + if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) + { + if($this->request_compression == 'gzip') + { + $a = @gzencode($payload); + if($a) + { + $payload = $a; + $encoding_hdr = "Content-Encoding: gzip\r\n"; + } + } + else + { + $a = @gzcompress($payload); + if($a) + { + $payload = $a; + $encoding_hdr = "Content-Encoding: deflate\r\n"; + } + } + } + else + { + $encoding_hdr = ''; + } + + // thanks to Grant Rauscher for this + $credentials=''; + if($username!='') + { + $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n"; + if ($authtype != 1) + { + error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported with HTTP 1.0'); + } + } + + $accepted_encoding = ''; + if(is_array($this->accepted_compression) && count($this->accepted_compression)) + { + $accepted_encoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n"; + } + + $proxy_credentials = ''; + if($proxyhost) + { + if($proxyport == 0) + { + $proxyport = 8080; + } + $connectserver = $proxyhost; + $connectport = $proxyport; + $uri = 'http://'.$server.':'.$port.$this->path; + if($proxyusername != '') + { + if ($proxyauthtype != 1) + { + error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported with HTTP 1.0'); + } + $proxy_credentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyusername.':'.$proxypassword) . "\r\n"; + } + } + else + { + $connectserver = $server; + $connectport = $port; + $uri = $this->path; + } + + // Cookie generation, as per rfc2965 (version 1 cookies) or + // netscape's rules (version 0 cookies) + $cookieheader=''; + if (count($this->cookies)) + { + $version = ''; + foreach ($this->cookies as $name => $cookie) + { + if ($cookie['version']) + { + $version = ' $Version="' . $cookie['version'] . '";'; + $cookieheader .= ' ' . $name . '="' . $cookie['value'] . '";'; + if ($cookie['path']) + $cookieheader .= ' $Path="' . $cookie['path'] . '";'; + if ($cookie['domain']) + $cookieheader .= ' $Domain="' . $cookie['domain'] . '";'; + if ($cookie['port']) + $cookieheader .= ' $Port="' . $cookie['port'] . '";'; + } + else + { + $cookieheader .= ' ' . $name . '=' . $cookie['value'] . ";"; + } + } + $cookieheader = 'Cookie:' . $version . substr($cookieheader, 0, -1) . "\r\n"; + } + + $op= 'POST ' . $uri. " HTTP/1.0\r\n" . + 'User-Agent: ' . $GLOBALS['xmlrpcName'] . ' ' . $GLOBALS['xmlrpcVersion'] . "\r\n" . + 'Host: '. $server . ':' . $port . "\r\n" . + $credentials . + $proxy_credentials . + $accepted_encoding . + $encoding_hdr . + 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" . + $cookieheader . + 'Content-Type: ' . $msg->content_type . "\r\nContent-Length: " . + strlen($payload) . "\r\n\r\n" . + $payload; + + if($this->debug > 1) + { + print "
\n---SENDING---\n" . htmlentities($op) . "\n---END---\n
"; + // let the client see this now in case http times out... + flush(); + } + + if($timeout>0) + { + $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr, $timeout); + } + else + { + $fp=@fsockopen($connectserver, $connectport, $this->errno, $this->errstr); + } + if($fp) + { + if($timeout>0 && function_exists('stream_set_timeout')) + { + stream_set_timeout($fp, $timeout); + } + } + else + { + $this->errstr='Connect error: '.$this->errstr; + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr . ' (' . $this->errno . ')'); + return $r; + } + + if(!fputs($fp, $op, strlen($op))) + { + fclose($fp); + $this->errstr='Write error'; + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $this->errstr); + return $r; + } + else + { + // reset errno and errstr on succesful socket connection + $this->errstr = ''; + } + // G. Giunta 2005/10/24: close socket before parsing. + // should yeld slightly better execution times, and make easier recursive calls (e.g. to follow http redirects) + $ipd=''; + do + { + // shall we check for $data === FALSE? + // as per the manual, it signals an error + $ipd.=fread($fp, 32768); + } while(!feof($fp)); + fclose($fp); + $r =& $msg->parseResponse($ipd, false, $this->return_type); + return $r; + + } + + /** + * @access private + */ + function &sendPayloadHTTPS($msg, $server, $port, $timeout=0, $username='', + $password='', $authtype=1, $cert='',$certpass='', $cacert='', $cacertdir='', + $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, + $keepalive=false, $key='', $keypass='') + { + $r =& $this->sendPayloadCURL($msg, $server, $port, $timeout, $username, + $password, $authtype, $cert, $certpass, $cacert, $cacertdir, $proxyhost, $proxyport, + $proxyusername, $proxypassword, $proxyauthtype, 'https', $keepalive, $key, $keypass); + return $r; + } + + /** + * Contributed by Justin Miller + * Requires curl to be built into PHP + * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers! + * @access private + */ + function &sendPayloadCURL($msg, $server, $port, $timeout=0, $username='', + $password='', $authtype=1, $cert='', $certpass='', $cacert='', $cacertdir='', + $proxyhost='', $proxyport=0, $proxyusername='', $proxypassword='', $proxyauthtype=1, $method='https', + $keepalive=false, $key='', $keypass='') + { + if(!function_exists('curl_init')) + { + $this->errstr='CURL unavailable on this install'; + return new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_curl'], $GLOBALS['xmlrpcstr']['no_curl']); + } + if($method == 'https') + { + if(($info = curl_version()) && + ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version'])))) + { + $this->errstr='SSL unavailable on this install'; + return new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_ssl'], $GLOBALS['xmlrpcstr']['no_ssl']); + } + } + + if($port == 0) + { + if($method == 'http') + { + $port = 80; + } + else + { + $port = 443; + } + } + + // Only create the payload if it was not created previously + if(empty($msg->payload)) + { + $msg->createPayload($this->request_charset_encoding); + } + + // Deflate request body and set appropriate request headers + $payload = $msg->payload; + if(function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) + { + if($this->request_compression == 'gzip') + { + $a = @gzencode($payload); + if($a) + { + $payload = $a; + $encoding_hdr = 'Content-Encoding: gzip'; + } + } + else + { + $a = @gzcompress($payload); + if($a) + { + $payload = $a; + $encoding_hdr = 'Content-Encoding: deflate'; + } + } + } + else + { + $encoding_hdr = ''; + } + + if($this->debug > 1) + { + print "
\n---SENDING---\n" . htmlentities($payload) . "\n---END---\n
"; + // let the client see this now in case http times out... + flush(); + } + + if(!$keepalive || !$this->xmlrpc_curl_handle) + { + $curl = curl_init($method . '://' . $server . ':' . $port . $this->path); + if($keepalive) + { + $this->xmlrpc_curl_handle = $curl; + } + } + else + { + $curl = $this->xmlrpc_curl_handle; + } + + // results into variable + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + + if($this->debug) + { + curl_setopt($curl, CURLOPT_VERBOSE, 1); + } + curl_setopt($curl, CURLOPT_USERAGENT, $GLOBALS['xmlrpcName'].' '.$GLOBALS['xmlrpcVersion']); + // required for XMLRPC: post the data + curl_setopt($curl, CURLOPT_POST, 1); + // the data + curl_setopt($curl, CURLOPT_POSTFIELDS, $payload); + + // return the header too + curl_setopt($curl, CURLOPT_HEADER, 1); + + // will only work with PHP >= 5.0 + // NB: if we set an empty string, CURL will add http header indicating + // ALL methods it is supporting. This is possibly a better option than + // letting the user tell what curl can / cannot do... + if(is_array($this->accepted_compression) && count($this->accepted_compression)) + { + //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression)); + // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?) + if (count($this->accepted_compression) == 1) + { + curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]); + } + else + curl_setopt($curl, CURLOPT_ENCODING, ''); + } + // extra headers + $headers = array('Content-Type: ' . $msg->content_type , 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings)); + // if no keepalive is wanted, let the server know it in advance + if(!$keepalive) + { + $headers[] = 'Connection: close'; + } + // request compression header + if($encoding_hdr) + { + $headers[] = $encoding_hdr; + } + + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + // timeout is borked + if($timeout) + { + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1); + } + + if($username && $password) + { + curl_setopt($curl, CURLOPT_USERPWD, $username.':'.$password); + if (defined('CURLOPT_HTTPAUTH')) + { + curl_setopt($curl, CURLOPT_HTTPAUTH, $authtype); + } + else if ($authtype != 1) + { + error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth is supported by the current PHP/curl install'); + } + } + + if($method == 'https') + { + // set cert file + if($cert) + { + curl_setopt($curl, CURLOPT_SSLCERT, $cert); + } + // set cert password + if($certpass) + { + curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certpass); + } + // whether to verify remote host's cert + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer); + // set ca certificates file/dir + if($cacert) + { + curl_setopt($curl, CURLOPT_CAINFO, $cacert); + } + if($cacertdir) + { + curl_setopt($curl, CURLOPT_CAPATH, $cacertdir); + } + // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?) + if($key) + { + curl_setopt($curl, CURLOPT_SSLKEY, $key); + } + // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?) + if($keypass) + { + curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keypass); + } + // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost); + } + + // proxy info + if($proxyhost) + { + if($proxyport == 0) + { + $proxyport = 8080; // NB: even for HTTPS, local connection is on port 8080 + } + curl_setopt($curl, CURLOPT_PROXY, $proxyhost.':'.$proxyport); + //curl_setopt($curl, CURLOPT_PROXYPORT,$proxyport); + if($proxyusername) + { + curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyusername.':'.$proxypassword); + if (defined('CURLOPT_PROXYAUTH')) + { + curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyauthtype); + } + else if ($proxyauthtype != 1) + { + error_log('XML-RPC: xmlrpc_client::send: warning. Only Basic auth to proxy is supported by the current PHP/curl install'); + } + } + } + + // NB: should we build cookie http headers by hand rather than let CURL do it? + // the following code does not honour 'expires', 'path' and 'domain' cookie attributes + // set to client obj the the user... + if (count($this->cookies)) + { + $cookieheader = ''; + foreach ($this->cookies as $name => $cookie) + { + $cookieheader .= $name . '=' . $cookie['value'] . '; '; + } + curl_setopt($curl, CURLOPT_COOKIE, substr($cookieheader, 0, -2)); + } + + $result = curl_exec($curl); + + if ($this->debug > 1) + { + print "
\n---CURL INFO---\n";
+				foreach(curl_getinfo($curl) as $name => $val)
+					 print $name . ': ' . htmlentities($val). "\n";
+				print "---END---\n
"; + } + + if(!$result) /// @todo we should use a better check here - what if we get back '' or '0'? + { + $this->errstr='no response'; + $resp=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['curl_fail'], $GLOBALS['xmlrpcstr']['curl_fail']. ': '. curl_error($curl)); + curl_close($curl); + if($keepalive) + { + $this->xmlrpc_curl_handle = null; + } + } + else + { + if(!$keepalive) + { + curl_close($curl); + } + $resp =& $msg->parseResponse($result, true, $this->return_type); + } + return $resp; + } + + /** + * Send an array of request messages and return an array of responses. + * Unless $this->no_multicall has been set to true, it will try first + * to use one single xmlrpc call to server method system.multicall, and + * revert to sending many successive calls in case of failure. + * This failure is also stored in $this->no_multicall for subsequent calls. + * Unfortunately, there is no server error code universally used to denote + * the fact that multicall is unsupported, so there is no way to reliably + * distinguish between that and a temporary failure. + * If you are sure that server supports multicall and do not want to + * fallback to using many single calls, set the fourth parameter to FALSE. + * + * NB: trying to shoehorn extra functionality into existing syntax has resulted + * in pretty much convoluted code... + * + * @param array $msgs an array of xmlrpcmsg objects + * @param integer $timeout connection timeout (in seconds) + * @param string $method the http protocol variant to be used + * @param boolean fallback When true, upon receiveing an error during multicall, multiple single calls will be attempted + * @return array + * @access public + */ + function multicall($msgs, $timeout=0, $method='', $fallback=true) + { + if ($method == '') + { + $method = $this->method; + } + if(!$this->no_multicall) + { + $results = $this->_try_multicall($msgs, $timeout, $method); + if(is_array($results)) + { + // System.multicall succeeded + return $results; + } + else + { + // either system.multicall is unsupported by server, + // or call failed for some other reason. + if ($fallback) + { + // Don't try it next time... + $this->no_multicall = true; + } + else + { + if (is_a($results, 'xmlrpcresp')) + { + $result = $results; + } + else + { + $result = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['multicall_error'], $GLOBALS['xmlrpcstr']['multicall_error']); + } + } + } + } + else + { + // override fallback, in case careless user tries to do two + // opposite things at the same time + $fallback = true; + } + + $results = array(); + if ($fallback) + { + // system.multicall is (probably) unsupported by server: + // emulate multicall via multiple requests + foreach($msgs as $msg) + { + $results[] =& $this->send($msg, $timeout, $method); + } + } + else + { + // user does NOT want to fallback on many single calls: + // since we should always return an array of responses, + // return an array with the same error repeated n times + foreach($msgs as $msg) + { + $results[] = $result; + } + } + return $results; + } + + /** + * Attempt to boxcar $msgs via system.multicall. + * Returns either an array of xmlrpcreponses, an xmlrpc error response + * or false (when received response does not respect valid multicall syntax) + * @access private + */ + function _try_multicall($msgs, $timeout, $method) + { + // Construct multicall message + $calls = array(); + foreach($msgs as $msg) + { + $call['methodName'] = new xmlrpcval($msg->method(),'string'); + $numParams = $msg->getNumParams(); + $params = array(); + for($i = 0; $i < $numParams; $i++) + { + $params[$i] = $msg->getParam($i); + } + $call['params'] = new xmlrpcval($params, 'array'); + $calls[] = new xmlrpcval($call, 'struct'); + } + $multicall = new xmlrpcmsg('system.multicall'); + $multicall->addParam(new xmlrpcval($calls, 'array')); + + // Attempt RPC call + $result =& $this->send($multicall, $timeout, $method); + + if($result->faultCode() != 0) + { + // call to system.multicall failed + return $result; + } + + // Unpack responses. + $rets = $result->value(); + + if ($this->return_type == 'xml') + { + return $rets; + } + else if ($this->return_type == 'phpvals') + { + ///@todo test this code branch... + $rets = $result->value(); + if(!is_array($rets)) + { + return false; // bad return type from system.multicall + } + $numRets = count($rets); + if($numRets != count($msgs)) + { + return false; // wrong number of return values. + } + + $response = array(); + for($i = 0; $i < $numRets; $i++) + { + $val = $rets[$i]; + if (!is_array($val)) { + return false; + } + switch(count($val)) + { + case 1: + if(!isset($val[0])) + { + return false; // Bad value + } + // Normal return value + $response[$i] = new xmlrpcresp($val[0], 0, '', 'phpvals'); + break; + case 2: + /// @todo remove usage of @: it is apparently quite slow + $code = @$val['faultCode']; + if(!is_int($code)) + { + return false; + } + $str = @$val['faultString']; + if(!is_string($str)) + { + return false; + } + $response[$i] = new xmlrpcresp(0, $code, $str); + break; + default: + return false; + } + } + return $response; + } + else // return type == 'xmlrpcvals' + { + $rets = $result->value(); + if($rets->kindOf() != 'array') + { + return false; // bad return type from system.multicall + } + $numRets = $rets->arraysize(); + if($numRets != count($msgs)) + { + return false; // wrong number of return values. + } + + $response = array(); + for($i = 0; $i < $numRets; $i++) + { + $val = $rets->arraymem($i); + switch($val->kindOf()) + { + case 'array': + if($val->arraysize() != 1) + { + return false; // Bad value + } + // Normal return value + $response[$i] = new xmlrpcresp($val->arraymem(0)); + break; + case 'struct': + $code = $val->structmem('faultCode'); + if($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') + { + return false; + } + $str = $val->structmem('faultString'); + if($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') + { + return false; + } + $response[$i] = new xmlrpcresp(0, $code->scalarval(), $str->scalarval()); + break; + default: + return false; + } + } + return $response; + } + } + } // end class xmlrpc_client + + class xmlrpcresp + { + var $val = 0; + var $valtyp; + var $errno = 0; + var $errstr = ''; + var $payload; + var $hdrs = array(); + var $_cookies = array(); + var $content_type = 'text/xml'; + var $raw_data = ''; + + /** + * @param mixed $val either an xmlrpcval obj, a php value or the xml serialization of an xmlrpcval (a string) + * @param integer $fcode set it to anything but 0 to create an error response + * @param string $fstr the error string, in case of an error response + * @param string $valtyp either 'xmlrpcvals', 'phpvals' or 'xml' + * + * @todo add check that $val / $fcode / $fstr is of correct type??? + * NB: as of now we do not do it, since it might be either an xmlrpcval or a plain + * php val, or a complete xml chunk, depending on usage of xmlrpc_client::send() inside which creator is called... + */ + function xmlrpcresp($val, $fcode = 0, $fstr = '', $valtyp='') + { + if($fcode != 0) + { + // error response + $this->errno = $fcode; + $this->errstr = $fstr; + //$this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later. + } + else + { + // successful response + $this->val = $val; + if ($valtyp == '') + { + // user did not declare type of response value: try to guess it + if (is_object($this->val) && is_a($this->val, 'xmlrpcval')) + { + $this->valtyp = 'xmlrpcvals'; + } + else if (is_string($this->val)) + { + $this->valtyp = 'xml'; + + } + else + { + $this->valtyp = 'phpvals'; + } + } + else + { + // user declares type of resp value: believe him + $this->valtyp = $valtyp; + } + } + } + + /** + * Returns the error code of the response. + * @return integer the error code of this response (0 for not-error responses) + * @access public + */ + function faultCode() + { + return $this->errno; + } + + /** + * Returns the error code of the response. + * @return string the error string of this response ('' for not-error responses) + * @access public + */ + function faultString() + { + return $this->errstr; + } + + /** + * Returns the value received by the server. + * @return mixed the xmlrpcval object returned by the server. Might be an xml string or php value if the response has been created by specially configured xmlrpc_client objects + * @access public + */ + function value() + { + return $this->val; + } + + /** + * Returns an array with the cookies received from the server. + * Array has the form: $cookiename => array ('value' => $val, $attr1 => $val1, $attr2 = $val2, ...) + * with attributes being e.g. 'expires', 'path', domain'. + * NB: cookies sent as 'expired' by the server (i.e. with an expiry date in the past) + * are still present in the array. It is up to the user-defined code to decide + * how to use the received cookies, and wheter they have to be sent back with the next + * request to the server (using xmlrpc_client::setCookie) or not + * @return array array of cookies received from the server + * @access public + */ + function cookies() + { + return $this->_cookies; + } + + /** + * Returns xml representation of the response. XML prologue not included + * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed + * @return string the xml representation of the response + * @access public + */ + function serialize($charset_encoding='') + { + if ($charset_encoding != '') + $this->content_type = 'text/xml; charset=' . $charset_encoding; + else + $this->content_type = 'text/xml'; + $result = "\n"; + if($this->errno) + { + // G. Giunta 2005/2/13: let non-ASCII response messages be tolerated by clients + // by xml-encoding non ascii chars + $result .= "\n" . +"\nfaultCode\n" . $this->errno . +"\n\n\nfaultString\n" . +xmlrpc_encode_entitites($this->errstr, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) . "\n\n" . +"\n\n"; + } + else + { + if(!is_object($this->val) || !is_a($this->val, 'xmlrpcval')) + { + if (is_string($this->val) && $this->valtyp == 'xml') + { + $result .= "\n\n" . + $this->val . + "\n"; + } + else + { + /// @todo try to build something serializable? + die('cannot serialize xmlrpcresp objects whose content is native php values'); + } + } + else + { + $result .= "\n\n" . + $this->val->serialize($charset_encoding) . + "\n"; + } + } + $result .= "\n"; + $this->payload = $result; + return $result; + } + } + + class xmlrpcmsg + { + var $payload; + var $methodname; + var $params=array(); + var $debug=0; + var $content_type = 'text/xml'; + + /** + * @param string $meth the name of the method to invoke + * @param array $pars array of parameters to be paased to the method (xmlrpcval objects) + */ + function xmlrpcmsg($meth, $pars=0) + { + $this->methodname=$meth; + if(is_array($pars) && count($pars)>0) + { + for($i=0; $iaddParam($pars[$i]); + } + } + } + + /** + * @access private + */ + function xml_header($charset_encoding='') + { + if ($charset_encoding != '') + { + return "\n\n"; + } + else + { + return "\n\n"; + } + } + + /** + * @access private + */ + function xml_footer() + { + return ''; + } + + /** + * @access private + */ + function kindOf() + { + return 'msg'; + } + + /** + * @access private + */ + function createPayload($charset_encoding='') + { + if ($charset_encoding != '') + $this->content_type = 'text/xml; charset=' . $charset_encoding; + else + $this->content_type = 'text/xml'; + $this->payload=$this->xml_header($charset_encoding); + $this->payload.='' . $this->methodname . "\n"; + $this->payload.="\n"; + for($i=0; $iparams); $i++) + { + $p=$this->params[$i]; + $this->payload.="\n" . $p->serialize($charset_encoding) . + "\n"; + } + $this->payload.="\n"; + $this->payload.=$this->xml_footer(); + } + + /** + * Gets/sets the xmlrpc method to be invoked + * @param string $meth the method to be set (leave empty not to set it) + * @return string the method that will be invoked + * @access public + */ + function method($meth='') + { + if($meth!='') + { + $this->methodname=$meth; + } + return $this->methodname; + } + + /** + * Returns xml representation of the message. XML prologue included + * @return string the xml representation of the message, xml prologue included + * @access public + */ + function serialize($charset_encoding='') + { + $this->createPayload($charset_encoding); + return $this->payload; + } + + /** + * Add a parameter to the list of parameters to be used upon method invocation + * @param xmlrpcval $par + * @return boolean false on failure + * @access public + */ + function addParam($par) + { + // add check: do not add to self params which are not xmlrpcvals + if(is_object($par) && is_a($par, 'xmlrpcval')) + { + $this->params[]=$par; + return true; + } + else + { + return false; + } + } + + /** + * Returns the nth parameter in the message. The index zero-based. + * @param integer $i the index of the parameter to fetch (zero based) + * @return xmlrpcval the i-th parameter + * @access public + */ + function getParam($i) { return $this->params[$i]; } + + /** + * Returns the number of parameters in the messge. + * @return integer the number of parameters currently set + * @access public + */ + function getNumParams() { return count($this->params); } + + /** + * Given an open file handle, read all data available and parse it as axmlrpc response. + * NB: the file handle is not closed by this function. + * NNB: might have trouble in rare cases to work on network streams, as we + * check for a read of 0 bytes instead of feof($fp). + * But since checking for feof(null) returns false, we would risk an + * infinite loop in that case, because we cannot trust the caller + * to give us a valid pointer to an open file... + * @access public + * @return xmlrpcresp + * @todo add 2nd & 3rd param to be passed to ParseResponse() ??? + */ + function &parseResponseFile($fp) + { + $ipd=''; + while($data=fread($fp, 32768)) + { + $ipd.=$data; + } + //fclose($fp); + $r =& $this->parseResponse($ipd); + return $r; + } + + /** + * Parses HTTP headers and separates them from data. + * @access private + */ + function parseResponseHeaders(&$data, $headers_processed=false) + { + // Support "web-proxy-tunelling" connections for https through proxies + if(preg_match('/^HTTP\/1\.[0-1] 200 Connection established/', $data)) + { + // Look for CR/LF or simple LF as line separator, + // (even though it is not valid http) + $pos = strpos($data,"\r\n\r\n"); + if($pos || is_int($pos)) + { + $bd = $pos+4; + } + else + { + $pos = strpos($data,"\n\n"); + if($pos || is_int($pos)) + { + $bd = $pos+2; + } + else + { + // No separation between response headers and body: fault? + $bd = 0; + } + } + if ($bd) + { + // this filters out all http headers from proxy. + // maybe we could take them into account, too? + $data = substr($data, $bd); + } + else + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTPS via proxy error, tunnel connection possibly failed'); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (HTTPS via proxy error, tunnel connection possibly failed)'); + return $r; + } + } + + // Strip HTTP 1.1 100 Continue header if present + while(preg_match('/^HTTP\/1\.1 1[0-9]{2} /', $data)) + { + $pos = strpos($data, 'HTTP', 12); + // server sent a Continue header without any (valid) content following... + // give the client a chance to know it + if(!$pos && !is_int($pos)) // works fine in php 3, 4 and 5 + { + break; + } + $data = substr($data, $pos); + } + if(!preg_match('/^HTTP\/[0-9.]+ 200 /', $data)) + { + $errstr= substr($data, 0, strpos($data, "\n")-1); + error_log('XML-RPC: xmlrpcmsg::parseResponse: HTTP error, got response: ' .$errstr); + $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['http_error'], $GLOBALS['xmlrpcstr']['http_error']. ' (' . $errstr . ')'); + return $r; + } + + $GLOBALS['_xh']['headers'] = array(); + $GLOBALS['_xh']['cookies'] = array(); + + // be tolerant to usage of \n instead of \r\n to separate headers and data + // (even though it is not valid http) + $pos = strpos($data,"\r\n\r\n"); + if($pos || is_int($pos)) + { + $bd = $pos+4; + } + else + { + $pos = strpos($data,"\n\n"); + if($pos || is_int($pos)) + { + $bd = $pos+2; + } + else + { + // No separation between response headers and body: fault? + // we could take some action here instead of going on... + $bd = 0; + } + } + // be tolerant to line endings, and extra empty lines + $ar = preg_split("#\r?\n#", trim(substr($data, 0, $pos))); + while(list(,$line) = @each($ar)) + { + // take care of multi-line headers and cookies + $arr = explode(':',$line,2); + if(count($arr) > 1) + { + $header_name = strtolower(trim($arr[0])); + /// @todo some other headers (the ones that allow a CSV list of values) + /// do allow many values to be passed using multiple header lines. + /// We should add content to $GLOBALS['_xh']['headers'][$header_name] + /// instead of replacing it for those... + if ($header_name == 'set-cookie' || $header_name == 'set-cookie2') + { + if ($header_name == 'set-cookie2') + { + // version 2 cookies: + // there could be many cookies on one line, comma separated + $cookies = explode(',', $arr[1]); + } + else + { + $cookies = array($arr[1]); + } + foreach ($cookies as $cookie) + { + // glue together all received cookies, using a comma to separate them + // (same as php does with getallheaders()) + if (isset($GLOBALS['_xh']['headers'][$header_name])) + $GLOBALS['_xh']['headers'][$header_name] .= ', ' . trim($cookie); + else + $GLOBALS['_xh']['headers'][$header_name] = trim($cookie); + // parse cookie attributes, in case user wants to correctly honour them + // feature creep: only allow rfc-compliant cookie attributes? + // @todo support for server sending multiple time cookie with same name, but using different PATHs + $cookie = explode(';', $cookie); + foreach ($cookie as $pos => $val) + { + $val = explode('=', $val, 2); + $tag = trim($val[0]); + $val = trim(@$val[1]); + /// @todo with version 1 cookies, we should strip leading and trailing " chars + if ($pos == 0) + { + $cookiename = $tag; + $GLOBALS['_xh']['cookies'][$tag] = array(); + $GLOBALS['_xh']['cookies'][$cookiename]['value'] = urldecode($val); + } + else + { + if ($tag != 'value') + { + $GLOBALS['_xh']['cookies'][$cookiename][$tag] = $val; + } + } + } + } + } + else + { + $GLOBALS['_xh']['headers'][$header_name] = trim($arr[1]); + } + } + elseif(isset($header_name)) + { + /// @todo version1 cookies might span multiple lines, thus breaking the parsing above + $GLOBALS['_xh']['headers'][$header_name] .= ' ' . trim($line); + } + } + + $data = substr($data, $bd); + + if($this->debug && count($GLOBALS['_xh']['headers'])) + { + print '
';
+					foreach($GLOBALS['_xh']['headers'] as $header => $value)
+					{
+						print htmlentities("HEADER: $header: $value\n");
+					}
+					foreach($GLOBALS['_xh']['cookies'] as $header => $value)
+					{
+						print htmlentities("COOKIE: $header={$value['value']}\n");
+					}
+					print "
\n"; + } + + // if CURL was used for the call, http headers have been processed, + // and dechunking + reinflating have been carried out + if(!$headers_processed) + { + // Decode chunked encoding sent by http 1.1 servers + if(isset($GLOBALS['_xh']['headers']['transfer-encoding']) && $GLOBALS['_xh']['headers']['transfer-encoding'] == 'chunked') + { + if(!$data = decode_chunked($data)) + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to rebuild the chunked data received from server'); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['dechunk_fail'], $GLOBALS['xmlrpcstr']['dechunk_fail']); + return $r; + } + } + + // Decode gzip-compressed stuff + // code shamelessly inspired from nusoap library by Dietrich Ayala + if(isset($GLOBALS['_xh']['headers']['content-encoding'])) + { + $GLOBALS['_xh']['headers']['content-encoding'] = str_replace('x-', '', $GLOBALS['_xh']['headers']['content-encoding']); + if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' || $GLOBALS['_xh']['headers']['content-encoding'] == 'gzip') + { + // if decoding works, use it. else assume data wasn't gzencoded + if(function_exists('gzinflate')) + { + if($GLOBALS['_xh']['headers']['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) + { + $data = $degzdata; + if($this->debug) + print "
---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---
"; + } + elseif($GLOBALS['_xh']['headers']['content-encoding'] == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) + { + $data = $degzdata; + if($this->debug) + print "
---INFLATED RESPONSE---[".strlen($data)." chars]---\n" . htmlentities($data) . "\n---END---
"; + } + else + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: errors occurred when trying to decode the deflated data received from server'); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['decompress_fail'], $GLOBALS['xmlrpcstr']['decompress_fail']); + return $r; + } + } + else + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: the server sent deflated data. Your php install must have the Zlib extension compiled in to support this.'); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['cannot_decompress'], $GLOBALS['xmlrpcstr']['cannot_decompress']); + return $r; + } + } + } + } // end of 'if needed, de-chunk, re-inflate response' + + // real stupid hack to avoid PHP 4 complaining about returning NULL by ref + $r = new StdClass(); + return null; + } + + /** + * Parse the xmlrpc response contained in the string $data and return an xmlrpcresp object. + * @param string $data the xmlrpc response, eventually including http headers + * @param bool $headers_processed when true prevents parsing HTTP headers for interpretation of content-encoding and consequent decoding + * @param string $return_type decides return type, i.e. content of response->value(). Either 'xmlrpcvals', 'xml' or 'phpvals' + * @return xmlrpcresp + * @access public + */ + function &parseResponse($data='', $headers_processed=false, $return_type='xmlrpcvals') + { + if($this->debug) + { + //by maHo, replaced htmlspecialchars with htmlentities + print "
---GOT---\n" . htmlentities($data) . "\n---END---\n
"; + } + + if($data == '') + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: no response received from server.'); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['no_data'], $GLOBALS['xmlrpcstr']['no_data']); + return $r; + } + + $GLOBALS['_xh']=array(); + + $raw_data = $data; + // parse the HTTP headers of the response, if present, and separate them from data + if(substr($data, 0, 4) == 'HTTP') + { + $r = $this->parseResponseHeaders($data, $headers_processed); + if ($r) + { + // failed processing of HTTP response headers + // save into response obj the full payload received, for debugging + $r->raw_data = $data; + return $r; + } + } + else + { + $GLOBALS['_xh']['headers'] = array(); + $GLOBALS['_xh']['cookies'] = array(); + } + + if($this->debug) + { + $start = strpos($data, '', $start); + $comments = substr($data, $start, $end-$start); + print "
---SERVER DEBUG INFO (DECODED) ---\n\t".htmlentities(str_replace("\n", "\n\t", base64_decode($comments)))."\n---END---\n
"; + } + } + + // be tolerant of extra whitespace in response body + $data = trim($data); + + /// @todo return an error msg if $data=='' ? + + // be tolerant of junk after methodResponse (e.g. javascript ads automatically inserted by free hosts) + // idea from Luca Mariano originally in PEARified version of the lib + $bd = false; + // Poor man's version of strrpos for php 4... + $pos = strpos($data, ''); + while($pos || is_int($pos)) + { + $bd = $pos+17; + $pos = strpos($data, '', $bd); + } + if($bd) + { + $data = substr($data, 0, $bd); + } + + // if user wants back raw xml, give it to him + if ($return_type == 'xml') + { + $r = new xmlrpcresp($data, 0, '', 'xml'); + $r->hdrs = $GLOBALS['_xh']['headers']; + $r->_cookies = $GLOBALS['_xh']['cookies']; + $r->raw_data = $raw_data; + return $r; + } + + // try to 'guestimate' the character encoding of the received response + $resp_encoding = guess_encoding(@$GLOBALS['_xh']['headers']['content-type'], $data); + + $GLOBALS['_xh']['ac']=''; + //$GLOBALS['_xh']['qt']=''; //unused... + $GLOBALS['_xh']['stack'] = array(); + $GLOBALS['_xh']['valuestack'] = array(); + $GLOBALS['_xh']['isf']=0; // 0 = OK, 1 for xmlrpc fault responses, 2 = invalid xmlrpc + $GLOBALS['_xh']['isf_reason']=''; + $GLOBALS['_xh']['rt']=''; // 'methodcall or 'methodresponse' + + // if response charset encoding is not known / supported, try to use + // the default encoding and parse the xml anyway, but log a warning... + if (!in_array($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) + // the following code might be better for mb_string enabled installs, but + // makes the lib about 200% slower... + //if (!is_valid_charset($resp_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) + { + error_log('XML-RPC: xmlrpcmsg::parseResponse: invalid charset encoding of received response: '.$resp_encoding); + $resp_encoding = $GLOBALS['xmlrpc_defencoding']; + } + $parser = xml_parser_create($resp_encoding); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); + // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell + // the xml parser to give us back data in the expected charset. + // What if internal encoding is not in one of the 3 allowed? + // we use the broadest one, ie. utf8 + // This allows to send data which is native in various charset, + // by extending xmlrpc_encode_entitites() and setting xmlrpc_internalencoding + if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) + { + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); + } + else + { + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']); + } + + if ($return_type == 'phpvals') + { + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast'); + } + else + { + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); + } + + xml_set_character_data_handler($parser, 'xmlrpc_cd'); + xml_set_default_handler($parser, 'xmlrpc_dh'); + + // first error check: xml not well formed + if(!xml_parse($parser, $data, count($data))) + { + // thanks to Peter Kocks + if((xml_get_current_line_number($parser)) == 1) + { + $errstr = 'XML error at line 1, check URL'; + } + else + { + $errstr = sprintf('XML error: %s at line %d, column %d', + xml_error_string(xml_get_error_code($parser)), + xml_get_current_line_number($parser), xml_get_current_column_number($parser)); + } + error_log($errstr); + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], $GLOBALS['xmlrpcstr']['invalid_return'].' ('.$errstr.')'); + xml_parser_free($parser); + if($this->debug) + { + print $errstr; + } + $r->hdrs = $GLOBALS['_xh']['headers']; + $r->_cookies = $GLOBALS['_xh']['cookies']; + $r->raw_data = $raw_data; + return $r; + } + xml_parser_free($parser); + // second error check: xml well formed but not xml-rpc compliant + if ($GLOBALS['_xh']['isf'] > 1) + { + if ($this->debug) + { + /// @todo echo something for user? + } + + $r = new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], + $GLOBALS['xmlrpcstr']['invalid_return'] . ' ' . $GLOBALS['_xh']['isf_reason']); + } + // third error check: parsing of the response has somehow gone boink. + // NB: shall we omit this check, since we trust the parsing code? + elseif ($return_type == 'xmlrpcvals' && !is_object($GLOBALS['_xh']['value'])) + { + // something odd has happened + // and it's time to generate a client side error + // indicating something odd went on + $r=new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['invalid_return'], + $GLOBALS['xmlrpcstr']['invalid_return']); + } + else + { + if ($this->debug) + { + print "
---PARSED---\n";
+					// somehow htmlentities chokes on var_export, and some full html string...
+					//print htmlentitites(var_export($GLOBALS['_xh']['value'], true));
+					print htmlspecialchars(var_export($GLOBALS['_xh']['value'], true));
+					print "\n---END---
"; + } + + // note that using =& will raise an error if $GLOBALS['_xh']['st'] does not generate an object. + $v =& $GLOBALS['_xh']['value']; + + if($GLOBALS['_xh']['isf']) + { + /// @todo we should test here if server sent an int and a string, + /// and/or coerce them into such... + if ($return_type == 'xmlrpcvals') + { + $errno_v = $v->structmem('faultCode'); + $errstr_v = $v->structmem('faultString'); + $errno = $errno_v->scalarval(); + $errstr = $errstr_v->scalarval(); + } + else + { + $errno = $v['faultCode']; + $errstr = $v['faultString']; + } + + if($errno == 0) + { + // FAULT returned, errno needs to reflect that + $errno = -1; + } + + $r = new xmlrpcresp(0, $errno, $errstr); + } + else + { + $r= new xmlrpcresp($v, 0, '', $return_type); + } + } + + $r->hdrs = $GLOBALS['_xh']['headers']; + $r->_cookies = $GLOBALS['_xh']['cookies']; + $r->raw_data = $raw_data; + return $r; + } + } + + class xmlrpcval + { + var $me=array(); + var $mytype=0; + var $_php_class=null; + + /** + * @param mixed $val + * @param string $type any valid xmlrpc type name (lowercase). If null, 'string' is assumed + */ + function xmlrpcval($val=-1, $type='') + { + /// @todo: optimization creep - do not call addXX, do it all inline. + /// downside: booleans will not be coerced anymore + if($val!==-1 || $type!='') + { + // optimization creep: inlined all work done by constructor + switch($type) + { + case '': + $this->mytype=1; + $this->me['string']=$val; + break; + case 'i4': + case 'int': + case 'double': + case 'string': + case 'boolean': + case 'dateTime.iso8601': + case 'base64': + case 'null': + $this->mytype=1; + $this->me[$type]=$val; + break; + case 'array': + $this->mytype=2; + $this->me['array']=$val; + break; + case 'struct': + $this->mytype=3; + $this->me['struct']=$val; + break; + default: + error_log("XML-RPC: xmlrpcval::xmlrpcval: not a known type ($type)"); + } + /*if($type=='') + { + $type='string'; + } + if($GLOBALS['xmlrpcTypes'][$type]==1) + { + $this->addScalar($val,$type); + } + elseif($GLOBALS['xmlrpcTypes'][$type]==2) + { + $this->addArray($val); + } + elseif($GLOBALS['xmlrpcTypes'][$type]==3) + { + $this->addStruct($val); + }*/ + } + } + + /** + * Add a single php value to an (unitialized) xmlrpcval + * @param mixed $val + * @param string $type + * @return int 1 or 0 on failure + */ + function addScalar($val, $type='string') + { + $typeof=@$GLOBALS['xmlrpcTypes'][$type]; + if($typeof!=1) + { + error_log("XML-RPC: xmlrpcval::addScalar: not a scalar type ($type)"); + return 0; + } + + // coerce booleans into correct values + // NB: we should iether do it for datetimes, integers and doubles, too, + // or just plain remove this check, implemnted on booleans only... + if($type==$GLOBALS['xmlrpcBoolean']) + { + if(strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false'))) + { + $val=true; + } + else + { + $val=false; + } + } + + switch($this->mytype) + { + case 1: + error_log('XML-RPC: xmlrpcval::addScalar: scalar xmlrpcval can have only one value'); + return 0; + case 3: + error_log('XML-RPC: xmlrpcval::addScalar: cannot add anonymous scalar to struct xmlrpcval'); + return 0; + case 2: + // we're adding a scalar value to an array here + //$ar=$this->me['array']; + //$ar[]=&new xmlrpcval($val, $type); + //$this->me['array']=$ar; + // Faster (?) avoid all the costly array-copy-by-val done here... + $this->me['array'][]=new xmlrpcval($val, $type); + return 1; + default: + // a scalar, so set the value and remember we're scalar + $this->me[$type]=$val; + $this->mytype=$typeof; + return 1; + } + } + + /** + * Add an array of xmlrpcval objects to an xmlrpcval + * @param array $vals + * @return int 1 or 0 on failure + * @access public + * + * @todo add some checking for $vals to be an array of xmlrpcvals? + */ + function addArray($vals) + { + if($this->mytype==0) + { + $this->mytype=$GLOBALS['xmlrpcTypes']['array']; + $this->me['array']=$vals; + return 1; + } + elseif($this->mytype==2) + { + // we're adding to an array here + $this->me['array'] = array_merge($this->me['array'], $vals); + return 1; + } + else + { + error_log('XML-RPC: xmlrpcval::addArray: already initialized as a [' . $this->kindOf() . ']'); + return 0; + } + } + + /** + * Add an array of named xmlrpcval objects to an xmlrpcval + * @param array $vals + * @return int 1 or 0 on failure + * @access public + * + * @todo add some checking for $vals to be an array? + */ + function addStruct($vals) + { + if($this->mytype==0) + { + $this->mytype=$GLOBALS['xmlrpcTypes']['struct']; + $this->me['struct']=$vals; + return 1; + } + elseif($this->mytype==3) + { + // we're adding to a struct here + $this->me['struct'] = array_merge($this->me['struct'], $vals); + return 1; + } + else + { + error_log('XML-RPC: xmlrpcval::addStruct: already initialized as a [' . $this->kindOf() . ']'); + return 0; + } + } + + // poor man's version of print_r ??? + // DEPRECATED! + function dump($ar) + { + foreach($ar as $key => $val) + { + echo "$key => $val
"; + if($key == 'array') + { + while(list($key2, $val2) = each($val)) + { + echo "-- $key2 => $val2
"; + } + } + } + } + + /** + * Returns a string containing "struct", "array" or "scalar" describing the base type of the value + * @return string + * @access public + */ + function kindOf() + { + switch($this->mytype) + { + case 3: + return 'struct'; + break; + case 2: + return 'array'; + break; + case 1: + return 'scalar'; + break; + default: + return 'undef'; + } + } + + /** + * @access private + */ + function serializedata($typ, $val, $charset_encoding='') + { + $rs=''; + switch(@$GLOBALS['xmlrpcTypes'][$typ]) + { + case 1: + switch($typ) + { + case $GLOBALS['xmlrpcBase64']: + $rs.="<${typ}>" . base64_encode($val) . ""; + break; + case $GLOBALS['xmlrpcBoolean']: + $rs.="<${typ}>" . ($val ? '1' : '0') . ""; + break; + case $GLOBALS['xmlrpcString']: + // G. Giunta 2005/2/13: do NOT use htmlentities, since + // it will produce named html entities, which are invalid xml + $rs.="<${typ}>" . xmlrpc_encode_entitites($val, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding). ""; + break; + case $GLOBALS['xmlrpcInt']: + case $GLOBALS['xmlrpcI4']: + $rs.="<${typ}>".(int)$val.""; + break; + case $GLOBALS['xmlrpcDouble']: + // avoid using standard conversion of float to string because it is locale-dependent, + // and also because the xmlrpc spec forbids exponential notation + // sprintf('%F') would be most likely ok but it is only available since PHP 4.3.10 and PHP 5.0.3. + // The code below tries its best at keeping max precision while avoiding exp notation, + // but there is of course no limit in the number of decimal places to be used... + $rs.="<${typ}>".preg_replace('/\\.?0+$/','',number_format((double)$val, 128, '.', '')).""; + break; + case $GLOBALS['xmlrpcNull']: + $rs.=""; + break; + default: + // no standard type value should arrive here, but provide a possibility + // for xmlrpcvals of unknown type... + $rs.="<${typ}>${val}"; + } + break; + case 3: + // struct + if ($this->_php_class) + { + $rs.='\n"; + } + else + { + $rs.="\n"; + } + foreach($val as $key2 => $val2) + { + $rs.=''.xmlrpc_encode_entitites($key2, $GLOBALS['xmlrpc_internalencoding'], $charset_encoding)."\n"; + //$rs.=$this->serializeval($val2); + $rs.=$val2->serialize($charset_encoding); + $rs.="\n"; + } + $rs.=''; + break; + case 2: + // array + $rs.="\n\n"; + for($i=0; $iserializeval($val[$i]); + $rs.=$val[$i]->serialize($charset_encoding); + } + $rs.="\n"; + break; + default: + break; + } + return $rs; + } + + /** + * Returns xml representation of the value. XML prologue not included + * @param string $charset_encoding the charset to be used for serialization. if null, US-ASCII is assumed + * @return string + * @access public + */ + function serialize($charset_encoding='') + { + // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals... + //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval'))) + //{ + reset($this->me); + list($typ, $val) = each($this->me); + return '' . $this->serializedata($typ, $val, $charset_encoding) . "\n"; + //} + } + + // DEPRECATED + function serializeval($o) + { + // add check? slower, but helps to avoid recursion in serializing broken xmlrpcvals... + //if (is_object($o) && (get_class($o) == 'xmlrpcval' || is_subclass_of($o, 'xmlrpcval'))) + //{ + $ar=$o->me; + reset($ar); + list($typ, $val) = each($ar); + return '' . $this->serializedata($typ, $val) . "\n"; + //} + } + + /** + * Checks wheter a struct member with a given name is present. + * Works only on xmlrpcvals of type struct. + * @param string $m the name of the struct member to be looked up + * @return boolean + * @access public + */ + function structmemexists($m) + { + return array_key_exists($m, $this->me['struct']); + } + + /** + * Returns the value of a given struct member (an xmlrpcval object in itself). + * Will raise a php warning if struct member of given name does not exist + * @param string $m the name of the struct member to be looked up + * @return xmlrpcval + * @access public + */ + function structmem($m) + { + return $this->me['struct'][$m]; + } + + /** + * Reset internal pointer for xmlrpcvals of type struct. + * @access public + */ + function structreset() + { + reset($this->me['struct']); + } + + /** + * Return next member element for xmlrpcvals of type struct. + * @return xmlrpcval + * @access public + */ + function structeach() + { + return each($this->me['struct']); + } + + // DEPRECATED! this code looks like it is very fragile and has not been fixed + // for a long long time. Shall we remove it for 2.0? + function getval() + { + // UNSTABLE + reset($this->me); + list($a,$b)=each($this->me); + // contributed by I Sofer, 2001-03-24 + // add support for nested arrays to scalarval + // i've created a new method here, so as to + // preserve back compatibility + + if(is_array($b)) + { + @reset($b); + while(list($id,$cont) = @each($b)) + { + $b[$id] = $cont->scalarval(); + } + } + + // add support for structures directly encoding php objects + if(is_object($b)) + { + $t = get_object_vars($b); + @reset($t); + while(list($id,$cont) = @each($t)) + { + $t[$id] = $cont->scalarval(); + } + @reset($t); + while(list($id,$cont) = @each($t)) + { + @$b->$id = $cont; + } + } + // end contrib + return $b; + } + + /** + * Returns the value of a scalar xmlrpcval + * @return mixed + * @access public + */ + function scalarval() + { + reset($this->me); + list(,$b)=each($this->me); + return $b; + } + + /** + * Returns the type of the xmlrpcval. + * For integers, 'int' is always returned in place of 'i4' + * @return string + * @access public + */ + function scalartyp() + { + reset($this->me); + list($a,)=each($this->me); + if($a==$GLOBALS['xmlrpcI4']) + { + $a=$GLOBALS['xmlrpcInt']; + } + return $a; + } + + /** + * Returns the m-th member of an xmlrpcval of struct type + * @param integer $m the index of the value to be retrieved (zero based) + * @return xmlrpcval + * @access public + */ + function arraymem($m) + { + return $this->me['array'][$m]; + } + + /** + * Returns the number of members in an xmlrpcval of array type + * @return integer + * @access public + */ + function arraysize() + { + return count($this->me['array']); + } + + /** + * Returns the number of members in an xmlrpcval of struct type + * @return integer + * @access public + */ + function structsize() + { + return count($this->me['struct']); + } + } + + + // date helpers + + /** + * Given a timestamp, return the corresponding ISO8601 encoded string. + * + * Really, timezones ought to be supported + * but the XML-RPC spec says: + * + * "Don't assume a timezone. It should be specified by the server in its + * documentation what assumptions it makes about timezones." + * + * These routines always assume localtime unless + * $utc is set to 1, in which case UTC is assumed + * and an adjustment for locale is made when encoding + * + * @param int $timet (timestamp) + * @param int $utc (0 or 1) + * @return string + */ + function iso8601_encode($timet, $utc=0) + { + if(!$utc) + { + $t=strftime("%Y%m%dT%H:%M:%S", $timet); + } + else + { + if(function_exists('gmstrftime')) + { + // gmstrftime doesn't exist in some versions + // of PHP + $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet); + } + else + { + $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z')); + } + } + return $t; + } + + /** + * Given an ISO8601 date string, return a timet in the localtime, or UTC + * @param string $idate + * @param int $utc either 0 or 1 + * @return int (datetime) + */ + function iso8601_decode($idate, $utc=0) + { + $t=0; + if(preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})/', $idate, $regs)) + { + if($utc) + { + $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); + } + else + { + $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); + } + } + return $t; + } + + /** + * Takes an xmlrpc value in PHP xmlrpcval object format and translates it into native PHP types. + * + * Works with xmlrpc message objects as input, too. + * + * Given proper options parameter, can rebuild generic php object instances + * (provided those have been encoded to xmlrpc format using a corresponding + * option in php_xmlrpc_encode()) + * PLEASE NOTE that rebuilding php objects involves calling their constructor function. + * This means that the remote communication end can decide which php code will + * get executed on your server, leaving the door possibly open to 'php-injection' + * style of attacks (provided you have some classes defined on your server that + * might wreak havoc if instances are built outside an appropriate context). + * Make sure you trust the remote server/client before eanbling this! + * + * @author Dan Libby (dan@libby.com) + * + * @param xmlrpcval $xmlrpc_val + * @param array $options if 'decode_php_objs' is set in the options array, xmlrpc structs can be decoded into php objects + * @return mixed + */ + function php_xmlrpc_decode($xmlrpc_val, $options=array()) + { + if (is_object($xmlrpc_val)) + switch($xmlrpc_val->kindOf()) + { + case 'scalar': + if (in_array('extension_api', $options)) + { + reset($xmlrpc_val->me); + list($typ,$val) = each($xmlrpc_val->me); + switch ($typ) + { + case 'dateTime.iso8601': + $xmlrpc_val->scalar = $val; + $xmlrpc_val->xmlrpc_type = 'datetime'; + $xmlrpc_val->timestamp = iso8601_decode($val); + return $xmlrpc_val; + case 'base64': + $xmlrpc_val->scalar = $val; + $xmlrpc_val->type = $typ; + return $xmlrpc_val; + default: + return $xmlrpc_val->scalarval(); + } + } + return $xmlrpc_val->scalarval(); + case 'array': + $size = $xmlrpc_val->arraysize(); + $arr = array(); + for($i = 0; $i < $size; $i++) + { + $arr[] = php_xmlrpc_decode($xmlrpc_val->arraymem($i), $options); + } + return $arr; + case 'struct': + $xmlrpc_val->structreset(); + // If user said so, try to rebuild php objects for specific struct vals. + /// @todo should we raise a warning for class not found? + // shall we check for proper subclass of xmlrpcval instead of + // presence of _php_class to detect what we can do? + if (in_array('decode_php_objs', $options) && $xmlrpc_val->_php_class != '' + && class_exists($xmlrpc_val->_php_class)) + { + $obj = @new $xmlrpc_val->_php_class; + while(list($key,$value)=$xmlrpc_val->structeach()) + { + $obj->$key = php_xmlrpc_decode($value, $options); + } + return $obj; + } + else + { + $arr = array(); + while(list($key,$value)=$xmlrpc_val->structeach()) + { + $arr[$key] = php_xmlrpc_decode($value, $options); + } + return $arr; + } + case 'msg': + $paramcount = $xmlrpc_val->getNumParams(); + $arr = array(); + for($i = 0; $i < $paramcount; $i++) + { + $arr[] = php_xmlrpc_decode($xmlrpc_val->getParam($i)); + } + return $arr; + } + } + + // This constant left here only for historical reasons... + // it was used to decide if we have to define xmlrpc_encode on our own, but + // we do not do it anymore + if(function_exists('xmlrpc_decode')) + { + define('XMLRPC_EPI_ENABLED','1'); + } + else + { + define('XMLRPC_EPI_ENABLED','0'); + } + + /** + * Takes native php types and encodes them into xmlrpc PHP object format. + * It will not re-encode xmlrpcval objects. + * + * Feature creep -- could support more types via optional type argument + * (string => datetime support has been added, ??? => base64 not yet) + * + * If given a proper options parameter, php object instances will be encoded + * into 'special' xmlrpc values, that can later be decoded into php objects + * by calling php_xmlrpc_decode() with a corresponding option + * + * @author Dan Libby (dan@libby.com) + * + * @param mixed $php_val the value to be converted into an xmlrpcval object + * @param array $options can include 'encode_php_objs', 'auto_dates', 'null_extension' or 'extension_api' + * @return xmlrpcval + */ + function &php_xmlrpc_encode($php_val, $options=array()) + { + $type = gettype($php_val); + switch($type) + { + case 'string': + if (in_array('auto_dates', $options) && preg_match('/^[0-9]{8}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/', $php_val)) + $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcDateTime']); + else + $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcString']); + break; + case 'integer': + $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcInt']); + break; + case 'double': + $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcDouble']); + break; + // + // Add support for encoding/decoding of booleans, since they are supported in PHP + case 'boolean': + $xmlrpc_val = new xmlrpcval($php_val, $GLOBALS['xmlrpcBoolean']); + break; + // + case 'array': + // PHP arrays can be encoded to either xmlrpc structs or arrays, + // depending on wheter they are hashes or plain 0..n integer indexed + // A shorter one-liner would be + // $tmp = array_diff(array_keys($php_val), range(0, count($php_val)-1)); + // but execution time skyrockets! + $j = 0; + $arr = array(); + $ko = false; + foreach($php_val as $key => $val) + { + $arr[$key] =& php_xmlrpc_encode($val, $options); + if(!$ko && $key !== $j) + { + $ko = true; + } + $j++; + } + if($ko) + { + $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']); + } + else + { + $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcArray']); + } + break; + case 'object': + if(is_a($php_val, 'xmlrpcval')) + { + $xmlrpc_val = $php_val; + } + else + { + $arr = array(); + while(list($k,$v) = each($php_val)) + { + $arr[$k] = php_xmlrpc_encode($v, $options); + } + $xmlrpc_val = new xmlrpcval($arr, $GLOBALS['xmlrpcStruct']); + if (in_array('encode_php_objs', $options)) + { + // let's save original class name into xmlrpcval: + // might be useful later on... + $xmlrpc_val->_php_class = get_class($php_val); + } + } + break; + case 'NULL': + if (in_array('extension_api', $options)) + { + $xmlrpc_val = new xmlrpcval('', $GLOBALS['xmlrpcString']); + } + if (in_array('null_extension', $options)) + { + $xmlrpc_val = new xmlrpcval('', $GLOBALS['xmlrpcNull']); + } + else + { + $xmlrpc_val = new xmlrpcval(); + } + break; + case 'resource': + if (in_array('extension_api', $options)) + { + $xmlrpc_val = new xmlrpcval((int)$php_val, $GLOBALS['xmlrpcInt']); + } + else + { + $xmlrpc_val = new xmlrpcval(); + } + // catch "user function", "unknown type" + default: + // giancarlo pinerolo + // it has to return + // an empty object in case, not a boolean. + $xmlrpc_val = new xmlrpcval(); + break; + } + return $xmlrpc_val; + } + + /** + * Convert the xml representation of a method response, method request or single + * xmlrpc value into the appropriate object (a.k.a. deserialize) + * @param string $xml_val + * @param array $options + * @return mixed false on error, or an instance of either xmlrpcval, xmlrpcmsg or xmlrpcresp + */ + function php_xmlrpc_decode_xml($xml_val, $options=array()) + { + $GLOBALS['_xh'] = array(); + $GLOBALS['_xh']['ac'] = ''; + $GLOBALS['_xh']['stack'] = array(); + $GLOBALS['_xh']['valuestack'] = array(); + $GLOBALS['_xh']['params'] = array(); + $GLOBALS['_xh']['pt'] = array(); + $GLOBALS['_xh']['isf'] = 0; + $GLOBALS['_xh']['isf_reason'] = ''; + $GLOBALS['_xh']['method'] = false; + $GLOBALS['_xh']['rt'] = ''; + /// @todo 'guestimate' encoding + $parser = xml_parser_create(); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); + // What if internal encoding is not in one of the 3 allowed? + // we use the broadest one, ie. utf8! + if (!in_array($GLOBALS['xmlrpc_internalencoding'], array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) + { + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); + } + else + { + xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']); + } + xml_set_element_handler($parser, 'xmlrpc_se_any', 'xmlrpc_ee'); + xml_set_character_data_handler($parser, 'xmlrpc_cd'); + xml_set_default_handler($parser, 'xmlrpc_dh'); + if(!xml_parse($parser, $xml_val, 1)) + { + $errstr = sprintf('XML error: %s at line %d, column %d', + xml_error_string(xml_get_error_code($parser)), + xml_get_current_line_number($parser), xml_get_current_column_number($parser)); + error_log($errstr); + xml_parser_free($parser); + return false; + } + xml_parser_free($parser); + if ($GLOBALS['_xh']['isf'] > 1) // test that $GLOBALS['_xh']['value'] is an obj, too??? + { + error_log($GLOBALS['_xh']['isf_reason']); + return false; + } + switch ($GLOBALS['_xh']['rt']) + { + case 'methodresponse': + $v =& $GLOBALS['_xh']['value']; + if ($GLOBALS['_xh']['isf'] == 1) + { + $vc = $v->structmem('faultCode'); + $vs = $v->structmem('faultString'); + $r = new xmlrpcresp(0, $vc->scalarval(), $vs->scalarval()); + } + else + { + $r = new xmlrpcresp($v); + } + return $r; + case 'methodcall': + $m = new xmlrpcmsg($GLOBALS['_xh']['method']); + for($i=0; $i < count($GLOBALS['_xh']['params']); $i++) + { + $m->addParam($GLOBALS['_xh']['params'][$i]); + } + return $m; + case 'value': + return $GLOBALS['_xh']['value']; + default: + return false; + } + } + + /** + * decode a string that is encoded w/ "chunked" transfer encoding + * as defined in rfc2068 par. 19.4.6 + * code shamelessly stolen from nusoap library by Dietrich Ayala + * + * @param string $buffer the string to be decoded + * @return string + */ + function decode_chunked($buffer) + { + // length := 0 + $length = 0; + $new = ''; + + // read chunk-size, chunk-extension (if any) and crlf + // get the position of the linebreak + $chunkend = strpos($buffer,"\r\n") + 2; + $temp = substr($buffer,0,$chunkend); + $chunk_size = hexdec( trim($temp) ); + $chunkstart = $chunkend; + while($chunk_size > 0) + { + $chunkend = strpos($buffer, "\r\n", $chunkstart + $chunk_size); + + // just in case we got a broken connection + if($chunkend == false) + { + $chunk = substr($buffer,$chunkstart); + // append chunk-data to entity-body + $new .= $chunk; + $length += strlen($chunk); + break; + } + + // read chunk-data and crlf + $chunk = substr($buffer,$chunkstart,$chunkend-$chunkstart); + // append chunk-data to entity-body + $new .= $chunk; + // length := length + chunk-size + $length += strlen($chunk); + // read chunk-size and crlf + $chunkstart = $chunkend + 2; + + $chunkend = strpos($buffer,"\r\n",$chunkstart)+2; + if($chunkend == false) + { + break; //just in case we got a broken connection + } + $temp = substr($buffer,$chunkstart,$chunkend-$chunkstart); + $chunk_size = hexdec( trim($temp) ); + $chunkstart = $chunkend; + } + return $new; + } + + /** + * xml charset encoding guessing helper function. + * Tries to determine the charset encoding of an XML chunk received over HTTP. + * NB: according to the spec (RFC 3023), if text/xml content-type is received over HTTP without a content-type, + * we SHOULD assume it is strictly US-ASCII. But we try to be more tolerant of unconforming (legacy?) clients/servers, + * which will be most probably using UTF-8 anyway... + * + * @param string $httpheaders the http Content-type header + * @param string $xmlchunk xml content buffer + * @param string $encoding_prefs comma separated list of character encodings to be used as default (when mb extension is enabled) + * + * @todo explore usage of mb_http_input(): does it detect http headers + post data? if so, use it instead of hand-detection!!! + */ + function guess_encoding($httpheader='', $xmlchunk='', $encoding_prefs=null) + { + // discussion: see http://www.yale.edu/pclt/encoding/ + // 1 - test if encoding is specified in HTTP HEADERS + + //Details: + // LWS: (\13\10)?( |\t)+ + // token: (any char but excluded stuff)+ + // quoted string: " (any char but double quotes and cointrol chars)* " + // header: Content-type = ...; charset=value(; ...)* + // where value is of type token, no LWS allowed between 'charset' and value + // Note: we do not check for invalid chars in VALUE: + // this had better be done using pure ereg as below + // Note 2: we might be removing whitespace/tabs that ought to be left in if + // the received charset is a quoted string. But nobody uses such charset names... + + /// @todo this test will pass if ANY header has charset specification, not only Content-Type. Fix it? + $matches = array(); + if(preg_match('/;\s*charset\s*=([^;]+)/i', $httpheader, $matches)) + { + return strtoupper(trim($matches[1], " \t\"")); + } + + // 2 - scan the first bytes of the data for a UTF-16 (or other) BOM pattern + // (source: http://www.w3.org/TR/2000/REC-xml-20001006) + // NOTE: actually, according to the spec, even if we find the BOM and determine + // an encoding, we should check if there is an encoding specified + // in the xml declaration, and verify if they match. + /// @todo implement check as described above? + /// @todo implement check for first bytes of string even without a BOM? (It sure looks harder than for cases WITH a BOM) + if(preg_match('/^(\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\x00\x00\xFF\xFE|\xFE\xFF\x00\x00)/', $xmlchunk)) + { + return 'UCS-4'; + } + elseif(preg_match('/^(\xFE\xFF|\xFF\xFE)/', $xmlchunk)) + { + return 'UTF-16'; + } + elseif(preg_match('/^(\xEF\xBB\xBF)/', $xmlchunk)) + { + return 'UTF-8'; + } + + // 3 - test if encoding is specified in the xml declaration + // Details: + // SPACE: (#x20 | #x9 | #xD | #xA)+ === [ \x9\xD\xA]+ + // EQ: SPACE?=SPACE? === [ \x9\xD\xA]*=[ \x9\xD\xA]* + if (preg_match('/^<\?xml\s+version\s*=\s*'. "((?:\"[a-zA-Z0-9_.:-]+\")|(?:'[a-zA-Z0-9_.:-]+'))". + '\s+encoding\s*=\s*' . "((?:\"[A-Za-z][A-Za-z0-9._-]*\")|(?:'[A-Za-z][A-Za-z0-9._-]*'))/", + $xmlchunk, $matches)) + { + return strtoupper(substr($matches[2], 1, -1)); + } + + // 4 - if mbstring is available, let it do the guesswork + // NB: we favour finding an encoding that is compatible with what we can process + if(extension_loaded('mbstring')) + { + if($encoding_prefs) + { + $enc = mb_detect_encoding($xmlchunk, $encoding_prefs); + } + else + { + $enc = mb_detect_encoding($xmlchunk); + } + // NB: mb_detect likes to call it ascii, xml parser likes to call it US_ASCII... + // IANA also likes better US-ASCII, so go with it + if($enc == 'ASCII') + { + $enc = 'US-'.$enc; + } + return $enc; + } + else + { + // no encoding specified: as per HTTP1.1 assume it is iso-8859-1? + // Both RFC 2616 (HTTP 1.1) and 1945 (HTTP 1.0) clearly state that for text/xxx content types + // this should be the standard. And we should be getting text/xml as request and response. + // BUT we have to be backward compatible with the lib, which always used UTF-8 as default... + return $GLOBALS['xmlrpc_defencoding']; + } + } + + /** + * Checks if a given charset encoding is present in a list of encodings or + * if it is a valid subset of any encoding in the list + * @param string $encoding charset to be tested + * @param mixed $validlist comma separated list of valid charsets (or array of charsets) + */ + function is_valid_charset($encoding, $validlist) + { + $charset_supersets = array( + 'US-ASCII' => array ('ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4', + 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', + 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-11', 'ISO-8859-12', + 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'UTF-8', + 'EUC-JP', 'EUC-', 'EUC-KR', 'EUC-CN') + ); + if (is_string($validlist)) + $validlist = explode(',', $validlist); + if (@in_array(strtoupper($encoding), $validlist)) + return true; + else + { + if (array_key_exists($encoding, $charset_supersets)) + foreach ($validlist as $allowed) + if (in_array($allowed, $charset_supersets[$encoding])) + return true; + return false; + } + } + +?> \ No newline at end of file diff --git a/modules/cloudcache/loading.gif b/modules/cloudcache/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..705a72b6f0b2d7d8e5762b2397db2346fac26106 GIT binary patch literal 16810 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

2#{GEe3>KVVsH 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 CS7jLh literal 0 HcmV?d00001 diff --git a/modules/cloudcache/logo.gif b/modules/cloudcache/logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..fca19d9f16993468b15dbeecc976dbf8dce4cad4 GIT binary patch literal 845 zcmV-T1G4;yP)7Kc zX0n-aoVm`-o%@>pi-gg(13&k`f6jNl$A6hzE{FeFTz52yo^TSY+zu!P@GK@{W*X28KRVD+4d>13v}d-KW;KT$n7Fb)*BfIJD{iJ*hZ zB?3hrTzo$P)iiOnw)DbuY@vrNlbDu~$%C6b&bvm|!08X)AzQ?3_b0Dyiex#na4RWi1Pke0vNF$y| zksl7YoDxb%-jTIc6fKXwc>F#SjLNt?G}Cdsdf8EdnlVi$bjCu5om_78yKwOF2&OVB zUhZ3uQA#I6y{@Ub;LZwf6BCRi&^0}e;h5a8yUbf3aPxMG&*WJcvuRZwji(Mbl^1OE z2^@x!ndGRPZSVb=9ISE+{d-n;!C8Z1QZ#M{_qN*We%t7mFsqwz5*wAWzWC|KgJD_k zztdP*_bMhotS|Q86e1}Yl!!?lv45we=mrw1js+^~D^u0wi?Oh#Z`t*HEV|m~?BC`W zajdQg1)>94Gq*I#|1N7_1oGh4y5*p;jlPJa_Lt;2XDTHgqQqhFR|+~YVE*@a{w=@& XA5u~rsatXe00000NkvXXu0mjfqFRb8 literal 0 HcmV?d00001 diff --git a/modules/cloudcache/override/classes/Tools.php b/modules/cloudcache/override/classes/Tools.php new file mode 100644 index 000000000..45efc3e21 --- /dev/null +++ b/modules/cloudcache/override/classes/Tools.php @@ -0,0 +1,180 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 12823 $ +* @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('CloudCache') && file_exists(dirname(__FILE__).'/../../modules/cloudcache/cloudcache.php')) + require(dirname(__FILE__).'/../../modules/cloudcache/cloudcache.php'); + +class Tools extends ToolsCore +{ + /** @var _totalServerCount Total count of all servers */ + private static $_totalServerCount = 0; + /** @var _servers Array containing all the media servers by type */ + private static $_servers = null; + /** @var _serversCount Array countaining the count of servers by type */ + private static $_serversCount = null; + /** @var _fileTypes Available file types */ + private static $_fileTypes = null; + /** @var _activatedModule Flag weither or not the module is active */ + private static $_activatedModule = false; + + public static $id_group_shop = 1; + public static $id_shop = 1; + + /** + * @brief Init the statics needed by getMediaServer + */ + private static function _initServers() + { + require_once(dirname(__FILE__).'/../../modules/cloudcache/backward_compatibility/backward.php'); + + $context = Context::getContext(); + self::$id_shop = $context->shop->id; + self::$id_group_shop = $context->shop->id_group_shop; + + // Init the statics + self::$_servers = array(); + self::$_serversCount = array(); + self::$_fileTypes = array(CLOUDCACHE_FILE_TYPE_IMG, CLOUDCACHE_FILE_TYPE_JS, + CLOUDCACHE_FILE_TYPE_CSS, CLOUDCACHE_FILE_TYPE_OTHER, + CLOUDCACHE_FILE_TYPE_ALL); + + // check if the module is active + self::$_activatedModule = Configuration::get('CLOUDCACHE_API_ACTIVE'); + + foreach (self::$_fileTypes as $type) + { + self::$_servers[$type] = array(); + self::$_serversCount[$type] = 0; + } + + $d = Db::getInstance()->executeS('SELECT `cdn_url`, `file_type` + FROM `'._DB_PREFIX_.'cloudcache_zone` + WHERE `file_type` != \''.CLOUDCACHE_FILE_TYPE_UNASSOCIATED.'\' AND `id_shop` = '.(int)self::$id_shop); + + $allOnly = false; + foreach ($d as $line) + if ($line['file_type'] == CLOUDCACHE_FILE_TYPE_ALL) + { + self::$_servers[CLOUDCACHE_FILE_TYPE_ALL][] = pSQL($line['cdn_url']); + self::$_serversCount[CLOUDCACHE_FILE_TYPE_ALL]++; + self::$_totalServerCount++; + $allOnly = true; + } + + foreach ($d as $line) + if ($line['file_type'] && !$allOnly) + { + self::$_servers[$line['file_type']][] = $line['cdn_url']; + self::$_serversCount[$line['file_type']]++; + self::$_totalServerCount++; + } + } + + public static function addJS($js_uri) + { + parent::addJS($js_uri); + global $js_files; + + foreach ($js_files as &$file) + if (!preg_match('/^http(s?):\/\//i', $file)) + $file = 'http://'.self::getMediaServer($file).$file; + } + + public static function addCSS($css_uri, $css_media_type = 'all') + { + parent::addCSS($css_uri, $css_media_type); + global $css_files; + + $new = array(); + foreach ($css_files as $key => $file) + { + if (!preg_match('/^http(s?):\/\//i', $key)) + $key = 'http://'.self::getMediaServer($key).$key; + $new[$key] = $file; + } + $css_files = $new; + } + + /** + * @brief Retrieve the media server to use + * + * @param filename Name of the file to serve (acually, part of the path) + * + * @todo Check performences + * + * @return URL of the server to use. + */ + public static function getMediaServer($filename) + { + // Override default behavior only if module is active + if (!class_exists('CloudCache')) + include(dirname(__FILE__).'/../../modules/cloudcache/cloudcache.php'); + + $module = new CloudCache(); + + if (!$module->active) + return parent::getMediaServer($filename); + + // Init the server list if needed + if (!self::$_servers) + self::_initServers(); + + if (!self::$_activatedModule) + return parent::getMediaServer($filename); + + // If there is a least one ALL server, then use one of them + if (self::$_serversCount[CLOUDCACHE_FILE_TYPE_ALL]) + // Return one of those server + return (self::$_servers[CLOUDCACHE_FILE_TYPE_ALL][(abs(crc32($filename)) % + self::$_serversCount[CLOUDCACHE_FILE_TYPE_ALL])]); + + + // If there is servers, then use them + if (self::$_totalServerCount) + { + // Loop on the file types to find the current one + foreach (self::$_fileTypes as $type) + // If we find the type in the filename, then it is our + if (strstr($filename, $type) && self::$_serversCount[$type]) + { + // Return one of those server + return (self::$_servers[$type][(abs(crc32($filename)) % + self::$_serversCount[$type])]); + } + + // If no file type found, then it is 'other' + // If there is server setted for the 'other' type, use it + if (self::$_serversCount[CLOUDCACHE_FILE_TYPE_OTHER]) + // Return one of the server setted up + return (self::$_servers[$type][(abs(crc32($filename)) % + self::$_serversCount[$type])]); + } + + // If there is no server setted up, then use the parent method + return parent::getMediaServer($filename); + } +} \ No newline at end of file diff --git a/modules/cloudcache/override/classes/controller/FrontController.php b/modules/cloudcache/override/classes/controller/FrontController.php new file mode 100644 index 000000000..bda6e18fc --- /dev/null +++ b/modules/cloudcache/override/classes/controller/FrontController.php @@ -0,0 +1,55 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 1.4 $ +* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +class FrontController extends FrontControllerCore +{ + public function addCSS($css_uri, $css_media_type = 'all') + { + if (!is_array($css_uri)) + $css_uri = array($css_uri); + + $new_uri = array(); + foreach ($css_uri as $uri) + if ($uri && !preg_match('/^http(s?):\/\//', $uri) && preg_match('#.css$#', $uri)) + $new_uri[] = 'http://'.Tools::getMediaServer($uri).$uri; + else + $new_uri[] = $uri; + + return parent::addCSS($new_uri, $css_media_type); + } + + public function addJS($js_uri) + { + if (!is_array($js_uri)) + $js_uri = array($js_uri); + + foreach ($js_uri as &$uri) + if ($uri && !preg_match('/^http(s?):\/\//', $uri)) + $uri = 'http://'.Tools::getMediaServer($uri).$uri; + return parent::addJS($js_uri); + } +} \ No newline at end of file diff --git a/modules/cloudcache/override/classes/controller/index.php b/modules/cloudcache/override/classes/controller/index.php new file mode 100644 index 000000000..a85fb997d --- /dev/null +++ b/modules/cloudcache/override/classes/controller/index.php @@ -0,0 +1,36 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 14011 $ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); +header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); + +header("Cache-Control: no-store, no-cache, must-revalidate"); +header("Cache-Control: post-check=0, pre-check=0", false); +header("Pragma: no-cache"); + +header("Location: ../"); +exit; \ No newline at end of file diff --git a/modules/cloudcache/override/classes/index.php b/modules/cloudcache/override/classes/index.php new file mode 100644 index 000000000..a85fb997d --- /dev/null +++ b/modules/cloudcache/override/classes/index.php @@ -0,0 +1,36 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 14011 $ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); +header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); + +header("Cache-Control: no-store, no-cache, must-revalidate"); +header("Cache-Control: post-check=0, pre-check=0", false); +header("Pragma: no-cache"); + +header("Location: ../"); +exit; \ No newline at end of file diff --git a/modules/cloudcache/override/index.php b/modules/cloudcache/override/index.php new file mode 100644 index 000000000..a85fb997d --- /dev/null +++ b/modules/cloudcache/override/index.php @@ -0,0 +1,36 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 14011 $ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); +header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); + +header("Cache-Control: no-store, no-cache, must-revalidate"); +header("Cache-Control: post-check=0, pre-check=0", false); +header("Pragma: no-cache"); + +header("Location: ../"); +exit; \ No newline at end of file diff --git a/modules/cloudcache/script.js b/modules/cloudcache/script.js new file mode 100644 index 000000000..eeafab90d --- /dev/null +++ b/modules/cloudcache/script.js @@ -0,0 +1,94 @@ +$(function(){ + $('.tabbed-form.content li').not('#tab1_content').hide(); + $('.tabbed-form.menu li').click(function(){ + $('.tabbed-form.menu li').removeClass('active'); + $(this).addClass('active'); + $('.tabbed-form.content li').hide(); + $('.tabbed-form.content li:eq(' + $(this).index() + ')').show(); + }); + $('.tabbed-form.menu li:eq(0)').addClass('active'); + $('.tabbed-form.content li:eq(0)').show(); + + /* Make table first column bolded (on table with 2 cols) */ + $('.bold-first-column td:even').addClass('bold'); + + /* Make sure all .tree.closed have (+) inside, and the content is not shown */ + $('span.tree-button').live('click', function(){ + $(this).parent().find('.details').toggle("fast"); + $(this).toggleClass('opened'); + + if ($(this).hasClass('opened')) + $(this).html('–'); + else + $(this).html('+'); + }); + + /* jExcerpt v1.1.1 */ + length = 20; + jExcerptClass = '.jexcerpt-short'; + $(jExcerptClass).each(function(){ + if ($(this).text().length > length) + { + // Create the .jexcerpt-long + $('

' + $(this).text() + '
').appendTo($(this).parent()); + + excerpt = $(this).text().substring(0, length); + $(this).text(excerpt + '...'); + } + }); + + $(jExcerptClass).mouseover(function(){ + $('.jexcerpt-long').hide(); + $(this).parent().attr('width', $(this).parent().width()); + $(this).parent().find('.jexcerpt-long') + .css('left', $(this).parent().offset().left + 4 + 'px') + .css('top', $(this).parent().offset().top + 9 + 'px') + .show(); + }); + + + $('.jexcerpt-long').live('mouseout', function(){ + $(this).parent().find(jExcerptClass).show(); + $(this).hide(); + }); + + /* Add span.tree for those .tree who have .details */ + $('+').prependTo($('.tree').has('.details')); + + if ($('#cloudcache_edit_zone_form').size()) + window.scroll(0, $('#cloudcache_edit_zone_form').offset().top-100); + + /* CRUD Zones*/ + $('#cloudcache_add_zone_form').hide(); + $('#cloudcache_add_zone').live('click', function(event){ + if ($('.tabbed-form.menu li.active').size()) + { + indexActiveTab = $('.tabbed-form.menu li.active').index(); + $('#cloudcache_add_zone_form:eq(' + indexActiveTab + ') #type').attr('value', 'pullzone'); + $('#cloudcache_add_zone_form:eq(' + indexActiveTab + ')').toggle('fast'); + } else { + $('#cloudcache_add_zone_form').toggle(); + } + + event.preventDefault(); + }); + $('.SubmitCloudcacheEditZone').click(function(event){ + $('.CloudcacheZone_action').val('edit'); + $(this).parent().submit(); + }); + $('.SubmitCloudcacheClearZoneCache').click(function(event){ + $('.CloudcacheZone_action').val('clear_zone_cache'); + $(this).parent().submit(); + }); + + /* Validate the required fields */ + $('#SubmitCloudcacheAdd_zone').click(function(){ + if ($('#name').val().length < 1 || $('#origin').val().length < 1) + { + alert($('#requiredFieldsTranslation').text()); + return false; + } + else + $(this).parent('form').submit(); + }); +}); diff --git a/modules/cloudcache/sql/index.php b/modules/cloudcache/sql/index.php new file mode 100644 index 000000000..a85fb997d --- /dev/null +++ b/modules/cloudcache/sql/index.php @@ -0,0 +1,36 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 14011 $ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); +header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); + +header("Cache-Control: no-store, no-cache, must-revalidate"); +header("Cache-Control: post-check=0, pre-check=0", false); +header("Pragma: no-cache"); + +header("Location: ../"); +exit; \ No newline at end of file diff --git a/modules/cloudcache/sql/sql-install.php b/modules/cloudcache/sql/sql-install.php new file mode 100644 index 000000000..27b6eb5ce --- /dev/null +++ b/modules/cloudcache/sql/sql-install.php @@ -0,0 +1,21 @@ + + $(function(){ +{/literal} + mediaServerForm = $('input[name="submitMediaServers"]').parent().parent(); + ccc = $('input[name="submitCCC"]').parent().parent(); +{if $isModuleActive} + legend = $(mediaServerForm).find('legend').html(); + $(mediaServerForm).empty(); + $('' + legend + '' + + '
' + + '{l s='You are currently saving even more performance by' mod='cloudcache'}' + + ' ' + + '{l s='using CloudCache module' mod='cloudcache'}, ' + + '{l s='the Best CDN service recommended by PrestaShop users!' mod='cloudcache'} ' + + '
').prependTo(mediaServerForm); +{else} + $('
' + '{l s='Save even more performance by' mod='cloudcache'}' + '{l s='activating CloudCache module' mod='cloudcache'}, {l s='the Best CDN service recommended by PrestaShop users!' mod='cloudcache'}
').prependTo(mediaServerForm); +{/if} + +{if $isModuleActive} + {if isset($compatibilityIssues)} + $('
' + + {foreach from=$compatibilityIssues key=i item=message} + '{if $i > 0}{$i} - {/if}{$message}' + + {/foreach} + '
').prependTo(ccc); + {else} + $('
{l s='Everything looks good for CloudCache' mod='cloudcache'}
').prependTo(ccc); + {/if} +{/if} + +{literal} +}); +{/literal} diff --git a/modules/cloudcache/views/content2.tpl b/modules/cloudcache/views/content2.tpl new file mode 100644 index 000000000..20c994a0e --- /dev/null +++ b/modules/cloudcache/views/content2.tpl @@ -0,0 +1,360 @@ + + + +
+
+

{l s='Opening your CloudCache.com Account' mod='cloudcache'}

+

{l s='CloudCache provides Prestashop users an exclusive discount of 25% per month on every available package. Please click the logo below to sign up: ' mod='cloudcache'}


+ cloudcache +
+
+
+
+ cloudcache +
+
+ {l s='This module allows you to acclerate your PrestaShop via the CloudCache Content Delivery Network.' mod='cloudcache'}

+ {l s='A content Delivery Network (CDN) is for every site owner who demands a high performance, primo visitor experience.' mod='cloudcache'} +
+
+
+
+{if isset($confirmMessage)} +
+ + {$confirmMessage['text']} +
+{/if} +{if isset($compatibilityIssues)} +
+ + + {l s='CloudCache Compatibilty Check' mod='cloudcache'} + +

+

+ + {foreach from=$compatibilityIssues key=i item=message} + {if $i > 0}{$i|strip_tags} - {/if}{$message|strip_tags}
+ {/foreach} +
+

+

+{else} + +
+ + + + {if isset($couponUrl) && isset($apiActive) && $apiActive == 1} + + {/if} +
+{/if} +
+
+ {l s='Cloudcache and PrestaShop' mod='cloudcache'} +

{l s='Learn more about Cloudcache at PrestaShop.com' mod='cloudcache'}

+

+
+ + diff --git a/modules/cloudcache/views/index.php b/modules/cloudcache/views/index.php new file mode 100644 index 000000000..a85fb997d --- /dev/null +++ b/modules/cloudcache/views/index.php @@ -0,0 +1,36 @@ + +* @copyright 2007-2012 PrestaShop SA +* @version Release: $Revision: 14011 $ +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ + +header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); +header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); + +header("Cache-Control: no-store, no-cache, must-revalidate"); +header("Cache-Control: post-check=0, pre-check=0", false); +header("Pragma: no-cache"); + +header("Location: ../"); +exit; \ No newline at end of file