Index: trunk/core/kernel/startup.php =================================================================== diff -u -N -r7855 -r8402 --- trunk/core/kernel/startup.php (.../startup.php) (revision 7855) +++ trunk/core/kernel/startup.php (.../startup.php) (revision 8402) @@ -36,6 +36,7 @@ if (isset($vars['WriteablePath'])) { define('WRITEABLE', FULL_PATH.$vars['WriteablePath']); + define('WRITEBALE_BASE', $vars['WriteablePath']); } if ($vars === false || count($vars) == 0) { Index: trunk/core/kernel/db/db_tag_processor.php =================================================================== diff -u -N -r8388 -r8402 --- trunk/core/kernel/db/db_tag_processor.php (.../db_tag_processor.php) (revision 8388) +++ trunk/core/kernel/db/db_tag_processor.php (.../db_tag_processor.php) (revision 8402) @@ -1441,7 +1441,7 @@ case 'multiselect': case 'radio': $field_options = $object->GetFieldOptions($field, 'options'); - + if ($object->GetDBField('DirectOptions')) { // used for custom fields $field_options['options'] = $object->GetDBField('DirectOptions'); @@ -1450,7 +1450,7 @@ // used for configuration $field_options['options'] = $helper->GetValuesHash( $object->GetDBField($params['value_list_field']) ); } - + $object->SetFieldOptions($field, $field_options); break; @@ -1884,6 +1884,34 @@ $this->Application->HandleEvent(new kEvent($prefix.':OnNew')); } } + + function PrintSerializedFields($params) + { + $object =& $this->getObject(); + $field = $this->SelectParam($params, 'field'); + $data = unserialize($object->GetDBField($field)); + + $o = ''; + $std_params['name'] = $params['render_as']; + $std_params['field'] = $params['field']; + $std_params['pass_params'] = true; + foreach ($data as $key => $row) { + $block_params = array_merge($std_params, $row, array('key'=>$key)); + $o .= $this->Application->ParseBlock($block_params); + } + return $o; + } + + /** + * Checks if current prefix is main item + * + * @param Array $params + * @return bool + */ + function IsTopmostPrefix($params) + { + return $this->Prefix == $this->Application->GetTopmostPrefix($this->Prefix); + } } ?> \ No newline at end of file Index: trunk/core/kernel/session/session.php =================================================================== diff -u -N -r8104 -r8402 --- trunk/core/kernel/session/session.php (.../session.php) (revision 8104) +++ trunk/core/kernel/session/session.php (.../session.php) (revision 8402) @@ -256,7 +256,7 @@ function LoadPersistentVars(&$session) { - $user_id = $this->Application->RecallVar('user_id'); + $user_id = $session->RecallVar('user_id'); if ($user_id != -2) { // root & normal users $sql = 'SELECT VariableValue, VariableName @@ -271,15 +271,33 @@ function StorePersistentVar(&$session, $var_name, $var_value) { + $user_id = $session->RecallVar('user_id'); + if ($user_id == -2 || $user_id === false) { + // -2 (when not logged in), false (when after u:OnLogout event) + return ; + } + $this->PersistentVars[$var_name] = $var_value; - - $replace_hash = Array ( - 'PortalUserId' => $this->Application->RecallVar('user_id'), - 'VariableName' => $var_name, - 'VariableValue' => $var_value - ); - $this->Conn->doInsert($replace_hash, TABLE_PREFIX.'PersistantSessionData', 'REPLACE'); - + + $key_clause = 'PortalUserId = '.$user_id.' AND VariableName = '.$this->Conn->qstr($var_name); + + $sql = 'SELECT VariableValue + FROM '.TABLE_PREFIX.'PersistantSessionData + WHERE '.$key_clause; + $record_found = $this->Conn->GetOne($sql); + + $fields_hash = Array ( + 'PortalUserId' => $user_id, + 'VariableName' => $var_name, + 'VariableValue' => $var_value, + ); + + if ($record_found) { + $this->Conn->doUpdate($fields_hash, TABLE_PREFIX.'PersistantSessionData', $key_clause); + } + else { + $this->Conn->doInsert($fields_hash, TABLE_PREFIX.'PersistantSessionData'); + } } function RecallPersistentVar(&$session, $var_name, $default = false) @@ -291,7 +309,7 @@ { unset($this->PersistentVars[$var_name]); - $user_id = $this->Application->RecallVar('user_id'); + $user_id = $session->RecallVar('user_id'); if ($user_id != -2) { $sql = 'DELETE FROM '.TABLE_PREFIX.'PersistantSessionData @@ -662,6 +680,8 @@ // front-session called from admin or otherwise, then save it's data $this->SaveData(); } + + $this->Application->resetCounters('UserSession'); } /** @@ -786,6 +806,31 @@ // save other last... variables for mistical purposes (customizations may be) $this->StoreVar('last_url', $_SERVER['REQUEST_URI']); // needed by ord:StoreContinueShoppingLink $this->StoreVar('last_env', substr($last_env, strlen(ENV_VAR_NAME)+1)); + + // save last_template in persistant session + if (!$wid) { + if ($this->Application->IsAdmin()) { + // only for main window, not popups, not login template, not temp mode (used in adm:MainFrameLink tag) + $temp_mode = false; + $passed = explode(',', $this->Application->GetVar('passed')); + foreach ($passed as $passed_prefix) { + if ($this->Application->GetVar($passed_prefix.'_mode')) { + $temp_mode = true; + break; + } + } + + if (!$temp_mode) { + $this->StorePersistentVar('last_template_popup', $last_template); + } + } + elseif ($this->Application->GetVar('admin') == 1) { + $admin_session =& $this->Application->recallObject('Session.admin'); + /* @var $admin_ses Session */ + + $admin_session->StorePersistentVar('last_template_popup', '../'.$last_template); + } + } } function getLastTemplateENV($t, $params) @@ -871,6 +916,7 @@ { return $this->Storage->DeleteExpired(); } + /** * Allows to check if user in this session is logged in or not * @@ -879,6 +925,7 @@ function LoggedIn() { $user_id = $this->RecallVar('user_id'); + $ret = $user_id > 0; if ($this->RecallVar('admin') == 1 && ($user_id == -1)) { $ret = true; Index: trunk/core/kernel/utility/http_query.php =================================================================== diff -u -N -r8178 -r8402 --- trunk/core/kernel/utility/http_query.php (.../http_query.php) (revision 8178) +++ trunk/core/kernel/utility/http_query.php (.../http_query.php) (revision 8402) @@ -360,12 +360,18 @@ // env=SID:TEMPLATE:m-1-1-1-1:l0-0-0:n-0-0-0:bb-0-0-1-1-1-0 $vars = Array (); if ($env_var) { + $more_vars = strpos($env_var, '&'); + if ($more_vars !== false) { + parse_str(substr($env_var, $more_vars + 1), $vars); + $env_var = substr($env_var, 0, $more_vars); + } + // replace escaped ":" symbol not to explode by it $env_var = str_replace('\:','_&+$$+&_', $env_var); // replace escaped "=" with spec-chars :) $parts = explode(':', $env_var); if (!$this->Application->RewriteURLs() || ($this->Application->RewriteURLs() && $this->Get('rewrite') != 'on')) { - $vars = $this->extractSIDAndTemplate($parts); + $vars = array_merge_recursive2($vars, $this->extractSIDAndTemplate($parts)); } if ($parts) { Index: trunk/core/units/users/users_event_handler.php =================================================================== diff -u -N -r8397 -r8402 --- trunk/core/units/users/users_event_handler.php (.../users_event_handler.php) (revision 8397) +++ trunk/core/units/users/users_event_handler.php (.../users_event_handler.php) (revision 8402) @@ -9,23 +9,28 @@ function mapPermissions() { parent::mapPermissions(); - $permissions = Array( - // admin - 'OnSetPersistantVariable' => Array('self' => 'view'), // because setting to logged in user only - 'OnUpdateRootPassword' => Array('self' => true), // because setting to logged in user only + $permissions = Array ( + // admin + 'OnSetPersistantVariable' => Array('self' => 'view'), // because setting to logged in user only + 'OnUpdateRootPassword' => Array('self' => true), // because setting to logged in user only - // front - 'OnRefreshForm' => Array('self' => true), + // front + 'OnRefreshForm' => Array('self' => true), - 'OnForgotPassword' => Array('self' => true), - 'OnResetPassword' => Array('self' => true), - 'OnResetPasswordConfirmed' => Array('self' => true), + 'OnForgotPassword' => Array('self' => true), + 'OnResetPassword' => Array('self' => true), + 'OnResetPasswordConfirmed' => Array('self' => true), - 'OnSubscribeQuery' => Array('self' => true), - 'OnSubscribeUser' => Array('self' => true), + 'OnSubscribeQuery' => Array('self' => true), + 'OnSubscribeUser' => Array('self' => true), - 'OnRecommend' => Array('self' => true), - ); + 'OnRecommend' => Array('self' => true), + ); + + if (!$this->Application->IsAdmin()) { + $permissions['OnItemBuild'] = Array('self' => true); + } + $this->permMapping = array_merge($this->permMapping, $permissions); } Index: trunk/core/units/visits/visits_config.php =================================================================== diff -u -N -r8015 -r8402 --- trunk/core/units/visits/visits_config.php (.../visits_config.php) (revision 8015) +++ trunk/core/units/visits/visits_config.php (.../visits_config.php) (revision 8402) @@ -41,6 +41,9 @@ 'IDField' => 'VisitId', 'TableName' => TABLE_PREFIX.'Visits', + + 'PermSection' => Array('main' => 'in-portal:visits'), + 'TitlePresets' => Array( 'default' => Array(), 'visits_list' => Array('prefixes' => Array('visits_List'), 'format' => "!la_title_Visits! (#visits_recordcount#)"), Index: trunk/core/units/themes/themes_config.php =================================================================== diff -u -N -r8099 -r8402 --- trunk/core/units/themes/themes_config.php (.../themes_config.php) (revision 8099) +++ trunk/core/units/themes/themes_config.php (.../themes_config.php) (revision 8402) @@ -18,6 +18,8 @@ 'IDField' => 'ThemeId', 'StatusField' => Array('Enabled', 'PrimaryTheme'), + 'PermSection' => Array('main' => 'in-portal:configure_themes'), + 'TitleField' => 'Name', 'TitlePresets' => Array ( Index: trunk/core/units/themes/themes_eh.php =================================================================== diff -u -N -r8075 -r8402 --- trunk/core/units/themes/themes_eh.php (.../themes_eh.php) (revision 8075) +++ trunk/core/units/themes/themes_eh.php (.../themes_eh.php) (revision 8402) @@ -3,6 +3,20 @@ class ThemesEventHandler extends kDBEventHandler { /** + * Allows to override standart permission mapping + * + */ + function mapPermissions() + { + parent::mapPermissions(); + $permissions = Array( + 'OnItemBuild' => Array('self' => true), + ); + + $this->permMapping = array_merge($this->permMapping, $permissions); + } + + /** * Allows to set selected theme as primary * * @param kEvent $event Index: trunk/kernel/units/visits/visits_config.php =================================================================== diff -u -N -r8015 -r8402 --- trunk/kernel/units/visits/visits_config.php (.../visits_config.php) (revision 8015) +++ trunk/kernel/units/visits/visits_config.php (.../visits_config.php) (revision 8402) @@ -41,6 +41,9 @@ 'IDField' => 'VisitId', 'TableName' => TABLE_PREFIX.'Visits', + + 'PermSection' => Array('main' => 'in-portal:visits'), + 'TitlePresets' => Array( 'default' => Array(), 'visits_list' => Array('prefixes' => Array('visits_List'), 'format' => "!la_title_Visits! (#visits_recordcount#)"), Index: trunk/core/units/languages/languages_event_handler.php =================================================================== diff -u -N -r8397 -r8402 --- trunk/core/units/languages/languages_event_handler.php (.../languages_event_handler.php) (revision 8397) +++ trunk/core/units/languages/languages_event_handler.php (.../languages_event_handler.php) (revision 8402) @@ -10,14 +10,16 @@ { parent::mapPermissions(); $permissions = Array( - 'OnChangeLanguage' => Array('self' => true), - 'OnSetPrimary' => Array('self' => 'advanced:set_primary|add|edit'), - 'OnImportLanguage' => Array('self' => 'advanced:import'), - 'OnImportProgress' => Array('self' => 'advanced:import'), - 'OnExportLanguage' => Array('self' => 'advanced:export'), - 'OnExportProgress' => Array('self' => 'advanced:export'), - - ); + 'OnChangeLanguage' => Array('self' => true), + 'OnSetPrimary' => Array('self' => 'advanced:set_primary|add|edit'), + 'OnImportLanguage' => Array('self' => 'advanced:import'), + 'OnImportProgress' => Array('self' => 'advanced:import'), + 'OnExportLanguage' => Array('self' => 'advanced:export'), + 'OnExportProgress' => Array('self' => 'advanced:export'), + + 'OnItemBuild' => Array('self' => true), + ); + $this->permMapping = array_merge($this->permMapping, $permissions); } Index: trunk/core/kernel/languages/phrases_cache.php =================================================================== diff -u -N -r7635 -r8402 --- trunk/core/kernel/languages/phrases_cache.php (.../phrases_cache.php) (revision 7635) +++ trunk/core/kernel/languages/phrases_cache.php (.../phrases_cache.php) (revision 8402) @@ -26,7 +26,7 @@ function Init($prefix, $special = '') { - $this->LanguageId = $this->Application->GetVar('m_lang'); + $this->LanguageId = constOn('IS_INSTALL') ? 1 : $this->Application->GetVar('m_lang'); if (isset($this->Application->Caches['PhraseList'])) { $this->LoadPhrases( $this->Application->Caches['PhraseList'] ); } Index: trunk/core/units/general/helpers/permissions_helper.php =================================================================== diff -u -N -r8369 -r8402 --- trunk/core/units/general/helpers/permissions_helper.php (.../permissions_helper.php) (revision 8369) +++ trunk/core/units/general/helpers/permissions_helper.php (.../permissions_helper.php) (revision 8402) @@ -121,6 +121,31 @@ } /** + * Returns owner + primary category for each item (used for permission checking) + * + * @param string $prefix + * @param string $ids + * @return Array + * @author Alex + */ + function GetCategoryItemData($prefix, $ids) + { + if (is_array($ids)) { + $ids = implode(',', $ids); + } + $id_field = $this->Application->getUnitOption($prefix, 'IDField'); + $table_name = $this->Application->getUnitOption($prefix, 'TableName'); + $ci_table = $this->Application->getUnitOption('ci', 'TableName'); + + $sql = 'SELECT '.$id_field.', CreatedById, ci.CategoryId + FROM '.$table_name.' item_table + LEFT JOIN '.$ci_table.' ci ON ci.ItemResourceId = item_table.ResourceId + WHERE '.$id_field.' IN ('.$ids.') AND (ci.PrimaryCat = 1)'; + return $this->Conn->Query($sql, $id_field); + } + + + /** * Checks non-system permission on event per category basis * * @param kEvent $event @@ -132,6 +157,8 @@ $top_prefix = $event->getEventParam('top_prefix'); $event_handler =& $this->Application->recallObject($event->Prefix.'_EventHandler'); + /* @var $event_handler kCatDBEventHandler */ + if ($event->Prefix != $top_prefix) { $top_event = new kEvent($top_prefix.':'.$event->Name); $id = $event_handler->getPassedID($top_event); @@ -140,11 +167,6 @@ $id = $event_handler->getPassedID($event); } - // 1. get primary category of category item - $id_field = $this->Application->getUnitOption($top_prefix, 'IDField'); - $table_name = $this->Application->getUnitOption($top_prefix, 'TableName'); - $ci_table = $this->Application->getUnitOption('ci', 'TableName'); - if (!$id) { // item being created -> check by current (before editing started, saved in OnPreCreate event) category permissions $category_id = $this->Application->IsAdmin() ? $this->Application->RecallVar('m_cat_id') : $this->Application->GetVar('m_cat_id'); @@ -154,41 +176,19 @@ } else { // item being edited -> check by it's primary category permissions - $sql = 'SELECT ci.CategoryId, main_table.CreatedById - FROM '.$table_name.' main_table - LEFT JOIN '.$ci_table.' ci ON ci.ItemResourceId = main_table.ResourceId - WHERE (main_table.'.$id_field.' = '.$id.') AND (ci.PrimaryCat = 1)'; - $item_info = $this->Conn->GetRow($sql); - $category_id = $item_info['CategoryId']; - $owner_id = $item_info['CreatedById']; + $items_info = $this->GetCategoryItemData($top_prefix, $id); + $category_id = $items_info[$id]['CategoryId']; + $owner_id = $items_info[$id]['CreatedById']; } // specific permission check for pending & owner permissions: begin - if (substr($event->Name, 0, 9) == 'OnPreSave' || $event->Name == 'OnCreate' || $event->Name == 'OnUpdate') { - if ($event_handler->isNewItemCreate($event)) { - $check_status = $this->AddCheckPermission($category_id, $top_prefix); - } - else { - $check_status = $this->AddCheckPermission($category_id, $top_prefix) || - $this->ModifyCheckPermission($owner_id, $category_id, $top_prefix); - } - - if (!$check_status) { - $event->status = erPERM_FAIL; - } + $new_item = $this->Application->IsAdmin() && $event_handler->isNewItemCreate($event) ? true : false; + $check_status = $this->checkCombinedPermissions($event, $owner_id, $category_id, $new_item); + if (isset($check_status)) { return $check_status; } - - if ($event->Name == 'OnMassDelete') { - $check_status = $this->DeleteCheckPermission($owner_id, $category_id, $top_prefix); - if (!$check_status) { - $event->status = erPERM_FAIL; - } - return $check_status; - } // specific permission check for pending & owner permissions: end - $perm_status = false; $check_perms = $this->getPermissionByEvent($event, $event_perm_mapping); @@ -205,7 +205,6 @@ continue; } $perm_name = $item_prefix.'.'.$perm_mapping[$perm_name]; - $this->showDebug('Event '.$event->Name.' permission(-s): '.$perm_name.'', Array()); $perm_status = $this->CheckPermission($perm_name, 0, $category_id); if ($perm_status) { @@ -227,12 +226,57 @@ return $perm_status; } - function showDebug($text, $params) + /** + * Allows to check combined permissions (*.owner, *.pending) for add/modify/delete operations from admin & front-end + * + * @param kEvent $event + * @param int $owner_id + * @param int $category_id + * @param bool $new_item + * @return mixed + */ + function checkCombinedPermissions(&$event, $owner_id, $category_id, $new_item = false) { - return ; - $is_ajax = $this->Application->GetVar('ajax') == 'yes' || isset($params['ajax']) || isset($params['tab_init']); - if (!$this->Application->isDebugMode() || $is_ajax) return true; - echo $text.'
'; + $ret = null; // true/false when used, null when not used + $top_prefix = $event->getEventParam('top_prefix'); + + // check admin permission + if (substr($event->Name, 0, 9) == 'OnPreSave') { + if ($new_item) { + $ret = $this->AddCheckPermission($category_id, $top_prefix); + } + else { + // add & modify because $new_item is false, when item is aready created & then saved in temp table (even with 0 id) + $ret = $this->AddCheckPermission($category_id, $top_prefix) || + $this->ModifyCheckPermission($owner_id, $category_id, $top_prefix); + } + } + + // check front-end permissions + switch ($event->Name) { + case 'OnItemLoad': + break; + + case 'OnCreate': + $ret = $this->AddCheckPermission($category_id, $top_prefix); + break; + + case 'OnUpdate': + $ret = $this->ModifyCheckPermission($owner_id, $category_id, $top_prefix); + break; + + case 'OnDelete': + case 'OnMassDelete': + $ret = $this->DeleteCheckPermission($owner_id, $category_id, $top_prefix); + break; + } + + if ($ret === 0) { + // permission check failed (user has no permission) + $event->status = erPERM_FAIL; + } + + return $ret; } function TagPermissionCheck($params, $tag_name) @@ -243,7 +287,6 @@ if ($permission_groups) { // check permissions by permission names in current category - $this->showDebug('Tag '.$tag_name.' permission(-s): '.$permission_groups.'', $params); $permission_groups = explode('|', $permission_groups); $group_has_permission = false; @@ -270,7 +313,6 @@ } elseif ($perm_event) { // check permission by event name - $this->showDebug('Tag '.$tag_name.' permission_event: '.$perm_event.'', $params); list($prefix, $event) = explode(':', $perm_event); $event_handler =& $this->Application->recallObject($prefix.'_EventHandler'); return $event_handler->CheckPermission( new kEvent($perm_event) ); @@ -414,14 +456,20 @@ // cached view permission of category: end } - if ($cat_id == 0) { + if (is_numeric($cat_id) && $cat_id == 0) { $cat_hierarchy = Array(0); } else { - $sql = 'SELECT ParentPath + if (strpos($cat_id, '|') !== false) { + $cat_hierarchy = $cat_id; + } + else { + $sql = 'SELECT ParentPath FROM '.$this->Application->getUnitOption('c', 'TableName').' WHERE CategoryId = '.$cat_id; - $cat_hierarchy = $this->Conn->GetOne($sql); + $cat_hierarchy = $this->Conn->GetOne($sql); + } + $cat_hierarchy = explode('|', substr($cat_hierarchy, 1, -1)); $cat_hierarchy = array_reverse($cat_hierarchy); array_push($cat_hierarchy, 0); @@ -479,6 +527,38 @@ } /** + * Allows to check VIEW & "OWNER.VIEW.PENDING" permission combinations on item + * + * @param int $owner_id user_id, that is owner of the item + * @param int $category_id primary category of item + * @param string $prefix prefix of item + * @return int {0 - no VIEW permission, 1 - has VIEW permission} + */ + function ViewCheckPermission($owner_id, $category_id, $prefix) + { + $perm_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix'); + + // in progress + $use_pending_editing = $this->Application->getUnitOption($prefix, 'UsePendingEditing'); + + + $live_delete = $this->CheckPermission($perm_prefix.'.VIEW', ptCATEGORY, $category_id); + if ($live_delete) { + return 1; + } + + if ($owner_id == $this->Application->RecallVar('user_id')) { + // user is item's OWNER -> check this permissions first + $live_delete = $this->CheckPermission($perm_prefix.'.OWNER.DELETE', ptCATEGORY, $category_id); + if ($live_delete) { + return 1; + } + } + + return 0; + } + + /** * Allows to check DELETE & OWNER.DELETE permission combinations on item * * @param int $owner_id user_id, that is owner of the item Index: trunk/core/kernel/db/dblist.php =================================================================== diff -u -N -r8388 -r8402 --- trunk/core/kernel/db/dblist.php (.../dblist.php) (revision 8388) +++ trunk/core/kernel/db/dblist.php (.../dblist.php) (revision 8402) @@ -350,6 +350,14 @@ $sql = $q.' '.$this->Conn->getLimitClause($this->Offset,$limit); $this->Records = $this->Conn->Query($sql); + + if (!$this->Records && ($this->Page > 1)) { + // no records & page > 1, try to reset to 1st page (works only when list in not counted before) + $this->Application->StoreVar($this->getPrefixSpecial().'_Page', 1); + $this->SetPage(1); + $this->Query($force); + } + $this->SelectedCount = count($this->Records); if (!$this->Counted) $this->RecordsCount = $this->SelectedCount; if (!$this->Counted && $this->SelectedCount > $this->PerPage && $this->PerPage != -1) $this->SelectedCount--; Index: trunk/core/kernel/processors/tag_processor.php =================================================================== diff -u -N -r8358 -r8402 --- trunk/core/kernel/processors/tag_processor.php (.../tag_processor.php) (revision 8358) +++ trunk/core/kernel/processors/tag_processor.php (.../tag_processor.php) (revision 8402) @@ -76,7 +76,6 @@ $this->Special = $backup_special; if ($flag_values['js_escape']) { - $ret = str_replace('\'', ''', $ret); $ret = addslashes($ret); $ret = str_replace(Array("\r", "\n"), Array('\r', '\n'), $ret); $ret = str_replace('', "", $ret); Index: trunk/core/admin_templates/tools/system_tools.tpl =================================================================== diff -u -N -r8397 -r8402 --- trunk/core/admin_templates/tools/system_tools.tpl (.../system_tools.tpl) (revision 8397) +++ trunk/core/admin_templates/tools/system_tools.tpl (.../system_tools.tpl) (revision 8402) @@ -23,7 +23,6 @@ ', '');" value="Go"> -   @@ -41,7 +40,7 @@ Table Structure: - + table name (prefix optional) OR unit config prefix Index: trunk/kernel/units/visits/visits_event_handler.php =================================================================== diff -u -N -r7391 -r8402 --- trunk/kernel/units/visits/visits_event_handler.php (.../visits_event_handler.php) (revision 7391) +++ trunk/kernel/units/visits/visits_event_handler.php (.../visits_event_handler.php) (revision 8402) @@ -3,6 +3,20 @@ class VisitsEventHandler extends kDBEventHandler { /** + * Allows to override standart permission mapping + * + */ + function mapPermissions() + { + parent::mapPermissions(); + $permissions = Array( + 'OnItemBuild' => Array('self' => true), + ); + + $this->permMapping = array_merge($this->permMapping, $permissions); + } + + /** * Registers user visit to site * * @param kEvent $event Index: trunk/core/kernel/application.php =================================================================== diff -u -N -r8178 -r8402 --- trunk/core/kernel/application.php (.../application.php) (revision 8178) +++ trunk/core/kernel/application.php (.../application.php) (revision 8402) @@ -325,6 +325,7 @@ return false; } $modules_helper =& $this->recallObject('ModulesHelper'); + $this->ModuleInfo = $this->Conn->Query('SELECT * FROM '.TABLE_PREFIX.'Modules WHERE Loaded = 1 ORDER BY LoadOrder', 'Name'); $sql = 'SELECT * FROM '.TABLE_PREFIX.'Modules WHERE '.$modules_helper->getWhereClause().' @@ -1867,6 +1868,7 @@ */ function &recallTagProcessor($prefix) { + $this->InitParser(); // because kDBTagProcesor is in TemplateParser dependencies $result =& $this->recallObject($prefix.'_TagProcessor'); return $result; } @@ -2245,8 +2247,8 @@ */ function LoggedIn() { - return $this->Session->LoggedIn(); - + // no session during expiration process + return is_null($this->Session) ? false : $this->Session->LoggedIn(); } /** @@ -2484,6 +2486,10 @@ */ function resetCounters($tables) { + if (constOn('IS_INSTALL')) { + return ; + } + $count_helper =& $this->Application->recallObject('CountHelper'); /* @var $count_helper kCountHelper */ Index: trunk/core/kernel/globals.php =================================================================== diff -u -N -r7887 -r8402 --- trunk/core/kernel/globals.php (.../globals.php) (revision 7887) +++ trunk/core/kernel/globals.php (.../globals.php) (revision 8402) @@ -113,7 +113,7 @@ { if(!defined($const_name)) define($const_name,$const_value); } - + if( !function_exists('parse_portal_ini') ) { function parse_portal_ini($file, $parse_section = false) @@ -335,6 +335,9 @@ curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $post); } + elseif ($request_type == 'GET' && isset($post) && strlen($post) > 0) { + curl_setopt($ch, CURLOPT_URL, preg_match('/\?/', $url) ? $url.'&'.$post : $url.'?'.$post); + } curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); @@ -488,4 +491,62 @@ } return $string; } + + /** + * Checks, that user IP address is within allowed range + * + * @param string $ip_list semi-column (by default) separated ip address list + * @param string $separator ip address separator (default ";") + * + * @return bool + */ + function ipMatch($ip_list, $separator = ';') + { + $ip_match = false; + $ip_addresses = $ip_list ? explode($separator, $ip_list) : Array (); + foreach ($ip_addresses as $ip_address) { + if (netMatch($ip_address, $_SERVER['REMOTE_ADDR'])) { + $ip_match = true; + break; + } + } + + return $ip_match; + } + + function netMatch($network, $ip) { + $network = trim($network); + $ip = trim($ip); + + if ($network == $ip) { + // comparing 2 ip addresses directly + return true; + } + + $d = strpos($network, '-'); + if ($d === false) { + // sigle subnet specified + $ip_arr = explode('/', $network); + + if (!preg_match("@\d*\.\d*\.\d*\.\d*@", $ip_arr[0], $matches)) { + $ip_arr[0] .= '.0'; // Alternate form 194.1.4/24 + } + + $network_long = ip2long($ip_arr[0]); + $x = ip2long($ip_arr[1]); + + $mask = long2ip($x) == $ip_arr[1] ? $x : (0xffffffff << (32 - $ip_arr[1])); + $ip_long = ip2long($ip); + + return ($ip_long & $mask) == ($network_long & $mask); + } + else { + // ip address range specified + $from = ip2long(trim(substr($network, 0, $d))); + $to = ip2long(trim(substr($network, $d + 1))); + + $ip = ip2long($ip); + return ($ip >= $from && $ip <= $to); + } + } ?> \ No newline at end of file Index: trunk/core/admin_templates/js/uploader.js =================================================================== diff -u -N -r8178 -r8402 --- trunk/core/admin_templates/js/uploader.js (.../uploader.js) (revision 8178) +++ trunk/core/admin_templates/js/uploader.js (.../uploader.js) (revision 8402) @@ -33,7 +33,7 @@ this.uploaded = 0; this.flash_id = UploadsManager.NextFlashId(); - this.swf = new SWFObject('SWFUpload.swf', this.flash_id, "0", "0", "8", "#000000"); + this.swf = new SWFObject('swfupload.swf', this.flash_id, "0", "0", "8", "#000000"); this.swf.setAttribute('style', ''); this.swf.addVariable("uploadScript", ''); this.swf.addVariable("maxFiles", escape(this.params.multiple)); @@ -48,7 +48,6 @@ this.swf.addVariable("uploadFileCancelCallback", escape('UploadsManager.FileCancelled')); this.swf.addVariable("uploadQueueCompleteCallback", escape('UploadsManager.UploadQueueComplete')); this.swf.addVariable("uploadFileErrorCallback", escape('UploadsManager.UploadError')); - this.swf.addVariable("uploadCancelCallback", escape('uploadCancel')); this.swf.addVariable("autoUpload", escape('false')); // this.swf.addVariable("flashLoadedCallback", 'flashLoadedCallback'); @@ -310,7 +309,7 @@ upl.files.push(file); upl.total += file.size; if (upl.files[0].uploaded) { - UploadsManager.DeleteFile(file.uploader_id, upl.files[0].name); + UploadsManager.DeleteFile(file.uploader_id, upl.files[0].name, true); } else { upl.flash.cancelFile(upl.files[0].id); @@ -337,15 +336,25 @@ UploadsManager.UploadError = function(errno, file, msg) { - alert('error '+errno) - alert('Error: '+msg+' on file '+file.name); + this.Uploaders[file.uploader_id].RemoveFile(file); + if (errno == -10) { + switch (msg.toString()) { + case '403': + msg = 'You don\'t have permission to upload'; + break; + case '500': + msg = 'Write permissions not set on the server, please contact server administrator'; + break; + } + } + alert('Error: '+msg+'\nOccured on file '+file.name); } -UploadsManager.DeleteFile = function(mov, fname) +UploadsManager.DeleteFile = function(mov, fname, confirmed) { - if (!confirm('Are you sure you want to delete this file?')) return; + if (!confirmed && !confirm('Are you sure you want to delete this file?')) return; Request.makeRequest( - this.Uploaders[mov].deleteURL.replace('#FILE#', fname), + this.Uploaders[mov].deleteURL.replace('#FILE#', fname).replace('#FIELD#', this.Uploaders[mov].params.field), false, '', function(req, fname, upl) { upl.RemoveFile({id:fname}) Index: trunk/core/units/general/cat_event_handler.php =================================================================== diff -u -N -r8369 -r8402 --- trunk/core/units/general/cat_event_handler.php (.../cat_event_handler.php) (revision 8369) +++ trunk/core/units/general/cat_event_handler.php (.../cat_event_handler.php) (revision 8402) @@ -49,31 +49,28 @@ $this->Application->StoreVar('m_cat_id', $root_category); } - if ($event->Name == 'OnEdit' || $event->Name == 'OnSave') { + $check_events = Array ('OnEdit', 'OnSave', 'OnMassDelete'); + if (in_array($event->Name, $check_events)) { // check each id from selected individually and only if all are allowed proceed next - if ($event->Name == 'OnEdit') { - $selected_ids = implode(',', $this->StoreSelectedIDs($event)); - } - else { + if ($event->Name == 'OnSave') { $selected_ids = implode(',', $this->getSelectedIDs($event, true)); if (!$selected_ids) { $selected_ids = 0; // when saving newly created item (OnPreCreate -> OnPreSave -> OnSave) } } + else { + $selected_ids = implode(',', $this->StoreSelectedIDs($event)); + } $perm_value = true; if (strlen($selected_ids)) { - $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); - $table_name = $this->Application->getUnitOption($event->Prefix, 'TableName'); - $sql = 'SELECT '.$id_field.', CreatedById, ci.CategoryId - FROM '.$table_name.' item_table - LEFT JOIN '.$this->Application->getUnitOption('ci', 'TableName').' ci ON ci.ItemResourceId = item_table.ResourceId - WHERE '.$id_field.' IN ('.$selected_ids.') AND (ci.PrimaryCat = 1)'; - $items = $this->Conn->Query($sql, $id_field); - $perm_helper =& $this->Application->recallObject('PermissionsHelper'); + /* @var $perm_helper kPermissionsHelper */ + + $items = $perm_helper->GetCategoryItemData($event->Prefix, $selected_ids); + $check_method = ($event->Name == 'OnMassDelete') ? 'DeleteCheckPermission' : 'ModifyCheckPermission'; foreach ($items as $item_id => $item_data) { - if ($perm_helper->ModifyCheckPermission($item_data['CreatedById'], $item_data['CategoryId'], $event->Prefix) == 0) { + if ($perm_helper->$check_method($item_data['CreatedById'], $item_data['CategoryId'], $event->Prefix) == 0) { // one of items selected has no permission $perm_value = false; break; @@ -357,7 +354,7 @@ if ( !$this->Application->IsAdmin() ) { - $object->addFilter('status_filter', $object->TableName.'.Status = 1'); + $object->addFilter('status_filter', '%1$s.Status = 1'); if ($this->Application->getUnitOption($event->Prefix, 'UsePendingEditing')) { // if category item uses pending editing abilities, then in no cases show pending copies on front $object->addFilter('original_filter', '%1$s.OrgId = 0 OR %1$s.OrgId IS NULL'); @@ -1749,14 +1746,37 @@ function OnCreate(&$event) { parent::OnCreate($event); + $this->SetFrontRedirectTemplate($event, 'suggest'); + } + + /** + * Creates category item & redirects to confirmation template (front-end only) + * + * @param kEvent $event + */ + function OnUpdate(&$event) + { + parent::OnUpdate($event); + $this->SetFrontRedirectTemplate($event, 'modify'); + } + + /** + * Sets next template to one required for front-end after adding/modifying item + * + * @param kEvent $event + * @param string $template_key - {suggest,modify} + */ + function SetFrontRedirectTemplate(&$event, $template_key) + { + if ($this->Application->IsAdmin() || $event->status != erSUCCESS) { + return ; + } - if (!$this->Application->IsAdmin()) { - $event->SetRedirectParam('opener', 's'); + $event->SetRedirectParam('opener', 's'); - $object =& $event->getObject(); - $next_template = $object->GetDBField('Status') == STATUS_ACTIVE ? 'suggest_confirm_template' : 'suggest_pending_confirm_template'; - $event->redirect = $this->Application->GetVar($next_template); - } + $object =& $event->getObject(); + $next_template = $object->GetDBField('Status') == STATUS_ACTIVE ? 'confirm_template' : 'pending_confirm_template'; + $event->redirect = $this->Application->GetVar($template_key.'_'.$next_template); } /** Index: trunk/core/units/visits/visits_event_handler.php =================================================================== diff -u -N -r7391 -r8402 --- trunk/core/units/visits/visits_event_handler.php (.../visits_event_handler.php) (revision 7391) +++ trunk/core/units/visits/visits_event_handler.php (.../visits_event_handler.php) (revision 8402) @@ -3,6 +3,20 @@ class VisitsEventHandler extends kDBEventHandler { /** + * Allows to override standart permission mapping + * + */ + function mapPermissions() + { + parent::mapPermissions(); + $permissions = Array( + 'OnItemBuild' => Array('self' => true), + ); + + $this->permMapping = array_merge($this->permMapping, $permissions); + } + + /** * Registers user visit to site * * @param kEvent $event Index: trunk/core/kernel/utility/formatters/unit_formatter.php =================================================================== diff -u -N -r7635 -r8402 --- trunk/core/kernel/utility/formatters/unit_formatter.php (.../unit_formatter.php) (revision 7635) +++ trunk/core/kernel/utility/formatters/unit_formatter.php (.../unit_formatter.php) (revision 8402) @@ -29,6 +29,7 @@ { if( !isset($options['master_field']) ) { + if ($value == -1) return; // for infinity setting, otherwise infinity is incorrectly converted back to Kg $regional =& $this->Application->recallObject('lang.current'); switch( $regional->GetDBField('UnitSystem') ) { Index: trunk/core/kernel/utility/formatters/serialized_formatter.php =================================================================== diff -u -N -r4758 -r8402 --- trunk/core/kernel/utility/formatters/serialized_formatter.php (.../serialized_formatter.php) (revision 4758) +++ trunk/core/kernel/utility/formatters/serialized_formatter.php (.../serialized_formatter.php) (revision 8402) @@ -2,10 +2,19 @@ class kSerializedFormatter extends kFormatter { + function Parse($value, $field_name, &$object) + { + $options = $object->GetFieldOptions($field_name); + $value = array_merge_recursive2(unserialize($options['default']), $value); + return serialize($value); + } + function Format($value, $field_name, $object, $format=null) { $data = unserialize($value); - return $data[$format]; + $format = explode('.', $format); + $format = '\''.implode('\', \'', $format).'\''; + return eval('return getArrayValue($data, '.$format.');'); } } Index: trunk/core/kernel/utility/debugger.php =================================================================== diff -u -N -r8078 -r8402 --- trunk/core/kernel/utility/debugger.php (.../debugger.php) (revision 8078) +++ trunk/core/kernel/utility/debugger.php (.../debugger.php) (revision 8402) @@ -840,6 +840,10 @@ $error_map['PHP5 Strict'] = Array(E_STRICT); } + if (defined('E_RECOVERABLE_ERROR')) { + $error_map['Fatal Error (recoverable)'] = Array(E_RECOVERABLE_ERROR); + } + foreach ($error_map as $error_name => $error_codes) { if (in_array($error_code, $error_codes)) { return $error_name; Index: trunk/core/kernel/db/db_event_handler.php =================================================================== diff -u -N -r8360 -r8402 --- trunk/core/kernel/db/db_event_handler.php (.../db_event_handler.php) (revision 8360) +++ trunk/core/kernel/db/db_event_handler.php (.../db_event_handler.php) (revision 8402) @@ -95,7 +95,10 @@ parent::mapPermissions(); $permissions = Array( 'OnLoad' => Array('self' => 'view', 'subitem' => 'view'), - + 'OnItemBuild' => Array('self' => 'view', 'subitem' => 'view'), + + 'OnBuild' => Array('self' => true), + 'OnNew' => Array('self' => 'add', 'subitem' => 'add|edit'), 'OnCreate' => Array('self' => 'add', 'subitem' => 'add|edit'), 'OnUpdate' => Array('self' => 'edit', 'subitem' => 'add|edit'), @@ -379,7 +382,19 @@ $auto_load = $this->Application->getUnitOption($event->Prefix,'AutoLoad'); $skip_autload = $event->getEventParam('skip_autoload'); - if($auto_load && !$skip_autload) $this->LoadItem($event); + if ($auto_load && !$skip_autload) { + $event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true)); + if (($this->Application->RecallVar('user_id') == -1) || $this->CheckPermission($event)) { + // don't autoload item, when user doesn't have view permission + $this->LoadItem($event); + } + else { + // when no permission to view item -> redirect to no pemrission template + trigger_error('ItemLoad Permission Failed for prefix ['.$event->getPrefixSpecial().']', E_USER_WARNING); + $next_template = $this->Application->IsAdmin() ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate'); + $this->Application->Redirect($next_template, Array('pass' => 'm')); + } + } $actions =& $this->Application->recallObject('kActions'); $actions->Set($event->Prefix_Special.'_GoTab', ''); @@ -483,7 +498,8 @@ $sql = $this->ListPrepareQuery($event); $sql = $this->Application->ReplaceLanguageTags($sql); $object->setSelectSQL($sql); - + $object->Counted = false; // when requery="1" should re-count records too! + $object->linkToParent( $this->getMainSpecial($event) ); $this->AddFilters($event); @@ -986,12 +1002,10 @@ { $object =& $event->getObject( Array('skip_autoload' => true) ); $object->ID = $this->getPassedID($event); - if( $object->Delete() ) - { + if ($object->Delete()) { $event->status = erSUCCESS; } - else - { + else { $event->status = erFAIL; $event->redirect = false; } @@ -1102,6 +1116,8 @@ { $this->setTempWindowID($event); $this->StoreSelectedIDs($event); + $var_name = $event->getPrefixSpecial().'_file_pending_actions'.$this->Application->GetVar('m_wid'); + $this->Application->RemoveVar($var_name); $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); /* @var $temp kTempTablesHandler */ @@ -1126,6 +1142,17 @@ if (!$this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $live_ids = $temp->SaveEdit($event->getEventParam('master_ids') ? $event->getEventParam('master_ids') : Array()); + + // Deleteing files scheduled for delete + $var_name = $event->getPrefixSpecial().'_file_pending_actions'.$this->Application->GetVar('m_wid'); + $schedule = $this->Application->RecallVar($var_name); + $schedule = $schedule ? unserialize($schedule) : array(); + foreach ($schedule as $data) { + if ($data['action'] == 'delete') { + unlink($data['file']); + } + } + if ($live_ids) { // ensure, that newly created item ids are avalable as if they were selected from grid // NOTE: only works if main item has subitems !!! @@ -1783,93 +1810,6 @@ } /** - * Dynamically fills customdata config - * - * @param kEvent $event - */ - function OnCreateCustomFields(&$event) - { - if (defined('IS_INSTALL') && IS_INSTALL && !$this->Application->TableFound('CustomField')) { - return false; - } - $main_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix'); - if (!$main_prefix) return false; - $item_type = $this->Application->getUnitOption($main_prefix, 'ItemType'); - if (!$item_type) { - // no main config of such type - return false; - } - - // 1. get custom field information - $sql = 'SELECT * - FROM '.TABLE_PREFIX.'CustomField - WHERE Type = '.$item_type.' - ORDER BY CustomFieldId'; - $custom_fields = $this->Conn->Query($sql, 'CustomFieldId'); - if (!$custom_fields) { - // config doesn't have custom fields - return false; - } - - // 2. create fields (for customdata item) - $fields = $this->Application->getUnitOption($event->Prefix, 'Fields', Array()); - $field_options = Array('type' => 'string', 'formatter' => 'kMultiLanguage', 'db_type' => 'text', 'default' => ''); - foreach ($custom_fields as $custom_id => $custom_params) { - if (isset($fields['cust_'.$custom_id])) continue; - $fields['cust_'.$custom_id] = $field_options; - } - $this->Application->setUnitOption($event->Prefix, 'Fields', $fields); - - // 3. create virtual & calculated fields (for main item) - $calculated_fields = Array(); - $virtual_fields = $this->Application->getUnitOption($main_prefix, 'VirtualFields', Array()); - - $cf_helper =& $this->Application->recallObject('InpCustomFieldsHelper'); - $field_options = Array('type' => 'string', 'not_null' => 1, 'default' => ''); - $ml_formatter =& $this->Application->recallObject('kMultiLanguage'); - - foreach ($custom_fields as $custom_id => $custom_params) { - switch ($custom_params['ElementType']) { - case 'date': - case 'datetime': - unset($field_options['options']); - $field_options['formatter'] = 'kDateFormatter'; - break; - - case 'select': - case 'multiselect': - case 'radio': - if ($custom_params['ValueList']) { - $field_options['options'] = $cf_helper->GetValuesHash($custom_params['ValueList']); - $field_options['formatter'] = 'kOptionsFormatter'; - } - break; - - default: - unset($field_options['options'], $field_options['formatter']); - break; - } - - $custom_name = $custom_params['FieldName']; - $calculated_fields['cust_'.$custom_name] = 'cust.'.$ml_formatter->LangFieldName('cust_'.$custom_id); - if (!isset($virtual_fields['cust_'.$custom_name])) { - $virtual_fields['cust_'.$custom_name] = Array(); - } - $virtual_fields['cust_'.$custom_name] = array_merge_recursive2($field_options, $virtual_fields['cust_'.$custom_name]); - $custom_fields[$custom_id] = $custom_name; - } - - $config_calculated_fields = $this->Application->getUnitOption($main_prefix, 'CalculatedFields', Array()); - foreach ($config_calculated_fields as $special => $special_fields) { - $config_calculated_fields[$special] = array_merge_recursive2($config_calculated_fields[$special], $calculated_fields); - } - $this->Application->setUnitOption($main_prefix, 'CalculatedFields', $config_calculated_fields); - - $this->Application->setUnitOption($main_prefix, 'CustomFields', $custom_fields); - $this->Application->setUnitOption($main_prefix, 'VirtualFields', $virtual_fields); - } - - /** * Saves selected user in needed field * * @param kEvent $event @@ -2067,6 +2007,8 @@ $section = $event->getSection(); if (!$perm_helper->CheckUserPermission($user, $section.'.add') && !$perm_helper->CheckUserPermission($user, $section.'.edit')) { $event->status = erPERM_FAIL; + header('HTTP/1.0 403 You don\'t have permissions to upload'); + exit; return; } @@ -2079,6 +2021,11 @@ $id = $this->Application->GetVar('id'); if ($id) $fname = $id.'_'.$fname; + if (!is_writable($tmp_path)) { + header('HTTP/1.0 500 Write permissions not set on the server'); + exit; + } + move_uploaded_file($value['tmp_name'], $tmp_path.$fname); exit; } @@ -2090,12 +2037,16 @@ */ function OnDeleteFile(&$event) { - $var_name = $event->getPrefixSpecial().'_file_pending_actions'; + if (strpos($this->Application->GetVar('file'), '../') !== false) return ; + $object =& $event->getObject(array('skip_autoload'=>true)); + $options = $object->GetFieldOptions($this->Application->GetVar('field')); + + $var_name = $event->getPrefixSpecial().'_file_pending_actions'.$this->Application->GetVar('m_wid'); $schedule = $this->Application->RecallVar($var_name); $schedule = $schedule ? unserialize($schedule) : array(); - $schedule[] = array('action'=>'delete', 'file'=>$this->Application->GetVar('file')); + $schedule[] = array('action'=>'delete', 'file'=>$path = FULL_PATH.$options['upload_dir'].$this->Application->GetVar('file')); $this->Application->StoreVar($var_name, serialize($schedule)); - exit; + $this->Application->Session->SaveData(); } /** @@ -2105,6 +2056,7 @@ */ function OnViewFile(&$event) { + if (strpos($this->Application->GetVar('file'), '../') !== false) return ; if ($this->Application->GetVar('tmp')) { $path = WRITEABLE.'/tmp/'.$this->Application->GetVar('id').'_'.$this->Application->GetVar('file'); } @@ -2120,6 +2072,8 @@ header('Content-Length: '.filesize($path)); header('Content-Type: '.$type); + safeDefine('DBG_SKIP_REPORTING',1); + readfile($path); exit(); } Index: trunk/core/kernel/utility/unit_config_reader.php =================================================================== diff -u -N -r8104 -r8402 --- trunk/core/kernel/utility/unit_config_reader.php (.../unit_config_reader.php) (revision 8104) +++ trunk/core/kernel/utility/unit_config_reader.php (.../unit_config_reader.php) (revision 8402) @@ -203,6 +203,7 @@ */ function ParseConfigs() { + // 1. process normal configs and their dependencies $prioritized_configs = array(); foreach ($this->configData as $prefix => $config) { if (isset($config['ConfigPriority'])) { @@ -218,10 +219,15 @@ $clones = $this->postProcessConfig($prefix, 'Clones', 'prefix'); } + // 2. process prioritized configs and their dependencies asort($prioritized_configs); foreach ($prioritized_configs as $prefix => $priority) { $this->parseConfig($prefix); } + + foreach ($prioritized_configs as $prefix => $priority) { + $this->ProcessDependencies($prefix); + } } function AfterConfigRead() @@ -233,10 +239,57 @@ $this->Application->HandleEvent( new kEvent($prefix.':OnAfterConfigRead') ); $this->AfterConfigProcessed[] = $prefix; } - if ($this->StoreCache) $this->CacheParsedData(); + + if ($this->StoreCache) { + $this->processDynamicClones(); + $this->CacheParsedData(); + + if ($this->Application->isDebugMode(false) && constOn('DBG_VALIDATE_CONFIGS')) { + // validate configs here to have changes from OnAfterConfigRead hooks to prefixes + foreach ($this->configData as $prefix => $config) { + if (!isset($config['TableName'])) continue; + $this->ValidateConfig($prefix); + } + } + } } /** + * Re-reads all configs + * + */ + function ReReadConfigs() + { + $this->includeConfigFiles(MODULES_PATH); + $this->ParseConfigs(); + $this->AfterConfigRead(); + $this->processDynamicClones(); + } + + /** + * Process clones, that were defined via OnAfterConfigRead event + * + */ + function processDynamicClones() + { + $new_clones = Array(); + foreach ($this->configData as $prefix => $config) { + $clones = $this->postProcessConfig($prefix, 'Clones', 'prefix'); + if ($clones) { + $new_clones = array_merge($new_clones, $clones); + } + } + + // call OnAfterConfigRead for cloned configs + $new_clones = array_unique($new_clones); + foreach ($new_clones as $prefix) { + if (in_array($prefix, $this->AfterConfigProcessed)) continue; + $this->Application->HandleEvent( new kEvent($prefix.':OnAfterConfigRead') ); + $this->AfterConfigProcessed[] = $prefix; + } + } + + /** * Register nessasary classes * This method should only process the data which is cached! * @@ -349,15 +402,14 @@ // replacement templates defined in this config $this->Application->ReplacementTemplates = array_merge_recursive2($this->Application->ReplacementTemplates, $config['ReplacementTemplates']); } - - if ( $this->Application->isDebugMode(false) && constOn('DBG_VALIDATE_CONFIGS') && isset($config['TableName']) ) { - $this->ValidateConfig($prefix); - } } + function ValidateConfig($prefix) { global $debugger; + $config =& $this->configData[$prefix]; + $tablename = $config['TableName']; $float_types = Array ('float', 'double', 'numeric'); $conn =& $this->Application->GetADODBConnection(); @@ -369,8 +421,10 @@ safeDefine('DBG_RAISE_ON_WARNINGS', 1); return ; } + $res = $conn->Query('DESCRIBE '.$tablename); $config_link = $debugger->getFileLink(FULL_PATH.$this->prefixFiles[$config['Prefix']], 1, $config['Prefix']); + $error_messages = Array ( 'field_not_found' => 'Field %s exists in the database, but is not defined in config', 'default_missing' => 'Default value for field %s not set in config', @@ -380,6 +434,7 @@ 'invalid_default' => 'Default value for field %s%s not sync. to db (in config = %s, in db = %s)', 'type_missing' => 'Type definition for field %s missing in config', ); + $config_errors = Array (); $tablename = preg_replace('/^'.preg_quote(TABLE_PREFIX, '/').'(.*)/', '\\1', $tablename); // remove table prefix @@ -391,6 +446,7 @@ // skip multilingual fields continue; } + if (!array_key_exists ($f_name, $config['Fields'])) { $config_errors[] = sprintf($error_messages['field_not_found'], $f_name); } @@ -405,6 +461,7 @@ $config_errors[] = sprintf($error_messages['default_missing'], $f_name); $default_missing = true; } + if ($field['Null'] != 'YES') { // field is NOT NULL in database (MySQL5 for null returns "NO", but MySQL4 returns "") if ( $f_name != $config['IDField'] && !isset($options['not_null']) && !isset($options['required']) ) { @@ -419,9 +476,11 @@ $config_errors[] = sprintf($error_messages['not_null_error3'], $f_name); } } + if (!array_key_exists('type', $options)) { $config_errors[] = sprintf($error_messages['type_missing'], $f_name); } + if (!$default_missing) { if ($f_name == $config['IDField'] && $options['type'] != 'string' && $options['default'] !== 0) { $config_errors[] = sprintf($error_messages['invalid_default'], 'IDField ', $f_name, $this->varDump($options['default']), $this->varDump($field['Default'])); @@ -433,18 +492,19 @@ } } } + if ($config_errors) { $error_prefix = 'Config Error'.(count($config_errors) > 1 ? 's' : '').': for prefix '.$config_link.' ('.$tablename.') in unit config:
'; $config_errors = $error_prefix.'   '.implode('
   ', $config_errors); + $debugger->appendHTML($config_errors); - safeDefine('DBG_RAISE_ON_WARNINGS', 1); - } + safeDefine('DBG_RAISE_ON_WARNINGS', 1); + } } function varDump($value) { return ''.var_export($value, true).' of '.gettype($value); - } function ProcessDependencies($prefix) @@ -464,7 +524,7 @@ function postProcessConfig($prefix, $config_key, $dst_prefix_var) { $main_config =& $this->configData[$prefix]; - $sub_configs = getArrayValue($main_config, $config_key); + $sub_configs = isset($main_config[$config_key]) && $main_config[$config_key] ? $main_config[$config_key] : false; // getArrayValue($main_config, $config_key); if (!$sub_configs) { return array(); } Index: trunk/core/units/general/cat_tag_processor.php =================================================================== diff -u -N -r8391 -r8402 --- trunk/core/units/general/cat_tag_processor.php (.../cat_tag_processor.php) (revision 8391) +++ trunk/core/units/general/cat_tag_processor.php (.../cat_tag_processor.php) (revision 8402) @@ -209,7 +209,7 @@ $object =& $this->getObject($params); /* @var $object kDBItem */ - $params['cat_id'] = $object->isLoaded() ? $object->GetDBField('CategoryId') : $this->Application->GetVar('m_cat_id'); + $params['cat_id'] = $object->isLoaded() ? $object->GetDBField('ParentPath') : $this->Application->GetVar('m_cat_id'); return $perm_helper->TagPermissionCheck($params, $this->getPrefixSpecial().'_HasPermission'); } Index: trunk/core/units/categories/categories_tag_processor.php =================================================================== diff -u -N -r8385 -r8402 --- trunk/core/units/categories/categories_tag_processor.php (.../categories_tag_processor.php) (revision 8385) +++ trunk/core/units/categories/categories_tag_processor.php (.../categories_tag_processor.php) (revision 8402) @@ -6,18 +6,15 @@ { $object =& $this->getObject($params); - $sql = 'SELECT COUNT(*) - FROM '.$object->TableName.' - WHERE ParentPath LIKE "'.$object->GetDBField('ParentPath').'%"'; - if (isset($params['today']) && $params['today']) { - $sql .= ' AND CreatedOn > '.(adodb_mktime() - 86400); + $sql = 'SELECT COUNT(*) + FROM '.$object->TableName.' + WHERE (ParentPath LIKE "'.$object->GetDBField('ParentPath').'%") AND (CreatedOn > '.(adodb_mktime() - 86400).')'; + return $this->Conn->GetOne($sql) - 1; } - if (!$this->Application->IsAdmin()) { - $sql .= ' AND Status = '.STATUS_ACTIVE; - } - return $this->Conn->GetOne($sql) - 1; + return $object->GetDBField('CachedDescendantCatsQty'); + } function IsNew($params) Index: trunk/core/kernel/utility/email_send.php =================================================================== diff -u -N -r7855 -r8402 --- trunk/core/kernel/utility/email_send.php (.../email_send.php) (revision 7855) +++ trunk/core/kernel/utility/email_send.php (.../email_send.php) (revision 8402) @@ -1913,27 +1913,51 @@ { if (isset($message)) { // if message is given directly, then use it - $message_headers = Array (); - list ($headers, $message_body) = explode("\n\n", $message, 2); - $headers = explode("\n", $headers); - foreach ($headers as $header) { - $header = explode(':', $header, 2); - $message_headers[ trim($header[0]) ] = trim($header[1]); + if (is_array($message)) { + $message_headers =& $message[0]; + $message_body =& $message[1]; } + else { + $message_headers = Array (); + list ($headers, $message_body) = explode("\n\n", $message, 2); + $headers = explode("\n", $headers); + foreach ($headers as $header) { + $header = explode(':', $header, 2); + $message_headers[ trim($header[0]) ] = trim($header[1]); + } + } $composed = true; } else { // direct message not given, then assemble message from available parts $composed = $this->GetHeadersAndBody($message_headers, $message_body); } - if ($composed && $immediate_send) { - $send_method = 'Send'.$this->sendMethod; - $result = $this->$send_method($message_headers, $message_body); - - if ($immediate_clear) { - $this->Clear(); + if ($composed) { + if ($immediate_send) { + $send_method = 'Send'.$this->sendMethod; + $result = $this->$send_method($message_headers, $message_body); + + if ($immediate_clear) { + $this->Clear(); + } + return $result; } - return $result; + else { + $fields_hash = Array ( + 'ToEmail' => $message_headers['To'], + 'Subject' => $message_headers['Subject'], + 'Queued' => adodb_mktime(), + 'SendRetries' => 0, + 'LastSendRetry' => 0, + ); + $fields_hash['MessageHeaders'] = serialize($message_headers); + $fields_hash['MessageBody'] =& $message_body; + $this->Conn->doInsert($fields_hash, TABLE_PREFIX.'EmailQueue'); + + if ($immediate_clear) { + $this->Clear(); + } + } } // if not immediate send, then send result is positive :)