Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F800476
in-portal
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Sat, Feb 22, 12:07 AM
Size
56 KB
Mime Type
text/x-diff
Expires
Mon, Feb 24, 12:07 AM (2 h, 12 m)
Engine
blob
Format
Raw Data
Handle
573728
Attached To
rINP In-Portal
in-portal
View Options
Index: branches/5.2.x/core/kernel/utility/cache.php
===================================================================
--- branches/5.2.x/core/kernel/utility/cache.php (revision 16752)
+++ branches/5.2.x/core/kernel/utility/cache.php (revision 16753)
@@ -1,977 +1,1087 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
/**
* Manager of all implemented caching handlers
*
*/
class kCache extends kBase {
/**
* Rebuild cache now
*
*/
const REBUILD_NOW = 1;
/**
* Rebuild cache later
*
*/
const REBUILD_LATER = 2;
/**
* Cache waiting step (in seconds)
*
*/
const WAIT_STEP = 2;
/**
* Object of cache handler
*
* @var FakeCacheHandler
*/
var $_handler = null;
/**
* Part of what we retrieve will be stored locally (per script run) not to bother memcache a lot
*
* @var Array
*/
var $_localStorage = Array ();
/**
* What type of caching is being used
*
* @var int
*/
var $cachingType = CACHING_TYPE_NONE;
/**
* Debug cache usage
*
* @var bool
*/
var $debugCache = false;
/**
* Displays cache usage statistics
*
* @var bool
* @access protected
*/
protected $_storeStatistics = false;
/**
* Site key name
* Prepended to each cached key name
*
* @var string
*/
var $siteKeyName = '';
/**
* Site key value
* Prepended to each cached key name
*
* @var string
*/
var $siteKeyValue = null;
/**
* Creates cache manager
*
* @access public
*/
public function __construct()
{
parent::__construct();
$this->siteKeyName = 'site_serial:' . crc32(SQL_TYPE . '://' . SQL_USER . ':' . SQL_PASS . '@' . SQL_SERVER . ':' . TABLE_PREFIX . ':' . SQL_DB);
// get cache handler class to use
$handler_class = kUtil::getSystemConfig()->get('CacheHandler', '') . 'CacheHandler';
// defined cache handler doesn't exist -> use default
if ( !class_exists($handler_class) ) {
$handler_class = 'FakeCacheHandler';
}
$handler = new $handler_class($this);
if ( !$handler->isWorking() ) {
// defined cache handler is not working -> use default
trigger_error('Failed to initialize "<strong>' . $handler_class . '</strong>" caching handler.', E_USER_WARNING);
$handler = new FakeCacheHandler($this);
}
elseif ( $this->Application->isDebugMode() && ($handler->getCachingType() == CACHING_TYPE_MEMORY) ) {
$this->Application->Debugger->appendHTML('Memory Caching: "<strong>' . $handler_class . '</strong>"');
}
$this->_handler = $handler;
$this->cachingType = $handler->getCachingType();
$this->debugCache = $handler->getCachingType() == CACHING_TYPE_MEMORY && $this->Application->isDebugMode();
$this->_storeStatistics = defined('DBG_CACHE') && DBG_CACHE;
if ( $this->_storeStatistics ) {
// don't use FileHelper, since kFactory isn't ready yet
if ( !file_exists(RESTRICTED . DIRECTORY_SEPARATOR . 'cache_usage') ) {
mkdir(RESTRICTED . DIRECTORY_SEPARATOR . 'cache_usage');
}
}
}
/**
* Returns caching type of current storage engine
*
* @return int
*/
function getCachingType()
{
return $this->cachingType;
}
/**
* Stores value to cache
*
* @param string $name
* @param mixed $value
* @param int $expiration cache record expiration time in seconds
* @return bool
*/
function setCache($name, $value, $expiration)
{
// 1. stores current version of serial for given cache key
$this->_setCache($name . '_serials', $this->replaceSerials($name), $expiration);
$this->storeStatistics($name, 'W');
// 2. don't replace serials within the key
$saved = $this->_setCache($name, $value, $expiration);
$this->storeStatistics($name, 'U');
// 3. remove rebuilding mark
$this->delete($name . '_rebuilding');
return $saved;
}
/**
* Stores value to cache
*
* @param string $name
* @param mixed $value
* @param int $expiration cache record expiration time in seconds
* @return bool
*/
function _setCache($name, $value, $expiration)
{
$prepared_name = $this->prepareKeyName($name);
$this->_localStorage[$prepared_name] = $value;
return $this->_handler->set($prepared_name, $value, $expiration);
}
/**
* Stores value to cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration cache record expiration time in seconds
* @return bool
*/
function addCache($name, $value, $expiration)
{
// 1. stores current version of serial for given cache key
$this->_setCache($name . '_serials', $this->replaceSerials($name), $expiration);
// 2. remove rebuilding mark
$this->delete($name . '_rebuilding');
// 3. don't replace serials within the key
return $this->_addCache($name, $value, $expiration);
}
/**
* Stores value to cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration cache record expiration time in seconds
* @return bool
*/
function _addCache($name, $value, $expiration)
{
$prepared_name = $this->prepareKeyName($name);
$added = $this->_handler->add($prepared_name, $value, $expiration);
if ( $added ) {
$this->_localStorage[$prepared_name] = $value;
}
return $added;
}
/**
* Sets rebuilding mode for given cache
*
* @param string $name
* @param int $mode
* @param int $max_rebuilding_time
* @param string $miss_type
* @return bool
*/
function rebuildCache($name, $mode = null, $max_rebuilding_time = 0, $miss_type = 'M')
{
if ( !isset($mode) || $mode == self::REBUILD_NOW ) {
$this->storeStatistics($name, $miss_type);
if ( !$max_rebuilding_time ) {
return true;
}
// prevent parallel rebuild attempt by using "add" instead of "set" method
if ( !$this->_addCache($name . '_rebuilding', 1, $max_rebuilding_time) ) {
$this->storeStatistics($name, 'l');
return false;
}
$this->storeStatistics($name, 'L');
$this->delete($name . '_rebuild');
}
elseif ( $mode == self::REBUILD_LATER ) {
$this->_setCache($name . '_rebuild', 1, 0);
$this->delete($name . '_rebuilding');
}
return true;
}
/**
* Returns value from cache
*
* @param string $name
* @param bool $store_locally store data locally after retrieved
* @param int $max_rebuild_seconds
* @return mixed
*/
function getCache($name, $store_locally = true, $max_rebuild_seconds = 0)
{
$cached_data = $this->_getCache(Array ($name . '_rebuild', $name . '_serials'), Array (true, true));
if ( $cached_data[$name . '_rebuild'] ) {
// cache rebuild requested -> rebuild now
$this->delete($name . '_rebuild');
if ( $this->rebuildCache($name, self::REBUILD_NOW, $max_rebuild_seconds, '[M1]') ) {
return false;
}
}
// There are 2 key types:
// - with serials, e.g. with_serial_key[%LangSerial%]
// - without serials, e.g. without_serial
// Evaluated serials of each cache key are stored in '{$name}_serials' cache key.
// If cache is present, but serial is outdated, then cache value is assumed to be outdated.
$new_serial = $this->replaceSerials($name);
$old_serial = $cached_data[$name . '_serials'];
if ( $name == $new_serial || $new_serial != $old_serial ) {
// no serials in cache key OR cache is outdated
$wait_seconds = $max_rebuild_seconds;
while (true) {
$cached_data = $this->_getCache(Array ($name, $name . '_rebuilding'), Array ($store_locally, false));
$cache = $cached_data[$name];
$rebuilding = $cached_data[$name . '_rebuilding'];
if ( ($cache === false) && (!$rebuilding || $wait_seconds == 0) ) {
// cache missing and nobody rebuilding it -> rebuild; enough waiting for cache to be ready
$this->rebuildCache($name, self::REBUILD_NOW, $max_rebuild_seconds, '[M2' . ($rebuilding ? 'R' : '!R') . ',WS=' . $wait_seconds . ']');
return false;
}
elseif ( $cache !== false ) {
// re-read serial, since it might have been changed in parallel process !!!
$old_serial = $this->_getCache($name . '_serials', false);
// cache present (if other user is rebuilding it, then it's outdated cache) -> return it
if ( $rebuilding || $new_serial == $old_serial ) {
$this->storeStatistics($name, $rebuilding ? 'h' : 'H');
return $cache;
}
$this->rebuildCache($name, self::REBUILD_NOW, $max_rebuild_seconds, '[M3' . ($rebuilding ? 'R' : '!R') . ',WS=' . $wait_seconds . ']');
return false;
}
$wait_seconds -= self::WAIT_STEP;
sleep(self::WAIT_STEP);
}
}
$cache = $this->_getCache($name, $store_locally);
if ( $cache === false ) {
$this->rebuildCache($name, self::REBUILD_NOW, $max_rebuild_seconds, '[M4]');
}
else {
$this->storeStatistics($name, 'H');
}
return $cache;
}
/**
* Returns cached value from local cache
*
* @param string $prepared_name Prepared key name from kCache::prepareKeyName() function
* @return mixed
* @see prepareKeyName
* @access public
*/
public function getFromLocalStorage($prepared_name)
{
return array_key_exists($prepared_name, $this->_localStorage) ? $this->_localStorage[$prepared_name] : false;
}
/**
* Returns value from cache
*
* @param string|Array $names
* @param bool|Array $store_locally store data locally after retrieved
* @return mixed
*/
function _getCache($names, $store_locally = true)
{
static $request_number = 1;
$res = Array ();
$names = (array)$names;
$store_locally = (array)$store_locally;
$to_get = $prepared_names = array_map(Array (&$this, 'prepareKeyName'), $names);
foreach ($prepared_names as $index => $prepared_name) {
$name = $names[$index];
if ( $store_locally[$index] && array_key_exists($prepared_name, $this->_localStorage) ) {
$res[$name] = $this->_localStorage[$prepared_name];
unset($to_get[$index]);
}
}
if ( $to_get ) {
$multi_res = $this->_handler->get($to_get);
foreach ($to_get as $index => $prepared_name) {
$name = $names[$index];
if ( array_key_exists($prepared_name, $multi_res) ) {
$res[$name] =& $multi_res[$prepared_name];
}
else {
$res[$name] = false;
}
$this->_postProcessGetCache($prepared_name, $res[$name], $store_locally[$index], $request_number);
}
$request_number++;
}
return count($res) == 1 ? array_pop($res) : $res;
}
/**
* Stores variable in local cache & collects debug info about cache
*
* @param string $name
* @param mixed $res
* @param bool $store_locally
* @param int $request_number
* @return void
* @access protected
*/
protected function _postProcessGetCache($name, &$res, $store_locally = true, $request_number)
{
if ( $this->debugCache ) {
// don't display subsequent serial cache retrievals (ones, that are part of keys)
if ( is_array($res) ) {
$this->Application->Debugger->appendHTML('r' . $request_number . ': Restoring key "' . $name . '". Type: ' . gettype($res) . '.');
}
else {
$res_display = strip_tags($res);
if ( strlen($res_display) > 200 ) {
$res_display = substr($res_display, 0, 50) . ' ...';
}
$this->Application->Debugger->appendHTML('r' . $request_number . ': Restoring key "' . $name . '" resulted [' . $res_display . ']');
}
}
if ( $store_locally /*&& ($res !== false)*/ ) {
$this->_localStorage[$name] = $res;
}
}
/**
* Deletes value from cache
*
* @param string $name
* @return mixed
*/
function delete($name)
{
$name = $this->prepareKeyName($name);
unset($this->_localStorage[$name]);
return $this->_handler->delete($name);
}
/**
* Reset's all memory cache at once
*/
function reset()
{
// don't check for enabled, because we maybe need to reset cache anyway
if ($this->cachingType == CACHING_TYPE_TEMPORARY) {
return ;
}
$site_key = $this->_cachePrefix(true);
$this->_handler->set($site_key, $this->_handler->get($site_key) + 1);
}
/**
* Replaces serials and adds unique site prefix to cache variable name
*
* @param string $name
* @return string
*/
protected function prepareKeyName($name)
{
if ( $this->cachingType == CACHING_TYPE_TEMPORARY ) {
return $name;
}
// add site-wide prefix to key
return $this->_cachePrefix() . $name;
}
/**
* Replaces serials within given string
*
* @param string $value
* @return string
* @access protected
*/
protected function replaceSerials($value)
{
if ( preg_match_all('/\[%(.*?)%\]/', $value, $regs) ) {
// [%LangSerial%] - prefix-wide serial in case of any change in "lang" prefix
// [%LangIDSerial:5%] - one id-wide serial in case of data, associated with given id was changed
// [%CiIDSerial:ItemResourceId:5%] - foreign key-based serial in case of data, associated with given foreign key was changed
$serial_names = $regs[1];
$serial_count = count($serial_names);
$store_locally = Array ();
for ($i = 0; $i < $serial_count; $i++) {
$store_locally[] = true;
}
$serial_values = $this->_getCache($serial_names, $store_locally);
if ( !is_array($serial_values) ) {
$serial_values = Array (current($serial_names) => $serial_values);
}
foreach ($serial_names as $serial_name) {
$value = str_replace('[%' . $serial_name . '%]', '[' . $serial_name . '=' . $serial_values[$serial_name] . ']', $value);
}
}
return $value;
}
/**
* Returns site-wide caching prefix
*
* @param bool $only_site_key_name
* @return string
*/
function _cachePrefix($only_site_key_name = false)
{
if ($only_site_key_name) {
return $this->siteKeyName;
}
if ( !isset($this->siteKeyValue) ) {
$this->siteKeyValue = $this->_handler->get($this->siteKeyName);
if (!$this->siteKeyValue) {
$this->siteKeyValue = 1;
$this->_handler->set($this->siteKeyName, $this->siteKeyValue);
}
}
return "{$this->siteKeyName}:{$this->siteKeyValue}:";
}
/**
* Stores statistics about cache usage in a file (one file per cache)
*
* @param string $name
* @param string $action_type {M - miss, L - lock, W - write, U - unlock, H - actual hit, h - outdated hit}
* @return void
* @access public
*/
public function storeStatistics($name, $action_type)
{
if ( !$this->_storeStatistics ) {
return;
}
$name = str_replace(Array ('/', '\\', ':'), '_', $name);
$fp = fopen(RESTRICTED . DIRECTORY_SEPARATOR . 'cache_usage' . DIRECTORY_SEPARATOR . $name, 'a');
fwrite($fp, $action_type);
fclose($fp);
}
}
abstract class kCacheHandler {
/**
* Remembers status of cache handler (working or not)
*
* @var bool
* @access protected
*/
protected $_enabled = false;
/**
* Caching type that caching handler implements
*
* @var int
* @access protected
*/
protected $cachingType;
/**
*
* @var kCache
* @access protected
*/
protected $parent;
public function __construct(kCache $parent)
{
$this->parent = $parent;
}
/**
* Retrieves value from cache
*
* @param string $names
* @return mixed
* @access public
*/
abstract public function get($names);
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
abstract public function set($name, $value, $expiration = 0);
/**
* Stores value in cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
abstract public function add($name, $value, $expiration = 0);
/**
* Deletes key from cach
*
* @param string $name
* @return bool
* @access public
*/
abstract public function delete($name);
/**
* Determines, that cache storage is working fine
*
* @return bool
* @access public
*/
public function isWorking()
{
return $this->_enabled;
}
/**
* Returns caching type of current storage engine
*
* @return int
* @access public
*/
public function getCachingType()
{
return $this->cachingType;
}
}
class FakeCacheHandler extends kCacheHandler {
public function __construct(kCache $parent)
{
parent::__construct($parent);
$this->_enabled = true;
$this->cachingType = CACHING_TYPE_TEMPORARY;
}
/**
* Retrieves value from cache
*
* @param string|Array $names
* @return mixed
* @access public
*/
public function get($names)
{
if ( is_array($names) ) {
$res = Array ();
foreach ($names as $name) {
$res[$name] = $this->parent->getFromLocalStorage($name);
}
return $res;
}
return $this->parent->getFromLocalStorage($names);
}
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function set($name, $value, $expiration = 0)
{
return true;
}
/**
* Stores value in cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function add($name, $value, $expiration = 0)
{
return true;
}
/**
* Deletes key from cach
*
* @param string $name
* @return bool
* @access public
*/
public function delete($name)
{
return true;
}
}
class MemcacheCacheHandler extends kCacheHandler {
/**
* Memcache connection
*
* @var Memcache
* @access protected
*/
protected $_handler = null;
public function __construct(kCache $parent, $default_servers = '')
{
parent::__construct($parent);
$this->cachingType = CACHING_TYPE_MEMORY;
$memcached_servers = kUtil::getSystemConfig()->get('MemcacheServers', $default_servers);
if ( $memcached_servers && class_exists('Memcache') ) {
$this->_enabled = true;
$this->_handler = new Memcache();
$servers = explode(';', $memcached_servers);
foreach ($servers as $server) {
if ( preg_match('/(.*):([\d]+)$/', $server, $regs) ) {
// "hostname:port" OR "unix:///path/to/socket:0"
$server = $regs[1];
$port = $regs[2];
}
else {
$port = 11211;
}
$this->_handler->addServer($server, $port);
}
// verify, that memcache server is working
if ( !$this->_handler->set('test', 1) ) {
$this->_enabled = false;
}
}
}
/**
* Retrieves value from cache
*
* @param string $name
* @return mixed
* @access public
*/
public function get($name)
{
return $this->_handler->get($name);
}
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function set($name, $value, $expiration = 0)
{
// 0 - don't use compression
return $this->_handler->set($name, $value, 0, $expiration);
}
/**
* Stores value in cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function add($name, $value, $expiration = 0)
{
// 0 - don't use compression
return $this->_handler->add($name, $value, 0, $expiration);
}
/**
* Deletes key from cache
*
* @param string $name
* @return bool
* @access public
*/
public function delete($name)
{
return $this->_handler->delete($name, 0);
}
}
+ class MemcachedCacheHandler extends kCacheHandler {
+
+ /**
+ * Memcache connection
+ *
+ * @var Memcached
+ * @access protected
+ */
+ protected $_handler = null;
+
+ /**
+ * MemcachedCacheHandler constructor.
+ *
+ * @param kCache $parent Parent.
+ * @param string $default_servers Default servers.
+ */
+ public function __construct(kCache $parent, $default_servers = '')
+ {
+ parent::__construct($parent);
+
+ $this->cachingType = CACHING_TYPE_MEMORY;
+
+ $memcached_servers = kUtil::getSystemConfig()->get('MemcacheServers', $default_servers);
+
+ if ( $memcached_servers && class_exists('Memcached') ) {
+ $this->_enabled = true;
+ $this->_handler = new Memcached();
+ $servers = explode(';', $memcached_servers);
+
+ foreach ( $servers as $server ) {
+ if ( preg_match('/(.*):([\d]+)$/', $server, $regs) ) {
+ // Possible format: "hostname:port" OR "unix:///path/to/socket:0".
+ $server = $regs[1];
+ $port = $regs[2];
+ }
+ else {
+ $port = 11211;
+ }
+
+ $this->_handler->addServer($server, $port);
+ }
+
+ // Verify, that memcache server is working.
+ if ( !$this->_handler->set('test', 1) ) {
+ $this->_enabled = false;
+ }
+ }
+ }
+
+ /**
+ * Retrieves value from cache
+ *
+ * @param string|array $name Name.
+ *
+ * @return mixed
+ * @access public
+ */
+ public function get($name)
+ {
+ if ( is_array($name) ) {
+ return $this->_handler->getMulti($name);
+ }
+
+ return $this->_handler->get($name);
+ }
+
+ /**
+ * Stores value in cache
+ *
+ * @param string $name Name.
+ * @param mixed $value Value.
+ * @param integer $expiration Expiration.
+ *
+ * @return boolean
+ * @access public
+ */
+ public function set($name, $value, $expiration = 0)
+ {
+ return $this->_handler->set($name, $value, $expiration);
+ }
+
+ /**
+ * Stores value in cache (only if it's not there already)
+ *
+ * @param string $name Name.
+ * @param mixed $value Value.
+ * @param integer $expiration Expiration.
+ *
+ * @return boolean
+ * @access public
+ */
+ public function add($name, $value, $expiration = 0)
+ {
+ return $this->_handler->add($name, $value, $expiration);
+ }
+
+ /**
+ * Deletes key from cache
+ *
+ * @param string $name Name.
+ *
+ * @return boolean
+ * @access public
+ */
+ public function delete($name)
+ {
+ return $this->_handler->delete($name);
+ }
+
+ }
class ApcCacheHandler extends kCacheHandler {
public function __construct(kCache $parent)
{
parent::__construct($parent);
$this->cachingType = CACHING_TYPE_MEMORY;
$this->_enabled = function_exists('apc_fetch');
// verify, that apc is working
if ( $this->_enabled && !$this->set('test', 1) ) {
$this->_enabled = false;
}
}
/**
* Retrieves value from cache
*
* @param string $name
* @return mixed
* @access public
*/
public function get($name)
{
return apc_fetch($name);
}
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function set($name, $value, $expiration = 0)
{
return apc_store($name, $value, $expiration);
}
/**
* Stores value in cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function add($name, $value, $expiration = 0)
{
return apc_add($name, $value, $expiration);
}
/**
* Deletes key from cache
*
* @param string $name
* @return bool
* @access public
*/
public function delete($name)
{
return apc_delete($name);
}
}
class XCacheCacheHandler extends kCacheHandler {
public function __construct(kCache $parent)
{
parent::__construct($parent);
$this->cachingType = CACHING_TYPE_MEMORY;
$this->_enabled = function_exists('xcache_get');
// verify, that xcache is working
if ( $this->_enabled && !$this->set('test', 1) ) {
$this->_enabled = false;
}
}
/**
* Retrieves value from cache
*
* @param string|Array $names
* @return mixed
* @access public
*/
public function get($names)
{
if ( is_array($names) ) {
$res = Array ();
foreach ($names as $name) {
$res[$name] = $this->get($name);
}
return $res;
}
return xcache_isset($names) ? xcache_get($names) : false;
}
/**
* Stores value in cache
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function set($name, $value, $expiration = 0)
{
return xcache_set($name, $value, $expiration);
}
/**
* Stores value in cache (only if it's not there already)
*
* @param string $name
* @param mixed $value
* @param int $expiration
* @return bool
* @access public
*/
public function add($name, $value, $expiration = 0)
{
// not atomic operation, like in Memcached and may fail
if ( xcache_isset($name) ) {
return false;
}
return $this->set($name, $value, $expiration);
}
/**
* Deletes key from cache
*
* @param string $name
* @return bool
* @access public
*/
public function delete($name)
{
return xcache_unset($name);
}
- }
\ No newline at end of file
+ }
Index: branches/5.2.x/core/install/install_toolkit.php
===================================================================
--- branches/5.2.x/core/install/install_toolkit.php (revision 16752)
+++ branches/5.2.x/core/install/install_toolkit.php (revision 16753)
@@ -1,1026 +1,1030 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
/**
* Upgrade sqls are located using this mask
*
*/
define('UPGRADES_FILE', FULL_PATH.'/%sinstall/upgrades.%s');
/**
* Prerequisit check classes are located using this mask
*
*/
define('PREREQUISITE_FILE', FULL_PATH.'/%sinstall/prerequisites.php');
/**
* Format of version identificator in upgrade files (normal, beta, release candidate)
*
*/
define('VERSION_MARK', '# ===== v ([\d]+\.[\d]+\.[\d]+|[\d]+\.[\d]+\.[\d]+-B[\d]+|[\d]+\.[\d]+\.[\d]+-RC[\d]+) =====');
if (!defined('GET_LICENSE_URL')) {
/**
* Url used for retrieving user licenses from Intechnic licensing server
*
*/
define('GET_LICENSE_URL', 'http://www.in-portal.com/license.php');
}
/**
* Misc functions, that are required during installation, when
*
*/
class kInstallToolkit {
/**
* Reference to kApplication class object
*
* @var kApplication
*/
var $Application = null;
/**
* Connection to database
*
* @var IDBConnection
*/
var $Conn = null;
/**
* System config object to access data from config.php
*
* @var Array
*/
public $systemConfig;
/**
* Installator instance
*
* @var kInstallator
*/
var $_installator = null;
/**
* Creates instance of kInstallToolkit class.
*/
public function __construct()
{
$this->systemConfig = new kSystemConfig(true, false);
if ( class_exists('kApplication') ) {
// auto-setup in case of separate module install
$this->Application =& kApplication::Instance();
$this->Application->Init(); // needed for standalone module install
$this->Conn =& $this->Application->GetADODBConnection();
}
}
/**
* Sets installator
*
* @param kInstallator $instance
*/
function setInstallator(&$instance)
{
$this->_installator =& $instance;
}
/**
* Checks prerequisities before module install or upgrade
*
* @param string $module_path
* @param string $versions
* @param string $mode upgrade mode = {install, standalone, upgrade}
* @return bool
*/
function CheckPrerequisites($module_path, $versions, $mode)
{
if ( !$versions ) {
return Array ();
}
/** @var InPortalPrerequisites $prerequisite_object */
$prerequisite_object =& $this->getPrerequisiteObject($module_path);
// some errors possible
return is_object($prerequisite_object) ? $prerequisite_object->CheckPrerequisites($versions, $mode) : Array ();
}
/**
* Call prerequisites method
*
* @param string $module_path
* @param string $method
* @return array
*/
function CallPrerequisitesMethod($module_path, $method)
{
/** @var InPortalPrerequisites $prerequisite_object */
$prerequisite_object =& $this->getPrerequisiteObject($module_path);
return is_object($prerequisite_object) ? $prerequisite_object->$method() : false;
}
/**
* Returns prerequisite object to be used for checks
*
* @param string $module_path
* @return kHelper
* @access protected
*/
protected function &getPrerequisiteObject($module_path)
{
static $prerequisite_classes = Array ();
$prerequisites_file = sprintf(PREREQUISITE_FILE, $module_path);
if ( !file_exists($prerequisites_file) ) {
$false = false;
return $false;
}
if ( !isset($prerequisite_classes[$module_path]) ) {
// save class name, because 2nd time
// (in after call $prerequisite_class variable will not be present)
include_once $prerequisites_file;
$prerequisite_classes[$module_path] = $prerequisite_class;
}
/** @var InPortalPrerequisites $prerequisite_object */
$prerequisite_object = new $prerequisite_classes[$module_path]();
if ( method_exists($prerequisite_object, 'setToolkit') ) {
$prerequisite_object->setToolkit($this);
}
return $prerequisite_object;
}
/**
* Processes one license, received from server
*
* @param string $file_data
*/
function processLicense($file_data)
{
/** @var kModulesHelper $modules_helper */
$modules_helper = $this->Application->recallObject('ModulesHelper');
$file_data = explode('Code==:', $file_data);
$file_data[0] = str_replace('In-Portal License File - do not edit!' . "\n", '', $file_data[0]);
$file_data = array_map('trim', $file_data);
if ($modules_helper->verifyLicense($file_data[0])) {
$this->systemConfig->set('License', 'Intechnic', $file_data[0]);
if (array_key_exists(1, $file_data)) {
$this->systemConfig->set('LicenseCode', 'Intechnic', $file_data[1]);
}
else {
$this->systemConfig->set('LicenseCode', 'Intechnic');
}
$this->systemConfig->save();
}
else {
// invalid license received from licensing server
$this->_installator->errorMessage = 'Invalid License File';
}
}
/**
* Saves given configuration values to database
*
* @param Array $config
*/
function saveConfigValues($config)
{
foreach ($config as $config_var => $value) {
$sql = 'UPDATE ' . TABLE_PREFIX . 'SystemSettings
SET VariableValue = ' . $this->Conn->qstr($value) . '
WHERE VariableName = ' . $this->Conn->qstr($config_var);
$this->Conn->Query($sql);
}
}
/**
* Sets module version to passed
*
* @param string $module_name
* @param string|bool $module_path
* @param string|bool $version
*/
function SetModuleVersion($module_name, $module_path = false, $version = false)
{
if ($version === false) {
if (!$module_path) {
throw new Exception('Module path must be given to "SetModuleVersion" method to auto-detect version');
return ;
}
$version = $this->GetMaxModuleVersion($module_path);
}
// get table prefix from config, because application may not be available here
$table_prefix = $this->systemConfig->get('TablePrefix', 'Database');
if ($module_name == 'kernel') {
$module_name = 'in-portal';
}
// don't use "adodb_mktime" here, because it's not yet included
$sql = 'UPDATE ' . $table_prefix . 'Modules
SET Version = "' . $version . '", BuildDate = ' . time() . '
WHERE LOWER(Name) = "' . strtolower($module_name) . '"';
$this->Conn->Query($sql);
}
/**
* Sets module root category to passed
*
* @param string $module_name
* @param int $category_id
*/
function SetModuleRootCategory($module_name, $category_id = 0)
{
// get table prefix from config, because application may not be available here
$table_prefix = $this->systemConfig->get('TablePrefix', 'Database');
if ($module_name == 'kernel') {
$module_name = 'in-portal';
}
$sql = 'UPDATE ' . $table_prefix . 'Modules
SET RootCat = ' . $category_id . '
WHERE LOWER(Name) = "' . strtolower($module_name) . '"';
$this->Conn->Query($sql);
}
/**
* Returns maximal version of given module by scanning it's upgrade scripts
*
* @param string $module_path
* @return string
*/
function GetMaxModuleVersion($module_path)
{
$module_path = rtrim(mb_strtolower($module_path), '/');
$upgrades_file = sprintf(UPGRADES_FILE, $module_path . '/', 'sql');
if (!file_exists($upgrades_file)) {
// no upgrade file
return '5.0.0';
}
$sqls = file_get_contents($upgrades_file);
$versions_found = preg_match_all('/'.VERSION_MARK.'/s', $sqls, $regs);
if (!$versions_found) {
// upgrades file doesn't contain version definitions
return '5.0.0';
}
return end($regs[1]);
}
/**
* Runs SQLs from file
*
* @param string $filename
* @param mixed $replace_from
* @param mixed $replace_to
*/
function RunSQL($filename, $replace_from = null, $replace_to = null)
{
if (!file_exists(FULL_PATH.$filename)) {
return ;
}
$sqls = file_get_contents(FULL_PATH.$filename);
if (!$this->RunSQLText($sqls, $replace_from, $replace_to)) {
if (is_object($this->_installator)) {
$this->_installator->Done();
}
else {
if (isset($this->Application)) {
$this->Application->Done();
}
exit;
}
}
}
/**
* Runs SQLs from string
*
* @param string $sqls
* @param mixed $replace_from
* @param mixed $replace_to
* @param int $start_from
* @return bool
*/
function RunSQLText(&$sqls, $replace_from = null, $replace_to = null, $start_from = 0)
{
$table_prefix = $this->systemConfig->get('TablePrefix', 'Database');
// add prefix to all tables
if (strlen($table_prefix) > 0) {
$replacements = Array ('INSERT INTO ', 'UPDATE ', 'ALTER TABLE ', 'DELETE FROM ', 'REPLACE INTO ');
foreach ($replacements as $replacement) {
$sqls = str_replace($replacement, $replacement . $table_prefix, $sqls);
}
}
$sqls = str_replace('CREATE TABLE ', 'CREATE TABLE IF NOT EXISTS ' . $table_prefix, $sqls);
$sqls = str_replace('DROP TABLE ', 'DROP TABLE IF EXISTS ' . $table_prefix, $sqls);
$sqls = str_replace('<%TABLE_PREFIX%>', $table_prefix, $sqls);
$primary_language = is_object($this->Application) ? $this->Application->GetDefaultLanguageId() : 1;
$sqls = str_replace('<%PRIMARY_LANGUAGE%>', $primary_language, $sqls);
if (isset($replace_from) && isset($replace_to)) {
// replace something additionally, e.g. module root category
$sqls = str_replace($replace_from, $replace_to, $sqls);
}
$sqls = str_replace("\r\n", "\n", $sqls); // convert to linux line endings
$no_comment_sqls = preg_replace("/#\s([^;]*?)\n/is", '', $sqls); // remove all comments "#" on new lines
if ($no_comment_sqls === null) {
// "ini.pcre.backtrack-limit" reached and error happened
$sqls = explode(";\n", $sqls . "\n"); // ensures that last sql won't have ";" in it
$sqls = array_map('trim', $sqls);
// remove all comments "#" on new lines (takes about 2 seconds for 53000 sqls)
$sqls = preg_replace("/#\s([^;]*?)/", '', $sqls);
}
else {
$sqls = explode(";\n", $no_comment_sqls . "\n"); // ensures that last sql won't have ";" in it
$sqls = array_map('trim', $sqls);
}
$sql_count = count($sqls);
$db_collation = $this->systemConfig->get('DBCollation', 'Database');
for ($i = $start_from; $i < $sql_count; $i++) {
$sql = $sqls[$i];
if (!$sql || (substr($sql, 0, 1) == '#')) {
continue; // usually last line
}
if (substr($sql, 0, 13) == 'CREATE TABLE ' && $db_collation) {
// it is CREATE TABLE statement -> add collation
$sql .= ' COLLATE \'' . $db_collation . '\'';
}
$this->Conn->Query($sql);
if ($this->Conn->getErrorCode() != 0) {
if (is_object($this->_installator)) {
$this->_installator->errorMessage = 'Error: ('.$this->Conn->getErrorCode().') '.$this->Conn->getErrorMsg().'<br /><br />Last Database Query:<br /><textarea cols="70" rows="10" readonly>'.htmlspecialchars($sql, ENT_QUOTES, 'UTF-8').'</textarea>';
$this->_installator->LastQueryNum = $i + 1;
}
return false;
}
}
return true;
}
/**
* Performs clean language import from given xml file
*
* @param string $lang_file
* @param bool $upgrade
* @todo Import for "core/install/english.lang" (322KB) takes 18 seconds to work on Windows
*/
function ImportLanguage($lang_file, $upgrade = false)
{
$lang_file = FULL_PATH.$lang_file.'.lang';
if (!file_exists($lang_file)) {
return ;
}
/** @var LanguageImportHelper $language_import_helper */
$language_import_helper = $this->Application->recallObject('LanguageImportHelper');
$language_import_helper->performImport($lang_file, '|0|1|2|', '', $upgrade ? LANG_SKIP_EXISTING : LANG_OVERWRITE_EXISTING);
}
/**
* Converts module version in format X.Y.Z[-BN/-RCM] to signle integer
*
* @param string $version
* @return int
*/
function ConvertModuleVersion($version)
{
if (preg_match('/(.*)-(B|RC)([\d]+)/', $version, $regs)) {
// -B<M> or RC-<N>
$parts = explode('.', $regs[1]);
$parts[] = $regs[2] == 'B' ? 1 : 2; // B reliases goes before RC releases
$parts[] = $regs[3];
}
else {
// releases without B/RC marks go after any B/RC releases
$parts = explode('.', $version . '.3.100');
}
$bin = '';
foreach ($parts as $part_index => $part) {
if ($part_index == 3) {
// version type only can be 1/2/3 (11 in binary form), so don't use padding at all
$pad_count = 2;
}
else {
$pad_count = 8;
}
$bin .= str_pad(decbin($part), $pad_count, '0', STR_PAD_LEFT);
}
return bindec($bin);
}
/**
* Returns themes, found in system
*
* @param bool $rebuild
* @return int
*/
function getThemes($rebuild = false)
{
if ($rebuild) {
$this->rebuildThemes();
}
$id_field = $this->Application->getUnitOption('theme', 'IDField');
$table_name = $this->Application->getUnitOption('theme', 'TableName');
$sql = 'SELECT Name, ' . $id_field . '
FROM ' . $table_name . '
ORDER BY Name ASC';
return $this->Conn->GetCol($sql, $id_field);
}
/**
* Checks if system config is present and is not empty
*
* @return bool
*/
function systemConfigFound()
{
return $this->systemConfig->exists();
}
/**
* Checks if given section is present in config
*
* @param string $section
* @return bool
*/
function sectionFound($section)
{
return $this->systemConfig->sectionFound($section);
}
/**
* Returns formatted module name based on it's root folder
*
* @param string $module_folder
* @return string
*/
function getModuleName($module_folder)
{
return implode('-', array_map('ucfirst', explode('-', $module_folder)));
}
/**
* Returns information about module (based on "install/module_info.xml" file)
*
* @param string $module_name
* @return Array
*/
function getModuleInfo($module_name)
{
if ( $module_name == 'core' ) {
$info_file = FULL_PATH . '/' . $module_name . '/install/module_info.xml';
}
else {
$info_file = MODULES_PATH . '/' . $module_name . '/install/module_info.xml';
}
if ( !file_exists($info_file) ) {
return Array ();
}
$ret = Array ();
/** @var SimpleXMLElement[] $module_info */
$module_info = simplexml_load_file($info_file);
if ( $module_info === false ) {
// non-valid xml file
return Array ();
}
foreach ($module_info as $node) {
$ret[strtolower($node->getName())] = trim($node);
}
return $ret;
}
/**
* Returns nice module string to be used on install/upgrade screens
*
* @param string $module_name
* @param string $version_string
* @return string
*/
function getModuleString($module_name, $version_string)
{
// image (if exists) <description> (<name> <version>)
$ret = Array ();
$module_info = $this->getModuleInfo($module_name);
if (array_key_exists('name', $module_info) && $module_info['name']) {
$module_name = $module_info['name'];
}
else {
$module_name = $this->getModuleName($module_name);
}
if (array_key_exists('image', $module_info) && $module_info['image']) {
$image_src = $module_info['image'];
if (!preg_match('/^(http|https):\/\//', $image_src)) {
// local image -> make absolute url
$image_src = $this->Application->BaseURL() . $image_src;
}
$ret[] = '<img src="' . $image_src . '" alt="' . htmlspecialchars($module_name, ENT_QUOTES, 'UTF-8') . '" title="' . htmlspecialchars($module_name, ENT_QUOTES, 'UTF-8') . '" style="vertical-align:middle; margin: 3px 0 3px 5px"/>';
}
if (array_key_exists('description', $module_info) && $module_info['description']) {
$ret[] = $module_info['description'];
}
else {
$ret[] = $module_name;
}
$ret[] = '(' . $module_name . ' ' . $version_string . ')';
return implode(' ', $ret);
}
/**
* Creates module root category in "Home" category using given data and returns it
*
* @param string $name
* @param string $description
* @param string $category_template
* @param string $category_icon
* @return kDBItem
*/
function &createModuleCategory($name, $description, $category_template = null, $category_icon = null)
{
static $fields = null;
if ( !isset($fields) ) {
/** @var kMultiLanguage $ml_formatter */
$ml_formatter = $this->Application->recallObject('kMultiLanguage');
$fields['name'] = $ml_formatter->LangFieldName('Name');
$fields['description'] = $ml_formatter->LangFieldName('Description');
}
/** @var kDBItem $category */
$category = $this->Application->recallObject('c', null, Array ('skip_autoload' => true));
$category_fields = Array (
$fields['name'] => $name, 'Filename' => $name, 'AutomaticFilename' => 1,
$fields['description'] => $description, 'Status' => STATUS_ACTIVE, 'Priority' => -9999,
// prevents empty link to module category on spearate module install
'NamedParentPath' => 'Content/' . $name,
);
$category_fields['ParentId'] = $this->Application->getBaseCategory();
if ( isset($category_template) ) {
$category_fields['Template'] = $category_template;
$category_fields['CachedTemplate'] = $category_template;
}
if ( isset($category_icon) ) {
$category_fields['UseMenuIconUrl'] = 1;
$category_fields['MenuIconUrl'] = $category_icon;
}
$category->Clear();
$category->SetDBFieldsFromHash($category_fields);
$category->Create();
/** @var kPriorityHelper $priority_helper */
$priority_helper = $this->Application->recallObject('PriorityHelper');
$event = new kEvent('c:OnListBuild');
// ensure, that newly created category has proper value in Priority field
$priority_helper->recalculatePriorities($event, 'ParentId = ' . $category_fields['ParentId']);
// update Priority field in object, becase "CategoriesItem::Update" method will be called
// from "kInstallToolkit::setModuleItemTemplate" and otherwise will set 0 to Priority field
$sql = 'SELECT Priority
FROM ' . $category->TableName . '
WHERE ' . $category->IDField . ' = ' . $category->GetID();
$category->SetDBField('Priority', $this->Conn->GetOne($sql));
return $category;
}
/**
* Sets category item template into custom field for given prefix
*
* @param kDBItem $category
* @param string $prefix
* @param string $item_template
*/
function setModuleItemTemplate(&$category, $prefix, $item_template)
{
$this->Application->removeObject('c-cdata');
// recreate all fields, because custom fields are added during install script
$category->Configure();
$category->SetDBField('cust_' . $prefix .'_ItemTemplate', $item_template);
$category->Update();
}
/**
* Link custom field records with search config records + create custom field columns
*
* @param string $module_folder
* @param string $prefix
* @param int $item_type
*/
function linkCustomFields($module_folder, $prefix, $item_type)
{
$module_folder = strtolower($module_folder);
$module_name = $module_folder;
if ( $module_folder == 'kernel' ) {
$module_name = 'in-portal';
$module_folder = 'core';
}
$db =& $this->Application->GetADODBConnection();
$sql = 'SELECT FieldName, CustomFieldId
FROM ' . TABLE_PREFIX . 'CustomFields
WHERE Type = ' . $item_type . ' AND IsSystem = 0'; // config is not read here yet :( $this->Application->getUnitOption('p', 'ItemType');
$custom_fields = $db->GetCol($sql, 'CustomFieldId');
foreach ($custom_fields as $cf_id => $cf_name) {
$sql = 'UPDATE ' . TABLE_PREFIX . 'SearchConfig
SET CustomFieldId = ' . $cf_id . '
WHERE (TableName = "CustomFields") AND (LOWER(ModuleName) = "' . $module_name . '") AND (FieldName = ' . $db->qstr($cf_name) . ')';
$db->Query($sql);
}
// because of configs was read only from installed before modules (in-portal), then reread configs
$this->Application->UnitConfigReader->scanModules(MODULES_PATH . DIRECTORY_SEPARATOR . $module_folder);
// create correct columns in CustomData table
/** @var kMultiLanguageHelper $ml_helper */
$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
$ml_helper->createFields($prefix . '-cdata', true);
}
/**
* Deletes cache, useful after separate module install and installator last step
*
* @param bool $refresh_permissions
* @return void
*/
function deleteCache($refresh_permissions = false)
{
$this->Application->HandleEvent(new kEvent('adm:OnResetMemcache')); // not in DB = 100% invalidate
$this->Application->HandleEvent(new kEvent('adm:OnResetConfigsCache'));
$this->Application->HandleEvent(new kEvent('adm:OnResetSections'));
$this->Application->HandleEvent(new kEvent('c:OnResetCMSMenuCache'));
$this->Conn->Query('DELETE FROM ' . TABLE_PREFIX . 'CachedUrls');
if ( $refresh_permissions ) {
$rebuild_mode = $this->Application->ConfigValue('CategoryPermissionRebuildMode');
if ( $rebuild_mode == CategoryPermissionRebuild::SILENT ) {
// refresh permission without progress bar
/** @var kPermCacheUpdater $updater */
$updater = $this->Application->makeClass('kPermCacheUpdater');
$updater->OneStepRun();
}
elseif ( $rebuild_mode == CategoryPermissionRebuild::AUTOMATIC ) {
// refresh permissions with ajax progress bar (when available)
$this->Application->setDBCache('ForcePermCacheUpdate', 1);
}
}
}
/**
* Deletes all temp tables (from active sessions too)
*
*/
function deleteEditTables()
{
$table_prefix = $this->systemConfig->get('TablePrefix', 'Database');
$tables = $this->Conn->GetCol('SHOW TABLES');
$mask_edit_table = '/' . $table_prefix . 'ses_(.*)_edit_(.*)/';
$mask_search_table = '/' . $table_prefix . 'ses_(.*?)_(.*)/';
foreach ($tables as $table) {
if ( preg_match($mask_edit_table, $table, $rets) || preg_match($mask_search_table, $table, $rets) ) {
$this->Conn->Query('DROP TABLE IF EXISTS ' . $table);
}
}
}
/**
* Perform redirect after separate module install
*
* @param string $module_folder
* @param bool $refresh_permissions
*/
function finalizeModuleInstall($module_folder, $refresh_permissions = false)
{
$this->SetModuleVersion(basename($module_folder), $module_folder);
if (!$this->Application->GetVar('redirect')) {
return ;
}
/** @var kThemesHelper $themes_helper */
$themes_helper = $this->Application->recallObject('ThemesHelper');
// use direct query, since module isn't yet in kApplication::ModuleInfo array
$sql = 'SELECT Name
FROM ' . TABLE_PREFIX . 'Modules
WHERE Path = ' . $this->Conn->qstr(rtrim($module_folder, '/') . '/');
$module_name = $this->Conn->GetOne($sql);
$themes_helper->synchronizeModule($module_name);
/** @var kMultiLanguageHelper $ml_helper */
$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
$ml_helper->massCreateFields();
$this->deleteCache($refresh_permissions);
$url_params = Array (
'pass' => 'm', 'admin' => 1,
'RefreshTree' => 1, 'index_file' => 'index.php',
);
$this->Application->Redirect('modules/modules_list', $url_params);
}
/**
* Performs rebuild of themes
*
*/
function rebuildThemes()
{
$this->Application->HandleEvent(new kEvent('adm:OnRebuildThemes'));
}
/**
* Checks that file is writable by group or others
*
* @param string $file
* @return boolean
*/
function checkWritePermissions($file)
{
if (DIRECTORY_SEPARATOR == '\\') {
// windows doen't allow to check permissions (always returns null)
return null;
}
$permissions = fileperms($file);
return $permissions & 0x0010 || $permissions & 0x0002;
}
/**
* Upgrades primary skin to the latest version
*
* @param Array $module_info
* @return string|bool
*/
function upgradeSkin($module_info)
{
$upgrades_file = sprintf(UPGRADES_FILE, $module_info['Path'], 'css');
$data = file_get_contents($upgrades_file);
// get all versions with their positions in file
$versions = Array ();
preg_match_all('/(' . VERSION_MARK . ')/s', $data, $matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
$from_version_int = $this->ConvertModuleVersion($module_info['FromVersion']);
foreach ($matches as $index => $match) {
$version_int = $this->ConvertModuleVersion($match[2][0]);
if ( $version_int < $from_version_int ) {
// only process versions, that were released after currently used version
continue;
}
$start_pos = $match[0][1] + strlen($match[0][0]);
$end_pos = array_key_exists($index + 1, $matches) ? $matches[$index + 1][0][1] : mb_strlen($data);
$patch_data = str_replace("\r\n", "\n", substr($data, $start_pos, $end_pos - $start_pos));
$versions[] = Array (
'Version' => $match[2][0],
// fixes trimmed leading spaces by modern text editor
'Data' => ltrim( str_replace("\n\n", "\n \n", $patch_data) ),
);
}
if ( !$versions ) {
// not skin changes -> quit
return true;
}
/** @var kDBItem $primary_skin */
$primary_skin = $this->Application->recallObject('skin.primary', null, Array ('skip_autoload' => true));
$primary_skin->Load(1, 'IsPrimary');
if ( !$primary_skin->isLoaded() ) {
// we always got primary skin, but just in case
return false;
}
/** @var kTempTablesHandler $temp_handler */
$temp_handler = $this->Application->recallObject('skin_TempHandler', 'kTempTablesHandler');
// clone current skin
$cloned_ids = $temp_handler->CloneItems('skin', '', Array ($primary_skin->GetID()));
if ( !$cloned_ids ) {
// can't clone
return false;
}
/** @var kDBItem $skin */
$skin = $this->Application->recallObject('skin.tmp', null, Array ('skip_autoload' => true));
$skin->Load($cloned_ids[0]);
// save css to temp file (for patching)
$skin_file = tempnam('/tmp', 'skin_css_');
$fp = fopen($skin_file, 'w');
fwrite($fp, str_replace("\r\n", "\n", $skin->GetDBField('CSS')));
fclose($fp);
$output = Array ();
$patch_file = tempnam('/tmp', 'skin_patch_');
foreach ($versions as $version_info) {
// for each left version get it's patch and apply to temp file
$fp = fopen($patch_file, 'w');
fwrite($fp, $version_info['Data']);
fclose($fp);
$output[ $version_info['Version'] ] = shell_exec('patch ' . $skin_file . ' ' . $patch_file . ' 2>&1') . "\n";
}
// place temp file content into cloned skin
$skin->SetDBField('Name', 'Upgraded to ' . $module_info['ToVersion']);
$skin->SetDBField('CSS', file_get_contents($skin_file));
$skin->Update();
unlink($skin_file);
unlink($patch_file);
$has_errors = false;
foreach ($output as $version => $version_output) {
$version_errors = trim(preg_replace("/(^|\n)(patching file .*?|Hunk #.*?\.)(\n|$)/m", '', $version_output));
if ( $version_errors ) {
$has_errors = true;
$output[$version] = trim(preg_replace("/(^|\n)(patching file .*?)(\n|$)/m", '', $output[$version]));
}
else {
unset($output[$version]);
}
}
if ( !$has_errors ) {
// copy patched css back to primary skin
$primary_skin->SetDBField('CSS', $skin->GetDBField('CSS'));
$primary_skin->Update();
// delete temporary skin record
$temp_handler->DeleteItems('skin', '', Array ($skin->GetID()));
return true;
}
// put clean skin from new version
$skin->SetDBField('CSS', file_get_contents(FULL_PATH . '/core/admin_templates/incs/style_template.css'));
$skin->Update();
// return output in case of errors
return $output;
}
/**
* Returns cache handlers, that are working
*
* @param string $current
* @return Array
*/
public function getWorkingCacheHandlers($current = null)
{
if ( !isset($current) ) {
$current = $this->systemConfig->get('CacheHandler', 'Misc');
}
$cache_handler = $this->Application->makeClass('kCache');
$cache_handlers = Array (
- 'Fake' => 'None', 'Memcache' => 'Memcached', 'XCache' => 'XCache', 'Apc' => 'Alternative PHP Cache'
+ 'Fake' => 'None',
+ 'Memcached' => 'Memcached (via "Memcached" extension)',
+ 'Memcache' => 'Memcached (via "Memcache" extension)',
+ 'XCache' => 'XCache',
+ 'Apc' => 'Alternative PHP Cache',
);
foreach ($cache_handlers AS $class_prefix => $title) {
$handler_class = $class_prefix . 'CacheHandler';
if ( !class_exists($handler_class) ) {
unset($cache_handlers[$class_prefix]);
}
else {
/** @var FakeCacheHandler $handler */
$handler = new $handler_class($cache_handler, 'localhost:11211');
if ( !$handler->isWorking() ) {
if ( $current == $class_prefix ) {
$cache_handlers[$class_prefix] .= ' (offline)';
}
else {
unset($cache_handlers[$class_prefix]);
}
}
}
}
return $cache_handlers;
}
/**
* Returns compression engines, that are working
*
* @param string $current
* @return Array
*/
public function getWorkingCompressionEngines($current = null)
{
if ( !isset($current) ) {
$current = $this->systemConfig->get('CompressionEngine', 'Misc');
}
$output = shell_exec('java -version 2>&1');
$compression_engines = Array ('' => 'None', 'yui' => 'YUICompressor (Java)', 'php' => 'PHP-based');
if ( stripos($output, 'java version') === false ) {
if ( $current == 'yui' ) {
$compression_engines['yui'] .= ' (offline)';
}
else {
unset($compression_engines['yui']);
}
}
return $compression_engines;
}
}
Event Timeline
Log In to Comment