Index: branches/5.0.x/core/units/categories/cache_updater.php
===================================================================
--- branches/5.0.x/core/units/categories/cache_updater.php	(revision 12957)
+++ branches/5.0.x/core/units/categories/cache_updater.php	(revision 12958)
@@ -1,538 +1,541 @@
 <?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!');
 
 	class clsRecursionStack {
 
 		var $Stack;
 
 		function clsRecursionStack()
 		{
 			$this->Stack = Array();
 		}
 
 		function Push($values)
 		{
 			array_push($this->Stack, $values);
 		}
 
 		function Pop()
 		{
 			if ($this->Count() > 0) {
 				return array_pop($this->Stack);
 			}
 			else {
 				return false;
 			}
 		}
 
 		function Get()
 		{
 			if ($this->Count() > 0) {
 	//			return end($this->Stack);
 				return $this->Stack[count($this->Stack)-1];
 			}
 			else {
 				return false;
 			}
 		}
 
 		function Update($values)
 		{
 			$this->Stack[count($this->Stack)-1] = $values;
 		}
 
 		function Count()
 		{
 			return count($this->Stack);
 		}
 	}
 
 	class clsCachedPermissions {
 
 		var $Allow = Array();
 		var $Deny = Array();
 		var $CatId;
 
 		/**
 		 * Table name used for inserting permissions
 		 *
 		 * @var string
 		 */
 		var $table = '';
 
 		function clsCachedPermissions($CatId, $table_name)
 		{
 			$this->CatId = $CatId;
 			$this->table = $table_name;
 		}
 
 		function SetCatId($CatId)
 		{
 			$this->CatId = $CatId;
 		}
 
 		function CheckPermArray($Perm)
 		{
 			if (!isset($this->Allow[$Perm])) {
 				$this->Allow[$Perm] = array();
 				$this->Deny[$Perm] = array();
 			}
 		}
 
 		function AddAllow($Perm, $GroupId)
 		{
 			$this->CheckPermArray($Perm);
 			if (!in_array($GroupId, $this->Allow[$Perm])) {
 				array_push($this->Allow[$Perm], $GroupId);
 				$this->RemoveDeny($Perm, $GroupId);
 			}
 		}
 
 		function AddDeny($Perm, $GroupId)
 		{
 			$this->CheckPermArray($Perm);
 			if (!in_array($GroupId, $this->Deny[$Perm])) {
 				array_push($this->Deny[$Perm], $GroupId);
 				$this->RemoveAllow($Perm, $GroupId);
 			}
 		}
 
 		function RemoveDeny($Perm, $GroupId)
 		{
 			if (in_array($GroupId, $this->Deny[$Perm])) {
 				array_splice($this->Deny[$Perm], array_search($GroupId, $this->Deny[$Perm]), 1);
 			}
 		}
 
 		function RemoveAllow($Perm, $GroupId)
 		{
 			if (in_array($GroupId, $this->Allow[$Perm])) {
 				array_splice($this->Allow[$Perm], array_search($GroupId, $this->Allow[$Perm]), 1);
 			}
 		}
 
 		function GetInsertSQL()
 		{
 			$values = array();
 			foreach ($this->Allow as $perm => $groups) {
 				if (count($groups) > 0) {
 					$values[] = '(' .$this->CatId. ', ' .$perm. ', "' .join(',', $groups). '")';
 				}
 			}
 			if (!$values) return '';
 			$sql = 'INSERT INTO '.$this->table.' (CategoryId, PermId, ACL) VALUES '.join(',', $values);
 			return $sql;
 		}
 	}
 
 	class kPermCacheUpdater extends kHelper {
 		/**
 		 * Holds Stack
 		 *
 		 * @var clsRecursionStack
 		 */
 		var $Stack;
 
 		/**
 		 * Rebuild process iteration
 		 *
 		 * @var int
 		 */
 		var $iteration;
 
 		/**
 		 * Categories count to process
 		 *
 		 * @var unknown_type
 		 */
 		var $totalCats = 0;
 
 		/**
 		 * Processed categories count
 		 *
 		 * @var int
 		 */
 		var $doneCats = 0;
 
 		/**
 		 * Temporary table name used for storing cache building progress
 		 *
 		 * @var string
 		 */
 		var $progressTable = '';
 
 		/**
 		 * Temporary table name used for storing not fully built permissions cache
 		 * 1. preserves previous cache while new cache is building
 		 * 2. when rebuild process fails allows previous cache (in live table) is used
 		 *
 		 * @var string
 		 */
 		var $permCacheTable = '';
 
 		var $primaryLanguageId = 0;
 		var $languageCount = 0;
 		var $root_prefixes = Array();
 
 		/**
 		 * Update cache only for requested categories and it's parent categories
 		 *
 		 * @var bool
 		 */
 		var $StrictPath = false;
 
 		function Init($prefix, $special, $event_params = null)
 		{
 			parent::Init($prefix, $special, $event_params);
 
 			$continuing = isset($event_params['continue']) ? $event_params['continue'] : 1;
 			$this->StrictPath = isset($event_params['strict_path']) ? $event_params['strict_path'] : false;
 			if ($this->StrictPath && !is_array($this->StrictPath)) {
 				$this->StrictPath = explode('|', trim($this->StrictPath, '|'));
 			}
 
 			// cache widely used values to speed up process: begin
 			$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
 			/* @var $ml_helper kMultiLanguageHelper */
 
 			$this->languageCount = $ml_helper->getLanguageCount();
 			$this->primaryLanguageId = $this->Application->GetDefaultLanguageId();
 			// cache widely used values to speed up process: end
 
 			foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
 				$this->root_prefixes[ $module_info['RootCat'] ] = $module_info['Var'];
 			}
 
 			$this->iteration = 0;
 			$this->progressTable = $this->Application->GetTempName('permCacheUpdate');
 			$this->permCacheTable = $this->Application->GetTempName(TABLE_PREFIX.'PermCache');
 
 			if ($continuing == 1) {
 				$this->InitUpdater();
 			}
 			elseif ($continuing == 2) {
 				$this->getData();
 			}
 		}
 
 		/**
 		 * Checks if language with specified id is created
 		 *
 		 * @param int $language_id
 		 * @return bool
 		 */
 		function LanguageFound($language_id)
 		{
 			static $language_ids = null;
 
 			if (!isset($language_ids)) {
 				$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
 				/* @var $ml_helper kMultiLanguageHelper */
 
 				$language_ids = $ml_helper->languagesIDs;
 			}
 
 			return in_array($language_id, $language_ids) || $language_id <= 5;
 		}
 
 		function InitUpdater()
 		{
 			$this->Stack = new clsRecursionStack();
 			$this->initData();
 		}
 
 		function getDonePercent()
 		{
 			if (!$this->totalCats) {
 				return 0;
 			}
 			return min(100, intval( floor( $this->doneCats / $this->totalCats * 100 ) ));
 		}
 
 		function getData()
 		{
 			$tmp = $this->Conn->GetOne('SELECT data FROM '.$this->progressTable);
 			if ($tmp) $tmp = unserialize($tmp);
 
 			$this->totalCats = isset($tmp['totalCats']) ? $tmp['totalCats'] : 0;
 			$this->doneCats = isset($tmp['doneCats']) ? $tmp['doneCats'] : 0;
 			if (isset($tmp['stack'])) {
 				$this->Stack = $tmp['stack'];
 			}
 			else {
 				$this->Stack = new clsRecursionStack();
 			}
 		}
 
 		function setData()
 		{
 			$tmp = Array (
 				'totalCats'	=>	$this->totalCats,
 				'doneCats'	=>	$this->doneCats,
 				'stack'		=>	$this->Stack,
 			);
 
 			$this->Conn->Query('DELETE FROM '.$this->progressTable);
 
 			$fields_hash = Array('data' => serialize($tmp));
 			$this->Conn->doInsert($fields_hash, $this->progressTable);
 		}
 
 		function initData()
 		{
 			$this->clearData(); // drop table before starting anyway
 
 			// 1. create table for rebuilding permissions cache
 			$this->Conn->Query('CREATE TABLE '.$this->permCacheTable.' LIKE '.TABLE_PREFIX.'PermCache');
 
 			if ($this->StrictPath) {
 				// when using strict path leave all other cache intact
 				$sql = 'INSERT INTO '.$this->permCacheTable.'
 						SELECT *
 						FROM '.TABLE_PREFIX.'PermCache';
 				$this->Conn->Query($sql);
 
 				// delete only cache related to categories in path
 				$sql = 'DELETE FROM '.$this->permCacheTable.'
 						WHERE CategoryId IN ('.implode(',', $this->StrictPath).')';
 				$this->Conn->Query($sql);
 			}
 
 			$add_charset = defined(SQL_CHARSET)? ' CHARACTER SET '.SQL_CHARSET.' ' : '';
 			$add_collation = defined(SQL_COLLATION)? ' COLLATE '.SQL_COLLATION.' ' : '';
 
 			$this->Conn->Query('CREATE TABLE '.$this->progressTable.'(data LONGTEXT'.$add_charset.$add_collation.') '.$add_charset.$add_collation);
 
 			$this->totalCats = (int)$this->Conn->GetOne('SELECT COUNT(*) FROM '.TABLE_PREFIX.'Category');
 			$this->doneCats = 0;
 		}
 
 		function clearData()
 		{
 			$this->Conn->Query('DROP TABLE IF EXISTS '.$this->progressTable);
 			$this->Conn->Query('DROP TABLE IF EXISTS '.$this->permCacheTable);
 			$this->Conn->Query('DELETE FROM '.TABLE_PREFIX.'Cache WHERE VarName = \'ForcePermCacheUpdate\'');
 			$this->Conn->Query('UPDATE '.TABLE_PREFIX.'ConfigurationValues SET VariableValue = VariableValue+1 WHERE VariableName = \'CategoriesRebuildSerial\'');
 		}
 
 		function SaveData()
 		{
 			// copy data from temp permission cache table back to live
 			$this->Conn->Query('TRUNCATE '.TABLE_PREFIX.'PermCache');
 
 			$sql = 'INSERT INTO '.TABLE_PREFIX.'PermCache
 					SELECT *
 					FROM '.$this->permCacheTable;
 			$this->Conn->Query($sql);
 			$this->clearData();
 		}
 
 		function DoTheJob()
 		{
 			$data = $this->Stack->Get();
 			if ($data === false) { //If Stack is empty
 				$data['current_id']			=	0;
 				$data['titles']				=	Array();
 				$data['parent_path']		=	Array();
 				$data['named_path']			=	Array();
 				$data['file_name']			=	'';
 				$data['template']	=	''; // design
 				$data['item_template']		=	'';
 				$data['children_count'] = 0;
 				$data['left'] = 0;
 				$data['right'] = 2;
 				$data['debug_title'] = 'ROOT';
 				$this->Stack->Push($data);
 			}
 
 			if (!isset($data['queried'])) {
 				$this->QueryTitle($data);
 				$this->QueryChildren($data);
 				$data['children_count'] = count($data['children']);
 				$this->QueryPermissions($data);
 				$data['queried'] = 1;
 				$data['right'] = $data['left']+1;
 
 				if ($sql = $data['perms']->GetInsertSQL()) {
 					$this->Conn->Query($sql);
 					// $this->doneCats++;  // moved to the place where it pops out of the stack by Kostja
 				}
 				$this->iteration++;
 			}
 
 			// start with first child if we haven't started yet
 			if (!isset($data['current_child'])) $data['current_child'] = 0;
 
 			// if we have more children on CURRENT LEVEL
 			if (isset($data['children'][$data['current_child']])) {
 				if ($this->StrictPath) {
 					while ( isset($data['children'][ $data['current_child'] ]) && !in_array($data['children'][ $data['current_child'] ], $this->StrictPath) ) {
 						$data['current_child']++;
 						continue;
 					}
 					if (!isset($data['children'][ $data['current_child'] ])) return false; //error
 				}
 				$next_data = Array();
 				$next_data['titles']			=	$data['titles'];
 				$next_data['parent_path']		=	$data['parent_path'];
 				$next_data['named_path']		=	$data['named_path'];
 				$next_data['template']			=	$data['template'];
 				$next_data['item_template']		=	$data['item_template'];
 				$next_data['current_id']		=	$data['children'][ $data['current_child'] ]; //next iteration should process child
-				$next_data['perms']				=	$data['perms']; //we should copy our permissions to child - inheritance
+
+				// TODO: use "clone" when on PHP5
+				$next_data['perms']				=	unserialize( serialize($data['perms']) ); // we should copy our permissions to child - inheritance
+
 				$next_data['perms']->SetCatId($next_data['current_id']);
 				$next_data['left'] = $data['right'];
 				$data['current_child']++;
 				$this->Stack->Update($data); //we need to update ourself for the iteration after the next (or further) return to next child
 				$this->Stack->Push($next_data); //next iteration should process this child
 				return true;
 			}
 			else {
 				$this->Stack->Update($data);
 				$prev_data = $this->Stack->Pop(); //remove ourself from stack if we have finished all the childs (or there are none)
 				$data['right'] = $prev_data['right'];
 				$this->UpdateCachedPath($data);
 				// we are getting here if we finished with current level, so check if it's first level - then bail out.
 
 				$this->doneCats++; // moved by Kostja from above, seems to fix the prob
 				$has_more = $this->Stack->Count() > 0;
 				if ($has_more) {
 					$next_data = $this->Stack->Get();
 					$next_data['right'] = $data['right']+1;
 					$next_data['children_count'] += $data['children_count'];
 					$this->Stack->Update($next_data);
 				}
 				return $has_more;
 			}
 		}
 
 		function UpdateCachedPath(&$data)
 		{
 			if ($data['current_id'] == 0) {
 				// don't update non-existing "Home" category
 				return ;
 			}
 
 			$fields_hash = Array (
 				'ParentPath'				=>	'|'.implode('|', $data['parent_path']).'|',
 				// allow old fashion system templates to work
 				'NamedParentPath'			=>	strpos($data['file_name'], '/') !== false ? $data['file_name'] : implode('/', $data['named_path'] ),
 				'CachedTemplate'			=>	$data['template'],
 				'CachedDescendantCatsQty'	=>	$data['children_count'],
 				'TreeLeft' => $data['left'],
 				'TreeRight' => $data['right'],
 			);
 
 			$i = 1;
 			while ($i <= $this->languageCount) {
 				if (!$this->LanguageFound($i)) {
 					continue;
 				}
 				$fields_hash['l'.$i.'_CachedNavbar'] = implode('&|&', $data['titles'][$i]);
 				$i++;
 			}
 
 			$this->Conn->doUpdate($fields_hash, TABLE_PREFIX.'Category', 'CategoryId = '.$data['current_id']);
 		}
 
 		function QueryTitle(&$data)
 		{
 			$category_id = $data['current_id'];
 			$sql = 'SELECT *
 					FROM '.TABLE_PREFIX.'Category
 					WHERE CategoryId = '.$category_id;
 
 			$record = $this->Conn->GetRow($sql);
 			if ($record) {
 				$i = 1;
 				while ($i <= $this->languageCount) {
 					$data['titles'][$i][] = $record['l'.$i.'_Name'] ? $record['l'.$i.'_Name'] : $record['l'.$this->primaryLanguageId.'_Name'];
 					$i++;
 				}
 				$data['debug_title'] = $record['l1_Name'];
 
 				$data['parent_path'][] = $category_id;
 				$data['named_path'][] = preg_replace('/^Content\\//', '', $record['Filename']);
 				$data['file_name'] = $record['Filename'];
 
 				// it is one of the modules root category
 				/*$root_prefix = isset($this->root_prefixes[$category_id]) ? $this->root_prefixes[$category_id] : false;
 				if ($root_prefix) {
 					$fields_hash = Array();
 					if (!$record['Template']) {
 						$record['Template'] = $this->Application->ConfigValue($root_prefix.'_CategoryTemplate');
 						$fields_hash['Template'] = $record['Template'];
 					}
 
 					$this->Conn->doUpdate($fields_hash, TABLE_PREFIX.'Category', 'CategoryId = '.$category_id);
 				}*/
 
 				// if explicitly set, then use it; use parent template otherwise
 				if ($record['Template'] && ($record['Template'] != CATEGORY_TEMPLATE_INHERIT)) {
 					$data['template'] = $record['Template'];
 				}
 			}
 
 		}
 
 		function QueryChildren(&$data)
 		{
 			$sql = 'SELECT CategoryId
 					FROM '.TABLE_PREFIX.'Category
 					WHERE ParentId = '.$data['current_id'];
 			 $data['children'] = $this->Conn->GetCol($sql);
 		}
 
 		function QueryPermissions(&$data)
 		{
 			// don't search for section "view" permissions here :)
 			$sql = 'SELECT ipc.PermissionConfigId, ip.GroupId, ip.PermissionValue
 					FROM '.TABLE_PREFIX.'Permissions AS ip
 					LEFT JOIN '.TABLE_PREFIX.'PermissionConfig AS ipc ON ipc.PermissionName = ip.Permission
 					WHERE (CatId = '.$data['current_id'].') AND (Permission LIKE "%.VIEW") AND (ip.Type = 0)';
 
 			$records = $this->Conn->Query($sql);
 
 			//create permissions array only if we don't have it yet (set by parent)
 			if (!isset($data['perms'])) {
 				$data['perms'] = new clsCachedPermissions($data['current_id'], $this->permCacheTable);
 			}
 
 			foreach ($records as $record) {
 				if ($record['PermissionValue'] == 1) {
 					$data['perms']->AddAllow($record['PermissionConfigId'], $record['GroupId']);
 				}
 				else {
 					$data['perms']->AddDeny($record['PermissionConfigId'], $record['GroupId']);
 				}
 			}
 		}
 
 		/**
 		 * Rebuild all cache in one step
 		 *
 		 */
 		function OneStepRun($path = '')
 		{
 			$this->InitUpdater();
 			$needs_more = true;
 			while ($needs_more) {
 				// until proceeeded in this step category count exceeds category per step limit
 				$needs_more = $this->DoTheJob();
 			}
 			$this->SaveData();
 		}
 	}
\ No newline at end of file