Index: branches/5.2.x/LICENSE =================================================================== --- branches/5.2.x/LICENSE (revision 16413) +++ branches/5.2.x/LICENSE (revision 16414) Property changes on: branches/5.2.x/LICENSE ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /in-portal/branches/5.3.x/LICENSE:r15987 Index: branches/5.2.x/robots.txt =================================================================== --- branches/5.2.x/robots.txt (revision 16413) +++ branches/5.2.x/robots.txt (revision 16414) Property changes on: branches/5.2.x/robots.txt ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /in-portal/branches/5.3.x/robots.txt:r15987 Index: branches/5.2.x/core/kernel/db/db_event_handler.php =================================================================== --- branches/5.2.x/core/kernel/db/db_event_handler.php (revision 16413) +++ branches/5.2.x/core/kernel/db/db_event_handler.php (revision 16414) @@ -1,3586 +1,3576 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); define('EH_CUSTOM_PROCESSING_BEFORE',1); define('EH_CUSTOM_PROCESSING_AFTER',2); /** * Note: * 1. When addressing variables from submit containing * Prefix_Special as part of their name use * $event->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'); } - if ( $event->Name == 'OnSaveWidths' ) { - return $this->Application->isAdminUser; - } - 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' => 'view', 'subitem' => 'view'), + 'OnSuggestValues' => 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' => 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' ) { $object = $this->Application->recallObject($event->getEventParam('item')); /* @var $object kDBItem */ $list_helper = $this->Application->recallObject('ListHelper'); /* @var $list_helper 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]) ) { // <inp2:lang.auto-phrase_Field name="DateFormat"/> - returns field DateFormat value from language (LanguageId is extracted from current phrase object) $main_object = $this->Application->recallObject($regs[1]); /* @var $main_object kDBItem */ $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 <strong>' . $event->getPrefixSpecial() . '</strong> <span class="debug_error">not passed</span>', 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' ) { $object = $event->getObject(); /* @var $object kDBItem */ 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 <strong>checkItemStatus</strong>, 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) { $object = $event->getObject(); /* @var $object kDBItem */ $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 $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 && !$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) $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); } } $actions = $this->Application->recallObject('kActions'); /* @var $actions Params */ $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 <strong>' . ($status_checked ? 'checkItemStatus' : 'CheckPermission') . '</strong>'; 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) { $object = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler'); /* @var $object kTempTablesHandler */ $parent_event = $event->getEventParam('parent_event'); /* @var $parent_event kEvent */ 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) { $object = $event->getObject(); /* @var $object kDBItem */ $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) ) { $actions = $this->Application->recallObject('kActions'); /* @var $actions Params */ $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) { $object = $event->getObject(); /* @var $object kDBList */ /*if ( $this->Application->isDebugMode() ) { $event_params = http_build_query($event->getEventParams()); $this->Application->Debugger->appendHTML('InitList "<strong>' . $event->getPrefixSpecial() . '</strong>" (' . $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); $actions = $this->Application->recallObject('kActions'); /* @var $actions Params */ $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 ) { $list_helper = $this->Application->recallObject('ListHelper'); /* @var $list_helper 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)); $list_helper = $this->Application->recallObject('ListHelper'); /* @var $list_helper 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'); $object = $event->getObject(Array ('main_list' => 1)); /* @var $object kDBList */ 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) { $object = $event->getObject(); /* @var $object kDBList */ // 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) { $object = $event->getObject(); /* @var $object kDBList */ $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 $list_helper = $this->Application->recallObject('ListHelper'); /* @var $list_helper 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'); $object = $event->getObject(); /* @var $object kDBList */ 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 $forced_sorting = getArrayValue($list_sortings, 'ForcedSorting'); /* @var $forced_sorting Array */ 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) { $object = $event->getObject(); /* @var $object kDBList */ $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); $temp_filter = $this->Application->makeClass('kMultipleFilter'); /* @var $temp_filter 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; } $object = $event->getObject(); /* @var $object kDBList */ $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); $object = $event->getObject(Array ('main_list' => 1)); /* @var $object kDBList */ $list_helper = $this->Application->recallObject('ListHelper'); /* @var $list_helper 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) { $object = $event->getObject(); /* @var $object kDBItem */ $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) { $object = $event->getObject(); /* @var $object kDBItem */ $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) { $object = $event->getObject(Array ('skip_autoload' => true)); /* @var $object kDBItem */ $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'); } /** * Updates data in database based on request * * @param kEvent $event * @return void * @access protected */ protected function _update(kEvent $event) { $object = $event->getObject(Array ('skip_autoload' => true)); /* @var $object kDBItem */ $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; } } } } /** * 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; } $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); /* @var $temp_handler kTempTablesHandler */ $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 ) { $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); /* @var $temp_handler kTempTablesHandler */ $temp_handler->DeleteItems($event->Prefix, $event->Special, $ids); } } /** * Prepares new kDBItem object * * @param kEvent $event * @return void * @access protected */ protected function OnNew(kEvent $event) { $object = $event->getObject(Array ('skip_autoload' => true)); /* @var $object kDBItem */ $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) { $object = $event->getObject(Array ('skip_autoload' => true)); /* @var $object kDBItem */ $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ( $items_info ) { $delete_ids = Array (); $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); /* @var $temp_handler kTempTablesHandler */ 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 ; } $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); /* @var $temp_handler kTempTablesHandler */ $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); $object = $event->getObject(Array('skip_autoload' => true)); /* @var $object kDBItem */ $object->setPendingActions(null, true); $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix); $this->Application->RemoveVar($changes_var_name); $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); /* @var $temp_handler kTempTablesHandler */ $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; $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); /* @var $temp_handler kTempTablesHandler */ $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); } $object = $event->getObject(); /* @var $object kDBItem */ $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) { $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); /* @var $temp_handler kTempTablesHandler */ $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) { $object = $event->getObject( Array ('raise_warnings' => 0) ); /* @var $object kDBItem */ 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) { $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); /* @var $ajax_form_helper 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 ) { $object = $event->getObject(); /* @var $object kDBItem */ $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()); $object = $event->getObject(Array ('skip_autoload' => true)); /* @var $object kDBItem */ $temp_handler = $this->Application->recallObject($event->Prefix . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); /* @var $temp_handler kTempTablesHandler */ $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) { $object = $event->getObject( Array('skip_autoload' => true) ); /* @var $object kDBItem */ $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 $object = $event->getObject( Array ('skip_autoload' => true) ); /* @var $object kDBItem */ $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 ; } $object = $event->getObject(Array ('skip_autoload' => true)); /* @var $object kDBItem */ $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; } if ( $object->Update() ) { $event->status = kEvent::erSUCCESS; } else { $event->status = kEvent::erFAIL; $event->redirect = false; break; } } } $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; } $temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event)); /* @var $temp_handler kTempTablesHandler */ $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) { $object = $event->getObject(); /* @var $object kDBItem */ $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) { $object = $event->getObject(); /* @var $object kDBItem */ 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) { $object = $event->getObject(); /* @var $object kDBItem */ 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) { $object = $event->getObject(); /* @var $object kDBItem */ // 1. delete direct subscriptions to item, that was deleted $this->_deleteSubscriptions($event->Prefix, 'ItemId', $object->GetID()); $sub_items = $this->Application->getUnitOption($event->Prefix, 'SubItems', Array ()); /* @var $sub_items 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; } $temp_handler = $this->Application->recallObject('system-event-subscription_TempHandler', 'kTempTablesHandler'); /* @var $temp_handler 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) { $object = $event->getObject(array('skip_autoload' => true)); /* @var $object kDBItem */ $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) { $object = $event->getObject(); /* @var $object kDBItem */ $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': $file_helper = $this->Application->recallObject('FileHelper'); /* @var $file_helper 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'); $search_helper = $this->Application->recallObject('SearchHelper'); /* @var $search_helper kSearchHelper */ $search_helper->performSearch($event); } /** * Clear search keywords * * @param kEvent $event * @return void * @access protected */ protected function OnSearchReset(kEvent $event) { $search_helper = $this->Application->recallObject('SearchHelper'); /* @var $search_helper kSearchHelper */ $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); $object = $event->getObject(); /* @var $object kDBItem */ $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')); $cdata = $this->Application->recallObject($t_prefixes[1], NULL, Array ('skip_autoload' => true)); /* @var $cdata kDBItem */ $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) { $object = $event->getObject(); /* @var $object kDBItem */ $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' ) { $export_helper = $this->Application->recallObject('CatItemExportHelper'); /* @var $export_helper kCatDBItemExportHelper */ $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) { $export_helper = $this->Application->recallObject('CatItemExportHelper'); /* @var $export_helper kCatDBItemExportHelper */ $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 swfuploader * * @param kEvent $event * @return void * @access protected */ protected function OnUploadFile(kEvent $event) { $event->status = kEvent::erSTOP; // define('DBG_SKIP_REPORTING', 0); $default_msg = "Flash requires that we output something or it won't fire the uploadSuccess event"; 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 -> handle such case. header('HTTP/1.0 413 File size exceeds allowed limit'); echo $default_msg; return; } if ( !$this->_checkFlashUploaderPermission($event) ) { // 403 Forbidden header('HTTP/1.0 403 You don\'t have permissions to upload'); echo $default_msg; return; } $value = $this->Application->GetVar('Filedata'); if ( !$value || ($value['error'] != UPLOAD_ERR_OK) ) { // 413 Request Entity Too Large (file uploads disabled OR uploaded file was // to large for web server to accept, see "upload_max_filesize" in php.ini) header('HTTP/1.0 413 File size exceeds allowed limit'); echo $default_msg; return; } $value = $this->Application->HttpQuery->unescapeRequestVariable($value); $tmp_path = WRITEABLE . '/tmp/'; $filename = $value['name'] . '.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 header('HTTP/1.0 500 Write permissions not set on the server'); echo $default_msg; return; } $file_helper = $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ $filename = $file_helper->ensureUniqueFilename($tmp_path, $filename); $storage_format = $this->_getStorageFormat($this->Application->GetVar('field'), $event); if ( $storage_format ) { $image_helper = $this->Application->recallObject('ImageHelper'); /* @var $image_helper ImageHelper */ move_uploaded_file($value['tmp_name'], $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 { move_uploaded_file($value['tmp_name'], $tmp_path . $filename); } echo preg_replace('/^' . preg_quote($id, '/') . '_/', '', $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); } } /** * Gets storage format for a given field * * @param string $field_name * @param kEvent $event * @return bool * @access protected */ 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; } /** * Delete temporary files, that won't be used for sure * * @param string $path * @return void * @access protected */ 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); } } } /** * Checks, that flash uploader is allowed to perform upload * * @param kEvent $event * @return bool */ protected function _checkFlashUploaderPermission(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'); $admin_ses = $this->Application->recallObject('Session.admin'); /* @var $admin_ses Session */ if ( $admin_ses->RecallVar('user_id') == USER_ROOT ) { 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_ses->RecallVar('user_id')); $backup_user_groups = $this->Application->RecallVar('UserGroups'); $this->Application->StoreVar('UserGroups', $admin_ses->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)); $allowed_to_upload = $this->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; } /** * 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; $filename = $this->_getSafeFilename(); if ( !$filename ) { return; } $object = $event->getObject(Array ('skip_autoload' => true)); /* @var $object kDBItem */ $field_id = $this->Application->GetVar('field_id'); if ( !preg_match_all('/\[([^\[\]]*)\]/', $field_id, $regs) ) { return; } $field = $regs[1][1]; $record_id = $regs[1][0]; $pending_actions = $object->getPendingActions($record_id); $upload_dir = $object->GetFieldOption($field, 'upload_dir'); $pending_actions[] = Array ( 'action' => 'delete', 'id' => $record_id, 'field' => $field, 'file' => FULL_PATH . $upload_dir . $filename ); $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; $filename = $this->_getSafeFilename(); if ( !$filename ) { return; } $object = $event->getObject(Array ('skip_autoload' => true)); /* @var $object kDBItem */ $field = $this->Application->GetVar('field'); $options = $object->GetFieldOptions($field); // set current uploaded file if ( $this->Application->GetVar('tmp') ) { $options['upload_dir'] = WRITEBALE_BASE . '/tmp/'; unset($options['include_path']); $object->SetFieldOptions($field, $options); $object->SetDBField($field, $this->Application->GetVar('id') . '_' . $filename); } else { $object->SetDBField($field, $filename); } // get url to uploaded file if ( $this->Application->GetVar('thumb') ) { $url = $object->GetField($field, $options['thumb_format']); } else { $url = $object->GetField($field, 'raw_url'); } $file_helper = $this->Application->recallObject('FileHelper'); /* @var $file_helper 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($filename) . '"'); readfile($path); } /** * 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; } /** * Validates MInput control fields * * @param kEvent $event * @return void * @access protected */ protected function OnValidateMInputFields(kEvent $event) { $minput_helper = $this->Application->recallObject('MInputHelper'); /* @var $minput_helper 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; } $object = $event->getObject(Array ('skip_autoload' => true)); /* @var $object kDBItem */ $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); } $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); /* @var $ajax_form_helper 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) { - if ( !$this->Application->isAdminUser ) { - // very careful here, because this event allows to - // view every object field -> limit only to logged-in admins - return; - } - $event->status = kEvent::erSTOP; $field = $this->Application->GetVar('field'); $cur_value = $this->Application->GetVar('cur_value'); $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); $object = $event->getObject(); if ( !$field || !$cur_value || !$object->isField($field) ) { return; } $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($cur_value . '%') . ' ORDER BY ' . $field . ' LIMIT 0,' . $limit; $data = $this->Conn->GetCol($sql); $this->Application->XMLHeader(); echo '<suggestions>'; foreach ($data as $item) { echo '<item>' . kUtil::escape($item, kUtil::ESCAPE_HTML) . '</item>'; } echo '</suggestions>'; } /** * 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/units/helpers/permissions_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/permissions_helper.php (revision 16413) +++ branches/5.2.x/core/units/helpers/permissions_helper.php (revision 16414) @@ -1,839 +1,847 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class kPermissionsHelper extends kHelper { /** * Current set of permissions for group being edited * * @var Array */ var $Permissions = Array(); function LoadPermissions($group_id, $cat_id, $type = 1, $prefix = '') { $perm_table = $this->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 <strong>' . $top_prefix . ' <- ' . $event->Prefix . ':' . $event->Name . '</strong>'); } 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' + 'debug' => 'DEBUG', 'admin' => 'ADMIN', ); $top_prefix = $event->getEventParam('top_prefix'); $event_handler = $this->Application->recallObject($event->Prefix . '_EventHandler'); /* @var $event_handler kCatDBEventHandler */ $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); $event_handler = $this->Application->recallObject($prefix . '_EventHandler'); /* @var $event_handler kEventHandler */ 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 <a> 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 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 Index: branches/5.2.x/core/units/configuration/configuration_event_handler.php =================================================================== --- branches/5.2.x/core/units/configuration/configuration_event_handler.php (revision 16413) +++ branches/5.2.x/core/units/configuration/configuration_event_handler.php (revision 16414) @@ -1,568 +1,562 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class ConfigurationEventHandler extends kDBEventHandler { /** * Allows to override standard permission mapping * * @return void * @access protected * @see kEventHandler::$permMapping */ protected function mapPermissions() { parent::mapPermissions(); $permissions = Array ( 'OnGenerateMaintenancePage' => Array ('self' => 'add|edit'), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Changes permission section to one from REQUEST, not from config * * @param kEvent $event * @return bool * @access public */ public function CheckPermission(kEvent $event) { $event->setEventParam('PermSection', $this->Application->GetVar('section')); return parent::CheckPermission($event); } /** * Apply any custom changes to list's sql query * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetCustomQuery(kEvent $event) { parent::SetCustomQuery($event); $object = $event->getObject(); /* @var $object kDBList */ $module = $this->Application->GetVar('module'); $section = $this->Application->GetVar('section'); $object->addFilter('module_filter', '%1$s.ModuleOwner = ' . $this->Conn->qstr($module)); $object->addFilter('section_filter', '%1$s.Section = ' . $this->Conn->qstr($section)); $can_change = $this->Application->ConfigValue('AllowAdminConsoleInterfaceChange'); if ( !$can_change && !$this->Application->isDebugMode() ) { $object->addFilter('interface_change_filter', '%1$s.VariableName NOT IN ("AdminConsoleInterface", "AllowAdminConsoleInterfaceChange")'); } if ( defined('IS_INSTALL') && IS_INSTALL ) { $object->addFilter('install_filter', '%1$s.Install = 1'); } $object->addFilter('visible_filter', '%1$s.Heading <> ""'); } /** * Presets new system setting fields * * @param kEvent $event * @return void * @access protected */ protected function OnPreCreate(kEvent $event) { parent::OnPreCreate($event); $object = $event->getObject(); /* @var $object kDBItem */ $object->SetDBField('Section', $this->Application->GetVar('section')); $object->SetDBField('ModuleOwner', $this->Application->GetVar('module')); } /** * Sets custom validation * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemLoad(kEvent $event) { static $default_field_options = null; parent::OnAfterItemLoad($event); $object = $event->getObject(); /* @var $object kDBItem */ // ability to validate each configuration variable separately if ( !isset($default_field_options) ) { $default_field_options = $object->GetFieldOptions('VariableValue'); } $new_field_options = $default_field_options; $validation = $object->GetDBField('Validation'); if ( $validation ) { $new_field_options = array_merge($new_field_options, unserialize($validation)); } $object->SetFieldOptions('VariableValue', $new_field_options); } /** * Performs custom validation * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemValidate(kEvent $event) { parent::OnBeforeItemValidate($event); $object = $event->getObject(); /* @var $object kDBItem */ // if password field is empty, then don't update if ( $object->GetDBField('ElementType') == 'password' ) { if ( trim($object->GetDBField('VariableValue')) != '' ) { $password_formatter = $this->Application->recallObject('kPasswordFormatter'); /* @var $password_formatter kPasswordFormatter */ $object->SetDBField('VariableValue', $password_formatter->hashPassword($object->GetDBField('VariableValue'))); } } $this->_processCountryState($event); $variable_name = $object->GetDBField('VariableName'); $new_value = $object->GetDBField('VariableValue'); if ( $variable_name == 'AdminConsoleInterface' ) { $can_change = $this->Application->ConfigValue('AllowAdminConsoleInterfaceChange'); if ( ($new_value != $object->GetOriginalField('VariableValue')) && !$can_change ) { $object->SetError('VariableValue', 'not_allowed', 'la_error_OperationNotAllowed'); } } elseif ( $variable_name == 'HardMaintenanceTemplate' ) { $compile = $event->MasterEvent->getEventParam('compile_maintenance_template'); $compile = $compile || $new_value != $object->GetOriginalField('VariableValue'); if ( $compile && !$this->_generateMaintenancePage($new_value) ) { $object->SetError('VariableValue', 'template_file_missing', 'la_error_TemplateFileMissing'); } } elseif ( $variable_name == 'DefaultEmailRecipients' ) { $email_event_data = $this->Application->GetVar('email-template_' . $event->Prefix); $object->SetDBField('VariableValue', $email_event_data[0]['Recipients']); } $sections_helper = $this->Application->recallObject('SectionsHelper'); /* @var $sections_helper kSectionsHelper */ $section = $object->GetDBField('Section'); if ( $section && !$sections_helper->getSectionData($section) ) { $object->SetError('Section', 'unknown_section'); } } /** * Checks, that state belongs to selected country * * @param kEvent $event * @access protected */ protected function _processCountryState(kEvent $event) { $object = $event->getObject(); /* @var $object kDBItem */ $country_iso = $this->_getCountryByState($event); $state_name = $object->GetDBField('VariableValue'); if ( !$country_iso || !$state_name ) { return; } $cs_helper = $this->Application->recallObject('CountryStatesHelper'); /* @var $cs_helper kCountryStatesHelper */ $state_iso = $cs_helper->getStateIso($state_name, $country_iso); if ( $state_iso !== false ) { $object->SetDBField('VariableValue', $state_iso); } else { // selected state doesn't belong to selected country $object->SetError('VariableValue', 'invalid_state', 'la_InvalidState'); } } /** * Returns country iso code, that matches current state variable name * * @param kEvent $event * @return bool * @access protected */ protected function _getCountryByState(kEvent $event) { $object = $event->getObject(); /* @var $object kDBItem */ $variable_name = $object->GetDBField('VariableName'); $state_country_hash = Array ( 'Comm_State' => 'Comm_Country', 'Comm_Shipping_State' => 'Comm_Shipping_Country' ); if ( !array_key_exists($variable_name, $state_country_hash) ) { return false; } $field_values = $this->Application->GetVar($event->getPrefixSpecial(true)); $sql = 'SELECT VariableId FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' WHERE VariableName = ' . $this->Conn->qstr($state_country_hash[$variable_name]); $country_variable_id = $this->Conn->GetOne($sql); return $field_values[$country_variable_id]['VariableValue']; } /** * Does custom password setting processong * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); $object = $event->getObject(); /* @var $object kDBItem */ // if password field is empty, then don't update if ( $object->GetDBField('ElementType') == 'password' && trim($object->GetDBField('VariableValue')) == '' ) { $object->SetFieldOption('VariableValue', 'skip_empty', 1); } } /** * Occurs after updating item * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemUpdate(kEvent $event) { static $skin_deleted = false; parent::OnAfterItemUpdate($event); $object = $event->getObject(); /* @var $object kDBItem */ if ( $object->GetDBField('ElementType') == 'password' && trim($object->GetDBField('VariableValue')) == '' ) { $object->SetFieldOption('VariableValue', 'skip_empty', 0); } // allows to check if variable's value was changed now $variable_name = $object->GetDBField('VariableName'); $changed = $this->Application->GetVar($event->getPrefixSpecial() . '_changed', Array ()); if ( $object->GetDBField('VariableValue') != $object->GetOriginalField('VariableValue') ) { $changed[] = $variable_name; $this->Application->SetVar($event->getPrefixSpecial() . '_changed', $changed); // update value in cache, so other code (during this script run) would use new value $this->Application->SetConfigValue($variable_name, $object->GetDBField('VariableValue'), true); } if ( $variable_name == 'Require_AdminSSL' || $variable_name == 'AdminSSL_URL' ) { // when administrative console is moved to SSL mode, then delete skin if ( in_array($variable_name, $changed) && !$skin_deleted ) { $skin_helper = $this->Application->recallObject('SkinHelper'); /* @var $skin_helper SkinHelper */ $skin_file = $skin_helper->getSkinPath(); if ( file_exists($skin_file) ) { unlink($skin_file); } $skin_deleted = true; } } } /** * 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; } $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); // 1. save user selected module root category $new_category_id = getArrayValue($items_info, 'ModuleRootCategory', 'VariableValue'); if ( $new_category_id !== false ) { unset($items_info['ModuleRootCategory']); } $object = $event->getObject( Array('skip_autoload' => true) ); /* @var $object kDBItem */ if ( $items_info ) { $has_error = false; foreach ($items_info as $id => $field_values) { $object->Clear(); // clear validation errors from previous variable $object->Load($id); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); if ( !$object->Update($id) ) { // don't stop when error found ! $has_error = true; } } $event->status = $has_error ? kEvent::erFAIL : kEvent::erSUCCESS; } if ( $event->status == kEvent::erSUCCESS ) { $event->SetRedirectParam('action_completed', 1); if ( $new_category_id !== false ) { // root category was submitted $module = $this->Application->GetVar('module'); $root_category_id = $this->Application->findModule('Name', $module, 'RootCat'); if ( $root_category_id != $new_category_id ) { // root category differs from one in db $fields_hash = Array ('RootCat' => $new_category_id); $this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Modules', 'Name = ' . $this->Conn->qstr($module)); } } // reset cache $changed = $this->Application->GetVar($event->getPrefixSpecial() . '_changed', Array ()); $require_refresh = Array ('AdvancedUserManagement', 'Site_Name', 'AdminConsoleInterface', 'UsePopups', 'Catalog_PreselectModuleTab'); $refresh_sections = array_intersect($require_refresh, $changed); $require_full_refresh = Array ('Site_Name', 'AdminConsoleInterface'); if ( array_intersect($require_full_refresh, $changed) ) { $event->SetRedirectParam('refresh_all', 1); } elseif ( $refresh_sections ) { $event->SetRedirectParam('refresh_tree', 1); } if ( $refresh_sections ) { // reset sections too, because of AdvancedUserManagement $this->Application->DeleteSectionCache(); } $this->Application->DeleteUnitCache($changed); } else{ $errors = $this->Application->GetVar('errors_' . $event->getPrefixSpecial()); if ( $errors ) { // because we have list out there, and this is item $this->Application->SetVar('first_error', key($errors)); $this->Application->removeObject($event->getPrefixSpecial()); } } // keeps module and section in REQUEST to ensure, that last admin template will work $event->SetRedirectParam('module', $this->Application->GetVar('module')); $event->SetRedirectParam('section', $this->Application->GetVar('section')); } /** * Process items from selector (selected_ids var, key - prefix, value - comma separated ids) * * @param kEvent $event */ function OnProcessSelected($event) { $selected_ids = $this->Application->GetVar('selected_ids'); $this->Application->StoreVar('ModuleRootCategory', $selected_ids['c']); $event->SetRedirectParam('opener', 'u'); } /** * Generates maintenance page * * @param kEvent $event * @return void * @access protected */ protected function OnGenerateMaintenancePage(kEvent $event) { $event->setEventParam('compile_maintenance_template', 1); $event->CallSubEvent('OnUpdate'); } /** * Generates HTML version of hard maintenance template * * @param string $template * @return bool * @access protected */ protected function _generateMaintenancePage($template = null) { if ( !isset($template) ) { $template = $this->Application->ConfigValue('HardMaintenanceTemplate'); } $curl_helper = $this->Application->recallObject('CurlHelper'); /* @var $curl_helper kCurlHelper */ $html = $curl_helper->Send($this->Application->BaseURL() . '?t=' . $template); if ( $curl_helper->isGoodResponseCode() ) { file_put_contents(WRITEABLE . DIRECTORY_SEPARATOR . 'maintenance.html', $html); return true; } return false; } /** * Returns auto-complete values for ajax-dropdown * * @param kEvent $event * @return void * @access protected */ protected function OnSuggestValues(kEvent $event) { - if ( !$this->Application->isAdminUser ) { - // very careful here, because this event allows to - // view every object field -> limit only to logged-in admins - return; - } - $event->status = kEvent::erSTOP; $field = $this->Application->GetVar('field'); $cur_value = $this->Application->GetVar('cur_value'); $object = $event->getObject(); /* @var $object kDBItem */ if ( !$field || !$cur_value || !$object->isField($field) ) { return; } $limit = $this->Application->GetVar('limit'); if ( !$limit ) { $limit = 20; } $sql = 'SELECT DISTINCT ' . $field . ', ModuleOwner FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' WHERE ' . $field . ' LIKE ' . $this->Conn->qstr('%' . $cur_value . '%') . ' ORDER BY ' . $field . ' ASC'; $raw_suggestions = $this->Conn->Query($sql); $suggestions = Array (); $this->Application->XMLHeader(); foreach ($raw_suggestions as $raw_suggestion) { $suggestion = $raw_suggestion[$field]; if ( !isset($suggestions[$suggestion]) ) { $suggestions[$suggestion] = Array (); } $suggestions[$suggestion][] = $raw_suggestion['ModuleOwner']; } array_splice($suggestions, $limit); echo '<suggestions>'; $of_label = $this->Application->Phrase('la_From', false); foreach ($suggestions as $suggestion_value => $suggestion_modules) { $suggestion_module = in_array('In-Portal', $suggestion_modules) ? 'In-Portal' : implode(', ', $suggestion_modules); $suggestion_title = $suggestion_value . ' <em style="color: grey;">' . $of_label . ' ' . $suggestion_module . '</em>'; echo '<item value="' . kUtil::escape($suggestion_value, kUtil::ESCAPE_HTML) . '">' . kUtil::escape($suggestion_title, kUtil::ESCAPE_HTML) . '</item>'; } echo '</suggestions>'; } /** * Prefills module dropdown * * @param kEvent $event * @return void * @access protected */ protected function OnAfterConfigRead(kEvent $event) { parent::OnAfterConfigRead($event); $options = Array (); foreach ($this->Application->ModuleInfo as $module_name => $module_info) { if ( $module_name == 'Core' ) { continue; } $options[$module_name] = $module_name; if ( $module_name == 'In-Portal' ) { $options['In-Portal:Users'] = 'In-Portal:Users'; } } $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); $fields['ModuleOwner']['options'] = $options; $this->Application->setUnitOption($event->Prefix, 'Fields', $fields); } } Index: branches/5.2.x/CREDITS =================================================================== --- branches/5.2.x/CREDITS (revision 16413) +++ branches/5.2.x/CREDITS (revision 16414) Property changes on: branches/5.2.x/CREDITS ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /in-portal/branches/5.3.x/CREDITS:r15987 Index: branches/5.2.x/README =================================================================== --- branches/5.2.x/README (revision 16413) +++ branches/5.2.x/README (revision 16414) Property changes on: branches/5.2.x/README ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /in-portal/branches/5.3.x/README:r15987 Index: branches/5.2.x/index.php =================================================================== --- branches/5.2.x/index.php (revision 16413) +++ branches/5.2.x/index.php (revision 16414) Property changes on: branches/5.2.x/index.php ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /in-portal/branches/5.3.x/index.php:r15987 Index: branches/5.2.x/LICENSES =================================================================== --- branches/5.2.x/LICENSES (revision 16413) +++ branches/5.2.x/LICENSES (revision 16414) Property changes on: branches/5.2.x/LICENSES ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /in-portal/branches/5.3.x/LICENSES:r15987 Index: branches/5.2.x/INSTALL =================================================================== --- branches/5.2.x/INSTALL (revision 16413) +++ branches/5.2.x/INSTALL (revision 16414) Property changes on: branches/5.2.x/INSTALL ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /in-portal/branches/5.3.x/INSTALL:r15987 Index: branches/5.2.x/COPYRIGHT =================================================================== --- branches/5.2.x/COPYRIGHT (revision 16413) +++ branches/5.2.x/COPYRIGHT (revision 16414) Property changes on: branches/5.2.x/COPYRIGHT ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /in-portal/branches/5.3.x/COPYRIGHT:r15987 Index: branches/5.2.x/.htaccess =================================================================== --- branches/5.2.x/.htaccess (revision 16413) +++ branches/5.2.x/.htaccess (revision 16414) Property changes on: branches/5.2.x/.htaccess ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /in-portal/branches/5.3.x/.htaccess:r15987 Index: branches/5.2.x =================================================================== --- branches/5.2.x (revision 16413) +++ branches/5.2.x (revision 16414) Property changes on: branches/5.2.x ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /in-portal/branches/5.3.x:r15987