Index: branches/5.2.x/core/kernel/db/db_event_handler.php =================================================================== --- branches/5.2.x/core/kernel/db/db_event_handler.php (revision 16560) +++ branches/5.2.x/core/kernel/db/db_event_handler.php (revision 16561) @@ -1,3480 +1,3480 @@ getPrefixSpecial(true) instead of * $event->getPrefixSpecial() as usual. This is due PHP * is converting "." symbols in variable names during * submit info "_". $event->getPrefixSpecial optional * 1st parameter returns correct current Prefix_Special * for variables being submitted such way (e.g. variable * name that will be converted by PHP: "users.read_only_id" * will be submitted as "users_read_only_id". * * 2. When using $this->Application-LinkVar on variables submitted * from form which contain $Prefix_Special then note 1st item. Example: * LinkVar($event->getPrefixSpecial(true).'_varname',$event->getPrefixSpecial().'_varname') * */ /** * EventHandler that is used to process * any database related events * */ class kDBEventHandler extends kEventHandler { /** * Checks permissions of user * * @param kEvent $event * @return bool * @access public */ public function CheckPermission(kEvent $event) { $section = $event->getSection(); if ( !$this->Application->isAdmin ) { $allow_events = Array ('OnSearch', 'OnSearchReset', 'OnNew'); if ( in_array($event->Name, $allow_events) ) { // allow search on front return true; } } elseif ( ($event->Name == 'OnPreSaveAndChangeLanguage') && !$this->UseTempTables($event) ) { // allow changing language in grids, when not in editing mode return $this->Application->CheckPermission($section . '.view', 1); } if ( !preg_match('/^CATEGORY:(.*)/', $section) ) { // only if not category item events if ( (substr($event->Name, 0, 9) == 'OnPreSave') || ($event->Name == 'OnSave') ) { if ( $this->isNewItemCreate($event) ) { return $this->Application->CheckPermission($section . '.add', 1); } else { return $this->Application->CheckPermission($section . '.add', 1) || $this->Application->CheckPermission($section . '.edit', 1); } } } if ( $event->Name == 'OnPreCreate' ) { // save category_id before item create (for item category selector not to destroy permission checking category) $this->Application->LinkVar('m_cat_id'); } return parent::CheckPermission($event); } /** * Allows to override standard permission mapping * * @return void * @access protected * @see kEventHandler::$permMapping */ protected function mapPermissions() { parent::mapPermissions(); $permissions = Array ( 'OnLoad' => Array ('self' => 'view', 'subitem' => 'view'), 'OnItemBuild' => Array ('self' => 'view', 'subitem' => 'view'), 'OnSuggestValues' => Array ('self' => 'admin', 'subitem' => 'admin'), 'OnSuggestValuesJSON' => Array ('self' => 'admin', 'subitem' => 'admin'), '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'), 'OnSetPrimary' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnDelete' => Array ('self' => 'delete', 'subitem' => 'add|edit'), 'OnDeleteAll' => Array ('self' => 'delete', 'subitem' => 'add|edit'), 'OnMassDelete' => Array ('self' => 'delete', 'subitem' => 'add|edit'), 'OnMassClone' => Array ('self' => 'add', 'subitem' => 'add|edit'), 'OnCut' => Array ('self'=>'edit', 'subitem' => 'edit'), 'OnCopy' => Array ('self'=>'edit', 'subitem' => 'edit'), 'OnPaste' => Array ('self'=>'edit', 'subitem' => 'edit'), 'OnSelectItems' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnProcessSelected' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnStoreSelected' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnSelectUser' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnMassApprove' => Array ('self' => 'advanced:approve|edit', 'subitem' => 'advanced:approve|add|edit'), 'OnMassDecline' => Array ('self' => 'advanced:decline|edit', 'subitem' => 'advanced:decline|add|edit'), 'OnMassMoveUp' => Array ('self' => 'advanced:move_up|edit', 'subitem' => 'advanced:move_up|add|edit'), 'OnMassMoveDown' => Array ('self' => 'advanced:move_down|edit', 'subitem' => 'advanced:move_down|add|edit'), 'OnPreCreate' => Array ('self' => 'add|add.pending', 'subitem' => 'edit|edit.pending'), 'OnEdit' => Array ('self' => 'edit|edit.pending', 'subitem' => 'edit|edit.pending'), 'OnExport' => Array ('self' => 'view|advanced:export'), 'OnExportBegin' => Array ('self' => 'view|advanced:export'), 'OnExportProgress' => Array ('self' => 'view|advanced:export'), 'OnSetAutoRefreshInterval' => Array ('self' => true, 'subitem' => true), 'OnAutoRefreshToggle' => Array ('self' => true, 'subitem' => true), // theese event do not harm, but just in case check them too :) 'OnCancelEdit' => Array ('self' => true, 'subitem' => true), 'OnCancel' => Array ('self' => true, 'subitem' => true), 'OnReset' => Array ('self' => true, 'subitem' => true), 'OnSetSorting' => Array ('self' => true, 'subitem' => true), 'OnSetSortingDirect' => Array ('self' => true, 'subitem' => true), 'OnResetSorting' => Array ('self' => true, 'subitem' => true), 'OnSetFilter' => Array ('self' => true, 'subitem' => true), 'OnApplyFilters' => Array ('self' => true, 'subitem' => true), 'OnRemoveFilters' => Array ('self' => true, 'subitem' => true), 'OnSetFilterPattern' => Array ('self' => true, 'subitem' => true), 'OnSetPerPage' => Array ('self' => true, 'subitem' => true), 'OnSetPage' => Array ('self' => true, 'subitem' => true), 'OnSearch' => Array ('self' => true, 'subitem' => true), 'OnSearchReset' => Array ('self' => true, 'subitem' => true), 'OnGoBack' => Array ('self' => true, 'subitem' => true), // it checks permission itself since flash uploader does not send cookies 'OnUploadFile' => Array ('self' => true, 'subitem' => true), 'OnDeleteFile' => Array ('self' => true, 'subitem' => true), 'OnViewFile' => Array ('self' => true, 'subitem' => true), 'OnSaveWidths' => Array ('self' => 'admin', 'subitem' => 'admin'), 'OnValidateMInputFields' => Array ('self' => 'view'), 'OnValidateField' => Array ('self' => true, 'subitem' => true), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Define alternative event processing method names * * @return void * @see kEventHandler::$eventMethods * @access protected */ protected function mapEvents() { $events_map = Array ( 'OnRemoveFilters' => 'FilterAction', 'OnApplyFilters' => 'FilterAction', 'OnMassApprove' => 'iterateItems', 'OnMassDecline' => 'iterateItems', 'OnMassMoveUp' => 'iterateItems', 'OnMassMoveDown' => 'iterateItems', ); $this->eventMethods = array_merge($this->eventMethods, $events_map); } /** * Returns ID of current item to be edited * by checking ID passed in get/post as prefix_id * or by looking at first from selected ids, stored. * Returned id is also stored in Session in case * it was explicitly passed as get/post * * @param kEvent $event * @return int * @access public */ public function getPassedID(kEvent $event) { if ( $event->getEventParam('raise_warnings') === false ) { $event->setEventParam('raise_warnings', 1); } if ( $event->Special == 'previous' || $event->Special == 'next' ) { /** @var kDBItem $object */ $object = $this->Application->recallObject($event->getEventParam('item')); /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); $select_clause = $this->Application->getUnitOption($object->Prefix, 'NavigationSelectClause', NULL); return $list_helper->getNavigationResource($object, $event->getEventParam('list'), $event->Special == 'next', $select_clause); } elseif ( $event->Special == 'filter' ) { // temporary object, used to print filter options only return 0; } if ( preg_match('/^auto-(.*)/', $event->Special, $regs) && $this->Application->prefixRegistred($regs[1]) ) { // - returns field DateFormat value from language (LanguageId is extracted from current phrase object) /** @var kDBItem $main_object */ $main_object = $this->Application->recallObject($regs[1]); $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); return $main_object->GetDBField($id_field); } // 1. get id from post (used in admin) $ret = $this->Application->GetVar($event->getPrefixSpecial(true) . '_id'); if ( ($ret !== false) && ($ret != '') ) { $event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true); return $ret; } // 2. get id from env (used in front) $ret = $this->Application->GetVar($event->getPrefixSpecial() . '_id'); if ( ($ret !== false) && ($ret != '') ) { $event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true); return $ret; } // recall selected ids array and use the first one $ids = $this->Application->GetVar($event->getPrefixSpecial() . '_selected_ids'); if ( $ids != '' ) { $ids = explode(',', $ids); if ( $ids ) { $ret = array_shift($ids); $event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true); } } else { // if selected ids are not yet stored $this->StoreSelectedIDs($event); // StoreSelectedIDs sets this variable. $ret = $this->Application->GetVar($event->getPrefixSpecial() . '_id'); if ( ($ret !== false) && ($ret != '') ) { $event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true); return $ret; } } return $ret; } /** * Prepares and stores selected_ids string * in Session and Application Variables * by getting all checked ids from grid plus * id passed in get/post as prefix_id * * @param kEvent $event * @param Array $direct_ids * @return Array * @access protected */ protected function StoreSelectedIDs(kEvent $event, $direct_ids = NULL) { $wid = $this->Application->GetTopmostWid($event->Prefix); $session_name = rtrim($event->getPrefixSpecial() . '_selected_ids_' . $wid, '_'); $ids = $event->getEventParam('ids'); if ( isset($direct_ids) || ($ids !== false) ) { // save ids directly if they given + reset array indexes $resulting_ids = $direct_ids ? array_values($direct_ids) : ($ids ? array_values($ids) : false); if ( $resulting_ids ) { $this->Application->SetVar($event->getPrefixSpecial() . '_selected_ids', implode(',', $resulting_ids)); $this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name, '', true); $this->Application->SetVar($event->getPrefixSpecial() . '_id', $resulting_ids[0]); return $resulting_ids; } return Array (); } $ret = Array (); // May be we don't need this part: ? $passed = $this->Application->GetVar($event->getPrefixSpecial(true) . '_id'); if ( $passed !== false && $passed != '' ) { array_push($ret, $passed); } $ids = Array (); // get selected ids from post & save them to session $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( $items_info ) { $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); foreach ($items_info as $id => $field_values) { if ( getArrayValue($field_values, $id_field) ) { array_push($ids, $id); } } //$ids = array_keys($items_info); } $ret = array_unique(array_merge($ret, $ids)); $this->Application->SetVar($event->getPrefixSpecial() . '_selected_ids', implode(',', $ret)); $this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name, '', !$ret); // optional when IDs are missing // This is critical - otherwise getPassedID will return last ID stored in session! (not exactly true) // this smells... needs to be refactored $first_id = getArrayValue($ret, 0); if ( ($first_id === false) && ($event->getEventParam('raise_warnings') == 1) ) { if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->appendTrace(); } trigger_error('Requested ID for prefix ' . $event->getPrefixSpecial() . ' not passed', E_USER_NOTICE); } $this->Application->SetVar($event->getPrefixSpecial() . '_id', $first_id); return $ret; } /** * Returns stored selected ids as an array * * @param kEvent $event * @param bool $from_session return ids from session (written, when editing was started) * @return Array * @access protected */ protected function getSelectedIDs(kEvent $event, $from_session = false) { if ( $from_session ) { $wid = $this->Application->GetTopmostWid($event->Prefix); $var_name = rtrim($event->getPrefixSpecial() . '_selected_ids_' . $wid, '_'); $ret = $this->Application->RecallVar($var_name); } else { $ret = $this->Application->GetVar($event->getPrefixSpecial() . '_selected_ids'); } return explode(',', $ret); } /** * Stores IDs, selected in grid in session * * @param kEvent $event * @return void * @access protected */ protected function OnStoreSelected(kEvent $event) { $this->StoreSelectedIDs($event); $id = $this->Application->GetVar($event->getPrefixSpecial() . '_id'); if ( $id !== false ) { $event->SetRedirectParam($event->getPrefixSpecial() . '_id', $id); $event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial()); } } /** * Returns associative array of submitted fields for current item * Could be used while creating/editing single item - * meaning on any edit form, except grid edit * * @param kEvent $event * @return Array * @access protected */ protected function getSubmittedFields(kEvent $event) { $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); $field_values = $items_info ? array_shift($items_info) : Array (); return $field_values; } /** * Removes any information about current/selected ids * from Application variables and Session * * @param kEvent $event * @return void * @access protected */ protected function clearSelectedIDs(kEvent $event) { $prefix_special = $event->getPrefixSpecial(); $ids = implode(',', $this->getSelectedIDs($event, true)); $event->setEventParam('ids', $ids); $wid = $this->Application->GetTopmostWid($event->Prefix); $session_name = rtrim($prefix_special . '_selected_ids_' . $wid, '_'); $this->Application->RemoveVar($session_name); $this->Application->SetVar($prefix_special . '_selected_ids', ''); $this->Application->SetVar($prefix_special . '_id', ''); // $event->getPrefixSpecial(true) . '_id' too may be } /** * Common builder part for Item & List * * @param kDBBase|kDBItem|kDBList $object * @param kEvent $event * @return void * @access protected */ protected function dbBuild(&$object, kEvent $event) { // for permission checking inside item/list build events $event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true)); if ( $event->getEventParam('form_name') !== false ) { $form_name = $event->getEventParam('form_name'); } else { $request_forms = $this->Application->GetVar('forms', Array ()); $form_name = (string)getArrayValue($request_forms, $object->getPrefixSpecial()); } $object->Configure($event->getEventParam('populate_ml_fields') || $this->Application->getUnitOption($event->Prefix, 'PopulateMlFields'), $form_name); $this->PrepareObject($object, $event); $parent_event = $event->getEventParam('parent_event'); if ( is_object($parent_event) ) { $object->setParentEvent($parent_event); } // force live table if specified or is original item $live_table = $event->getEventParam('live_table') || $event->Special == 'original'; if ( $this->UseTempTables($event) && !$live_table ) { $object->SwitchToTemp(); } $this->Application->setEvent($event->getPrefixSpecial(), ''); $save_event = $this->UseTempTables($event) && $this->Application->GetTopmostPrefix($event->Prefix) == $event->Prefix ? 'OnSave' : 'OnUpdate'; $this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', $save_event); } /** * Checks, that currently loaded item is allowed for viewing (non permission-based) * * @param kEvent $event * @return bool * @access protected */ protected function checkItemStatus(kEvent $event) { $status_fields = $this->Application->getUnitOption($event->Prefix, 'StatusField'); if ( !$status_fields ) { return true; } $status_field = array_shift($status_fields); if ( $status_field == 'Status' || $status_field == 'Enabled' ) { /** @var kDBItem $object */ $object = $event->getObject(); if ( !$object->isLoaded() ) { return true; } return $object->GetDBField($status_field) == STATUS_ACTIVE; } return true; } /** * Shows not found template content * * @param kEvent $event * @return void * @access protected */ protected function _errorNotFound(kEvent $event) { if ( $event->getEventParam('raise_warnings') === 0 ) { // when it's possible, that autoload fails do nothing return; } if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->appendTrace(); } trigger_error('ItemLoad Permission Failed for prefix [' . $event->getPrefixSpecial() . '] in checkItemStatus, leading to "404 Not Found"', E_USER_NOTICE); $this->Application->UrlManager->show404(); } /** * Builds item (loads if needed) * * Pattern: Prototype Manager * * @param kEvent $event * @access protected */ protected function OnItemBuild(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $this->dbBuild($object, $event); $sql = $this->ItemPrepareQuery($event); $sql = $this->Application->ReplaceLanguageTags($sql); $object->setSelectSQL($sql); // 2. loads if allowed $auto_load = $this->Application->getUnitOption($event->Prefix,'AutoLoad'); $skip_autoload = $event->getEventParam('skip_autoload'); if ( $auto_load && !$skip_autoload ) { $perm_status = true; $user_id = $this->Application->InitDone ? $this->Application->RecallVar('user_id') : USER_ROOT; $event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true)); $status_checked = false; - if ( $user_id == USER_ROOT || $this->CheckPermission($event) ) { - // don't autoload item, when user doesn't have view permission + if ( $this->Application->permissionCheckingDisabled($user_id) || $this->CheckPermission($event) ) { + // Don't autoload item, when user doesn't have view permission. $this->LoadItem($event); $status_checked = true; $editing_mode = defined('EDITING_MODE') ? EDITING_MODE : false; $id_from_request = $event->getEventParam(kEvent::FLAG_ID_FROM_REQUEST); - if ( $user_id != USER_ROOT + if ( !$this->Application->permissionCheckingDisabled($user_id) && !$this->Application->isAdmin && !($editing_mode || ($id_from_request ? $this->checkItemStatus($event) : true)) ) { - // non-root user AND on front-end AND (not editing mode || incorrect status) + // Permissions are being checked AND on Front-End AND (not editing mode || incorrect status). $perm_status = false; } } else { $perm_status = false; } if ( !$perm_status ) { // when no permission to view item -> redirect to no permission template $this->_processItemLoadingError($event, $status_checked); } } /** @var Params $actions */ $actions = $this->Application->recallObject('kActions'); $actions->Set($event->getPrefixSpecial() . '_GoTab', ''); $actions->Set($event->getPrefixSpecial() . '_GoId', ''); $actions->Set('forms[' . $event->getPrefixSpecial() . ']', $object->getFormName()); } /** * Processes case, when item wasn't loaded because of lack of permissions * * @param kEvent $event * @param bool $status_checked * @throws kNoPermissionException * @return void * @access protected */ protected function _processItemLoadingError($event, $status_checked) { $current_template = $this->Application->GetVar('t'); $redirect_template = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate'); $error_msg = 'ItemLoad Permission Failed for prefix [' . $event->getPrefixSpecial() . '] in ' . ($status_checked ? 'checkItemStatus' : 'CheckPermission') . ''; if ( $current_template == $redirect_template ) { // don't perform "no_permission" redirect if already on a "no_permission" template if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->appendTrace(); } trigger_error($error_msg, E_USER_NOTICE); return; } if ( MOD_REWRITE ) { $redirect_params = Array ( 'm_cat_id' => 0, 'next_template' => 'external:' . $_SERVER['REQUEST_URI'], ); } else { $redirect_params = Array ( 'next_template' => $current_template, ); } $exception = new kNoPermissionException($error_msg); $exception->setup($redirect_template, $redirect_params); throw $exception; } /** * Build sub-tables array from configs * * @param kEvent $event * @return void * @access protected */ protected function OnTempHandlerBuild(kEvent $event) { /** @var kTempTablesHandler $object */ $object = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler'); /** @var kEvent $parent_event */ $parent_event = $event->getEventParam('parent_event'); if ( is_object($parent_event) ) { $object->setParentEvent($parent_event); } $object->BuildTables($event->Prefix, $this->getSelectedIDs($event)); } /** * Checks, that object used in event should use temp tables * * @param kEvent $event * @return bool * @access protected */ protected function UseTempTables(kEvent $event) { $top_prefix = $this->Application->GetTopmostPrefix($event->Prefix); // passed parent, not always actual $special = ($top_prefix == $event->Prefix) ? $event->Special : $this->getMainSpecial($event); return $this->Application->IsTempMode($event->Prefix, $special); } /** * Load item if id is available * * @param kEvent $event * @return void * @access protected */ protected function LoadItem(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $id = $this->getPassedID($event); if ( $object->isLoaded() && !is_array($id) && ($object->GetID() == $id) ) { // object is already loaded by same id return ; } if ( $object->Load($id) ) { /** @var Params $actions */ $actions = $this->Application->recallObject('kActions'); $actions->Set($event->getPrefixSpecial() . '_id', $object->GetID()); } else { $object->setID( is_array($id) ? false : $id ); } } /** * Builds list * * Pattern: Prototype Manager * * @param kEvent $event * @access protected */ protected function OnListBuild(kEvent $event) { /** @var kDBList $object */ $object = $event->getObject(); /*if ( $this->Application->isDebugMode() ) { $event_params = http_build_query($event->getEventParams()); $this->Application->Debugger->appendHTML('InitList "' . $event->getPrefixSpecial() . '" (' . $event_params . ')'); }*/ $this->dbBuild($object, $event); if ( !$object->isMainList() && $event->getEventParam('main_list') ) { // once list is set to main, then even "requery" parameter can't remove that /*$passed = $this->Application->GetVar('passed'); $this->Application->SetVar('passed', $passed . ',' . $event->Prefix);*/ $object->becameMain(); } $object->setGridName($event->getEventParam('grid')); $sql = $this->ListPrepareQuery($event); $sql = $this->Application->ReplaceLanguageTags($sql); $object->setSelectSQL($sql); $object->reset(); if ( $event->getEventParam('skip_parent_filter') === false ) { $object->linkToParent($this->getMainSpecial($event)); } $this->AddFilters($event); $this->SetCustomQuery($event); // new!, use this for dynamic queries based on specials for ex. $this->SetPagination($event); $this->SetSorting($event); /** @var Params $actions */ $actions = $this->Application->recallObject('kActions'); $actions->Set('remove_specials[' . $event->getPrefixSpecial() . ']', '0'); $actions->Set($event->getPrefixSpecial() . '_GoTab', ''); } /** * Returns special of main item for linking with sub-item * * @param kEvent $event * @return string * @access protected */ protected function getMainSpecial(kEvent $event) { $main_special = $event->getEventParam('main_special'); if ( $main_special === false ) { // main item's special not passed if ( substr($event->Special, -5) == '-item' ) { // temp handler added "-item" to given special -> process that here return substr($event->Special, 0, -5); } // by default subitem's special is used for main item searching return $event->Special; } return $main_special; } /** * Apply any custom changes to list's sql query * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetCustomQuery(kEvent $event) { } /** * Set's new per-page for grid * * @param kEvent $event * @return void * @access protected */ protected function OnSetPerPage(kEvent $event) { $per_page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_PerPage'); $event->SetRedirectParam($event->getPrefixSpecial() . '_PerPage', $per_page); $event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial()); if ( !$this->Application->isAdminUser ) { /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); $this->_passListParams($event, 'per_page'); } } /** * Occurs when page is changed (only for hooking) * * @param kEvent $event * @return void * @access protected */ protected function OnSetPage(kEvent $event) { $page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page'); $event->SetRedirectParam($event->getPrefixSpecial() . '_Page', $page); $event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial()); if ( !$this->Application->isAdminUser ) { $this->_passListParams($event, 'page'); } } /** * Passes through main list pagination and sorting * * @param kEvent $event * @param string $skip_var * @return void * @access protected */ protected function _passListParams($event, $skip_var) { $param_names = array_diff(Array ('page', 'per_page', 'sort_by'), Array ($skip_var)); /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); foreach ($param_names as $param_name) { $value = $this->Application->GetVar($param_name); switch ($param_name) { case 'page': if ( $value > 1 ) { $event->SetRedirectParam('page', $value); } break; case 'per_page': if ( $value > 0 ) { if ( $value != $list_helper->getDefaultPerPage($event->Prefix) ) { $event->SetRedirectParam('per_page', $value); } } break; case 'sort_by': $event->setPseudoClass('_List'); /** @var kDBList $object */ $object = $event->getObject(Array ('main_list' => 1)); if ( $list_helper->hasUserSorting($object) ) { $event->SetRedirectParam('sort_by', $value); } break; } } } /** * Set's correct page for list based on data provided with event * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetPagination(kEvent $event) { /** @var kDBList $object */ $object = $event->getObject(); // get PerPage (forced -> session -> config -> 10) $object->SetPerPage($this->getPerPage($event)); // main lists on Front-End have special get parameter for page $page = $object->isMainList() ? $this->Application->GetVar('page') : false; if ( !$page ) { // page is given in "env" variable for given prefix $page = $this->Application->GetVar($event->getPrefixSpecial() . '_Page'); } if ( !$page && $event->Special ) { // when not part of env, then variables like "prefix.special_Page" are // replaced (by PHP) with "prefix_special_Page", so check for that too $page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page'); } if ( !$object->isMainList() ) { // main lists doesn't use session for page storing $this->Application->StoreVarDefault($event->getPrefixSpecial() . '_Page', 1, true); // true for optional if ( $page ) { // page found in request -> store in session $this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional } else { // page not found in request -> get from session $page = $this->Application->RecallVar($event->getPrefixSpecial() . '_Page'); } if ( !$event->getEventParam('skip_counting') ) { // when stored page is larger, then maximal list page number // (such case is also processed in kDBList::Query method) $pages = $object->GetTotalPages(); if ( $page > $pages ) { $page = 1; $this->Application->StoreVar($event->getPrefixSpecial() . '_Page', 1, true); } } } $object->SetPage($page); } /** * Returns current per-page setting for list * * @param kEvent $event * @return int * @access protected */ protected function getPerPage(kEvent $event) { /** @var kDBList $object */ $object = $event->getObject(); $per_page = $event->getEventParam('per_page'); if ( $per_page ) { // per-page is passed as tag parameter to PrintList, InitList, etc. $config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping'); // 2. per-page setting is stored in configuration variable if ( $config_mapping ) { // such pseudo per-pages are only defined in templates directly switch ($per_page) { case 'short_list': $per_page = $this->Application->ConfigValue($config_mapping['ShortListPerPage']); break; case 'default': $per_page = $this->Application->ConfigValue($config_mapping['PerPage']); break; } } return $per_page; } if ( !$per_page && $object->isMainList() ) { // main lists on Front-End have special get parameter for per-page $per_page = $this->Application->GetVar('per_page'); } if ( !$per_page ) { // per-page is given in "env" variable for given prefix $per_page = $this->Application->GetVar($event->getPrefixSpecial() . '_PerPage'); } if ( !$per_page && $event->Special ) { // when not part of env, then variables like "prefix.special_PerPage" are // replaced (by PHP) with "prefix_special_PerPage", so check for that too $per_page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_PerPage'); } if ( !$object->isMainList() ) { // per-page given in env and not in main list $view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view'); if ( $per_page ) { // per-page found in request -> store in session and persistent session $this->setListSetting($event, 'PerPage', $per_page); } else { // per-page not found in request -> get from pesistent session (or session) $per_page = $this->getListSetting($event, 'PerPage'); } } if ( !$per_page ) { // per page wan't found in request/session/persistent session /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); // allow to override default per-page value from tag $default_per_page = $event->getEventParam('default_per_page'); if ( !is_numeric($default_per_page) ) { $default_per_page = $this->Application->ConfigValue('DefaultGridPerPage'); } $per_page = $list_helper->getDefaultPerPage($event->Prefix, $default_per_page); } return $per_page; } /** * Set's correct sorting for list based on data provided with event * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetSorting(kEvent $event) { $event->setPseudoClass('_List'); /** @var kDBList $object */ $object = $event->getObject(); if ( $object->isMainList() ) { $sort_by = $this->Application->GetVar('sort_by'); $cur_sort1 = $cur_sort1_dir = $cur_sort2 = $cur_sort2_dir = false; if ( $sort_by ) { $sortings = explode('|', $sort_by); list ($cur_sort1, $cur_sort1_dir) = explode(',', $sortings[0]); if ( isset($sortings[1]) ) { list ($cur_sort2, $cur_sort2_dir) = explode(',', $sortings[1]); } } } else { $sorting_settings = $this->getListSetting($event, 'Sortings'); $cur_sort1 = getArrayValue($sorting_settings, 'Sort1'); $cur_sort1_dir = getArrayValue($sorting_settings, 'Sort1_Dir'); $cur_sort2 = getArrayValue($sorting_settings, 'Sort2'); $cur_sort2_dir = getArrayValue($sorting_settings, 'Sort2_Dir'); } $tag_sort_by = $event->getEventParam('sort_by'); if ( $tag_sort_by ) { if ( $tag_sort_by == 'random' ) { $object->AddOrderField('RAND()', ''); } else { // multiple sortings could be specified at once $tag_sort_by = explode('|', $tag_sort_by); foreach ($tag_sort_by as $sorting_element) { list ($by, $dir) = explode(',', $sorting_element); $object->AddOrderField($by, $dir); } } } $list_sortings = $this->_getDefaultSorting($event); // use default if not specified in session if ( !$cur_sort1 || !$cur_sort1_dir ) { $sorting = getArrayValue($list_sortings, 'Sorting'); if ( $sorting ) { reset($sorting); $cur_sort1 = key($sorting); $cur_sort1_dir = current($sorting); if ( next($sorting) ) { $cur_sort2 = key($sorting); $cur_sort2_dir = current($sorting); } } } // always add forced sorting before any user sorting fields /** @var Array $forced_sorting */ $forced_sorting = getArrayValue($list_sortings, 'ForcedSorting'); if ( $forced_sorting ) { foreach ($forced_sorting as $field => $dir) { $object->AddOrderField($field, $dir); } } // add user sorting fields if ( $cur_sort1 != '' && $cur_sort1_dir != '' ) { $object->AddOrderField($cur_sort1, $cur_sort1_dir); } if ( $cur_sort2 != '' && $cur_sort2_dir != '' ) { $object->AddOrderField($cur_sort2, $cur_sort2_dir); } } /** * Returns default list sortings * * @param kEvent $event * @return Array * @access protected */ protected function _getDefaultSorting(kEvent $event) { $list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings', Array ()); $sorting_prefix = array_key_exists($event->Special, $list_sortings) ? $event->Special : ''; $sorting_configs = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping'); if ( $sorting_configs && array_key_exists('DefaultSorting1Field', $sorting_configs) ) { // sorting defined in configuration variables overrides one from unit config $list_sortings[$sorting_prefix]['Sorting'] = Array ( $this->Application->ConfigValue($sorting_configs['DefaultSorting1Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting1Dir']), $this->Application->ConfigValue($sorting_configs['DefaultSorting2Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting2Dir']), ); // TODO: lowercase configuration variable values in db, instead of here $list_sortings[$sorting_prefix]['Sorting'] = array_map('strtolower', $list_sortings[$sorting_prefix]['Sorting']); } return isset($list_sortings[$sorting_prefix]) ? $list_sortings[$sorting_prefix] : Array (); } /** * Gets list setting by name (persistent or real session) * * @param kEvent $event * @param string $variable_name * @return string|Array * @access protected */ protected function getListSetting(kEvent $event, $variable_name) { $view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view'); $storage_prefix = $event->getEventParam('same_special') ? $event->Prefix : $event->getPrefixSpecial(); // get sorting from persistent session $default_value = $this->Application->isAdmin ? ALLOW_DEFAULT_SETTINGS : false; $variable_value = $this->Application->RecallPersistentVar($storage_prefix . '_' . $variable_name . '.' . $view_name, $default_value); /*if ( !$variable_value ) { // get sorting from session $variable_value = $this->Application->RecallVar($storage_prefix . '_' . $variable_name); }*/ if ( kUtil::IsSerialized($variable_value) ) { $variable_value = unserialize($variable_value); } return $variable_value; } /** * Sets list setting by name (persistent and real session) * * @param kEvent $event * @param string $variable_name * @param string|Array $variable_value * @return void * @access protected */ protected function setListSetting(kEvent $event, $variable_name, $variable_value = NULL) { $view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view'); // $this->Application->StoreVar($event->getPrefixSpecial() . '_' . $variable_name, $variable_value, true); //true for optional if ( isset($variable_value) ) { if ( is_array($variable_value) ) { $variable_value = serialize($variable_value); } $this->Application->StorePersistentVar($event->getPrefixSpecial() . '_' . $variable_name . '.' . $view_name, $variable_value, true); //true for optional } else { $this->Application->RemovePersistentVar($event->getPrefixSpecial() . '_' . $variable_name . '.' . $view_name); } } /** * Add filters found in session * * @param kEvent $event * @return void * @access protected */ protected function AddFilters(kEvent $event) { /** @var kDBList $object */ $object = $event->getObject(); $edit_mark = rtrim($this->Application->GetSID() . '_' . $this->Application->GetTopmostWid($event->Prefix), '_'); // add search filter $filter_data = $this->Application->RecallVar($event->getPrefixSpecial() . '_search_filter'); if ( $filter_data ) { $filter_data = unserialize($filter_data); foreach ($filter_data as $filter_field => $filter_params) { $filter_type = ($filter_params['type'] == 'having') ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER; $filter_value = str_replace(EDIT_MARK, $edit_mark, $filter_params['value']); $object->addFilter($filter_field, $filter_value, $filter_type, kDBList::FLT_SEARCH); } } // add custom filter $view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view'); $custom_filters = $this->Application->RecallPersistentVar($event->getPrefixSpecial() . '_custom_filter.' . $view_name); if ( $custom_filters ) { $grid_name = $event->getEventParam('grid'); $custom_filters = unserialize($custom_filters); if ( isset($custom_filters[$grid_name]) ) { foreach ($custom_filters[$grid_name] as $field_name => $field_options) { list ($filter_type, $field_options) = each($field_options); if ( isset($field_options['value']) && $field_options['value'] ) { $filter_type = ($field_options['sql_filter_type'] == 'having') ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER; $filter_value = str_replace(EDIT_MARK, $edit_mark, $field_options['value']); $object->addFilter($field_name, $filter_value, $filter_type, kDBList::FLT_CUSTOM); } } } } // add view filter $view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter'); if ( $view_filter ) { $view_filter = unserialize($view_filter); /** @var kMultipleFilter $temp_filter */ $temp_filter = $this->Application->makeClass('kMultipleFilter'); $filter_menu = $this->Application->getUnitOption($event->Prefix, 'FilterMenu'); $group_key = 0; $group_count = count($filter_menu['Groups']); while ($group_key < $group_count) { $group_info = $filter_menu['Groups'][$group_key]; $temp_filter->setType(constant('kDBList::FLT_TYPE_' . $group_info['mode'])); $temp_filter->clearFilters(); foreach ($group_info['filters'] as $flt_id) { $sql_key = getArrayValue($view_filter, $flt_id) ? 'on_sql' : 'off_sql'; if ( $filter_menu['Filters'][$flt_id][$sql_key] != '' ) { $temp_filter->addFilter('view_filter_' . $flt_id, $filter_menu['Filters'][$flt_id][$sql_key]); } } $object->addFilter('view_group_' . $group_key, $temp_filter, $group_info['type'], kDBList::FLT_VIEW); $group_key++; } } // add item filter if ( $object->isMainList() ) { $this->applyItemFilters($event); } } /** * Applies item filters * * @param kEvent $event * @return void * @access protected */ protected function applyItemFilters($event) { $filter_values = $this->Application->GetVar('filters', Array ()); if ( !$filter_values ) { return; } /** @var kDBList $object */ $object = $event->getObject(); $where_clause = Array ( 'ItemPrefix = ' . $this->Conn->qstr($object->Prefix), 'FilterField IN (' . implode(',', $this->Conn->qstrArray(array_keys($filter_values))) . ')', 'Enabled = 1', ); $sql = 'SELECT * FROM ' . $this->Application->getUnitOption('item-filter', 'TableName') . ' WHERE (' . implode(') AND (', $where_clause) . ')'; $filters = $this->Conn->Query($sql, 'FilterField'); foreach ($filters as $filter_field => $filter_data) { $filter_value = $filter_values[$filter_field]; if ( "$filter_value" === '' ) { // ListManager don't pass empty values, but check here just in case continue; } $table_name = $object->isVirtualField($filter_field) ? '' : '%1$s.'; switch ($filter_data['FilterType']) { case 'radio': $filter_value = $table_name . '`' . $filter_field . '` = ' . $this->Conn->qstr($filter_value); break; case 'checkbox': $filter_value = explode('|', substr($filter_value, 1, -1)); $filter_value = $this->Conn->qstrArray($filter_value, 'escape'); if ( $object->GetFieldOption($filter_field, 'multiple') ) { $filter_value = $table_name . '`' . $filter_field . '` LIKE "%|' . implode('|%" OR ' . $table_name . '`' . $filter_field . '` LIKE "%|', $filter_value) . '|%"'; } else { $filter_value = $table_name . '`' . $filter_field . '` IN (' . implode(',', $filter_value) . ')'; } break; case 'range': $filter_value = $this->Conn->qstrArray(explode('-', $filter_value)); $filter_value = $table_name . '`' . $filter_field . '` BETWEEN ' . $filter_value[0] . ' AND ' . $filter_value[1]; break; } $object->addFilter('item_filter_' . $filter_field, $filter_value, $object->isVirtualField($filter_field) ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER); } } /** * Set's new sorting for list * * @param kEvent $event * @return void * @access protected */ protected function OnSetSorting(kEvent $event) { $sorting_settings = $this->getListSetting($event, 'Sortings'); $cur_sort1 = getArrayValue($sorting_settings, 'Sort1'); $cur_sort1_dir = getArrayValue($sorting_settings, 'Sort1_Dir'); $use_double_sorting = $this->Application->ConfigValue('UseDoubleSorting'); if ( $use_double_sorting ) { $cur_sort2 = getArrayValue($sorting_settings, 'Sort2'); $cur_sort2_dir = getArrayValue($sorting_settings, 'Sort2_Dir'); } $passed_sort1 = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Sort1'); if ( $cur_sort1 == $passed_sort1 ) { $cur_sort1_dir = $cur_sort1_dir == 'asc' ? 'desc' : 'asc'; } else { if ( $use_double_sorting ) { $cur_sort2 = $cur_sort1; $cur_sort2_dir = $cur_sort1_dir; } $cur_sort1 = $passed_sort1; $cur_sort1_dir = 'asc'; } $sorting_settings = Array ('Sort1' => $cur_sort1, 'Sort1_Dir' => $cur_sort1_dir); if ( $use_double_sorting ) { $sorting_settings['Sort2'] = $cur_sort2; $sorting_settings['Sort2_Dir'] = $cur_sort2_dir; } $this->setListSetting($event, 'Sortings', $sorting_settings); } /** * Set sorting directly to session (used for category item sorting (front-end), grid sorting (admin, view menu) * * @param kEvent $event * @return void * @access protected */ protected function OnSetSortingDirect(kEvent $event) { // used on Front-End in category item lists $prefix_special = $event->getPrefixSpecial(); $combined = $this->Application->GetVar($event->getPrefixSpecial(true) . '_CombinedSorting'); if ( $combined ) { list ($field, $dir) = explode('|', $combined); if ( $this->Application->isAdmin || !$this->Application->GetVar('main_list') ) { $this->setListSetting($event, 'Sortings', Array ('Sort1' => $field, 'Sort1_Dir' => $dir)); } else { $event->setPseudoClass('_List'); $this->Application->SetVar('sort_by', $field . ',' . $dir); /** @var kDBList $object */ $object = $event->getObject(Array ('main_list' => 1)); /** @var ListHelper $list_helper */ $list_helper = $this->Application->recallObject('ListHelper'); $this->_passListParams($event, 'sort_by'); if ( $list_helper->hasUserSorting($object) ) { $event->SetRedirectParam('sort_by', $field . ',' . strtolower($dir)); } $event->SetRedirectParam('pass', 'm'); } return; } // used in "View Menu -> Sort" menu in administrative console $field_pos = $this->Application->GetVar($event->getPrefixSpecial(true) . '_SortPos'); $this->Application->LinkVar($event->getPrefixSpecial(true) . '_Sort' . $field_pos, $prefix_special . '_Sort' . $field_pos); $this->Application->LinkVar($event->getPrefixSpecial(true) . '_Sort' . $field_pos . '_Dir', $prefix_special . '_Sort' . $field_pos . '_Dir'); } /** * Reset grid sorting to default (from config) * * @param kEvent $event * @return void * @access protected */ protected function OnResetSorting(kEvent $event) { $this->setListSetting($event, 'Sortings'); } /** * Sets grid refresh interval * * @param kEvent $event * @return void * @access protected */ protected function OnSetAutoRefreshInterval(kEvent $event) { $refresh_interval = $this->Application->GetVar('refresh_interval'); $view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view'); $this->Application->StorePersistentVar($event->getPrefixSpecial() . '_refresh_interval.' . $view_name, $refresh_interval); } /** * Changes auto-refresh state for grid * * @param kEvent $event * @return void * @access protected */ protected function OnAutoRefreshToggle(kEvent $event) { $refresh_intervals = $this->Application->ConfigValue('AutoRefreshIntervals'); if ( !$refresh_intervals ) { return; } $view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view'); $auto_refresh = $this->Application->RecallPersistentVar($event->getPrefixSpecial() . '_auto_refresh.' . $view_name); if ( $auto_refresh === false ) { $refresh_intervals = explode(',', $refresh_intervals); $this->Application->StorePersistentVar($event->getPrefixSpecial() . '_refresh_interval.' . $view_name, $refresh_intervals[0]); } $this->Application->StorePersistentVar($event->getPrefixSpecial() . '_auto_refresh.' . $view_name, $auto_refresh ? 0 : 1); } /** * Creates needed sql query to load item, * if no query is defined in config for * special requested, then use list query * * @param kEvent $event * @return string * @access protected */ protected function ItemPrepareQuery(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $sqls = $object->getFormOption('ItemSQLs', Array ()); $special = isset($sqls[$event->Special]) ? $event->Special : ''; // preferred special not found in ItemSQLs -> use analog from ListSQLs return isset($sqls[$special]) ? $sqls[$special] : $this->ListPrepareQuery($event); } /** * Creates needed sql query to load list, * if no query is defined in config for * special requested, then use default * query * * @param kEvent $event * @return string * @access protected */ protected function ListPrepareQuery(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $sqls = $object->getFormOption('ListSQLs', Array ()); return $sqls[array_key_exists($event->Special, $sqls) ? $event->Special : '']; } /** * Apply custom processing to item * * @param kEvent $event * @param string $type * @return void * @access protected */ protected function customProcessing(kEvent $event, $type) { } /* Edit Events mostly used in Admin */ /** * Creates new kDBItem * * @param kEvent $event * @return void * @access protected */ protected function OnCreate(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( !$items_info ) { return; } list($id, $field_values) = each($items_info); $object->setID($id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); $this->customProcessing($event, 'before'); // look at kDBItem' Create for ForceCreateId description, it's rarely used and is NOT set by default if ( $object->Create($event->getEventParam('ForceCreateId')) ) { $this->customProcessing($event, 'after'); $event->SetRedirectParam('opener', 'u'); return; } $event->redirect = false; $event->status = kEvent::erFAIL; $this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate'); } /** * Updates kDBItem * * @param kEvent $event * @return void * @access protected */ protected function OnUpdate(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } $this->_update($event); $event->SetRedirectParam('opener', 'u'); if ( $event->status == kEvent::erSUCCESS ) { $this->saveChangesToLiveTable($event->Prefix); } } /** * Updates data in database based on request * * @param kEvent $event * @return void * @access protected */ protected function _update(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if ( $items_info ) { foreach ($items_info as $id => $field_values) { $object->Load($id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); $this->customProcessing($event, 'before'); if ( $object->Update($id) ) { $this->customProcessing($event, 'after'); $event->status = kEvent::erSUCCESS; } else { $event->status = kEvent::erFAIL; $event->redirect = false; break; } } } } /** * Automatically saves data to live table after sub-item was updated in Content Mode. * * @param string $prefix Prefix. * * @return void */ protected function saveChangesToLiveTable($prefix) { $parent_prefix = $this->Application->getUnitOption($prefix, 'ParentPrefix'); if ( $parent_prefix === false ) { return; } if ( $this->Application->GetVar('admin') && $this->Application->IsTempMode($parent_prefix) ) { $this->Application->HandleEvent(new kEvent($parent_prefix . ':OnSave')); } } /** * Delete's kDBItem object * * @param kEvent $event * @return void * @access protected */ protected function OnDelete(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); $temp_handler->DeleteItems($event->Prefix, $event->Special, Array ($this->getPassedID($event))); } /** * Deletes all records from table * * @param kEvent $event * @return void * @access protected */ protected function OnDeleteAll(kEvent $event) { $sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . ' FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName'); $ids = $this->Conn->GetCol($sql); if ( $ids ) { /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); $temp_handler->DeleteItems($event->Prefix, $event->Special, $ids); } } /** * Prepares new kDBItem object * * @param kEvent $event * @return void * @access protected */ protected function OnNew(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); $object->Clear(0); $this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate'); if ( $event->getEventParam('top_prefix') != $event->Prefix ) { // this is subitem prefix, so use main item special $table_info = $object->getLinkedInfo($this->getMainSpecial($event)); } else { $table_info = $object->getLinkedInfo(); } $object->SetDBField($table_info['ForeignKey'], $table_info['ParentId']); $event->redirect = false; } /** * Cancels kDBItem Editing/Creation * * @param kEvent $event * @return void * @access protected */ protected function OnCancel(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( $items_info ) { $delete_ids = Array (); /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); foreach ($items_info as $id => $field_values) { $object->Load($id); // record created for using with selector (e.g. Reviews->Select User), and not validated => Delete it if ( $object->isLoaded() && !$object->Validate() && ($id <= 0) ) { $delete_ids[] = $id; } } if ( $delete_ids ) { $temp_handler->DeleteItems($event->Prefix, $event->Special, $delete_ids); } } $event->SetRedirectParam('opener', 'u'); } /** * Deletes all selected items. * Automatically recurse into sub-items using temp handler, and deletes sub-items * by calling its Delete method if sub-item has AutoDelete set to true in its config file * * @param kEvent $event * @return void * @access protected */ protected function OnMassDelete(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return ; } /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); $ids = $this->StoreSelectedIDs($event); $event->setEventParam('ids', $ids); $this->customProcessing($event, 'before'); $ids = $event->getEventParam('ids'); if ( $ids ) { $temp_handler->DeleteItems($event->Prefix, $event->Special, $ids); } $this->clearSelectedIDs($event); } /** * Sets window id (of first opened edit window) to temp mark in uls * * @param kEvent $event * @return void * @access protected */ protected function setTempWindowID(kEvent $event) { $prefixes = Array ($event->Prefix, $event->getPrefixSpecial(true)); foreach ($prefixes as $prefix) { $mode = $this->Application->GetVar($prefix . '_mode'); if ($mode == 't') { $wid = $this->Application->GetVar('m_wid'); $this->Application->SetVar(str_replace('_', '.', $prefix) . '_mode', 't' . $wid); break; } } } /** * Prepare temp tables and populate it * with items selected in the grid * * @param kEvent $event * @return void * @access protected */ protected function OnEdit(kEvent $event) { $this->setTempWindowID($event); $ids = $this->StoreSelectedIDs($event); /** @var kDBItem $object */ $object = $event->getObject(Array('skip_autoload' => true)); $object->setPendingActions(null, true); $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $this->Application->RemoveVar($changes_var_name); /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); $temp_handler->PrepareEdit(); $event->SetRedirectParam('m_lang', $this->Application->GetDefaultLanguageId()); $event->SetRedirectParam($event->getPrefixSpecial() . '_id', array_shift($ids)); $event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial()); $simultaneous_edit_message = $this->Application->GetVar('_simultaneous_edit_message'); if ( $simultaneous_edit_message ) { $event->SetRedirectParam('_simultaneous_edit_message', $simultaneous_edit_message); } } /** * Saves content of temp table into live and * redirects to event' default redirect (normally grid template) * * @param kEvent $event * @return void * @access protected */ protected function OnSave(kEvent $event) { $event->CallSubEvent('OnPreSave'); if ( $event->status != kEvent::erSUCCESS ) { return; } $skip_master = false; /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); if ( !$this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $live_ids = $temp_handler->SaveEdit($event->getEventParam('master_ids') ? $event->getEventParam('master_ids') : Array ()); if ( $live_ids === false ) { // coping from table failed, because we have another coping process to same table, that wasn't finished $event->status = kEvent::erFAIL; return; } if ( $live_ids ) { // ensure, that newly created item ids are available as if they were selected from grid // NOTE: only works if main item has sub-items !!! $this->StoreSelectedIDs($event, $live_ids); } /** @var kDBItem $object */ $object = $event->getObject(); $this->SaveLoggedChanges($changes_var_name, $object->ShouldLogChanges()); } else { $event->status = kEvent::erFAIL; } $this->clearSelectedIDs($event); $event->SetRedirectParam('opener', 'u'); $this->Application->RemoveVar($event->getPrefixSpecial() . '_modified'); // all temp tables are deleted here => all after hooks should think, that it's live mode now $this->Application->SetVar($event->Prefix . '_mode', ''); } /** * Saves changes made in temporary table to log * * @param string $changes_var_name * @param bool $save * @return void * @access public */ public function SaveLoggedChanges($changes_var_name, $save = true) { // 1. get changes, that were made $changes = $this->Application->RecallVar($changes_var_name); $changes = $changes ? unserialize($changes) : Array (); $this->Application->RemoveVar($changes_var_name); if (!$changes) { // no changes, skip processing return ; } // TODO: 2. optimize change log records (replace multiple changes to same record with one change record) $to_increment = Array (); // 3. collect serials to reset based on foreign keys foreach ($changes as $index => $rec) { if (array_key_exists('DependentFields', $rec)) { foreach ($rec['DependentFields'] as $field_name => $field_value) { // will be "ci|ItemResourceId:345" $to_increment[] = $rec['Prefix'] . '|' . $field_name . ':' . $field_value; // also reset sub-item prefix general serial $to_increment[] = $rec['Prefix']; } unset($changes[$index]['DependentFields']); } unset($changes[$index]['ParentId'], $changes[$index]['ParentPrefix']); } // 4. collect serials to reset based on changed ids foreach ($changes as $change) { $to_increment[] = $change['MasterPrefix'] . '|' . $change['MasterId']; if ($change['MasterPrefix'] != $change['Prefix']) { // also reset sub-item prefix general serial $to_increment[] = $change['Prefix']; // will be "ci|ItemResourceId" $to_increment[] = $change['Prefix'] . '|' . $change['ItemId']; } } // 5. reset serials collected before $to_increment = array_unique($to_increment); $this->Application->incrementCacheSerial($this->Prefix); foreach ($to_increment as $to_increment_mixed) { if (strpos($to_increment_mixed, '|') !== false) { list ($to_increment_prefix, $to_increment_id) = explode('|', $to_increment_mixed, 2); $this->Application->incrementCacheSerial($to_increment_prefix, $to_increment_id); } else { $this->Application->incrementCacheSerial($to_increment_mixed); } } // save changes to database $sesion_log_id = $this->Application->RecallVar('_SessionLogId_'); if (!$save || !$sesion_log_id) { // saving changes to database disabled OR related session log missing return ; } $add_fields = Array ( 'PortalUserId' => $this->Application->RecallVar('user_id'), 'SessionLogId' => $sesion_log_id, ); $change_log_table = $this->Application->getUnitOption('change-log', 'TableName'); foreach ($changes as $rec) { $this->Conn->doInsert(array_merge($rec, $add_fields), $change_log_table); } $this->Application->incrementCacheSerial('change-log'); $sql = 'UPDATE ' . $this->Application->getUnitOption('session-log', 'TableName') . ' SET AffectedItems = AffectedItems + ' . count($changes) . ' WHERE SessionLogId = ' . $sesion_log_id; $this->Conn->Query($sql); $this->Application->incrementCacheSerial('session-log'); } /** * Cancels edit * Removes all temp tables and clears selected ids * * @param kEvent $event * @return void * @access protected */ protected function OnCancelEdit(kEvent $event) { /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); $temp_handler->CancelEdit(); $this->clearSelectedIDs($event); $this->Application->RemoveVar($event->getPrefixSpecial() . '_modified'); $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $this->Application->RemoveVar($changes_var_name); $event->SetRedirectParam('opener', 'u'); } /** * Allows to determine if we are creating new item or editing already created item * * @param kEvent $event * @return bool * @access public */ public function isNewItemCreate(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject( Array ('raise_warnings' => 0) ); return !$object->isLoaded(); } /** * Saves edited item into temp table * If there is no id, new item is created in temp table * * @param kEvent $event * @return void * @access protected */ protected function OnPreSave(kEvent $event) { // if there is no id - it means we need to create an item if ( is_object($event->MasterEvent) ) { $event->MasterEvent->setEventParam('IsNew', false); } if ( $this->isNewItemCreate($event) ) { $event->CallSubEvent('OnPreSaveCreated'); if ( is_object($event->MasterEvent) ) { $event->MasterEvent->setEventParam('IsNew', true); } return ; } // don't just call OnUpdate event here, since it maybe overwritten to Front-End specific behavior $this->_update($event); } /** * Analog of OnPreSave event for usage in AJAX request * * @param kEvent $event * * @return void */ protected function OnPreSaveAjax(kEvent $event) { /** @var AjaxFormHelper $ajax_form_helper */ $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); $ajax_form_helper->transitEvent($event, 'OnPreSave'); } /** * [HOOK] Saves sub-item * * @param kEvent $event * @return void * @access protected */ protected function OnPreSaveSubItem(kEvent $event) { $not_created = $this->isNewItemCreate($event); $event->CallSubEvent($not_created ? 'OnCreate' : 'OnUpdate'); if ( $event->status == kEvent::erSUCCESS ) { /** @var kDBItem $object */ $object = $event->getObject(); $this->Application->SetVar($event->getPrefixSpecial() . '_id', $object->GetID()); } else { $event->MasterEvent->status = $event->status; } $event->SetRedirectParam('opener', 's'); } /** * Saves edited item in temp table and loads * item with passed id in current template * Used in Prev/Next buttons * * @param kEvent $event * @return void * @access protected */ protected function OnPreSaveAndGo(kEvent $event) { $event->CallSubEvent('OnPreSave'); if ( $event->status == kEvent::erSUCCESS ) { $id = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoId'); $event->SetRedirectParam($event->getPrefixSpecial() . '_id', $id); } } /** * Saves edited item in temp table and goes * to passed tabs, by redirecting to it with OnPreSave event * * @param kEvent $event * @return void * @access protected */ protected function OnPreSaveAndGoToTab(kEvent $event) { $event->CallSubEvent('OnPreSave'); if ( $event->status == kEvent::erSUCCESS ) { $event->redirect = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoTab'); } } /** * Saves editable list and goes to passed tab, * by redirecting to it with empty event * * @param kEvent $event * @return void * @access protected */ protected function OnUpdateAndGoToTab(kEvent $event) { $event->setPseudoClass('_List'); $event->CallSubEvent('OnUpdate'); if ( $event->status == kEvent::erSUCCESS ) { $event->redirect = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoTab'); } } /** * Prepare temp tables for creating new item * but does not create it. Actual create is * done in OnPreSaveCreated * * @param kEvent $event * @return void * @access protected */ protected function OnPreCreate(kEvent $event) { $this->setTempWindowID($event); $this->clearSelectedIDs($event); $this->Application->SetVar('m_lang', $this->Application->GetDefaultLanguageId()); /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->Prefix . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); $temp_handler->PrepareEdit(); $object->setID(0); $this->Application->SetVar($event->getPrefixSpecial() . '_id', 0); $this->Application->SetVar($event->getPrefixSpecial() . '_PreCreate', 1); $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $this->Application->RemoveVar($changes_var_name); $event->redirect = false; } /** * Creates a new item in temp table and * stores item id in App vars and Session on success * * @param kEvent $event * @return void * @access protected */ protected function OnPreSaveCreated(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject( Array('skip_autoload' => true) ); $object->setID(0); $field_values = $this->getSubmittedFields($event); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); $this->customProcessing($event, 'before'); if ( $object->Create() ) { $this->customProcessing($event, 'after'); $event->SetRedirectParam($event->getPrefixSpecial(true) . '_id', $object->GetID()); } else { $event->status = kEvent::erFAIL; $event->redirect = false; } } /** * Reloads form to loose all changes made during item editing * * @param kEvent $event * @return void * @access protected */ protected function OnReset(kEvent $event) { //do nothing - should reset :) if ( $this->isNewItemCreate($event) ) { // just reset id to 0 in case it was create /** @var kDBItem $object */ $object = $event->getObject( Array ('skip_autoload' => true) ); $object->setID(0); $this->Application->SetVar($event->getPrefixSpecial() . '_id', 0); } } /** * Apply same processing to each item being selected in grid * * @param kEvent $event * @return void * @access protected */ protected function iterateItems(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return ; } /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); $ids = $this->StoreSelectedIDs($event); if ( $ids ) { $status_field = $object->getStatusField(); $order_field = $this->Application->getUnitOption($event->Prefix, 'OrderField'); if ( !$order_field ) { $order_field = 'Priority'; } foreach ($ids as $id) { $object->Load($id); switch ( $event->Name ) { case 'OnMassApprove': $object->SetDBField($status_field, 1); break; case 'OnMassDecline': $object->SetDBField($status_field, 0); break; case 'OnMassMoveUp': $object->SetDBField($order_field, $object->GetDBField($order_field) + 1); break; case 'OnMassMoveDown': $object->SetDBField($order_field, $object->GetDBField($order_field) - 1); break; } $object->Update(); } } $this->clearSelectedIDs($event); } /** * Clones selected items in list * * @param kEvent $event * @return void * @access protected */ protected function OnMassClone(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); $ids = $this->StoreSelectedIDs($event); if ( $ids ) { $temp_handler->CloneItems($event->Prefix, $event->Special, $ids); } $this->clearSelectedIDs($event); } /** * Checks if given value is present in given array * * @param Array $records * @param string $field * @param mixed $value * @return bool * @access protected */ protected function check_array($records, $field, $value) { foreach ($records as $record) { if ($record[$field] == $value) { return true; } } return false; } /** * Saves data from editing form to database without checking required fields * * @param kEvent $event * @return void * @access protected */ protected function OnPreSavePopup(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $this->RemoveRequiredFields($object); $event->CallSubEvent('OnPreSave'); $event->SetRedirectParam('opener', 'u'); } /* End of Edit events */ // III. Events that allow to put some code before and after Update,Load,Create and Delete methods of item /** * Occurs before loading item, 'id' parameter * allows to get id of item being loaded * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemLoad(kEvent $event) { } /** * Occurs after loading item, 'id' parameter * allows to get id of item that was loaded * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemLoad(kEvent $event) { } /** * Occurs before creating item * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { } /** * Occurs after creating item * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemCreate(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); if ( !$object->IsTempTable() ) { $this->_processPendingActions($event); } } /** * Occurs before updating item * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { } /** * Occurs after updating item * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemUpdate(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); if ( !$object->IsTempTable() ) { $this->_processPendingActions($event); } } /** * Occurs before deleting item, id of item being * deleted is stored as 'id' event param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemDelete(kEvent $event) { } /** * Occurs after deleting item, id of deleted item * is stored as 'id' param of event * * Also deletes subscriptions to that particual item once it's deleted * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemDelete(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); // 1. delete direct subscriptions to item, that was deleted $this->_deleteSubscriptions($event->Prefix, 'ItemId', $object->GetID()); /** @var Array $sub_items */ $sub_items = $this->Application->getUnitOption($event->Prefix, 'SubItems', Array ()); // 2. delete this item sub-items subscriptions, that reference item, that was deleted foreach ($sub_items as $sub_prefix) { $this->_deleteSubscriptions($sub_prefix, 'ParentItemId', $object->GetID()); } } /** * Deletes all subscriptions, associated with given item * * @param string $prefix * @param string $field * @param int $value * @return void * @access protected */ protected function _deleteSubscriptions($prefix, $field, $value) { $sql = 'SELECT TemplateId FROM ' . $this->Application->getUnitOption('email-template', 'TableName') . ' WHERE BindToSystemEvent REGEXP "' . $this->Conn->escape($prefix) . '(\\\\.[^:]*:.*|:.*)"'; $email_template_ids = $this->Conn->GetCol($sql); if ( !$email_template_ids ) { return; } // e-mail events, connected to that unit prefix are found $sql = 'SELECT SubscriptionId FROM ' . TABLE_PREFIX . 'SystemEventSubscriptions WHERE ' . $field . ' = ' . $value . ' AND EmailTemplateId IN (' . implode(',', $email_template_ids) . ')'; $ids = $this->Conn->GetCol($sql); if ( !$ids ) { return; } /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject('system-event-subscription_TempHandler', 'kTempTablesHandler'); $temp_handler->DeleteItems('system-event-subscription', '', $ids); } /** * Occurs before validation attempt * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemValidate(kEvent $event) { } /** * Occurs after successful item validation * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemValidate(kEvent $event) { } /** * Occurs after an item has been copied to temp * Id of copied item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnAfterCopyToTemp(kEvent $event) { } /** * Occurs before an item is deleted from live table when copying from temp * (temp handler deleted all items from live and then copy over all items from temp) * Id of item being deleted is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeDeleteFromLive(kEvent $event) { } /** * Occurs before an item is copied to live table (after all foreign keys have been updated) * Id of item being copied is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeCopyToLive(kEvent $event) { } /** * Occurs after an item has been copied to live table * Id of copied item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnAfterCopyToLive(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(array('skip_autoload' => true)); $object->SwitchToLive(); $object->Load($event->getEventParam('id')); $this->_processPendingActions($event); } /** * Processing file pending actions (e.g. delete scheduled files) * * @param kEvent $event * @return void * @access protected */ protected function _processPendingActions(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $update_required = false; $temp_id = $event->getEventParam('temp_id'); $id = $temp_id !== false ? $temp_id : $object->GetID(); foreach ($object->getPendingActions($id) as $data) { switch ( $data['action'] ) { case 'delete': unlink($data['file']); break; case 'make_live': /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); if ( !file_exists($data['file']) ) { // file removal was requested too continue; } $old_name = basename($data['file']); $new_name = $file_helper->ensureUniqueFilename(dirname($data['file']), kUtil::removeTempExtension($old_name)); rename($data['file'], dirname($data['file']) . '/' . $new_name); $db_value = $object->GetDBField($data['field']); $object->SetDBField($data['field'], str_replace($old_name, $new_name, $db_value)); $update_required = true; break; default: trigger_error('Unsupported pending action "' . $data['action'] . '" for "' . $event->getPrefixSpecial() . '" unit', E_USER_WARNING); break; } } // remove pending actions before updating to prevent recursion $object->setPendingActions(); if ( $update_required ) { $object->Update(); } } /** * Occurs before an item has been cloned * Id of newly created item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeClone(kEvent $event) { } /** * Occurs after an item has been cloned * Id of newly created item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnAfterClone(kEvent $event) { } /** * Occurs after list is queried * * @param kEvent $event * @return void * @access protected */ protected function OnAfterListQuery(kEvent $event) { } /** * Ensures that popup will be closed automatically * and parent window will be refreshed with template * passed * * @param kEvent $event * @return void * @access protected * @deprecated */ protected function finalizePopup(kEvent $event) { $event->SetRedirectParam('opener', 'u'); } /** * Create search filters based on search query * * @param kEvent $event * @return void * @access protected */ protected function OnSearch(kEvent $event) { $event->setPseudoClass('_List'); /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); $search_helper->performSearch($event); } /** * Clear search keywords * * @param kEvent $event * @return void * @access protected */ protected function OnSearchReset(kEvent $event) { /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); $search_helper->resetSearch($event); } /** * Set's new filter value (filter_id meaning from config) * * @param kEvent $event * @return void * @access protected * @deprecated */ protected function OnSetFilter(kEvent $event) { $filter_id = $this->Application->GetVar('filter_id'); $filter_value = $this->Application->GetVar('filter_value'); $view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter'); $view_filter = $view_filter ? unserialize($view_filter) : Array (); $view_filter[$filter_id] = $filter_value; $this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter)); } /** * Sets view filter based on request * * @param kEvent $event * @return void * @access protected */ protected function OnSetFilterPattern(kEvent $event) { $filters = $this->Application->GetVar($event->getPrefixSpecial(true) . '_filters'); if ( !$filters ) { return; } $view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter'); $view_filter = $view_filter ? unserialize($view_filter) : Array (); $filters = explode(',', $filters); foreach ($filters as $a_filter) { list($id, $value) = explode('=', $a_filter); $view_filter[$id] = $value; } $this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter)); $event->redirect = false; } /** * Add/Remove all filters applied to list from "View" menu * * @param kEvent $event * @return void * @access protected */ protected function FilterAction(kEvent $event) { $view_filter = Array (); $filter_menu = $this->Application->getUnitOption($event->Prefix, 'FilterMenu'); switch ($event->Name) { case 'OnRemoveFilters': $filter_value = 1; break; case 'OnApplyFilters': $filter_value = 0; break; default: $filter_value = 0; break; } foreach ($filter_menu['Filters'] as $filter_key => $filter_params) { if ( !$filter_params ) { continue; } $view_filter[$filter_key] = $filter_value; } $this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter)); } /** * Enter description here... * * @param kEvent $event * @access protected */ protected function OnPreSaveAndOpenTranslator(kEvent $event) { $this->Application->SetVar('allow_translation', true); /** @var kDBItem $object */ $object = $event->getObject(); $this->RemoveRequiredFields($object); $event->CallSubEvent('OnPreSave'); if ( $event->status == kEvent::erSUCCESS ) { $resource_id = $this->Application->GetVar('translator_resource_id'); if ( $resource_id ) { $t_prefixes = explode(',', $this->Application->GetVar('translator_prefixes')); /** @var kDBItem $cdata */ $cdata = $this->Application->recallObject($t_prefixes[1], NULL, Array ('skip_autoload' => true)); $cdata->Load($resource_id, 'ResourceId'); if ( !$cdata->isLoaded() ) { $cdata->SetDBField('ResourceId', $resource_id); $cdata->Create(); } $this->Application->SetVar($cdata->getPrefixSpecial() . '_id', $cdata->GetID()); } $event->redirect = $this->Application->GetVar('translator_t'); $redirect_params = Array ( 'pass' => 'all,trans,' . $this->Application->GetVar('translator_prefixes'), 'opener' => 's', $event->getPrefixSpecial(true) . '_id' => $object->GetID(), 'trans_event' => 'OnLoad', 'trans_prefix' => $this->Application->GetVar('translator_prefixes'), 'trans_field' => $this->Application->GetVar('translator_field'), 'trans_multi_line' => $this->Application->GetVar('translator_multi_line'), ); $event->setRedirectParams($redirect_params); // 1. SAVE LAST TEMPLATE TO SESSION (really needed here, because of tweaky redirect) $last_template = $this->Application->RecallVar('last_template'); preg_match('/index4\.php\|' . $this->Application->GetSID() . '-(.*):/U', $last_template, $rets); $this->Application->StoreVar('return_template', $this->Application->GetVar('t')); } } /** * Makes all fields non-required * * @param kDBItem $object * @return void * @access protected */ protected function RemoveRequiredFields(&$object) { // making all field non-required to achieve successful presave $fields = array_keys( $object->getFields() ); foreach ($fields as $field) { if ( $object->isRequired($field) ) { $object->setRequired($field, false); } } } /** * Saves selected user in needed field * * @param kEvent $event * @return void * @access protected */ protected function OnSelectUser(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $items_info = $this->Application->GetVar('u'); if ( $items_info ) { list ($user_id, ) = each($items_info); $this->RemoveRequiredFields($object); $is_new = !$object->isLoaded(); $is_main = substr($this->Application->GetVar($event->Prefix . '_mode'), 0, 1) == 't'; if ( $is_new ) { $new_event = $is_main ? 'OnPreCreate' : 'OnNew'; $event->CallSubEvent($new_event); $event->redirect = true; } $object->SetDBField($this->Application->RecallVar('dst_field'), $user_id); if ( $is_new ) { $object->Create(); } else { $object->Update(); } } $event->SetRedirectParam($event->getPrefixSpecial() . '_id', $object->GetID()); $event->SetRedirectParam('opener', 'u'); } /** EXPORT RELATED **/ /** * Shows export dialog * * @param kEvent $event * @return void * @access protected */ protected function OnExport(kEvent $event) { $selected_ids = $this->StoreSelectedIDs($event); if ( implode(',', $selected_ids) == '' ) { // K4 fix when no ids found bad selected ids array is formed $selected_ids = false; } $this->Application->StoreVar($event->Prefix . '_export_ids', $selected_ids ? implode(',', $selected_ids) : ''); $this->Application->LinkVar('export_finish_t'); $this->Application->LinkVar('export_progress_t'); $this->Application->StoreVar('export_special', $event->Special); $this->Application->StoreVar('export_grid', $this->Application->GetVar('grid', 'Default')); $redirect_params = Array ( $this->Prefix . '.export_event' => 'OnNew', 'pass' => 'all,' . $this->Prefix . '.export' ); $event->setRedirectParams($redirect_params); } /** * Apply some special processing to object being * recalled before using it in other events that * call prepareObject * * @param kDBItem|kDBList $object * @param kEvent $event * @return void * @access protected */ protected function prepareObject(&$object, kEvent $event) { if ( $event->Special == 'export' || $event->Special == 'import' ) { /** @var kCatDBItemExportHelper $export_helper */ $export_helper = $this->Application->recallObject('CatItemExportHelper'); $export_helper->prepareExportColumns($event); } } /** * Returns specific to each item type columns only * * @param kEvent $event * @return Array * @access public */ public function getCustomExportColumns(kEvent $event) { return Array (); } /** * Export form validation & processing * * @param kEvent $event * @return void * @access protected */ protected function OnExportBegin(kEvent $event) { /** @var kCatDBItemExportHelper $export_helper */ $export_helper = $this->Application->recallObject('CatItemExportHelper'); $export_helper->OnExportBegin($event); } /** * Enter description here... * * @param kEvent $event * @return void * @access protected */ protected function OnExportCancel(kEvent $event) { $this->OnGoBack($event); } /** * Allows configuring export options * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeExportBegin(kEvent $event) { } /** * Deletes export preset * * @param kEvent $event * @return void * @access protected */ protected function OnDeleteExportPreset(kEvent $event) { $field_values = $this->getSubmittedFields($event); if ( !$field_values ) { return ; } $preset_key = $field_values['ExportPresets']; $export_settings = $this->Application->RecallPersistentVar('export_settings'); if ( !$export_settings ) { return ; } $export_settings = unserialize($export_settings); if ( !isset($export_settings[$event->Prefix]) ) { return ; } $to_delete = ''; foreach ($export_settings[$event->Prefix] as $key => $val) { if ( implode('|', $val['ExportColumns']) == $preset_key ) { $to_delete = $key; break; } } if ( $to_delete ) { unset($export_settings[$event->Prefix][$to_delete]); $this->Application->StorePersistentVar('export_settings', serialize($export_settings)); } } /** * Saves changes & changes language * * @param kEvent $event * @return void * @access protected */ protected function OnPreSaveAndChangeLanguage(kEvent $event) { if ( $this->UseTempTables($event) ) { $event->CallSubEvent('OnPreSave'); } if ( $event->status == kEvent::erSUCCESS ) { $this->Application->SetVar('m_lang', $this->Application->GetVar('language')); $data = $this->Application->GetVar('st_id'); if ( $data ) { $event->SetRedirectParam('st_id', $data); } } } /** * Used to save files uploaded via Plupload * * @param kEvent $event * @return void * @access protected */ protected function OnUploadFile(kEvent $event) { $event->status = kEvent::erSTOP; /** @var kUploadHelper $upload_helper */ $upload_helper = $this->Application->recallObject('kUploadHelper'); try { $filename = $upload_helper->handle($event); $response = array( 'jsonrpc' => '2.0', 'status' => 'success', 'result' => $filename, ); } catch ( kUploaderException $e ) { $response = array( 'jsonrpc' => '2.0', 'status' => 'error', 'error' => array('code' => $e->getCode(), 'message' => $e->getMessage()), ); } echo json_encode($response); } /** * Remembers, that file should be deleted on item's save from temp table * * @param kEvent $event * @return void * @access protected */ protected function OnDeleteFile(kEvent $event) { $event->status = kEvent::erSTOP; $field_id = $this->Application->GetVar('field_id'); if ( !preg_match_all('/\[([^\[\]]*)\]/', $field_id, $regs) ) { return; } $field = $regs[1][1]; $record_id = $regs[1][0]; /** @var kUploadHelper $upload_helper */ $upload_helper = $this->Application->recallObject('kUploadHelper'); $object = $upload_helper->prepareUploadedFile($event, $field); if ( !$object->GetDBField($field) ) { return; } $pending_actions = $object->getPendingActions($record_id); $pending_actions[] = Array ( 'action' => 'delete', 'id' => $record_id, 'field' => $field, 'file' => $object->GetField($field, 'full_path'), ); $object->setPendingActions($pending_actions, $record_id); } /** * Returns url for viewing uploaded file * * @param kEvent $event * @return void * @access protected */ protected function OnViewFile(kEvent $event) { $event->status = kEvent::erSTOP; $field = $this->Application->GetVar('field'); /** @var kUploadHelper $upload_helper */ $upload_helper = $this->Application->recallObject('kUploadHelper'); $object = $upload_helper->prepareUploadedFile($event, $field); if ( !$object->GetDBField($field) ) { return; } // get url to uploaded file if ( $this->Application->GetVar('thumb') ) { $url = $object->GetField($field, $object->GetFieldOption($field, 'thumb_format')); } else { $url = $object->GetField($field, 'raw_url'); } /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); $path = $file_helper->urlToPath($url); if ( !file_exists($path) ) { exit; } header('Content-Length: ' . filesize($path)); $this->Application->setContentType(kUtil::mimeContentType($path), false); header('Content-Disposition: inline; filename="' . kUtil::removeTempExtension($object->GetDBField($field)) . '"'); readfile($path); } /** * Validates MInput control fields * * @param kEvent $event * @return void * @access protected */ protected function OnValidateMInputFields(kEvent $event) { /** @var MInputHelper $minput_helper */ $minput_helper = $this->Application->recallObject('MInputHelper'); $minput_helper->OnValidateMInputFields($event); } /** * Validates individual object field and returns the result * * @param kEvent $event * @return void * @access protected */ protected function OnValidateField(kEvent $event) { $event->status = kEvent::erSTOP; $field = $this->Application->GetVar('field'); if ( ($this->Application->GetVar('ajax') != 'yes') || !$field ) { return; } /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( !$items_info ) { return; } list ($id, $field_values) = each($items_info); $object->Load($id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); $object->setID($id); $response = Array ('status' => 'OK'); $event->CallSubEvent($object->isLoaded() ? 'OnBeforeItemUpdate' : 'OnBeforeItemCreate'); // validate all fields, since "Password_plain" field sets error to "Password" field, which is passed here $error_field = $object->GetFieldOption($field, 'error_field', false, $field); if ( !$object->Validate() && $object->GetErrorPseudo($error_field) ) { $response['status'] = $object->GetErrorMsg($error_field, false); } /** @var AjaxFormHelper $ajax_form_helper */ $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); $response['other_errors'] = $ajax_form_helper->getErrorMessages($object); $response['uploader_info'] = $ajax_form_helper->getUploaderInfo($object, array_keys($field_values)); $event->status = kEvent::erSTOP; // since event's OnBefore... events can change this event status echo json_encode($response); } /** * Returns auto-complete values for ajax-dropdown * * @param kEvent $event * @return void * @access protected */ protected function OnSuggestValues(kEvent $event) { $event->status = kEvent::erSTOP; $this->Application->XMLHeader(); $data = $this->getAutoCompleteSuggestions($event, $this->Application->GetVar('cur_value')); $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); echo ''; if ( kUtil::isAssoc($data) ) { foreach ($data as $key => $title) { echo '' . kUtil::escape($title, kUtil::ESCAPE_HTML) . ''; } } else { foreach ($data as $title) { echo '' . kUtil::escape($title, kUtil::ESCAPE_HTML) . ''; } } echo ''; } /** * Returns auto-complete values for jQueryUI.AutoComplete * * @param kEvent $event * @return void * @access protected */ protected function OnSuggestValuesJSON(kEvent $event) { $event->status = kEvent::erSTOP; $data = $this->getAutoCompleteSuggestions($event, $this->Application->GetVar('term')); if ( kUtil::isAssoc($data) ) { $transformed_data = array(); foreach ($data as $key => $title) { $transformed_data[] = array('value' => $key, 'label' => $title); } $data = $transformed_data; } echo json_encode($data); } /** * Prepares a suggestion list based on a given term. * * @param kEvent $event Event. * @param string $term Term. * * @return Array * @access protected */ protected function getAutoCompleteSuggestions(kEvent $event, $term) { /** @var kDBItem $object */ $object = $event->getObject(); $field = $this->Application->GetVar('field'); if ( !$field || !$term || !$object->isField($field) ) { return array(); } $limit = $this->Application->GetVar('limit'); if ( !$limit ) { $limit = 20; } $sql = 'SELECT DISTINCT ' . $field . ' FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' WHERE ' . $field . ' LIKE ' . $this->Conn->qstr($term . '%') . ' ORDER BY ' . $field . ' LIMIT 0,' . $limit; return $this->Conn->GetCol($sql); } /** * Enter description here... * * @param kEvent $event * @return void * @access protected */ protected function OnSaveWidths(kEvent $event) { $event->status = kEvent::erSTOP; // $this->Application->setContentType('text/xml'); $picker_helper = new kColumnPickerHelper( $event->getPrefixSpecial(), $this->Application->GetVar('grid_name') ); $picker_helper->saveWidths($this->Application->GetVar('widths')); echo 'OK'; } /** * Called from CSV import script after item fields * are set and validated, but before actual item create/update. * If event status is kEvent::erSUCCESS, line will be imported, * else it will not be imported but added to skipped lines * and displayed in the end of import. * Event status is preset from import script. * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeCSVLineImport(kEvent $event) { // abstract, for hooking } /** * [HOOK] Allows to add cloned subitem to given prefix * * @param kEvent $event * @return void * @access protected */ protected function OnCloneSubItem(kEvent $event) { $clones = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'Clones'); $subitem_prefix = $event->Prefix . '-' . preg_replace('/^#/', '', $event->MasterEvent->Prefix); $clones[$subitem_prefix] = Array ('ParentPrefix' => $event->Prefix); $this->Application->setUnitOption($event->MasterEvent->Prefix, 'Clones', $clones); } /** * Returns constrain for priority calculations * * @param kEvent $event * @return void * @see PriorityEventHandler * @access protected */ protected function OnGetConstrainInfo(kEvent $event) { $event->setEventParam('constrain_info', Array ('', '')); } } Index: branches/5.2.x/core/kernel/application.php =================================================================== --- branches/5.2.x/core/kernel/application.php (revision 16560) +++ branches/5.2.x/core/kernel/application.php (revision 16561) @@ -1,3100 +1,3116 @@ * The class encapsulates the main run-cycle of the script, provide access to all other objects in the framework.
*
* The class is a singleton, which means that there could be only one instance of kApplication in the script.
* This could be guaranteed by NOT calling the class constructor directly, but rather calling kApplication::Instance() method, * which returns an instance of the application. The method guarantees that it will return exactly the same instance for any call.
* See singleton pattern by GOF. */ class kApplication implements kiCacheable { /** * Location of module helper class (used in installator too) */ const MODULE_HELPER_PATH = '/../units/helpers/modules_helper.php'; /** * Is true, when Init method was called already, prevents double initialization * * @var bool */ public $InitDone = false; /** * Holds internal NParser object * * @var NParser * @access public */ public $Parser; /** * Holds parser output buffer * * @var string * @access protected */ protected $HTML = ''; /** * The main Factory used to create * almost any class of kernel and * modules * * @var kFactory * @access protected */ protected $Factory; /** * Template names, that will be used instead of regular templates * * @var Array * @access public */ public $ReplacementTemplates = Array (); /** * Mod-Rewrite listeners used during url building and parsing * * @var Array * @access public */ public $RewriteListeners = Array (); /** * Reference to debugger * * @var Debugger * @access public */ public $Debugger = null; /** * Holds all phrases used * in code and template * * @var PhrasesCache * @access public */ public $Phrases; /** * Modules table content, key - module name * * @var Array * @access public */ public $ModuleInfo = Array (); /** * Holds DBConnection * * @var IDBConnection * @access public */ public $Conn = null; /** * Reference to event log * * @var Array|kLogger * @access public */ protected $_logger = Array (); // performance needs: /** * Holds a reference to httpquery * * @var kHttpQuery * @access public */ public $HttpQuery = null; /** * Holds a reference to UnitConfigReader * * @var kUnitConfigReader * @access public */ public $UnitConfigReader = null; /** * Holds a reference to Session * * @var Session * @access public */ public $Session = null; /** * Holds a ref to kEventManager * * @var kEventManager * @access public */ public $EventManager = null; /** * Holds a ref to kUrlManager * * @var kUrlManager * @access public */ public $UrlManager = null; /** * Ref for TemplatesCache * * @var TemplatesCache * @access public */ public $TemplatesCache = null; /** * Holds current NParser tag while parsing, can be used in error messages to display template file and line * * @var _BlockTag * @access public */ public $CurrentNTag = null; /** * Object of unit caching class * * @var kCacheManager * @access public */ public $cacheManager = null; /** * Tells, that administrator has authenticated in administrative console * Should be used to manipulate data change OR data restrictions! * * @var bool * @access public */ public $isAdminUser = false; /** * Tells, that admin version of "index.php" was used, nothing more! * Should be used to manipulate data display! * * @var bool * @access public */ public $isAdmin = false; /** * Instance of site domain object * * @var kDBItem * @access public * @todo move away into separate module */ public $siteDomain = null; /** * Prevent kApplication class to be created directly, only via Instance method * * @access private */ private function __construct() { } final private function __clone() {} /** * Returns kApplication instance anywhere in the script. * * This method should be used to get single kApplication object instance anywhere in the * Kernel-based application. The method is guaranteed to return the SAME instance of kApplication. * Anywhere in the script you could write: * * $application =& kApplication::Instance(); * * or in an object: * * $this->Application =& kApplication::Instance(); * * to get the instance of kApplication. Note that we call the Instance method as STATIC - directly from the class. * To use descendant of standard kApplication class in your project you would need to define APPLICATION_CLASS constant * BEFORE calling kApplication::Instance() for the first time. If APPLICATION_CLASS is not defined the method would * create and return default KernelApplication instance. * * Pattern: Singleton * * @static * @return kApplication * @access public */ public static function &Instance() { static $instance = false; if ( !$instance ) { $class = defined('APPLICATION_CLASS') ? APPLICATION_CLASS : 'kApplication'; $instance = new $class(); } return $instance; } /** * Initializes the Application * * @param string $factory_class * @return bool Was Init actually made now or before * @access public * @see kHTTPQuery * @see Session * @see TemplatesCache */ public function Init($factory_class = 'kFactory') { if ( $this->InitDone ) { return false; } if ( preg_match('/utf-8/i', CHARSET) ) { setlocale(LC_ALL, 'en_US.UTF-8'); mb_internal_encoding('UTF-8'); } $this->isAdmin = kUtil::constOn('ADMIN'); if ( !kUtil::constOn('SKIP_OUT_COMPRESSION') ) { ob_start(); // collect any output from method (other then tags) into buffer } if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application before Init:'); } $this->_logger = new kLogger($this->_logger); $this->Factory = new $factory_class(); $this->registerDefaultClasses(); $system_config = new kSystemConfig(true); $vars = $system_config->getData(); $db_class = isset($vars['Databases']) ? 'kDBLoadBalancer' : ($this->isDebugMode() ? 'kDBConnectionDebug' : 'kDBConnection'); $this->Conn = $this->Factory->makeClass($db_class, Array (SQL_TYPE, Array ($this->_logger, 'handleSQLError'))); $this->Conn->setup($vars); $this->cacheManager = $this->makeClass('kCacheManager'); $this->cacheManager->InitCache(); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Before UnitConfigReader'); } // init config reader and all managers $this->UnitConfigReader = $this->makeClass('kUnitConfigReader'); $this->UnitConfigReader->scanModules(MODULES_PATH); // will also set RewriteListeners when existing cache is read $this->registerModuleConstants(); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('After UnitConfigReader'); } define('MOD_REWRITE', $this->ConfigValue('UseModRewrite') && !$this->isAdmin ? 1 : 0); // start processing request $this->HttpQuery = $this->recallObject('HTTPQuery'); $this->HttpQuery->process(); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Processed HTTPQuery initial'); } $this->Session = $this->recallObject('Session'); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Processed Session'); } $this->Session->ValidateExpired(); // needs mod_rewrite url already parsed to keep user at proper template after session expiration if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Processed HTTPQuery AfterInit'); } $this->cacheManager->LoadApplicationCache(); $site_timezone = $this->ConfigValue('Config_Site_Time'); if ( $site_timezone ) { date_default_timezone_set($site_timezone); } if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Loaded cache and phrases'); } $this->ValidateLogin(); // must be called before AfterConfigRead, because current user should be available there $this->UnitConfigReader->AfterConfigRead(); // will set RewriteListeners when missing cache is built first time if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendTimestamp('Processed AfterConfigRead'); } if ( $this->GetVar('m_cat_id') === false ) { $this->SetVar('m_cat_id', 0); } if ( !$this->RecallVar('curr_iso') && !(defined('IS_INSTALL') && IS_INSTALL) ) { $this->StoreVar('curr_iso', $this->GetPrimaryCurrency(), true); // true for optional } $visit_id = $this->RecallVar('visit_id'); if ( $visit_id !== false ) { $this->SetVar('visits_id', $visit_id); } if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->profileFinish('kernel4_startup'); } $this->InitDone = true; if ( PHP_SAPI !== 'cli' && !$this->isAdmin ) { $this->HandleEvent(new kEvent('adm:OnStartup')); } return true; } /** * Performs initialization of manager classes, that can be overridden from unit configs * * @return void * @access public * @throws Exception */ public function InitManagers() { if ( $this->InitDone ) { throw new Exception('Duplicate call of ' . __METHOD__, E_USER_ERROR); return; } $this->UrlManager = $this->makeClass('kUrlManager'); $this->EventManager = $this->makeClass('EventManager'); $this->Phrases = $this->makeClass('kPhraseCache'); $this->RegisterDefaultBuildEvents(); } /** * Returns module information. Searches module by requested field * * @param string $field * @param mixed $value * @param string $return_field field value to returns, if not specified, then return all fields * @return Array */ public function findModule($field, $value, $return_field = null) { $found = $module_info = false; foreach ($this->ModuleInfo as $module_info) { if ( strtolower($module_info[$field]) == strtolower($value) ) { $found = true; break; } } if ( $found ) { return isset($return_field) ? $module_info[$return_field] : $module_info; } return false; } /** * Refreshes information about loaded modules * * @return void * @access public */ public function refreshModuleInfo() { if ( defined('IS_INSTALL') && IS_INSTALL && !$this->TableFound('Modules', true) ) { $this->registerModuleConstants(); return; } // use makeClass over recallObject, since used before kApplication initialization during installation /** @var kModulesHelper $modules_helper */ $modules_helper = $this->makeClass('ModulesHelper'); $this->Conn->nextQueryCachable = true; $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'Modules WHERE ' . $modules_helper->getWhereClause() . ' ORDER BY LoadOrder'; $this->ModuleInfo = $this->Conn->Query($sql, 'Name'); $this->registerModuleConstants(); } /** * Checks if passed language id if valid and sets it to primary otherwise * * @return void * @access public */ public function VerifyLanguageId() { /** @var LanguagesItem $lang */ $lang = $this->recallObject('lang.current'); if ( !$lang->isLoaded() || (!$this->isAdmin && !$lang->GetDBField('Enabled')) ) { if ( !defined('IS_INSTALL') ) { $this->ApplicationDie('Unknown or disabled language'); } } } /** * Checks if passed theme id if valid and sets it to primary otherwise * * @return void * @access public */ public function VerifyThemeId() { if ( $this->isAdmin ) { kUtil::safeDefine('THEMES_PATH', '/core/admin_templates'); return; } $path = $this->GetFrontThemePath(); if ( $path === false ) { $this->ApplicationDie('No Primary Theme Selected or Current Theme is Unknown or Disabled'); } kUtil::safeDefine('THEMES_PATH', $path); } /** * Returns relative path to current front-end theme * * @param bool $force * @return string * @access public */ public function GetFrontThemePath($force = false) { static $path = null; if ( !$force && isset($path) ) { return $path; } /** @var ThemeItem $theme */ $theme = $this->recallObject('theme.current'); if ( !$theme->isLoaded() || !$theme->GetDBField('Enabled') ) { return false; } // assign & then return, since it's static variable $path = '/themes/' . $theme->GetDBField('Name'); return $path; } /** * Returns primary front/admin language id * * @param bool $init * @return int * @access public */ public function GetDefaultLanguageId($init = false) { $cache_key = 'primary_language_info[%LangSerial%]'; $language_info = $this->getCache($cache_key); if ( $language_info === false ) { // cache primary language info first $table = $this->getUnitOption('lang', 'TableName'); $id_field = $this->getUnitOption('lang', 'IDField'); $this->Conn->nextQueryCachable = true; $sql = 'SELECT ' . $id_field . ', IF(AdminInterfaceLang, "Admin", "Front") AS LanguageKey FROM ' . $table . ' WHERE (AdminInterfaceLang = 1 OR PrimaryLang = 1) AND (Enabled = 1)'; $language_info = $this->Conn->GetCol($sql, 'LanguageKey'); if ( $language_info !== false ) { $this->setCache($cache_key, $language_info); } } $language_key = ($this->isAdmin && $init) || count($language_info) == 1 ? 'Admin' : 'Front'; if ( array_key_exists($language_key, $language_info) && $language_info[$language_key] > 0 ) { // get from cache return $language_info[$language_key]; } $language_id = $language_info && array_key_exists($language_key, $language_info) ? $language_info[$language_key] : false; if ( !$language_id && defined('IS_INSTALL') && IS_INSTALL ) { $language_id = 1; } return $language_id; } /** * Returns front-end primary theme id (even, when called from admin console) * * @param bool $force_front * @return int * @access public */ public function GetDefaultThemeId($force_front = false) { static $theme_id = 0; if ( $theme_id > 0 ) { return $theme_id; } if ( kUtil::constOn('DBG_FORCE_THEME') ) { $theme_id = DBG_FORCE_THEME; } elseif ( !$force_front && $this->isAdmin ) { $theme_id = 999; } else { $cache_key = 'primary_theme[%ThemeSerial%]'; $theme_id = $this->getCache($cache_key); if ( $theme_id === false ) { $this->Conn->nextQueryCachable = true; $sql = 'SELECT ' . $this->getUnitOption('theme', 'IDField') . ' FROM ' . $this->getUnitOption('theme', 'TableName') . ' WHERE (PrimaryTheme = 1) AND (Enabled = 1)'; $theme_id = $this->Conn->GetOne($sql); if ( $theme_id !== false ) { $this->setCache($cache_key, $theme_id); } } } return $theme_id; } /** * Returns site primary currency ISO code * * @return string * @access public * @todo Move into In-Commerce */ public function GetPrimaryCurrency() { $cache_key = 'primary_currency[%CurrSerial%][%SiteDomainSerial%]:' . $this->siteDomainField('DomainId'); $currency_iso = $this->getCache($cache_key); if ( $currency_iso === false ) { if ( $this->prefixRegistred('curr') ) { $this->Conn->nextQueryCachable = true; $currency_id = $this->siteDomainField('PrimaryCurrencyId'); $sql = 'SELECT ISO FROM ' . $this->getUnitOption('curr', 'TableName') . ' WHERE ' . ($currency_id > 0 ? 'CurrencyId = ' . $currency_id : 'IsPrimary = 1'); $currency_iso = $this->Conn->GetOne($sql); } else { $currency_iso = 'USD'; } $this->setCache($cache_key, $currency_iso); } return $currency_iso; } /** * Returns site domain field. When none of site domains are found false is returned. * * @param string $field * @param bool $formatted * @param string $format * @return mixed * @todo Move into separate module */ public function siteDomainField($field, $formatted = false, $format = null) { if ( $this->isAdmin ) { // don't apply any filtering in administrative console return false; } if ( !$this->siteDomain ) { $this->siteDomain = $this->recallObject('site-domain.current', null, Array ('live_table' => true)); /** @var kDBItem $site_domain */ } if ( $this->siteDomain->isLoaded() ) { return $formatted ? $this->siteDomain->GetField($field, $format) : $this->siteDomain->GetDBField($field); } return false; } /** * Registers default classes such as kDBEventHandler, kUrlManager * * Called automatically while initializing kApplication * * @return void * @access public */ public function RegisterDefaultClasses() { $this->registerClass('kHelper', KERNEL_PATH . '/kbase.php'); $this->registerClass('kMultipleFilter', KERNEL_PATH . '/utility/filters.php'); $this->registerClass('kiCacheable', KERNEL_PATH . '/interfaces/cacheable.php'); $this->registerClass('kEventManager', KERNEL_PATH . '/event_manager.php', 'EventManager'); $this->registerClass('kHookManager', KERNEL_PATH . '/managers/hook_manager.php'); $this->registerClass('kScheduledTaskManager', KERNEL_PATH . '/managers/scheduled_task_manager.php'); $this->registerClass('kRequestManager', KERNEL_PATH . '/managers/request_manager.php'); $this->registerClass('kSubscriptionManager', KERNEL_PATH . '/managers/subscription_manager.php'); $this->registerClass('kSubscriptionItem', KERNEL_PATH . '/managers/subscription_manager.php'); $this->registerClass('kUrlManager', KERNEL_PATH . '/managers/url_manager.php'); $this->registerClass('kUrlProcessor', KERNEL_PATH . '/managers/url_processor.php'); $this->registerClass('kPlainUrlProcessor', KERNEL_PATH . '/managers/plain_url_processor.php'); $this->registerClass('kRewriteUrlProcessor', KERNEL_PATH . '/managers/rewrite_url_processor.php'); $this->registerClass('kCacheManager', KERNEL_PATH . '/managers/cache_manager.php'); $this->registerClass('PhrasesCache', KERNEL_PATH . '/languages/phrases_cache.php', 'kPhraseCache'); $this->registerClass('kTempTablesHandler', KERNEL_PATH . '/utility/temp_handler.php'); $this->registerClass('kValidator', KERNEL_PATH . '/utility/validator.php'); $this->registerClass('kOpenerStack', KERNEL_PATH . '/utility/opener_stack.php'); $this->registerClass('kLogger', KERNEL_PATH . '/utility/logger.php'); $this->registerClass('kUnitConfigReader', KERNEL_PATH . '/utility/unit_config_reader.php'); $this->registerClass('PasswordHash', KERNEL_PATH . '/utility/php_pass.php'); // Params class descendants $this->registerClass('kArray', KERNEL_PATH . '/utility/params.php'); $this->registerClass('Params', KERNEL_PATH . '/utility/params.php'); $this->registerClass('Params', KERNEL_PATH . '/utility/params.php', 'kActions'); $this->registerClass('kCache', KERNEL_PATH . '/utility/cache.php', 'kCache', 'Params'); $this->registerClass('kHTTPQuery', KERNEL_PATH . '/utility/http_query.php', 'HTTPQuery'); // session $this->registerClass('Session', KERNEL_PATH . '/session/session.php'); $this->registerClass('SessionStorage', KERNEL_PATH . '/session/session_storage.php'); $this->registerClass('InpSession', KERNEL_PATH . '/session/inp_session.php', 'Session'); $this->registerClass('InpSessionStorage', KERNEL_PATH . '/session/inp_session_storage.php', 'SessionStorage'); // template parser $this->registerClass('kTagProcessor', KERNEL_PATH . '/processors/tag_processor.php'); $this->registerClass('kMainTagProcessor', KERNEL_PATH . '/processors/main_processor.php', 'm_TagProcessor'); $this->registerClass('kDBTagProcessor', KERNEL_PATH . '/db/db_tag_processor.php'); $this->registerClass('kCatDBTagProcessor', KERNEL_PATH . '/db/cat_tag_processor.php'); $this->registerClass('NParser', KERNEL_PATH . '/nparser/nparser.php'); $this->registerClass('TemplatesCache', KERNEL_PATH . '/nparser/template_cache.php'); // database $this->registerClass('kDBConnection', KERNEL_PATH . '/db/db_connection.php'); $this->registerClass('kDBConnectionDebug', KERNEL_PATH . '/db/db_connection.php'); $this->registerClass('kDBLoadBalancer', KERNEL_PATH . '/db/db_load_balancer.php'); $this->registerClass('kDBItem', KERNEL_PATH . '/db/dbitem.php'); $this->registerClass('kCatDBItem', KERNEL_PATH . '/db/cat_dbitem.php'); $this->registerClass('kDBList', KERNEL_PATH . '/db/dblist.php'); $this->registerClass('kCatDBList', KERNEL_PATH . '/db/cat_dblist.php'); $this->registerClass('kDBEventHandler', KERNEL_PATH . '/db/db_event_handler.php'); $this->registerClass('kCatDBEventHandler', KERNEL_PATH . '/db/cat_event_handler.php'); // email sending $this->registerClass('kEmail', KERNEL_PATH . '/utility/email.php'); $this->registerClass('kEmailSendingHelper', KERNEL_PATH . '/utility/email_send.php', 'EmailSender'); $this->registerClass('kSocket', KERNEL_PATH . '/utility/socket.php', 'Socket'); // do not move to config - this helper is used before configs are read $this->registerClass('kModulesHelper', KERNEL_PATH . self::MODULE_HELPER_PATH, 'ModulesHelper'); $this->registerClass('CKEditor', FULL_PATH . '/core/ckeditor/ckeditor_php5.php'); } /** * Registers default build events * * @return void */ public function RegisterDefaultBuildEvents() { $this->EventManager->registerBuildEvent('kTempTablesHandler', 'OnTempHandlerBuild'); } /** * Returns cached category information by given cache name. All given category * information is recached, when at least one of 4 caches is missing. * * @param int $category_id * @param string $name cache name = {filenames, category_designs, category_tree} * @return string * @access public */ public function getCategoryCache($category_id, $name) { return $this->cacheManager->getCategoryCache($category_id, $name); } /** * Returns caching type (none, memory, temporary) * * @param int $caching_type * @return bool * @access public */ public function isCachingType($caching_type) { return $this->cacheManager->isCachingType($caching_type); } /** * Increments serial based on prefix and it's ID (optional) * * @param string $prefix * @param int $id ID (value of IDField) or ForeignKeyField:ID * @param bool $increment * @return string * @access public */ public function incrementCacheSerial($prefix, $id = null, $increment = true) { return $this->cacheManager->incrementCacheSerial($prefix, $id, $increment); } /** * Returns cached $key value from cache named $cache_name * * @param int $key key name from cache * @param bool $store_locally store data locally after retrieved * @param int $max_rebuild_seconds * @return mixed * @access public */ public function getCache($key, $store_locally = true, $max_rebuild_seconds = 0) { return $this->cacheManager->getCache($key, $store_locally, $max_rebuild_seconds); } /** * Stores new $value in cache with $key name * * @param int $key key name to add to cache * @param mixed $value value of cached record * @param int $expiration when value expires (0 - doesn't expire) * @return bool * @access public */ public function setCache($key, $value, $expiration = 0) { return $this->cacheManager->setCache($key, $value, $expiration); } /** * Stores new $value in cache with $key name (only if it's not there) * * @param int $key key name to add to cache * @param mixed $value value of cached record * @param int $expiration when value expires (0 - doesn't expire) * @return bool * @access public */ public function addCache($key, $value, $expiration = 0) { return $this->cacheManager->addCache($key, $value, $expiration); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time * @return bool * @access public */ public function rebuildCache($name, $mode = null, $max_rebuilding_time = 0) { return $this->cacheManager->rebuildCache($name, $mode, $max_rebuilding_time); } /** * Deletes key from cache * * @param string $key * @return void * @access public */ public function deleteCache($key) { $this->cacheManager->deleteCache($key); } /** * Reset's all memory cache at once * * @return void * @access public */ public function resetCache() { $this->cacheManager->resetCache(); } /** * Returns value from database cache * * @param string $name key name * @param int $max_rebuild_seconds * @return mixed * @access public */ public function getDBCache($name, $max_rebuild_seconds = 0) { return $this->cacheManager->getDBCache($name, $max_rebuild_seconds); } /** * Sets value to database cache * * @param string $name * @param mixed $value * @param int|bool $expiration * @return void * @access public */ public function setDBCache($name, $value, $expiration = false) { $this->cacheManager->setDBCache($name, $value, $expiration); } /** * Sets rebuilding mode for given cache * * @param string $name * @param int $mode * @param int $max_rebuilding_time * @return bool * @access public */ public function rebuildDBCache($name, $mode = null, $max_rebuilding_time = 0) { return $this->cacheManager->rebuildDBCache($name, $mode, $max_rebuilding_time); } /** * Deletes key from database cache * * @param string $name * @return void * @access public */ public function deleteDBCache($name) { $this->cacheManager->deleteDBCache($name); } /** * Registers each module specific constants if any found * * @return bool * @access protected */ protected function registerModuleConstants() { if ( file_exists(KERNEL_PATH . '/constants.php') ) { kUtil::includeOnce(KERNEL_PATH . '/constants.php'); } if ( !$this->ModuleInfo ) { return false; } foreach ($this->ModuleInfo as $module_info) { $constants_file = FULL_PATH . '/' . $module_info['Path'] . 'constants.php'; if ( file_exists($constants_file) ) { kUtil::includeOnce($constants_file); } } return true; } /** * Performs redirect to hard maintenance template * * @return void * @access public */ public function redirectToMaintenance() { $maintenance_page = WRITEBALE_BASE . '/maintenance.html'; $query_string = ''; // $this->isAdmin ? '' : '?next_template=' . kUtil::escape($_SERVER['REQUEST_URI'], kUtil::ESCAPE_URL); if ( file_exists(FULL_PATH . $maintenance_page) ) { header('Location: ' . BASE_PATH . $maintenance_page . $query_string); exit; } } /** * Actually runs the parser against current template and stores parsing result * * This method gets 't' variable passed to the script, loads the template given in 't' variable and * parses it. The result is store in {@link $this->HTML} property. * * @return void * @access public */ public function Run() { // process maintenance mode redirect: begin $maintenance_mode = $this->getMaintenanceMode(); if ( $maintenance_mode == MaintenanceMode::HARD ) { $this->redirectToMaintenance(); } elseif ( $maintenance_mode == MaintenanceMode::SOFT ) { $maintenance_template = $this->isAdmin ? 'login' : $this->ConfigValue('SoftMaintenanceTemplate'); if ( $this->GetVar('t') != $maintenance_template ) { $redirect_params = Array (); if ( !$this->isAdmin ) { $redirect_params['next_template'] = $_SERVER['REQUEST_URI']; } $this->Redirect($maintenance_template, $redirect_params); } } // process maintenance mode redirect: end if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application before Run:'); } if ( $this->isAdminUser ) { // for permission checking in events & templates $this->LinkVar('module'); // for common configuration templates $this->LinkVar('module_key'); // for common search templates $this->LinkVar('section'); // for common configuration templates if ( $this->GetVar('m_opener') == 'p' ) { $this->LinkVar('main_prefix'); // window prefix, that opened selector $this->LinkVar('dst_field'); // field to set value choosed in selector } if ( $this->GetVar('ajax') == 'yes' && !$this->GetVar('debug_ajax') ) { // hide debug output from ajax requests automatically kUtil::safeDefine('DBG_SKIP_REPORTING', 1); // safeDefine, because debugger also defines it } } elseif ( $this->GetVar('admin') ) { /** @var Session $admin_session */ $admin_session = $this->recallObject('Session.admin'); // store Admin Console User's ID to Front-End's session for cross-session permission checks $this->StoreVar('admin_user_id', (int)$admin_session->RecallVar('user_id')); if ( $this->CheckAdminPermission('CATEGORY.MODIFY', 0, $this->getBaseCategory()) ) { // user can edit cms blocks (when viewing front-end through admin's frame) $editing_mode = $this->GetVar('editing_mode'); define('EDITING_MODE', $editing_mode ? $editing_mode : EDITING_MODE_BROWSE); } } kUtil::safeDefine('EDITING_MODE', ''); // user can't edit anything $this->Phrases->setPhraseEditing(); $this->EventManager->ProcessRequest(); $this->InitParser(); $t = $this->GetVar('render_template', $this->GetVar('t')); if ( !$this->TemplatesCache->TemplateExists($t) && !$this->isAdmin ) { /** @var CategoriesEventHandler $cms_handler */ $cms_handler = $this->recallObject('st_EventHandler'); $t = ltrim($cms_handler->GetDesignTemplate(), '/'); if ( defined('DEBUG_MODE') && $this->isDebugMode() ) { $this->Debugger->appendHTML('Design Template: ' . $t . '; CategoryID: ' . $this->GetVar('m_cat_id')); } } /*else { $cms_handler->SetCatByTemplate(); }*/ if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application before Parsing:'); } $this->HTML = $this->Parser->Run($t); if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application after Parsing:'); } } /** * Replaces current rendered template with given one. * * @param string|null $template Template. * * @return void */ public function QuickRun($template) { /** @var kThemesHelper $themes_helper */ $themes_helper = $this->recallObject('ThemesHelper'); // Set Web Request variables to affect link building on template itself. $this->SetVar('t', $template); $this->SetVar('m_cat_id', $themes_helper->getPageByTemplate($template)); $this->SetVar('passed', 'm'); // Replace current page content with given template. $this->InitParser(); $this->Parser->Clear(); $this->HTML = $this->Parser->Run($template); } /** * Performs template parser/cache initialization * * @param bool|string $theme_name * @return void * @access public */ public function InitParser($theme_name = false) { if ( !is_object($this->Parser) ) { $this->Parser = $this->recallObject('NParser'); $this->TemplatesCache = $this->recallObject('TemplatesCache'); } $this->TemplatesCache->forceThemeName = $theme_name; } /** * Send the parser results to browser * * Actually send everything stored in {@link $this->HTML}, to the browser by echoing it. * * @return void * @access public */ public function Done() { $this->HandleEvent(new kEvent('adm:OnBeforeShutdown')); $debug_mode = defined('DEBUG_MODE') && $this->isDebugMode(); if ( $debug_mode ) { if ( kUtil::constOn('DBG_PROFILE_MEMORY') ) { $this->Debugger->appendMemoryUsage('Application before Done:'); } $this->Session->SaveData(); // adds session data to debugger report $this->HTML = ob_get_clean() . $this->HTML . $this->Debugger->printReport(true); } else { // send "Set-Cookie" header before any output is made $this->Session->SetSession(); $this->HTML = ob_get_clean() . $this->HTML; } $this->_outputPage(); $this->cacheManager->UpdateApplicationCache(); if ( !$debug_mode ) { $this->Session->SaveData(); } $this->EventManager->runScheduledTasks(); if ( defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !$this->isAdmin ) { $this->_storeStatistics(); } } /** * Outputs generated page content to end-user * * @return void * @access protected */ protected function _outputPage() { $this->setContentType(); ob_start(); if ( $this->UseOutputCompression() ) { $compression_level = $this->ConfigValue('OutputCompressionLevel'); if ( !$compression_level || $compression_level < 0 || $compression_level > 9 ) { $compression_level = 7; } header('Content-Encoding: gzip'); echo gzencode($this->HTML, $compression_level); } else { // when gzip compression not used connection won't be closed early! echo $this->HTML; } // send headers to tell the browser to close the connection header('Content-Length: ' . ob_get_length()); header('Connection: close'); // flush all output ob_end_flush(); if ( ob_get_level() ) { ob_flush(); } flush(); // close current session if ( session_id() ) { session_write_close(); } } /** * Stores script execution statistics to database * * @return void * @access protected */ protected function _storeStatistics() { global $start; $script_time = microtime(true) - $start; $query_statistics = $this->Conn->getQueryStatistics(); // time & count $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'StatisticsCapture WHERE TemplateName = ' . $this->Conn->qstr($this->GetVar('t')); $data = $this->Conn->GetRow($sql); if ( $data ) { $this->_updateAverageStatistics($data, 'ScriptTime', $script_time); $this->_updateAverageStatistics($data, 'SqlTime', $query_statistics['time']); $this->_updateAverageStatistics($data, 'SqlCount', $query_statistics['count']); $data['Hits']++; $data['LastHit'] = adodb_mktime(); $this->Conn->doUpdate($data, TABLE_PREFIX . 'StatisticsCapture', 'StatisticsId = ' . $data['StatisticsId']); } else { $data['ScriptTimeMin'] = $data['ScriptTimeAvg'] = $data['ScriptTimeMax'] = $script_time; $data['SqlTimeMin'] = $data['SqlTimeAvg'] = $data['SqlTimeMax'] = $query_statistics['time']; $data['SqlCountMin'] = $data['SqlCountAvg'] = $data['SqlCountMax'] = $query_statistics['count']; $data['TemplateName'] = $this->GetVar('t'); $data['Hits'] = 1; $data['LastHit'] = adodb_mktime(); $this->Conn->doInsert($data, TABLE_PREFIX . 'StatisticsCapture'); } } /** * Calculates average time for statistics * * @param Array $data * @param string $field_prefix * @param float $current_value * @return void * @access protected */ protected function _updateAverageStatistics(&$data, $field_prefix, $current_value) { $data[$field_prefix . 'Avg'] = (($data['Hits'] * $data[$field_prefix . 'Avg']) + $current_value) / ($data['Hits'] + 1); if ( $current_value < $data[$field_prefix . 'Min'] ) { $data[$field_prefix . 'Min'] = $current_value; } if ( $current_value > $data[$field_prefix . 'Max'] ) { $data[$field_prefix . 'Max'] = $current_value; } } /** * Remembers slow query SQL and execution time into log * * @param string $slow_sql * @param int $time * @return void * @access public */ public function logSlowQuery($slow_sql, $time) { $query_crc = kUtil::crc32($slow_sql); $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'SlowSqlCapture WHERE QueryCrc = ' . $query_crc; $data = $this->Conn->Query($sql, null, true); if ( $data ) { $this->_updateAverageStatistics($data, 'Time', $time); $template_names = explode(',', $data['TemplateNames']); array_push($template_names, $this->GetVar('t')); $data['TemplateNames'] = implode(',', array_unique($template_names)); $data['Hits']++; $data['LastHit'] = adodb_mktime(); $this->Conn->doUpdate($data, TABLE_PREFIX . 'SlowSqlCapture', 'CaptureId = ' . $data['CaptureId']); } else { $data['TimeMin'] = $data['TimeAvg'] = $data['TimeMax'] = $time; $data['SqlQuery'] = $slow_sql; $data['QueryCrc'] = $query_crc; $data['TemplateNames'] = $this->GetVar('t'); $data['Hits'] = 1; $data['LastHit'] = adodb_mktime(); $this->Conn->doInsert($data, TABLE_PREFIX . 'SlowSqlCapture'); } } /** * Checks if output compression options is available * * @return bool * @access protected */ protected function UseOutputCompression() { if ( kUtil::constOn('IS_INSTALL') || kUtil::constOn('DBG_ZEND_PRESENT') || kUtil::constOn('SKIP_OUT_COMPRESSION') ) { return false; } $accept_encoding = isset($_SERVER['HTTP_ACCEPT_ENCODING']) ? $_SERVER['HTTP_ACCEPT_ENCODING'] : ''; return $this->ConfigValue('UseOutputCompression') && function_exists('gzencode') && strstr($accept_encoding, 'gzip'); } // Facade /** * Returns current session id (SID) * * @return int * @access public */ public function GetSID() { /** @var Session $session */ $session = $this->recallObject('Session'); return $session->GetID(); } /** * Destroys current session * * @return void * @access public * @see UserHelper::logoutUser() */ public function DestroySession() { /** @var Session $session */ $session = $this->recallObject('Session'); $session->Destroy(); } /** * Returns variable passed to the script as GET/POST/COOKIE * * @param string $name Name of variable to retrieve * @param mixed $default default value returned in case if variable not present * @return mixed * @access public */ public function GetVar($name, $default = false) { return isset($this->HttpQuery->_Params[$name]) ? $this->HttpQuery->_Params[$name] : $default; } /** * Removes forceful escaping done to the variable upon Front-End submission. * * @param string|array $value Value. * * @return string|array * @see kHttpQuery::StripSlashes * @todo Temporary method for marking problematic places to take care of, when forceful escaping will be removed. */ public function unescapeRequestVariable($value) { return $this->HttpQuery->unescapeRequestVariable($value); } /** * Returns variable passed to the script as $type * * @param string $name Name of variable to retrieve * @param string $type Get/Post/Cookie * @param mixed $default default value returned in case if variable not present * @return mixed * @access public */ public function GetVarDirect($name, $type, $default = false) { // $type = ucfirst($type); $array = $this->HttpQuery->$type; return isset($array[$name]) ? $array[$name] : $default; } /** * Returns ALL variables passed to the script as GET/POST/COOKIE * * @return Array * @access public * @deprecated */ public function GetVars() { return $this->HttpQuery->GetParams(); } /** * Set the variable 'as it was passed to the script through GET/POST/COOKIE' * * This could be useful to set the variable when you know that * other objects would relay on variable passed from GET/POST/COOKIE * or you could use SetVar() / GetVar() pairs to pass the values between different objects.
* * @param string $var Variable name to set * @param mixed $val Variable value * @return void * @access public */ public function SetVar($var,$val) { $this->HttpQuery->Set($var, $val); } /** * Deletes kHTTPQuery variable * * @param string $var * @return void * @todo Think about method name */ public function DeleteVar($var) { $this->HttpQuery->Remove($var); } /** * Deletes Session variable * * @param string $var * @return void * @access public */ public function RemoveVar($var) { $this->Session->RemoveVar($var); } /** * Removes variable from persistent session * * @param string $var * @return void * @access public */ public function RemovePersistentVar($var) { $this->Session->RemovePersistentVar($var); } /** * Restores Session variable to it's db version * * @param string $var * @return void * @access public */ public function RestoreVar($var) { $this->Session->RestoreVar($var); } /** * Returns session variable value * * Return value of $var variable stored in Session. An optional default value could be passed as second parameter. * * @param string $var Variable name * @param mixed $default Default value to return if no $var variable found in session * @return mixed * @access public * @see Session::RecallVar() */ public function RecallVar($var,$default=false) { return $this->Session->RecallVar($var,$default); } /** * Returns variable value from persistent session * * @param string $var * @param mixed $default * @return mixed * @access public * @see Session::RecallPersistentVar() */ public function RecallPersistentVar($var, $default = false) { return $this->Session->RecallPersistentVar($var, $default); } /** * Stores variable $val in session under name $var * * Use this method to store variable in session. Later this variable could be recalled. * * @param string $var Variable name * @param mixed $val Variable value * @param bool $optional * @return void * @access public * @see kApplication::RecallVar() */ public function StoreVar($var, $val, $optional = false) { /** @var Session $session */ $session = $this->recallObject('Session'); $this->Session->StoreVar($var, $val, $optional); } /** * Stores variable to persistent session * * @param string $var * @param mixed $val * @param bool $optional * @return void * @access public */ public function StorePersistentVar($var, $val, $optional = false) { $this->Session->StorePersistentVar($var, $val, $optional); } /** * Stores default value for session variable * * @param string $var * @param string $val * @param bool $optional * @return void * @access public * @see Session::RecallVar() * @see Session::StoreVar() */ public function StoreVarDefault($var, $val, $optional = false) { /** @var Session $session */ $session = $this->recallObject('Session'); $this->Session->StoreVarDefault($var, $val, $optional); } /** * Links HTTP Query variable with session variable * * If variable $var is passed in HTTP Query it is stored in session for later use. If it's not passed it's recalled from session. * This method could be used for making sure that GetVar will return query or session value for given * variable, when query variable should overwrite session (and be stored there for later use).
* This could be used for passing item's ID into popup with multiple tab - * in popup script you just need to call LinkVar('id', 'current_id') before first use of GetVar('id'). * After that you can be sure that GetVar('id') will return passed id or id passed earlier and stored in session * * @param string $var HTTP Query (GPC) variable name * @param mixed $ses_var Session variable name * @param mixed $default Default variable value * @param bool $optional * @return void * @access public */ public function LinkVar($var, $ses_var = null, $default = '', $optional = false) { if ( !isset($ses_var) ) { $ses_var = $var; } if ( $this->GetVar($var) !== false ) { $this->StoreVar($ses_var, $this->GetVar($var), $optional); } else { $this->SetVar($var, $this->RecallVar($ses_var, $default)); } } /** * Returns variable from HTTP Query, or from session if not passed in HTTP Query * * The same as LinkVar, but also returns the variable value taken from HTTP Query if passed, or from session if not passed. * Returns the default value if variable does not exist in session and was not passed in HTTP Query * * @param string $var HTTP Query (GPC) variable name * @param mixed $ses_var Session variable name * @param mixed $default Default variable value * @return mixed * @access public * @see LinkVar */ public function GetLinkedVar($var, $ses_var = null, $default = '') { $this->LinkVar($var, $ses_var, $default); return $this->GetVar($var); } /** * Renders given tag and returns it's output * * @param string $prefix * @param string $tag * @param Array $params * @return mixed * @access public * @see kApplication::InitParser() */ public function ProcessParsedTag($prefix, $tag, $params) { /** @var kDBTagProcessor $processor */ $processor = $this->Parser->GetProcessor($prefix); return $processor->ProcessParsedTag($tag, $params, $prefix); } /** * Return object of IDBConnection interface * * Return object of IDBConnection interface already connected to the project database, configurable in config.php * * @return IDBConnection * @access public */ public function &GetADODBConnection() { return $this->Conn; } /** * Allows to parse given block name or include template * * @param Array $params Parameters to pass to block. Reserved parameter "name" used to specify block name. * @param bool $pass_params Forces to pass current parser params to this block/template. Use with caution, because you can accidentally pass "block_no_data" parameter. * @param bool $as_template * @return string * @access public */ public function ParseBlock($params, $pass_params = false, $as_template = false) { if ( substr($params['name'], 0, 5) == 'html:' ) { return substr($params['name'], 5); } return $this->Parser->ParseBlock($params, $pass_params, $as_template); } /** * Checks, that we have given block defined * * @param string $name * @return bool * @access public */ public function ParserBlockFound($name) { return $this->Parser->blockFound($name); } /** * Allows to include template with a given name and given parameters * * @param Array $params Parameters to pass to template. Reserved parameter "name" used to specify template name. * @return string * @access public */ public function IncludeTemplate($params) { return $this->Parser->IncludeTemplate($params, isset($params['is_silent']) ? 1 : 0); } /** * Return href for template * * @param string $t Template path * @param string $prefix index.php prefix - could be blank, 'admin' * @param Array $params * @param string $index_file * @return string */ public function HREF($t, $prefix = '', $params = Array (), $index_file = null) { return $this->UrlManager->HREF($t, $prefix, $params, $index_file); } /** * Returns theme template filename and it's corresponding page_id based on given seo template * * @param string $seo_template * @return string * @access public */ public function getPhysicalTemplate($seo_template) { return $this->UrlManager->getPhysicalTemplate($seo_template); } /** * Returns template name, that corresponds with given virtual (not physical) page id * * @param int $page_id * @return string|bool * @access public */ public function getVirtualPageTemplate($page_id) { return $this->UrlManager->getVirtualPageTemplate($page_id); } /** * Returns section template for given physical/virtual template * * @param string $template * @param int $theme_id * @return string * @access public */ public function getSectionTemplate($template, $theme_id = null) { return $this->UrlManager->getSectionTemplate($template, $theme_id); } /** * Returns variables with values that should be passed through with this link + variable list * * @param Array $params * @return Array * @access public */ public function getPassThroughVariables(&$params) { return $this->UrlManager->getPassThroughVariables($params); } /** * Builds url * * @param string $t * @param Array $params * @param string $pass * @param bool $pass_events * @param bool $env_var * @return string * @access public */ public function BuildEnv($t, $params, $pass = 'all', $pass_events = false, $env_var = true) { return $this->UrlManager->plain->build($t, $params, $pass, $pass_events, $env_var); } /** * Process QueryString only, create * events, ids, based on config * set template name and sid in * desired application variables. * * @param string $env_var environment string value * @param string $pass_name * @return Array * @access public */ public function processQueryString($env_var, $pass_name = 'passed') { return $this->UrlManager->plain->parse($env_var, $pass_name); } /** * Parses rewrite url and returns parsed variables * * @param string $url * @param string $pass_name * @return Array * @access public */ public function parseRewriteUrl($url, $pass_name = 'passed') { return $this->UrlManager->rewrite->parse($url, $pass_name); } /** * Returns base part of all urls, build on website * * @param string $prefix * @param bool $ssl * @param bool $add_port * @return string * @access public */ public function BaseURL($prefix = '', $ssl = null, $add_port = true) { if ( $ssl === null ) { // stay on same encryption level return PROTOCOL . SERVER_NAME . ($add_port && defined('PORT') ? ':' . PORT : '') . BASE_PATH . $prefix . '/'; } if ( $ssl ) { // going from http:// to https:// $base_url = $this->isAdmin ? $this->ConfigValue('AdminSSL_URL') : false; if ( !$base_url ) { $ssl_url = $this->siteDomainField('SSLUrl'); $base_url = $ssl_url !== false ? $ssl_url : $this->ConfigValue('SSL_URL'); } return rtrim($base_url, '/') . $prefix . '/'; } // going from https:// to http:// $domain = $this->siteDomainField('DomainName'); if ( $domain === false ) { $domain = DOMAIN; } return 'http://' . $domain . ($add_port && defined('PORT') ? ':' . PORT : '') . BASE_PATH . $prefix . '/'; } /** * Redirects user to url, that's build based on given parameters * * @param string $t * @param Array $params * @param string $prefix * @param string $index_file * @return void * @access public */ public function Redirect($t = '', $params = Array(), $prefix = '', $index_file = null) { $js_redirect = getArrayValue($params, 'js_redirect'); if ( $t == '' || $t === true ) { $t = $this->GetVar('t'); } // pass prefixes and special from previous url if ( array_key_exists('js_redirect', $params) ) { unset($params['js_redirect']); } // allows to send custom responce code along with redirect header if ( array_key_exists('response_code', $params) ) { $response_code = (int)$params['response_code']; unset($params['response_code']); } else { $response_code = 302; // Found } if ( !array_key_exists('pass', $params) ) { $params['pass'] = 'all'; } if ( $this->GetVar('ajax') == 'yes' && $t == $this->GetVar('t') ) { // redirects to the same template as current $params['ajax'] = 'yes'; } $location = $this->HREF($t, $prefix, $params, $index_file); if ( $this->isDebugMode() && (kUtil::constOn('DBG_REDIRECT') || (kUtil::constOn('DBG_RAISE_ON_WARNINGS') && $this->Debugger->WarningCount)) ) { $this->Debugger->appendTrace(); echo 'Debug output above !!!
' . "\n"; if ( array_key_exists('HTTP_REFERER', $_SERVER) ) { echo 'Referer: ' . kUtil::escape($_SERVER['HTTP_REFERER'], kUtil::ESCAPE_HTML) . '
' . "\n"; } echo "Proceed to redirect: {$location}
\n"; } else { if ( $js_redirect ) { // show "redirect" template instead of redirecting, // because "Set-Cookie" header won't work, when "Location" // header is used later $this->SetVar('t', 'redirect'); $this->SetVar('redirect_to', $location); // make all additional parameters available on "redirect" template too foreach ($params as $name => $value) { $this->SetVar($name, $value); } return; } else { if ( $this->GetVar('ajax') == 'yes' && ($t != $this->GetVar('t') || !$this->isSOPSafe($location, $t)) ) { // redirection to other then current template during ajax request OR SOP violation kUtil::safeDefine('DBG_SKIP_REPORTING', 1); echo '#redirect#' . $location; } elseif ( headers_sent() != '' ) { // some output occurred -> redirect using javascript echo ''; } else { // no output before -> redirect using HTTP header // header('HTTP/1.1 302 Found'); header('Location: ' . $location, true, $response_code); } } } // session expiration is called from session initialization, // that's why $this->Session may be not defined here /** @var Session $session */ $session = $this->recallObject('Session'); if ( $this->InitDone ) { // if redirect happened in the middle of application initialization don't call event, // that presumes that application was successfully initialized $this->HandleEvent(new kEvent('adm:OnBeforeShutdown')); } $session->SaveData(); ob_end_flush(); exit; } /** * Determines if real redirect should be made within AJAX request. * * @param string $url Location. * @param string $template Template. * * @return boolean * @link http://en.wikipedia.org/wiki/Same-origin_policy */ protected function isSOPSafe($url, $template) { $parsed_url = parse_url($url); if ( $parsed_url['scheme'] . '://' != PROTOCOL ) { return false; } if ( $parsed_url['host'] != SERVER_NAME ) { return false; } if ( defined('PORT') && isset($parsed_url['port']) && $parsed_url['port'] != PORT ) { return false; } return true; } /** * Returns translation of given label * * @param string $label * @param bool $allow_editing return translation link, when translation is missing on current language * @param bool $use_admin use current Admin Console language to translate phrase * @return string * @access public */ public function Phrase($label, $allow_editing = true, $use_admin = false) { return $this->Phrases->GetPhrase($label, $allow_editing, $use_admin); } /** * Replace language tags in exclamation marks found in text * * @param string $text * @param bool $force_escape force escaping, not escaping of resulting string * @return string * @access public */ public function ReplaceLanguageTags($text, $force_escape = null) { return $this->Phrases->ReplaceLanguageTags($text, $force_escape); } /** * Checks if user is logged in, and creates * user object if so. User object can be recalled * later using "u.current" prefix_special. Also you may * get user id by getting "u.current_id" variable. * * @return void * @access protected */ protected function ValidateLogin() { /** @var Session $session */ $session = $this->recallObject('Session'); $user_id = $session->GetField('PortalUserId'); if ( !$user_id && $user_id != USER_ROOT ) { $user_id = USER_GUEST; } $this->SetVar('u.current_id', $user_id); if ( !$this->isAdmin ) { // needed for "profile edit", "registration" forms ON FRONT ONLY $this->SetVar('u_id', $user_id); } $this->StoreVar('user_id', $user_id, $user_id == USER_GUEST); // storing Guest user_id (-2) is optional $this->isAdminUser = $this->isAdmin && $this->LoggedIn(); if ( $this->GetVar('expired') == 1 ) { // this parameter is set only from admin /** @var UsersItem $user */ $user = $this->recallObject('u.login-admin', null, Array ('form_name' => 'login')); $user->SetError('UserLogin', 'session_expired', 'la_text_sess_expired'); } $this->HandleEvent(new kEvent('adm:OnLogHttpRequest')); if ( $user_id != USER_GUEST ) { // normal users + root $this->LoadPersistentVars(); } $user_timezone = $this->Session->GetField('TimeZone'); if ( $user_timezone ) { date_default_timezone_set($user_timezone); } } /** * Loads current user persistent session data * * @return void * @access public */ public function LoadPersistentVars() { $this->Session->LoadPersistentVars(); } /** * Returns configuration option value by name * * @param string $name * @return string * @access public */ public function ConfigValue($name) { return $this->cacheManager->ConfigValue($name); } /** * Changes value of individual configuration variable (+resets cache, when needed) * * @param string $name * @param string $value * @param bool $local_cache_only * @return string * @access public */ public function SetConfigValue($name, $value, $local_cache_only = false) { return $this->cacheManager->SetConfigValue($name, $value, $local_cache_only); } /** * Allows to process any type of event * * @param kEvent $event * @param Array $params * @param Array $specific_params * @return void * @access public */ public function HandleEvent($event, $params = null, $specific_params = null) { if ( isset($params) ) { $event = new kEvent($params, $specific_params); } $this->EventManager->HandleEvent($event); } /** * Notifies event subscribers, that event has occured * * @param kEvent $event * @return void */ public function notifyEventSubscribers(kEvent $event) { $this->EventManager->notifySubscribers($event); } /** * Allows to process any type of event * * @param kEvent $event * @return bool * @access public */ public function eventImplemented(kEvent $event) { return $this->EventManager->eventImplemented($event); } /** * Registers new class in the factory * * @param string $real_class Real name of class as in class declaration * @param string $file Filename in what $real_class is declared * @param string $pseudo_class Name under this class object will be accessed using getObject method * @return void * @access public */ public function registerClass($real_class, $file, $pseudo_class = null) { $this->Factory->registerClass($real_class, $file, $pseudo_class); } /** * Unregisters existing class from factory * * @param string $real_class Real name of class as in class declaration * @param string $pseudo_class Name under this class object is accessed using getObject method * @return void * @access public */ public function unregisterClass($real_class, $pseudo_class = null) { $this->Factory->unregisterClass($real_class, $pseudo_class); } /** * Finds the absolute path to the file where the class is defined. * * @param string $class The name of the class. * * @return string|false */ public function findClassFile($class) { return $this->Factory->findClassFile($class); } /** * Add new scheduled task * * @param string $short_name name to be used to store last maintenance run info * @param string $event_string * @param int $run_schedule run schedule like for Cron * @param int $status * @access public */ public function registerScheduledTask($short_name, $event_string, $run_schedule, $status = STATUS_ACTIVE) { $this->EventManager->registerScheduledTask($short_name, $event_string, $run_schedule, $status); } /** * Registers Hook from subprefix event to master prefix event * * Pattern: Observer * * @param string $hook_event * @param string $do_event * @param int $mode * @param bool $conditional * @access public */ public function registerHook($hook_event, $do_event, $mode = hAFTER, $conditional = false) { $this->EventManager->registerHook($hook_event, $do_event, $mode, $conditional); } /** * Registers build event for given pseudo class * * @param string $pseudo_class * @param string $event_name * @access public */ public function registerBuildEvent($pseudo_class, $event_name) { $this->EventManager->registerBuildEvent($pseudo_class, $event_name); } /** * Allows one TagProcessor tag act as other TagProcessor tag * * @param Array $tag_info * @return void * @access public */ public function registerAggregateTag($tag_info) { /** @var kArray $aggregator */ $aggregator = $this->recallObject('TagsAggregator', 'kArray'); $tag_data = Array ( $tag_info['LocalPrefix'], $tag_info['LocalTagName'], getArrayValue($tag_info, 'LocalSpecial') ); $aggregator->SetArrayValue($tag_info['AggregateTo'], $tag_info['AggregatedTagName'], $tag_data); } /** * Returns object using params specified, creates it if is required * * @param string $name * @param string $pseudo_class * @param Array $event_params * @param Array $arguments * @return kBase */ public function recallObject($name, $pseudo_class = null, array $event_params = array(), array $arguments = array()) { /*if ( !$this->hasObject($name) && $this->isDebugMode() && ($name == '_prefix_here_') ) { // first time, when object with "_prefix_here_" prefix is accessed $this->Debugger->appendTrace(); }*/ return $this->Factory->getObject($name, $pseudo_class, $event_params, $arguments); } /** * Returns tag processor for prefix specified * * @param string $prefix * @return kDBTagProcessor * @access public */ public function recallTagProcessor($prefix) { $this->InitParser(); // because kDBTagProcesor is in NParser dependencies return $this->recallObject($prefix . '_TagProcessor'); } /** * Checks if object with prefix passes was already created in factory * * @param string $name object pseudo_class, prefix * @return bool * @access public */ public function hasObject($name) { return $this->Factory->hasObject($name); } /** * Removes object from storage by given name * * @param string $name Object's name in the Storage * @return void * @access public */ public function removeObject($name) { $this->Factory->DestroyObject($name); } /** * Get's real class name for pseudo class, includes class file and creates class instance * * Pattern: Factory Method * * @param string $pseudo_class * @param Array $arguments * @return kBase * @access public */ public function makeClass($pseudo_class, array $arguments = array()) { return $this->Factory->makeClass($pseudo_class, $arguments); } /** * Checks if application is in debug mode * * @param bool $check_debugger check if kApplication debugger is initialized too, not only for defined DEBUG_MODE constant * @return bool * @author Alex * @access public */ public function isDebugMode($check_debugger = true) { $debug_mode = defined('DEBUG_MODE') && DEBUG_MODE; if ($check_debugger) { $debug_mode = $debug_mode && is_object($this->Debugger); } return $debug_mode; } /** * Apply url rewriting used by mod_rewrite or not * * @param bool|null $ssl Force ssl link to be build * @return bool * @access public */ public function RewriteURLs($ssl = false) { // case #1,#4: // we want to create https link from http mode // we want to create https link from https mode // conditions: ($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL') // case #2,#3: // we want to create http link from https mode // we want to create http link from http mode // conditions: !$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://') $allow_rewriting = (!$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://')) // always allow mod_rewrite for http || // or allow rewriting for redirect TO httpS or when already in httpS (($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL')); // but only if it's allowed in config! return kUtil::constOn('MOD_REWRITE') && $allow_rewriting; } /** * Reads unit (specified by $prefix) * option specified by $option * * @param string $prefix * @param string $option * @param mixed $default * @return string * @access public */ public function getUnitOption($prefix, $option, $default = false) { return $this->UnitConfigReader->getUnitOption($prefix, $option, $default); } /** * Set's new unit option value * * @param string $prefix * @param string $option * @param string $value * @access public */ public function setUnitOption($prefix, $option, $value) { $this->UnitConfigReader->setUnitOption($prefix,$option,$value); } /** * Read all unit with $prefix options * * @param string $prefix * @return Array * @access public */ public function getUnitOptions($prefix) { return $this->UnitConfigReader->getUnitOptions($prefix); } /** * Returns true if config exists and is allowed for reading * * @param string $prefix * @return bool */ public function prefixRegistred($prefix) { return $this->UnitConfigReader->prefixRegistred($prefix); } /** * Splits any mixing of prefix and * special into correct ones * * @param string $prefix_special * @return Array * @access public */ public function processPrefix($prefix_special) { return $this->Factory->processPrefix($prefix_special); } /** * Set's new event for $prefix_special * passed * * @param string $prefix_special * @param string $event_name * @return void * @access public */ public function setEvent($prefix_special, $event_name) { $this->EventManager->setEvent($prefix_special, $event_name); } /** * SQL Error Handler * * @param int $code * @param string $msg * @param string $sql * @return bool * @access public * @throws Exception * @deprecated */ public function handleSQLError($code, $msg, $sql) { return $this->_logger->handleSQLError($code, $msg, $sql); } /** * Returns & blocks next ResourceId available in system * * @return int * @access public */ public function NextResourceId() { $table_name = TABLE_PREFIX . 'IdGenerator'; $this->Conn->Query('LOCK TABLES ' . $table_name . ' WRITE'); $this->Conn->Query('UPDATE ' . $table_name . ' SET lastid = lastid + 1'); $id = $this->Conn->GetOne('SELECT lastid FROM ' . $table_name); if ( $id === false ) { $this->Conn->Query('INSERT INTO ' . $table_name . ' (lastid) VALUES (2)'); $id = 2; } $this->Conn->Query('UNLOCK TABLES'); return $id - 1; } /** * Returns genealogical main prefix for sub-table prefix passes * OR prefix, that has been found in REQUEST and some how is parent of passed sub-table prefix * * @param string $current_prefix * @param bool $real_top if set to true will return real topmost prefix, regardless of its id is passed or not * @return string * @access public */ public function GetTopmostPrefix($current_prefix, $real_top = false) { // 1. get genealogical tree of $current_prefix $prefixes = Array ($current_prefix); while ($parent_prefix = $this->getUnitOption($current_prefix, 'ParentPrefix')) { if ( !$this->prefixRegistred($parent_prefix) ) { // stop searching, when parent prefix is not registered break; } $current_prefix = $parent_prefix; array_unshift($prefixes, $current_prefix); } if ( $real_top ) { return $current_prefix; } // 2. find what if parent is passed $passed = explode(',', $this->GetVar('all_passed')); foreach ($prefixes as $a_prefix) { if ( in_array($a_prefix, $passed) ) { return $a_prefix; } } return $current_prefix; } /** * Triggers email event of type Admin * * @param string $email_template_name * @param int $to_user_id * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text * @return kEvent * @access public */ public function emailAdmin($email_template_name, $to_user_id = null, $send_params = Array ()) { return $this->_email($email_template_name, EmailTemplate::TEMPLATE_TYPE_ADMIN, $to_user_id, $send_params); } /** * Triggers email event of type User * * @param string $email_template_name * @param int $to_user_id * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text * @return kEvent * @access public */ public function emailUser($email_template_name, $to_user_id = null, $send_params = Array ()) { return $this->_email($email_template_name, EmailTemplate::TEMPLATE_TYPE_FRONTEND, $to_user_id, $send_params); } /** * Triggers general email event * * @param string $email_template_name * @param int $email_template_type (0 for User, 1 for Admin) * @param int $to_user_id * @param array $send_params associative array of direct send params, * possible keys: to_email, to_name, from_email, from_name, message, message_text * @return kEvent * @access protected */ protected function _email($email_template_name, $email_template_type, $to_user_id = null, $send_params = Array ()) { /** @var kEmail $email */ $email = $this->makeClass('kEmail'); if ( !$email->findTemplate($email_template_name, $email_template_type) ) { return false; } $email->setParams($send_params); return $email->send($to_user_id); } /** * Allows to check if user in this session is logged in or not * * @return bool * @access public */ public function LoggedIn() { // no session during expiration process return is_null($this->Session) ? false : $this->Session->LoggedIn(); } /** + * Determines if access permissions should not be checked. + * + * @param integer|null $user_id User ID. + * + * @return boolean + */ + public function permissionCheckingDisabled($user_id = null) + { + if ( !isset($user_id) ) { + $user_id = $this->RecallVar('user_id'); + } + + return $user_id == USER_ROOT; + } + + /** * Check current user permissions based on it's group permissions in specified category * * @param string $name permission name * @param int $cat_id category id, current used if not specified * @param int $type permission type {1 - system, 0 - per category} * @return int * @access public */ public function CheckPermission($name, $type = 1, $cat_id = null) { /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->recallObject('PermissionsHelper'); return $perm_helper->CheckPermission($name, $type, $cat_id); } /** * Check current admin permissions based on it's group permissions in specified category * * @param string $name permission name * @param int $cat_id category id, current used if not specified * @param int $type permission type {1 - system, 0 - per category} * @return int * @access public */ public function CheckAdminPermission($name, $type = 1, $cat_id = null) { /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->recallObject('PermissionsHelper'); return $perm_helper->CheckAdminPermission($name, $type, $cat_id); } /** * Set's any field of current visit * * @param string $field * @param mixed $value * @return void * @access public * @todo move to separate module */ public function setVisitField($field, $value) { if ( $this->isAdmin || !$this->ConfigValue('UseVisitorTracking') ) { // admin logins are not registered in visits list return; } /** @var kDBItem $visit */ $visit = $this->recallObject('visits', null, Array ('raise_warnings' => 0)); if ( $visit->isLoaded() ) { $visit->SetDBField($field, $value); $visit->Update(); } } /** * Allows to check if in-portal is installed * * @return bool * @access public */ public function isInstalled() { return $this->InitDone && (count($this->ModuleInfo) > 0); } /** * Allows to determine if module is installed & enabled * * @param string $module_name * @return bool * @access public */ public function isModuleEnabled($module_name) { return $this->findModule('Name', $module_name) !== false; } /** * Returns Window ID of passed prefix main prefix (in edit mode) * * @param string $prefix * @return int * @access public */ public function GetTopmostWid($prefix) { $top_prefix = $this->GetTopmostPrefix($prefix); $mode = $this->GetVar($top_prefix . '_mode'); return $mode != '' ? substr($mode, 1) : ''; } /** * Get temp table name * * @param string $table * @param mixed $wid * @return string * @access public */ public function GetTempName($table, $wid = '') { return $this->GetTempTablePrefix($wid) . $table; } /** * Builds temporary table prefix based on given window id * * @param string $wid * @return string * @access public */ public function GetTempTablePrefix($wid = '') { if ( preg_match('/prefix:(.*)/', $wid, $regs) ) { $wid = $this->GetTopmostWid($regs[1]); } return TABLE_PREFIX . 'ses_' . $this->GetSID() . ($wid ? '_' . $wid : '') . '_edit_'; } /** * Checks if given table is a temporary table * * @param string $table * @return bool * @access public */ public function IsTempTable($table) { static $cache = Array (); if ( !array_key_exists($table, $cache) ) { $cache[$table] = preg_match('/' . TABLE_PREFIX . 'ses_' . $this->GetSID() . '(_[\d]+){0,1}_edit_(.*)/', $table); } return (bool)$cache[$table]; } /** * Checks, that given prefix is in temp mode * * @param string $prefix * @param string $special * @return bool * @access public */ public function IsTempMode($prefix, $special = '') { $top_prefix = $this->GetTopmostPrefix($prefix); $var_names = Array ( $top_prefix, rtrim($top_prefix . '_' . $special, '_'), // from post rtrim($top_prefix . '.' . $special, '.'), // assembled locally ); $var_names = array_unique($var_names); $temp_mode = false; foreach ($var_names as $var_name) { $value = $this->GetVar($var_name . '_mode'); if ( $value && (substr($value, 0, 1) == 't') ) { $temp_mode = true; break; } } return $temp_mode; } /** * Return live table name based on temp table name * * @param string $temp_table * @return string */ public function GetLiveName($temp_table) { if ( preg_match('/' . TABLE_PREFIX . 'ses_' . $this->GetSID() . '(_[\d]+){0,1}_edit_(.*)/', $temp_table, $rets) ) { // cut wid from table end if any return $rets[2]; } else { return $temp_table; } } /** * Stops processing of user request and displays given message * * @param string $message * @access public */ public function ApplicationDie($message = '') { while ( ob_get_level() ) { ob_end_clean(); } if ( $this->isDebugMode() ) { $message .= $this->Debugger->printReport(true); } $this->HTML = $message; $this->_outputPage(); } /** * Returns comma-separated list of groups from given user * * @param int $user_id * @return string */ public function getUserGroups($user_id) { switch ($user_id) { case USER_ROOT: $user_groups = $this->ConfigValue('User_LoggedInGroup'); break; case USER_GUEST: $user_groups = $this->ConfigValue('User_LoggedInGroup') . ',' . $this->ConfigValue('User_GuestGroup'); break; default: $sql = 'SELECT GroupId FROM ' . TABLE_PREFIX . 'UserGroupRelations WHERE PortalUserId = ' . (int)$user_id; $res = $this->Conn->GetCol($sql); $user_groups = Array ($this->ConfigValue('User_LoggedInGroup')); if ( $res ) { $user_groups = array_merge($user_groups, $res); } $user_groups = implode(',', $user_groups); } return $user_groups; } /** * Allows to detect if page is browsed by spider (293 scheduled_tasks supported) * * @return bool * @access public */ /*public function IsSpider() { static $is_spider = null; if ( !isset($is_spider) ) { $user_agent = trim($_SERVER['HTTP_USER_AGENT']); $robots = file(FULL_PATH . '/core/robots_list.txt'); foreach ($robots as $robot_info) { $robot_info = explode("\t", $robot_info, 3); if ( $user_agent == trim($robot_info[2]) ) { $is_spider = true; break; } } } return $is_spider; }*/ /** * Allows to detect table's presence in database * * @param string $table_name * @param bool $force * @return bool * @access public */ public function TableFound($table_name, $force = false) { return $this->Conn->TableFound($table_name, $force); } /** * Returns counter value * * @param string $name counter name * @param Array $params counter parameters * @param string $query_name specify query name directly (don't generate from parameters) * @param bool $multiple_results * @return mixed * @access public */ public function getCounter($name, $params = Array (), $query_name = null, $multiple_results = false) { /** @var kCountHelper $count_helper */ $count_helper = $this->recallObject('CountHelper'); return $count_helper->getCounter($name, $params, $query_name, $multiple_results); } /** * Resets counter, which are affected by one of specified tables * * @param string $tables comma separated tables list used in counting sqls * @return void * @access public */ public function resetCounters($tables) { if ( kUtil::constOn('IS_INSTALL') ) { return; } /** @var kCountHelper $count_helper */ $count_helper = $this->recallObject('CountHelper'); $count_helper->resetCounters($tables); } /** * Sends XML header + optionally displays xml heading * * @param string|bool $xml_version * @return string * @access public * @author Alex */ public function XMLHeader($xml_version = false) { $this->setContentType('text/xml'); return $xml_version ? '' : ''; } /** * Returns category tree * * @param int $category_id * @return Array * @access public */ public function getTreeIndex($category_id) { $tree_index = $this->getCategoryCache($category_id, 'category_tree'); if ( $tree_index ) { $ret = Array (); list ($ret['TreeLeft'], $ret['TreeRight']) = explode(';', $tree_index); return $ret; } return false; } /** * Base category of all categories * Usually replaced category, with ID = 0 in category-related operations. * * @return int * @access public */ public function getBaseCategory() { // same, what $this->findModule('Name', 'Core', 'RootCat') does // don't cache while IS_INSTALL, because of kInstallToolkit::createModuleCategory and upgrade return $this->ModuleInfo['Core']['RootCat']; } /** * Deletes all data, that was cached during unit config parsing (excluding unit config locations) * * @param Array $config_variables * @access public */ public function DeleteUnitCache($config_variables = null) { $this->cacheManager->DeleteUnitCache($config_variables); } /** * Deletes cached section tree, used during permission checking and admin console tree display * * @return void * @access public */ public function DeleteSectionCache() { $this->cacheManager->DeleteSectionCache(); } /** * Sets data from cache to object * * @param Array $data * @access public */ public function setFromCache(&$data) { $this->Factory->setFromCache($data); $this->UnitConfigReader->setFromCache($data); $this->EventManager->setFromCache($data); $this->ReplacementTemplates = $data['Application.ReplacementTemplates']; $this->RewriteListeners = $data['Application.RewriteListeners']; $this->ModuleInfo = $data['Application.ModuleInfo']; } /** * Gets object data for caching * The following caches should be reset based on admin interaction (adjusting config, enabling modules etc) * * @access public * @return Array */ public function getToCache() { return array_merge( $this->Factory->getToCache(), $this->UnitConfigReader->getToCache(), $this->EventManager->getToCache(), Array ( 'Application.ReplacementTemplates' => $this->ReplacementTemplates, 'Application.RewriteListeners' => $this->RewriteListeners, 'Application.ModuleInfo' => $this->ModuleInfo, ) ); } public function delayUnitProcessing($method, $params) { $this->cacheManager->delayUnitProcessing($method, $params); } /** * Returns current maintenance mode state * * @param bool $check_ips * @return int * @access public */ public function getMaintenanceMode($check_ips = true) { $exception_ips = defined('MAINTENANCE_MODE_IPS') ? MAINTENANCE_MODE_IPS : ''; $setting_name = $this->isAdmin ? 'MAINTENANCE_MODE_ADMIN' : 'MAINTENANCE_MODE_FRONT'; if ( defined($setting_name) && constant($setting_name) > MaintenanceMode::NONE ) { $exception_ip = $check_ips ? kUtil::ipMatch($exception_ips) : false; if ( !$exception_ip ) { return constant($setting_name); } } return MaintenanceMode::NONE; } /** * Sets content type of the page * * @param string $content_type * @param bool $include_charset * @return void * @access public */ public function setContentType($content_type = 'text/html', $include_charset = null) { static $already_set = false; if ( $already_set ) { return; } $header = 'Content-type: ' . $content_type; if ( !isset($include_charset) ) { $include_charset = $content_type = 'text/html' || $content_type == 'text/plain' || $content_type = 'text/xml'; } if ( $include_charset ) { $header .= '; charset=' . CHARSET; } $already_set = true; header($header); } /** * Posts message to event log * * @param string $message * @param int $code * @param bool $write_now Allows further customization of log record by returning kLog object * @return bool|int|kLogger * @access public */ public function log($message, $code = null, $write_now = false) { $log = $this->_logger->prepare($message, $code)->addSource($this->_logger->createTrace(null, 1)); if ( $write_now ) { return $log->write(); } return $log; } /** * Deletes log with given id from database or disk, when database isn't available * * @param int $unique_id * @param int $storage_medium * @return void * @access public * @throws InvalidArgumentException */ public function deleteLog($unique_id, $storage_medium = kLogger::LS_AUTOMATIC) { $this->_logger->delete($unique_id, $storage_medium); } /** * Returns the client IP address. * * @return string The client IP address * @access public */ public function getClientIp() { return $this->HttpQuery->getClientIp(); } } Index: branches/5.2.x/core/kernel/managers/request_manager.php =================================================================== --- branches/5.2.x/core/kernel/managers/request_manager.php (revision 16560) +++ branches/5.2.x/core/kernel/managers/request_manager.php (revision 16561) @@ -1,484 +1,484 @@ processed ) { return; } $this->dumpRequest(); $this->processOpener(); $events = $this->getEvents(); $all_passed = $this->getAllPassed($events); // set "all_passed" before kApplication::GetTopmostPrefix method call ! $this->Application->SetVar('all_passed', implode(',', $all_passed)); foreach ($events as $prefix_special => $event_name) { $event =& $this->runEvent($prefix_special, $event_name); if ( $event->status == kEvent::erSTOP ) { // event requested to stop processing at this point kUtil::safeDefine('DBG_SKIP_REPORTING', 1); $this->Application->Session->SaveData(); exit; } if ( $event->status == kEvent::erPERM_FAIL ) { $this->processPermissionError($event); } if ( ($event->status == kEvent::erSUCCESS || $event->status == kEvent::erPERM_FAIL) && $this->canRedirect($event) ) { $this->performRedirect($event); } } $this->Application->SetVar('events', $events); $this->Application->SetVar('passed', implode(',', $all_passed)); $this->processed = true; } /** * Dumps user request to debugger (only when enabled) * * @return void * @access protected */ protected function dumpRequest() { if ( defined('DEBUG_MODE') && $this->Application->isDebugMode() && kUtil::constOn('DBG_SHOW_HTTPQUERY') ) { $this->Application->Debugger->appendHTML('HTTPQuery:'); $this->Application->Debugger->dumpVars($this->Application->HttpQuery->_Params); } } /** * Returns event names, given in request (post, get) * * @return Array * @access protected */ protected function getEvents() { $post_events = $this->getEventsFromPost(); return $post_events ? $post_events : $this->getEventsFromGet(); } /** * Get all passed prefixes * * @param Array $events * @return Array * @access protected */ protected function getAllPassed($events) { $ret = explode(',', $this->Application->GetVar('passed')); foreach ($events as $prefix_special => $event_name) { if (!$event_name) { continue; } if ($this->Application->isAdmin) { array_push($ret, $prefix_special); } else { // don't add special on Front-end because of category item list special is autogenerated $prefix_special = explode('.', $prefix_special); array_push($ret, $prefix_special[0]); } } return $ret; } /** * Creates and runs event. Returns false, when prefix of given event isn't registered * * @param string $prefix_special * @param string $event_name * * @return kEvent|false * @access protected */ protected function &runEvent($prefix_special, $event_name) { $event = new kEvent($prefix_special . ':' . $event_name); if ( preg_match('/(.*?)-(.*)/', $event->Prefix, $regs) && $this->Application->prefixRegistred($regs[1]) ) { // this is event from cloned config -> load parent config to create valid clone $this->Application->UnitConfigReader->loadConfig($regs[1]); $this->Application->UnitConfigReader->runAfterConfigRead($regs[1]); } if ( !$this->Application->EventManager->verifyEventPrefix($event, true) ) { $false = false; return $false; } $event->SetRedirectParam('opener', 's'); // stay on same page after event is called $event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true)); /** @var kEventHandler $event_handler */ $event_handler = $this->Application->recallObject($event->Prefix . '_EventHandler'); - if ( ($this->Application->RecallVar('user_id') == USER_ROOT) || $event_handler->CheckPermission($event) ) { + if ( $this->Application->permissionCheckingDisabled() || $event_handler->CheckPermission($event) ) { $this->Application->HandleEvent($event); $this->Application->notifyEventSubscribers($event); } return $event; } /** * Processes case, when event finished with permission error * * @param kEvent $event * @access protected */ protected function processPermissionError($event) { // should do redirect but to no_permissions template $event->redirect = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate'); $event->SetRedirectParam('pass', 'm'); /** @var kThemesHelper $themes_helper */ $themes_helper = $this->Application->recallObject('ThemesHelper'); $event->SetRedirectParam( 'm_cat_id', $themes_helper->getPageByTemplate($event->redirect) ); // restore stuff, that processOpener() changed $wid = $this->Application->GetVar('m_wid'); $this->Application->RestoreVar( rtrim('opener_stack_' . $wid, '_') ); // don't save last_template, because no_permission template does js history.back and could cause invalid opener_stack content $this->Application->SetVar('skip_last_template', 1); } /** * Performs redirect after event execution * * @param kEvent $event * @access protected */ protected function performRedirect($event) { // we need to pass category if the action was submitted to self-template, with the category passed // and it has not explicitly set redirect template or pass_category param if ( $this->samePageRedirect($event) && ($event->getEventParam('pass_category') === false) && $this->Application->GetVar('m_cat_id') ) { $event->SetRedirectParam('pass_category', 1); } $wid = $this->Application->GetVar('m_wid'); $redirect_params = $event->getRedirectParams(); if ( $wid && $event->getRedirectParam('opener') == 'u' ) { // update last element in current opener stack unset($redirect_params['opener']); $redirect_template = is_string($event->redirect) ? $event->redirect : null; $this->openerStackChange($redirect_template, $redirect_params); // reset opener, because kApplication::HREF will react differently when 'opener' => 'u' $event->SetRedirectParam('opener', 's'); $event->redirect = defined('CLOSE_POPUP_TPL') ? CLOSE_POPUP_TPL : 'incs/close_popup'; } if ( $event->getRedirectParam('pass') === false ) { // pass all discovered units to redirected page unless developer decided otherwise $event->SetRedirectParam('pass', 'all'); } $this->Application->Redirect($event->redirect, $event->getRedirectParams(), '', $event->redirectScript); } /** * Checks, if redirect can be made * * @param kEvent $event * @return bool * @access protected */ protected function canRedirect($event) { return $this->samePageRedirect($event) || strlen($event->redirect) > 0; } /** * Checks, that current template will be displayed after redirect * * @param kEvent $event * @return bool * @access protected */ protected function samePageRedirect($event) { return $event->redirect === true || $event->redirect == $this->Application->GetVar('t'); } /** * Returns event names given in GET * * @return Array * @access protected */ protected function getEventsFromGet() { $events = Array (); $discovered_units = $this->Application->HttpQuery->getDiscoveredUnits(false); foreach ($discovered_units as $prefix_special => $query_string) { $query_string = array_flip($query_string); if ( !isset($query_string['event']) ) { continue; } $event_name = $this->Application->GetVar($prefix_special . '_event'); // we need to check for pre 5.1.0 url format, because of "PerPage" // query string part (that was added in place of "event" query // string part) is available only since 5.1.0 version if ($event_name && !is_numeric($event_name)) { $events[$prefix_special] = $event_name; } } return $events; } /** * Returns event names given in POST * * @return Array * @access protected */ protected function getEventsFromPost() { $ret = Array (); $events = $this->Application->GetVar('events'); if (!$events) { return Array (); } foreach ($events as $prefix_special => $event_name) { if (!$event_name) { continue; } if ( is_array($event_name) ) { // HTML-input names like "events[prefix.special][event_name]", input value don't matter $event_name = key($event_name); $this->Application->SetVar($prefix_special . '_event', $event_name); } // HTML-input names like "events[prefix.special]", input value is event name $ret[$prefix_special] = $event_name; } return $ret; } /** * Processes window opener stack * * @access protected */ protected function processOpener() { /** @var kOpenerStack $opener_stack */ $opener_stack = $this->Application->makeClass('kOpenerStack'); switch ( $this->Application->GetVar('m_opener') ) { case 'r': $opener_stack->reset(); break; case 'd': // "down/push" new template to opener stack, deeplevel++ if ( $this->Application->GetVar('front') ) { /** @var Session $front_session */ $front_session = $this->Application->recallObject('Session.front'); $opener_stack->pushRaw( '../' . $front_session->RecallVar('last_template') ); } else { $opener_stack->pushRaw( $this->Application->RecallVar('last_template') ); } break; case 'u': // "up/pop" last template from opener stack, deeplevel-- $opener_stack->pop(); break; case 'p': // pop-up - generate new wid $parent_wid = $this->Application->GetVar('m_wid'); // window_id of popup's parent window $popup_wid = (int)$this->Application->RecallVar('last_wid') + 1; $this->Application->StoreVar('last_wid', $popup_wid); $this->Application->SetVar('m_wid', $popup_wid); /** @var kOpenerStack $popup_opener_stack */ $popup_opener_stack = $this->Application->makeClass('kOpenerStack', Array ($popup_wid)); $popup_opener_stack->pushRaw( $this->getLastTemplate($parent_wid) ); $popup_opener_stack->save(); $this->Application->SetVar('m_opener', 's'); /*// store window relations $window_relations = $this->Application->RecallVar('window_relations'); $window_relations = $window_relations ? unserialize($window_relations) : Array (); $window_relations[$popup_wid] = $parent_wid; $this->Application->StoreVar('window_relations', serialize($window_relations));*/ return; break; default: // "s/0," stay on same deep level break; } $this->Application->SetVar('m_opener', 's'); $opener_stack->save(); } /** * Returns last template from window with given id * * @param int $window_id * @return string * @access protected */ protected function getLastTemplate($window_id) { if ( $this->Application->GetVar('front') ) { /** @var Session $front_session */ $front_session = $this->Application->recallObject('Session.front'); return '../' . $front_session->RecallVar( rtrim('last_template_popup_' . $window_id, '_') ); } if ( $this->Application->GetVar('merge_opener_stack') ) { // get last template from parent (that was closed) window opener stack /** @var kOpenerStack $parent_opener_stack */ $parent_opener_stack = $this->Application->makeClass('kOpenerStack', Array ($window_id)); $last_template = $parent_opener_stack->pop(true); $parent_opener_stack->save(true); } else { $last_template = $this->Application->RecallVar( rtrim('last_template_popup_' . $window_id, '_') ); } return $last_template; } /** * Allows to add new element to opener stack * * @param string $template * @param Array $params * @param int $wid * @access public */ public function openerStackPush($template = '', $params = Array (), $wid = null) { if ( !isset($params['pass']) ) { $params['pass'] = 'all'; } /*// get parent window wid, when this was popup $window_relations = $this->Application->RecallVar('window_relations'); $window_relations = $window_relations ? unserialize($window_relations) : Array (); $wid = isset($window_relations[$wid]) ? $window_relations[$wid] : false;*/ /** @var kOpenerStack $opener_stack */ $opener_stack = $this->Application->makeClass('kOpenerStack', Array ($wid)); // change opener stack $default_params = Array ('m_opener' => 'u'); if ( !$this->Application->ConfigValue('UsePopups') && $opener_stack->getWindowID() ) { // remove wid to show combined header block in editing window $default_params['m_wid'] = ''; list ($last_template, $last_params, ) = $opener_stack->get(kOpenerStack::LAST_ELEMENT); // move last popup's opener stack element to main window's opener stack if ( $last_params ) { $last_params = array_merge($last_params, $default_params); $this->openerStackPush($last_template, $last_params, ''); } } $params = array_merge($default_params, $params); $opener_stack->push($template, $params, 'index.php'); $opener_stack->save(); } /** * Allows to change last element in opener stack * * @param string $new_template * @param Array $new_params * @access protected */ protected function openerStackChange($new_template = null, $new_params = null) { /** @var kOpenerStack $opener_stack */ $opener_stack = $this->Application->makeClass('kOpenerStack'); list ($template, $params, $index_file) = $opener_stack->pop(); if ( isset($new_template) ) { $template = $new_template; } if ( isset($new_params) ) { $params = array_merge($params, $new_params); } if ( !isset($params['pass_events']) ) { // don't pass events, unless requested $params['pass_events'] = false; } $opener_stack->push($template, $params, $index_file); $opener_stack->save(); } -} \ No newline at end of file +} Index: branches/5.2.x/core/units/helpers/upload_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/upload_helper.php (revision 16560) +++ branches/5.2.x/core/units/helpers/upload_helper.php (revision 16561) @@ -1,332 +1,332 @@ disableBrowserCache(); // Uncomment this one to fake upload time // sleep(5); if ( !$this->Application->HttpQuery->Post ) { // Variables {field, id, flashsid} are always submitted through POST! // When file size is larger, then "upload_max_filesize" (in php.ini), // then these variables also are not submitted. throw new kUploaderException('File size exceeds allowed limit.', 413); } if ( !$this->checkPermissions($event) ) { // 403 Forbidden throw new kUploaderException('You don\'t have permissions to upload.', 403); } $value = $this->Application->GetVar('file'); if ( !$value || ($value['error'] != UPLOAD_ERR_OK) ) { // 413 Request Entity Too Large (file uploads disabled OR uploaded file was // too large for web server to accept, see "upload_max_filesize" in php.ini) throw new kUploaderException('File size exceeds allowed limit.', 413); } $value = $this->Application->unescapeRequestVariable($value); $tmp_path = WRITEABLE . '/tmp/'; $filename = $this->getUploadedFilename() . '.tmp'; $id = $this->Application->GetVar('id'); if ( $id ) { $filename = $id . '_' . $filename; } if ( !is_writable($tmp_path) ) { // 500 Internal Server Error // check both temp and live upload directory throw new kUploaderException('Write permissions not set on the server, please contact server administrator.', 500); } /** @var FileHelper $file_helper */ $file_helper = $this->Application->recallObject('FileHelper'); $filename = $file_helper->ensureUniqueFilename($tmp_path, $filename); $storage_format = $this->getStorageFormat($this->Application->GetVar('field'), $event); if ( $storage_format ) { /** @var ImageHelper $image_helper */ $image_helper = $this->Application->recallObject('ImageHelper'); $this->moveUploadedFile($value['tmp_name'] . '.jpg'); // add extension, so ResizeImage can work $url = $image_helper->ResizeImage($value['tmp_name'] . '.jpg', $storage_format); $tmp_name = preg_replace('/^' . preg_quote($this->Application->BaseURL(), '/') . '/', '/', $url); rename($tmp_name, $tmp_path . $filename); } else { $this->moveUploadedFile($tmp_path . $filename); } $this->deleteTempFiles($tmp_path); $thumbs_path = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $tmp_path, 1); $thumbs_path = FULL_PATH . THUMBS_PATH . $thumbs_path; if ( file_exists($thumbs_path) ) { $this->deleteTempFiles($thumbs_path); } return preg_replace('/^' . preg_quote($id, '/') . '_/', '', $filename); } /** * Sends headers to ensure, that response is never cached. * * @return void */ protected function disableBrowserCache() { header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); header('Cache-Control: no-store, no-cache, must-revalidate'); header('Cache-Control: post-check=0, pre-check=0', false); header('Pragma: no-cache'); } /** * Checks, that flash uploader is allowed to perform upload * * @param kEvent $event * @return bool */ protected function checkPermissions(kEvent $event) { // Flash uploader does NOT send correct cookies, so we need to make our own check $cookie_name = 'adm_' . $this->Application->ConfigValue('SessionCookieName'); $this->Application->HttpQuery->Cookie['cookies_on'] = 1; $this->Application->HttpQuery->Cookie[$cookie_name] = $this->Application->GetVar('flashsid'); // this prevents session from auto-expiring when KeepSessionOnBrowserClose & FireFox is used $this->Application->HttpQuery->Cookie[$cookie_name . '_live'] = $this->Application->GetVar('flashsid'); /** @var Session $admin_session */ $admin_session = $this->Application->recallObject('Session.admin'); - if ( $admin_session->RecallVar('user_id') == USER_ROOT ) { + if ( $this->Application->permissionCheckingDisabled($admin_session->RecallVar('user_id')) ) { return true; } // copy some data from given session to current session $backup_user_id = $this->Application->RecallVar('user_id'); $this->Application->StoreVar('user_id', $admin_session->RecallVar('user_id')); $backup_user_groups = $this->Application->RecallVar('UserGroups'); $this->Application->StoreVar('UserGroups', $admin_session->RecallVar('UserGroups')); // check permissions using event, that have "add|edit" rule $check_event = new kEvent($event->getPrefixSpecial() . ':OnProcessSelected'); $check_event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true)); /** @var kEventHandler $event_handler */ $event_handler = $this->Application->recallObject($event->Prefix . '_EventHandler'); $allowed_to_upload = $event_handler->CheckPermission($check_event); // restore changed data, so nothing gets saved to database $this->Application->StoreVar('user_id', $backup_user_id); $this->Application->StoreVar('UserGroups', $backup_user_groups); return $allowed_to_upload; } /** * Returns uploaded filename. * * @return string */ protected function getUploadedFilename() { if ( isset($_REQUEST['name']) ) { $file_name = $_REQUEST['name']; } elseif ( !empty($_FILES) ) { $file_name = $_FILES['file']['name']; } else { $file_name = uniqid('file_'); } return $file_name; } /** * Gets storage format for a given field. * * @param string $field_name * @param kEvent $event * @return bool */ protected function getStorageFormat($field_name, kEvent $event) { $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); $virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields'); $field_options = array_key_exists($field_name, $fields) ? $fields[$field_name] : $virtual_fields[$field_name]; return isset($field_options['storage_format']) ? $field_options['storage_format'] : false; } /** * Moves uploaded file to given location. * * @param string $file_path File path. * * @return void * @throws kUploaderException When upload could not be handled properly. */ protected function moveUploadedFile($file_path) { // Chunking might be enabled $chunk = (int)$this->Application->GetVar('chunk', 0); $chunks = (int)$this->Application->GetVar('chunks', 0); // Open temp file if ( !$out = @fopen("{$file_path}.part", $chunks ? 'ab' : 'wb') ) { throw new kUploaderException('Failed to open output stream.', 102); } if ( !empty($_FILES) ) { if ( $_FILES['file']['error'] || !is_uploaded_file($_FILES['file']['tmp_name']) ) { throw new kUploaderException('Failed to move uploaded file.', 103); } // Read binary input stream and append it to temp file if ( !$in = @fopen($_FILES['file']['tmp_name'], 'rb') ) { throw new kUploaderException('Failed to open input stream.', 101); } } else { if ( !$in = @fopen('php://input', 'rb') ) { throw new kUploaderException('Failed to open input stream.', 101); } } while ( $buff = fread($in, 4096) ) { fwrite($out, $buff); } @fclose($out); @fclose($in); // Check if file has been uploaded if ( !$chunks || $chunk == $chunks - 1 ) { // Strip the temp .part suffix off rename("{$file_path}.part", $file_path); } } /** * Delete temporary files, that won't be used for sure * * @param string $path * @return void */ protected function deleteTempFiles($path) { $files = glob($path . '*.*'); $max_file_date = strtotime('-1 day'); foreach ( $files as $file ) { if ( filemtime($file) < $max_file_date ) { unlink($file); } } } /** * Prepares object for operations with file on given field. * * @param kEvent $event Event. * @param string $field Field. * * @return kDBItem */ public function prepareUploadedFile(kEvent $event, $field) { /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); $filename = $this->getSafeFilename(); if ( !$filename ) { $object->SetDBField($field, ''); return $object; } // set current uploaded file if ( $this->Application->GetVar('tmp') ) { $options = $object->GetFieldOptions($field); $options['upload_dir'] = WRITEBALE_BASE . '/tmp/'; unset($options['include_path']); $object->SetFieldOptions($field, $options); $filename = $this->Application->GetVar('id') . '_' . $filename; } $object->SetDBField($field, $filename); return $object; } /** * Returns safe version of filename specified in url * * @return bool|string * @access protected */ protected function getSafeFilename() { $filename = $this->Application->GetVar('file'); $filename = $this->Application->unescapeRequestVariable($filename); if ( (strpos($filename, '../') !== false) || (trim($filename) !== $filename) ) { // when relative paths or special chars are found template names from url, then it's hacking attempt return false; } return $filename; } } class kUploaderException extends Exception { } Index: branches/5.2.x/core/units/helpers/permissions_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/permissions_helper.php (revision 16560) +++ branches/5.2.x/core/units/helpers/permissions_helper.php (revision 16561) @@ -1,848 +1,847 @@ Application->getUnitOption('perm', 'TableName'); $perm_table = $this->Application->GetTempName($perm_table, 'prefix:'.$prefix); $sql = 'SELECT * FROM '.$perm_table.' WHERE (GroupId = '.$group_id.') AND (CatId = '.$cat_id.') AND (Type = '.$type.')'; $permissions = $this->Conn->Query($sql, 'Permission'); $this->Permissions = Array(); foreach ($permissions as $perm_name => $perm_options) { $perm_record['value'] = $perm_options['PermissionValue']; $perm_record['id'] = $perm_options['PermissionId']; $this->Permissions[$perm_name] = $perm_record; } } function getPermissionValue($perm_name) { return isset($this->Permissions[$perm_name]) ? $this->Permissions[$perm_name]['value'] : 0; } function getPermissionID($perm_name) { return isset($this->Permissions[$perm_name]) ? $this->Permissions[$perm_name]['id'] : 0; } /** * This is old permission like ADMIN or LOGIN * * @param string $section_name * @param string $perm_name * @return bool */ function isOldPermission($section_name, $perm_name) { return $section_name == 'in-portal:root' && $perm_name != 'view'; } /** * Returns permission names to check based on event name and item prefix (main item or subitem) * * @param kEvent $event * @param Array $perm_mapping * @return Array */ function getPermissionByEvent($event, $perm_mapping) { $top_prefix = $event->getEventParam('top_prefix'); $prefix_type = ($top_prefix == $event->Prefix) ? 'self' : 'subitem'; $perm_mapping = getArrayValue($perm_mapping, $event->Name); if (!$perm_mapping[$prefix_type]) { throw new Exception('Permission mappings not defined for event ' . $top_prefix . ' <- ' . $event->Prefix . ':' . $event->Name . ''); } if ($perm_mapping[$prefix_type] === true) { // event is defined in mapping but is not checked by permissions return true; } return explode('|', $perm_mapping[$prefix_type]); } /** * Common event permission checking method * * @param kEvent $event * @param Array $perm_mapping * @return bool */ function CheckEventPermission($event, $perm_mapping) { $section = $event->getSection(); if (preg_match('/^CATEGORY:(.*)/', $section)) { return $this->CheckEventCategoryPermission($event, $perm_mapping); } $top_prefix = $event->getEventParam('top_prefix'); $check_perms = $this->getPermissionByEvent($event, $perm_mapping); if ($check_perms === true) { // event is defined in mapping but is not checked by permissions return true; } $perm_status = false; foreach ($check_perms as $perm_name) { // check if at least one of required permissions is set if ($perm_name == 'debug' && $this->Application->isDebugMode(false)) { // universal "debug" permission return true; } elseif ( $perm_name == 'admin' && $this->Application->isAdminUser ) { // any logged-in admin user will suffice return true; } $perm_name = $section.'.'.$perm_name; $perm_status = $this->CheckPermission($perm_name, 1); if (($perm_name == $section.'.add') && $perm_status && ($top_prefix == $event->Prefix)) { // main item, add permission allowed, but ID is > 0, then deny permission // how to get id here } if ($perm_status) { return $perm_status; } } return $this->finalizePermissionCheck($event, $perm_status); } /** * Returns owner + primary category for each item (used for permission checking) * * @param string $prefix * @param string $ids * @param bool $temp_mode * @return Array * @author Alex */ function GetCategoryItemData($prefix, $ids, $temp_mode = false) { 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'); if ($temp_mode) { $table_name = $this->Application->GetTempName($table_name, 'prefix:' . $prefix); $ci_table = $this->Application->GetTempName($ci_table, 'prefix:' . $prefix); } $owner_field = $this->Application->getUnitOption($prefix, 'OwnerField'); if (!$owner_field) { $owner_field = 'CreatedById'; } $sql = 'SELECT item_table.'.$id_field.', item_table.'.$owner_field.' AS CreatedById, ci.CategoryId FROM '.$table_name.' item_table LEFT JOIN '.$ci_table.' ci ON ci.ItemResourceId = item_table.ResourceId WHERE item_table.'.$id_field.' IN ('.$ids.') AND (ci.PrimaryCat = 1)'; return $this->Conn->Query($sql, $id_field); } /** * Check category-based permissions for category items * * @param kEvent $event * @param Array $event_perm_mapping * @return bool */ function _frontCheckEventCategoryPermission($event, $event_perm_mapping) { // mapping between specific permissions and common permissions static $perm_mapping = Array( 'add' => 'ADD', 'add.pending' => 'ADD.PENDING', 'edit' => 'MODIFY', 'edit.pending' => 'MODIFY.PENDING', 'delete' => 'DELETE', 'view' => 'VIEW', 'debug' => 'DEBUG', 'admin' => 'ADMIN', ); $top_prefix = $event->getEventParam('top_prefix'); /** @var kCatDBEventHandler $event_handler */ $event_handler = $this->Application->recallObject($event->Prefix . '_EventHandler'); $raise_warnings = $event->getEventParam('raise_warnings'); $event->setEventParam('raise_warnings', 0); if ( $event->Prefix != $top_prefix ) { $top_event = new kEvent($top_prefix . ':' . $event->Name); $id = $event_handler->getPassedID($top_event); } else { $id = $event_handler->getPassedID($event); } $event->setEventParam('raise_warnings', $raise_warnings); $owner_id = USER_ROOT; // owner is root if not detected if ( !$id ) { // item being created -> check by current (before editing started, saved in OnPreCreate event) category permissions // note: category in session is placed on catalog data import start $category_id = $this->Application->isAdmin ? $this->Application->RecallVar('m_cat_id') : $this->Application->GetVar('m_cat_id'); } elseif ( $top_prefix == 'c' || $top_prefix == 'st' ) { $category_id = $id; } else { // item being edited -> check by it's primary category permissions $items_info = $this->GetCategoryItemData($top_prefix, $id); if ( $items_info ) { $category_id = $items_info[$id]['CategoryId']; $owner_id = $items_info[$id]['CreatedById']; } else { // item wasn't found in database $category_id = $this->Application->GetVar('m_cat_id'); } } // specific permission check for pending & owner permissions: begin $uploader_events = Array ('OnUploadFile', 'OnDeleteFile', 'OnViewFile'); if ( in_array($event->Name, $uploader_events) ) { // don't recall target object during uploader-related, because OnItemLoad will use incorrect // $user_id in Firefox (during Flash problems session will be used from Internet Exploere) $new_item = false; } else { $new_item = $this->Application->isAdminUser && $event_handler->isNewItemCreate($event) ? true : false; $check_status = $this->checkCombinedPermissions($event, $owner_id, (int)$category_id, $new_item); } if ( isset($check_status) ) { return $this->finalizePermissionCheck($event, $check_status); } // specific permission check for pending & owner permissions: end $perm_status = false; $check_perms = $this->getPermissionByEvent($event, $event_perm_mapping); if ( $check_perms === true ) { // event is defined in mapping but is not checked by permissions return true; } $item_prefix = $this->Application->getUnitOption($top_prefix, 'PermItemPrefix'); foreach ($check_perms as $perm_name) { // check if at least one of required permissions is set if ( !isset($perm_mapping[$perm_name]) ) { // not mapped permission (e.g. advanced:approve) -> skip continue; } if ( $perm_name == 'debug' && $this->Application->isDebugMode(false) ) { // universal "debug" permission return true; } elseif ( $perm_name == 'admin' && $this->Application->isAdminUser ) { // any logged-in admin user will suffice return true; } $perm_name = $item_prefix . '.' . $perm_mapping[$perm_name]; $perm_status = $this->CheckPermission($perm_name, 0, (int)$category_id); if ( $perm_status ) { return $perm_status; } } return $this->finalizePermissionCheck($event, $perm_status); } /** * Finalizes permission checking (with additional debug output, when in debug mode) * * @param kEvent $event * @param bool $perm_status * @return bool */ function finalizePermissionCheck($event, $perm_status) { if (!$perm_status) { if (MOD_REWRITE) { // $event->SetRedirectParam('m_cat_id', 0); // category means nothing on admin login screen $event->SetRedirectParam('next_template', 'external:' . $_SERVER['REQUEST_URI']); } else { $event->SetRedirectParam('next_template', $this->Application->GetVar('t')); } if ($this->Application->isDebugMode()) { // for debugging purposes $event->SetRedirectParam('section', $event->getSection()); $event->SetRedirectParam('main_prefix', $event->getEventParam('top_prefix')); $event->SetRedirectParam('event_name', $event->Name); } $event->status = kEvent::erPERM_FAIL; } return $perm_status; } /** * 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) { $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 '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 = kEvent::erPERM_FAIL; } return $ret; } /** * Simplified permission check for category items, when adding/editing them from advanced view. * * @param kEvent $event * @param Array $event_perm_mapping * @return mixed */ function CheckEventCategoryPermission($event, $event_perm_mapping) { if (!$this->Application->isAdmin) { // check front-end permission by old scheme return $this->_frontCheckEventCategoryPermission($event, $event_perm_mapping); } if (substr($event->Name, 0, 9) == 'OnPreSave') { // check separately, because permission mapping is not defined for OnPreSave* events $check_perms = Array ('add', 'edit'); } else { $check_perms = $this->getPermissionByEvent($event, $event_perm_mapping); } if ($check_perms === true) { // event is defined in mapping but is not checked by permissions return true; } // 1. most of events does require admin login only $perm_status = $this->Application->isAdminUser; // 2. in case, when event require more, then "view" right, then restrict it to temporary tables only if (!in_array('view', $check_perms)) { $perm_status = $perm_status && $this->Application->IsTempMode($event->Prefix, $event->Special); } return $this->finalizePermissionCheck($event, $perm_status); } function TagPermissionCheck($params, $is_owner = false) { $perm_prefix = getArrayValue($params, 'perm_prefix'); $perm_event = getArrayValue($params, 'perm_event'); $permission_groups = getArrayValue($params, 'permissions'); $check_admin = isset($params['admin']) && $params['admin']; if ($permission_groups && !$perm_event) { // check permissions by permission names in current category $permission_groups = explode('|', $permission_groups); $group_has_permission = false; $perm_category = isset($params['cat_id']) ? $params['cat_id'] : $this->Application->GetVar('m_cat_id'); if ($perm_prefix) { // use primary category of item with id from {perm_prefix}_id as base for permission checking $perm_category = $this->getPrimaryCategory($perm_prefix); } $is_system = isset($params['system']) && $params['system'] ? 1 : 0; foreach ($permission_groups as $permission_group) { $has_permission = true; $permissions = explode(',', $permission_group); if ( $check_admin ) { foreach ($permissions as $permission) { $owner_checked = (strpos($permission, '.OWNER.') !== false) ? $is_owner : true; $has_permission = $has_permission && $this->CheckAdminPermission($permission, $is_system, $perm_category) && $owner_checked; } } else { foreach ($permissions as $permission) { $owner_checked = (strpos($permission, '.OWNER.') !== false) ? $is_owner : true; $has_permission = $has_permission && $this->CheckPermission($permission, $is_system, $perm_category) && $owner_checked; } } $group_has_permission = $group_has_permission || $has_permission; if ($group_has_permission) { return true; } } return false; } elseif ($perm_event) { // check permission by event name list ($prefix, ) = explode(':', $perm_event); /** @var kEventHandler $event_handler */ $event_handler = $this->Application->recallObject($prefix . '_EventHandler'); return $event_handler->CheckPermission( new kEvent($perm_event) ); } return true; } /** * Returns item's primary category (get item_id from request) * * @param string $prefix * @return int */ function getPrimaryCategory($prefix) { $id_field = $this->Application->getUnitOption($prefix, 'IDField'); $table_name = $this->Application->getUnitOption($prefix, 'TableName'); $id = $this->Application->GetVar($prefix.'_id'); if (!$id) { return $this->Application->GetVar('m_cat_id'); } $sql = 'SELECT ResourceId FROM '.$table_name.' WHERE '.$id_field.' = '.(int)$id; $resource_id = $this->Conn->GetOne($sql); $sql = 'SELECT CategoryId FROM '.$this->Application->getUnitOption('ci', 'TableName').' WHERE ItemResourceId = '.$resource_id.' AND PrimaryCat = 1'; return $this->Conn->GetOne($sql); } /** * Returns no permission template to redirect to * * @param Array $params * @return Array */ function getPermissionTemplate($params) { $t = $this->Application->GetVar('t'); $next_t = getArrayValue($params, 'next_template'); if ( $next_t ) { $t = $next_t; } $redirect_params = $this->Application->HttpQuery->getRedirectParams(true); if (array_key_exists('pass_category', $params)) { $redirect_params['pass_category'] = $params['pass_cateogry']; } if (MOD_REWRITE) { // TODO: $next_t variable is ignored !!! (is anyone using m_RequireLogin tag with "next_template" parameter?) $redirect_params = Array ( 'm_cat_id' => 0, // category means nothing on admin login screen 'next_template' => 'external:' . $_SERVER['REQUEST_URI'], ); } else { $redirect_params['next_template'] = $t; } if ($this->Application->isAdmin) { $redirect_params['m_wid'] = ''; // remove wid, otherwise parent window may add wid to its name breaking all the frameset (for targets) $redirect_params['pass'] = 'm'; // don't pass any other (except "m") prefixes to admin login template } if (!$this->Application->LoggedIn()) { $redirect_template = array_key_exists('login_template', $params) ? $params['login_template'] : ''; if (!$redirect_template && $this->Application->isAdmin) { $redirect_template = 'login'; } } else { if (array_key_exists('no_permissions_template', $params)) { $redirect_template = $params['no_permissions_template']; } else { $redirect_template = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate'); } if ($this->Application->isDebugMode()) { $redirect_params['from_template'] = 1; $redirect_params['perms'] = $params[ isset($params['permissions']) ? 'permissions' : 'perm_event' ]; } } if (isset($params['index_file']) && $params['index_file']) { $redirect_params['index_file'] = $params['index_file']; } return Array ($redirect_template, $redirect_params); } /** * Check current user permissions based on it's group permissions in specified category (for non-system permissions) or just checks if system permission is set * * @param string $name permission name * @param int $cat_id category id, current used if not specified * @param int $type permission type {1 - system, 0 - per category} * @return int */ function CheckPermission($name, $type = 1, $cat_id = null) { $user_id = $this->Application->RecallVar('user_id'); return $this->CheckUserPermission($user_id, $name, $type, $cat_id); } /** * Check current admin permissions (when called from Front-End) based on it's group permissions in specified category (for non-system permissions) or just checks if system permission is set * * @param string $name permission name * @param int $cat_id category id, current used if not specified * @param int $type permission type {1 - system, 0 - per category} * @return int */ function CheckAdminPermission($name, $type = 1, $cat_id = null) { if ( $this->Application->isAdmin ) { return $this->CheckPermission($name, $type, $cat_id); } $user_id = $this->Application->RecallVar('admin_user_id'); return $this->CheckUserPermission($user_id, $name, $type, $cat_id); } function CheckUserPermission($user_id, $name, $type = 1, $cat_id = null) { $user_id = (int)$user_id; - if ( $user_id == USER_ROOT ) { - // "root" is allowed anywhere + if ( $this->Application->permissionCheckingDisabled($user_id) ) { return substr($name, -5) == '.deny' || $name == 'SYSTEM_ACCESS.READONLY' ? 0 : 1; } if ( !isset($cat_id) ) { $cat_id = $this->Application->GetVar('m_cat_id'); } if ( $type == 1 ) { // "system" permission are always checked per "Home" category (ID = 0) $cat_id = 0; } elseif ( "$cat_id" === "0" ) { $cat_id = $this->Application->getBaseCategory(); } // perm cache is build only based on records in db, that's why if permission is not explicitly denied, then // that (perm cache creator) code thinks that it is allowed & adds corresponding record and code below will // return incorrect results if ( $user_id == $this->Application->RecallVar('user_id') ) { $groups = $this->Application->RecallVar('UserGroups'); } else { // checking not current user $groups = $this->Application->RecallVar('UserGroups:' . $user_id); if ( $groups === false ) { // die('me'); $sql = 'SELECT GroupId FROM '.TABLE_PREFIX.'UserGroupRelations WHERE (PortalUserId = '.$user_id.') AND ( (MembershipExpires IS NULL) OR ( MembershipExpires >= UNIX_TIMESTAMP() ) )'; $groups = $this->Conn->GetCol($sql); array_push($groups, $this->Application->ConfigValue('User_LoggedInGroup') ); $groups = implode(',', $groups); $this->Application->StoreVar('UserGroups:' . $user_id, $groups); } } $groups = explode(',', $groups); $cache_key = $name . '|' . $type . '|' . $cat_id . '|' . implode(',', $groups); $perm_value = $this->Application->getCache('permissions[%' . ($type == 1 ? 'G' : 'C') . 'PermSerial%]:' . $cache_key); if ( $perm_value !== false ) { return $perm_value; } if ( preg_match('/(.*)\.VIEW$/', $name) && ($type == 0) ) { // cached view permission of category: begin if ( $this->Application->ConfigValue('CheckViewPermissionsInCatalog') ) { if ( strpos($cat_id, '|') !== false ) { $category_path = explode('|', substr($cat_id, 1, -1)); $cat_id = end($category_path); } $sql = 'SELECT PermissionConfigId FROM ' . TABLE_PREFIX . 'CategoryPermissionsConfig WHERE PermissionName = ' . $this->Conn->qstr($name); $perm_id = $this->Conn->GetOne($sql); $sql = 'SELECT PermId FROM ' . TABLE_PREFIX . 'CategoryPermissionsCache WHERE (PermId = ' . $perm_id . ') AND (CategoryId = ' . (int)$cat_id . ')'; $view_filters = Array (); foreach ($groups as $group) { $view_filters[] = 'FIND_IN_SET(' . $group . ', ACL)'; } $sql .= ' AND (' . implode(' OR ', $view_filters) . ')'; $perm_value = $this->Conn->GetOne($sql) ? 1 : 0; } else { $perm_value = 1; } $this->Application->setCache('permissions[%CPermSerial%]:' . $cache_key, $perm_value); return $perm_value; // cached view permission of category: end } if ( is_numeric($cat_id) && $cat_id == 0 ) { $cat_hierarchy = Array (0); } else { 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); if ( $cat_hierarchy === false ) { // category was deleted, but reference to it stays in other tables -> data integrity is broken $cat_hierarchy = '|' . $this->Application->getBaseCategory() . '|'; } } $cat_hierarchy = explode('|', substr($cat_hierarchy, 1, -1)); $cat_hierarchy = array_reverse($cat_hierarchy); array_push($cat_hierarchy, 0); } $perm_value = 0; $groups = implode(',', $groups); foreach ($cat_hierarchy as $category_id) { $sql = 'SELECT SUM(PermissionValue) FROM ' . TABLE_PREFIX . 'Permissions WHERE Permission = "' . $name . '" AND CatId = ' . $category_id . ' AND GroupId IN (' . $groups . ') AND Type = ' . $type; $res = $this->Conn->GetOne($sql); if ( $res !== false && !is_null($res) ) { $perm_value = $res ? 1 : 0; break; } } $this->Application->setCache('permissions[%' . ($type == 1 ? 'G' : 'C') . 'PermSerial%]:' . $cache_key, $perm_value); return $perm_value; } /** * Returns categories, where given permission is set to "1" * * @param string $permission_name * @return Array */ function getPermissionCategories($permission_name) { $groups = $this->Application->RecallVar('UserGroups'); // get categories, where given permission is explicitely defined $sql = 'SELECT SUM(PermissionValue), CatId FROM ' . TABLE_PREFIX . 'Permissions WHERE Permission = "' . $permission_name . '" AND GroupId IN (' . $groups . ') AND Type = 0 GROUP BY CatId'; $permissions = $this->Conn->GetCol($sql, 'CatId'); // get all categories along with their parent path $sql = 'SELECT ParentPath, CategoryId FROM ' . TABLE_PREFIX . 'Categories'; $parent_paths = $this->Conn->GetCol($sql, 'CategoryId'); foreach ($parent_paths as $category_id => $parent_path) { if (array_key_exists($category_id, $permissions)) { // permission for given category is set explicitly continue; } $perm_value = 0; $parent_path = explode('|', substr($parent_path, 1, -1)); $parent_path = array_reverse($parent_path); array_push($parent_path, 0); foreach ($parent_path as $parent_category_id) { if (array_key_exists($parent_category_id, $permissions)) { $perm_value = $permissions[$parent_category_id] ? 1 : 0; break; } } $permissions[$category_id] = $perm_value; } // remove categories, where given permissions is denied foreach ($permissions as $category_id => $perm_value) { if (!$perm_value) { unset($permissions[$category_id]); } } return array_keys($permissions); } /** * Allows to check MODIFY & OWNER.MODFY +/- 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 MODIFY permission, 1 - has MODIFY permission, 2 - has MODIFY.PENDING permission} */ function ModifyCheckPermission($owner_id, $category_id, $prefix) { $perm_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix'); $live_modify = $this->CheckPermission($perm_prefix.'.MODIFY', ptCATEGORY, $category_id); if ($live_modify) { return 1; } else if ($this->CheckPermission($perm_prefix.'.MODIFY.PENDING', ptCATEGORY, $category_id)) { return 2; } if ($owner_id == $this->Application->RecallVar('user_id')) { // user is item's OWNER -> check this permissions first $live_modify = $this->CheckPermission($perm_prefix.'.OWNER.MODIFY', ptCATEGORY, $category_id); if ($live_modify) { return 1; } else if ($this->CheckPermission($perm_prefix.'.OWNER.MODIFY.PENDING', ptCATEGORY, $category_id)) { return 2; } } return 0; } /** * Allows to check DELETE & OWNER.DELETE 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 DELETE permission, 1 - has DELETE/OWNER.DELETE permission} */ function DeleteCheckPermission($owner_id, $category_id, $prefix) { $perm_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix'); $live_delete = $this->CheckPermission($perm_prefix.'.DELETE', 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 ADD +/- PENDING permission combinations on item * * @param int $category_id primary category of item * @param string $prefix prefix of item * @return int {0 - no ADD permission, 1 - has ADD permission, 2 - has ADD.PENDING permission} */ function AddCheckPermission($category_id, $prefix) { $perm_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix'); $live_add = $this->CheckPermission($perm_prefix.'.ADD', ptCATEGORY, $category_id); if ($live_add) { return 1; } else if ($this->CheckPermission($perm_prefix.'.ADD.PENDING', ptCATEGORY, $category_id)) { return 2; } return 0; } - } \ No newline at end of file + }