Page MenuHomeIn-Portal Phabricator

in-portal
No OneTemporary

File Metadata

Created
Sun, Feb 2, 1:10 AM

in-portal

Index: branches/5.2.x/core/kernel/db/db_event_handler.php
===================================================================
--- branches/5.2.x/core/kernel/db/db_event_handler.php (revision 16823)
+++ branches/5.2.x/core/kernel/db/db_event_handler.php (revision 16824)
@@ -1,3535 +1,3459 @@
<?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');
}
return parent::CheckPermission($event);
}
/**
* Allows to override standard permission mapping
*
* @return void
* @access protected
* @see kEventHandler::$permMapping
*/
protected function mapPermissions()
{
parent::mapPermissions();
$permissions = Array (
'OnLoad' => Array ('self' => 'view', 'subitem' => 'view'),
'OnItemBuild' => Array ('self' => 'view', 'subitem' => 'view'),
'OnSuggestValues' => Array ('self' => 'admin', 'subitem' => 'admin'),
'OnSuggestValuesJSON' => Array ('self' => 'admin', 'subitem' => 'admin'),
'OnBuild' => Array ('self' => true),
'OnNew' => Array ('self' => 'add', 'subitem' => 'add|edit'),
'OnCreate' => Array ('self' => 'add', 'subitem' => 'add|edit'),
'OnUpdate' => Array ('self' => 'edit', 'subitem' => 'add|edit'),
'OnSetPrimary' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
'OnDelete' => Array ('self' => 'delete', 'subitem' => 'add|edit'),
'OnDeleteAll' => Array ('self' => 'delete', 'subitem' => 'add|edit'),
'OnMassDelete' => Array ('self' => 'delete', 'subitem' => 'add|edit'),
'OnMassClone' => Array ('self' => 'add', 'subitem' => 'add|edit'),
'OnCut' => Array ('self'=>'edit', 'subitem' => 'edit'),
'OnCopy' => Array ('self'=>'edit', 'subitem' => 'edit'),
'OnPaste' => Array ('self'=>'edit', 'subitem' => 'edit'),
'OnSelectItems' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
'OnProcessSelected' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
'OnStoreSelected' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
'OnSelectUser' => Array ('self' => 'add|edit', 'subitem' => 'add|edit'),
'OnMassApprove' => Array ('self' => 'advanced:approve|edit', 'subitem' => 'advanced:approve|add|edit'),
'OnMassDecline' => Array ('self' => 'advanced:decline|edit', 'subitem' => 'advanced:decline|add|edit'),
'OnMassMoveUp' => Array ('self' => 'advanced:move_up|edit', 'subitem' => 'advanced:move_up|add|edit'),
'OnMassMoveDown' => Array ('self' => 'advanced:move_down|edit', 'subitem' => 'advanced:move_down|add|edit'),
'OnPreCreate' => Array ('self' => 'add|add.pending', 'subitem' => 'edit|edit.pending'),
'OnEdit' => Array ('self' => 'edit|edit.pending', 'subitem' => 'edit|edit.pending'),
'OnDeleteExportPreset' => array('self' => 'view'),
'OnExport' => Array ('self' => 'view|advanced:export'),
'OnExportBegin' => Array ('self' => 'view|advanced:export'),
'OnExportProgress' => Array ('self' => 'view|advanced:export'),
'OnExportCancel' => Array ('self' => 'view|advanced:export'),
'OnSetAutoRefreshInterval' => Array ('self' => true, 'subitem' => true),
'OnAutoRefreshToggle' => Array ('self' => true, 'subitem' => true),
// theese event do not harm, but just in case check them too :)
'OnCancelEdit' => Array ('self' => true, 'subitem' => true),
'OnCancel' => Array ('self' => true, 'subitem' => true),
'OnReset' => Array ('self' => true, 'subitem' => true),
'OnSetSorting' => Array ('self' => true, 'subitem' => true),
'OnSetSortingDirect' => Array ('self' => true, 'subitem' => true),
'OnResetSorting' => Array ('self' => true, 'subitem' => true),
'OnSetFilter' => Array ('self' => true, 'subitem' => true),
'OnApplyFilters' => Array ('self' => true, 'subitem' => true),
'OnRemoveFilters' => Array ('self' => true, 'subitem' => true),
'OnSetFilterPattern' => Array ('self' => true, 'subitem' => true),
'OnSetPerPage' => Array ('self' => true, 'subitem' => true),
'OnSetPage' => Array ('self' => true, 'subitem' => true),
'OnSearch' => Array ('self' => true, 'subitem' => true),
'OnSearchReset' => Array ('self' => true, 'subitem' => true),
'OnGoBack' => Array ('self' => true, 'subitem' => true),
// it checks permission itself since flash uploader does not send cookies
'OnUploadFile' => Array ('self' => true, 'subitem' => true),
'OnDeleteFile' => Array ('self' => true, 'subitem' => true),
'OnViewFile' => Array ('self' => true, 'subitem' => true),
'OnSaveWidths' => Array ('self' => 'admin', 'subitem' => 'admin'),
'OnValidateMInputFields' => Array ('self' => 'view'),
'OnValidateField' => Array ('self' => true, 'subitem' => true),
);
$this->permMapping = array_merge($this->permMapping, $permissions);
}
/**
* Define alternative event processing method names
*
* @return void
* @see kEventHandler::$eventMethods
* @access protected
*/
protected function mapEvents()
{
$events_map = Array (
'OnRemoveFilters' => 'FilterAction',
'OnApplyFilters' => 'FilterAction',
'OnMassApprove' => 'iterateItems',
'OnMassDecline' => 'iterateItems',
'OnMassMoveUp' => 'iterateItems',
'OnMassMoveDown' => 'iterateItems',
);
$this->eventMethods = array_merge($this->eventMethods, $events_map);
}
/**
* Returns ID of current item to be edited
* by checking ID passed in get/post as prefix_id
* or by looking at first from selected ids, stored.
* Returned id is also stored in Session in case
* it was explicitly passed as get/post
*
* @param kEvent $event
* @return int
* @access public
*/
public function getPassedID(kEvent $event)
{
if ( $event->getEventParam('raise_warnings') === false ) {
$event->setEventParam('raise_warnings', 1);
}
if ( $event->Special == 'previous' || $event->Special == 'next' ) {
/** @var kDBItem $object */
$object = $this->Application->recallObject($event->getEventParam('item'));
/** @var ListHelper $list_helper */
$list_helper = $this->Application->recallObject('ListHelper');
$select_clause = $this->Application->getUnitOption($object->Prefix, 'NavigationSelectClause', NULL);
return $list_helper->getNavigationResource($object, $event->getEventParam('list'), $event->Special == 'next', $select_clause);
}
elseif ( $event->Special == 'filter' ) {
// temporary object, used to print filter options only
return 0;
}
if ( preg_match('/^auto-(.*)/', $event->Special, $regs) && $this->Application->prefixRegistred($regs[1]) ) {
// <inp2:lang.auto-phrase_Field name="DateFormat"/> - returns field DateFormat value from language (LanguageId is extracted from current phrase object)
/** @var kDBItem $main_object */
$main_object = $this->Application->recallObject($regs[1]);
$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
return $main_object->GetDBField($id_field);
}
// 1. get id from post (used in admin)
$ret = $this->Application->GetVar($event->getPrefixSpecial(true) . '_id');
if ( ($ret !== false) && ($ret != '') ) {
$event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true);
return $ret;
}
// 2. get id from env (used in front)
$ret = $this->Application->GetVar($event->getPrefixSpecial() . '_id');
if ( ($ret !== false) && ($ret != '') ) {
$event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true);
return $ret;
}
// recall selected ids array and use the first one
$ids = $this->Application->GetVar($event->getPrefixSpecial() . '_selected_ids');
if ( $ids != '' ) {
$ids = explode(',', $ids);
if ( $ids ) {
$ret = array_shift($ids);
$event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true);
}
}
else { // if selected ids are not yet stored
$this->StoreSelectedIDs($event);
// StoreSelectedIDs sets this variable.
$ret = $this->Application->GetVar($event->getPrefixSpecial() . '_id');
if ( ($ret !== false) && ($ret != '') ) {
$event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true);
return $ret;
}
}
return $ret;
}
/**
* Prepares and stores selected_ids string
* in Session and Application Variables
* by getting all checked ids from grid plus
* id passed in get/post as prefix_id
*
* @param kEvent $event
* @param Array $direct_ids
* @return Array
* @access protected
*/
protected function StoreSelectedIDs(kEvent $event, $direct_ids = NULL)
{
$wid = $this->Application->GetTopmostWid($event->Prefix);
$session_name = rtrim($event->getPrefixSpecial() . '_selected_ids_' . $wid, '_');
$ids = $event->getEventParam('ids');
if ( isset($direct_ids) || ($ids !== false) ) {
// save ids directly if they given + reset array indexes
$resulting_ids = $direct_ids ? array_values($direct_ids) : ($ids ? array_values($ids) : false);
if ( $resulting_ids ) {
$this->Application->SetVar($event->getPrefixSpecial() . '_selected_ids', implode(',', $resulting_ids));
$this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name, '', true);
$this->Application->SetVar($event->getPrefixSpecial() . '_id', $resulting_ids[0]);
return $resulting_ids;
}
return Array ();
}
$ret = Array ();
// May be we don't need this part: ?
$passed = $this->Application->GetVar($event->getPrefixSpecial(true) . '_id');
if ( $passed !== false && $passed != '' ) {
array_push($ret, $passed);
}
$ids = Array ();
// get selected ids from post & save them to session
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( $items_info ) {
$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
foreach ($items_info as $id => $field_values) {
if ( getArrayValue($field_values, $id_field) ) {
array_push($ids, $id);
}
}
//$ids = array_keys($items_info);
}
$ret = array_unique(array_merge($ret, $ids));
$this->Application->SetVar($event->getPrefixSpecial() . '_selected_ids', implode(',', $ret));
$this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name, '', !$ret); // optional when IDs are missing
// This is critical - otherwise getPassedID will return last ID stored in session! (not exactly true)
// this smells... needs to be refactored
$first_id = getArrayValue($ret, 0);
if ( ($first_id === false) && ($event->getEventParam('raise_warnings') == 1) ) {
if ( $this->Application->isDebugMode() ) {
$this->Application->Debugger->appendTrace();
}
trigger_error('Requested ID for prefix <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' ) {
/** @var kDBItem $object */
$object = $event->getObject();
if ( !$object->isLoaded() ) {
return true;
}
return $object->GetDBField($status_field) == STATUS_ACTIVE;
}
return true;
}
/**
* Shows not found template content
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function _errorNotFound(kEvent $event)
{
if ( $event->getEventParam('raise_warnings') === 0 ) {
// when it's possible, that autoload fails do nothing
return;
}
if ( $this->Application->isDebugMode() ) {
$this->Application->Debugger->appendTrace();
}
trigger_error('ItemLoad Permission Failed for prefix [' . $event->getPrefixSpecial() . '] in <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)
{
/** @var kDBItem $object */
$object = $event->getObject();
$this->dbBuild($object, $event);
$sql = $this->ItemPrepareQuery($event);
$sql = $this->Application->ReplaceLanguageTags($sql);
$object->setSelectSQL($sql);
// 2. loads if allowed
$auto_load = $this->Application->getUnitOption($event->Prefix,'AutoLoad');
$skip_autoload = $event->getEventParam('skip_autoload');
if ( $auto_load && !$skip_autoload ) {
$perm_status = true;
$user_id = $this->Application->InitDone ? $this->Application->RecallVar('user_id') : USER_ROOT;
$event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true));
$status_checked = false;
if ( $this->Application->permissionCheckingDisabled($user_id) || $this->CheckPermission($event) ) {
// Don't autoload item, when user doesn't have view permission.
$this->LoadItem($event);
$status_checked = true;
$editing_mode = defined('EDITING_MODE') ? EDITING_MODE : false;
$id_from_request = $event->getEventParam(kEvent::FLAG_ID_FROM_REQUEST);
if ( !$this->Application->permissionCheckingDisabled($user_id)
&& !$this->Application->isAdmin
&& !($editing_mode || ($id_from_request ? $this->checkItemStatus($event) : true))
) {
// Permissions are being checked AND on Front-End AND (not editing mode || incorrect status).
$perm_status = false;
}
}
else {
$perm_status = false;
}
if ( !$perm_status ) {
// when no permission to view item -> redirect to no permission template
$this->_processItemLoadingError($event, $status_checked);
}
}
/** @var Params $actions */
$actions = $this->Application->recallObject('kActions');
$actions->Set($event->getPrefixSpecial() . '_GoTab', '');
$actions->Set($event->getPrefixSpecial() . '_GoId', '');
$actions->Set('forms[' . $event->getPrefixSpecial() . ']', $object->getFormName());
}
/**
* Processes case, when item wasn't loaded because of lack of permissions
*
* @param kEvent $event
* @param bool $status_checked
* @throws kNoPermissionException
* @return void
* @access protected
*/
protected function _processItemLoadingError($event, $status_checked)
{
$current_template = $this->Application->GetVar('t');
$redirect_template = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate');
$error_msg = 'ItemLoad Permission Failed for prefix [' . $event->getPrefixSpecial() . '] in <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'],
'pass' => 'm',
);
}
else {
$redirect_params = Array (
'next_template' => $current_template,
'pass' => 'm',
);
}
$exception = new kNoPermissionException($error_msg);
$exception->setup($redirect_template, $redirect_params);
throw $exception;
}
/**
* Build sub-tables array from configs
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnTempHandlerBuild(kEvent $event)
{
/** @var kTempTablesHandler $object */
$object = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
/** @var kEvent $parent_event */
$parent_event = $event->getEventParam('parent_event');
if ( is_object($parent_event) ) {
$object->setParentEvent($parent_event);
}
$object->BuildTables($event->Prefix, $this->getSelectedIDs($event));
}
/**
* Checks, that object used in event should use temp tables
*
* @param kEvent $event
* @return bool
* @access protected
*/
protected function UseTempTables(kEvent $event)
{
$top_prefix = $this->Application->GetTopmostPrefix($event->Prefix); // passed parent, not always actual
$special = ($top_prefix == $event->Prefix) ? $event->Special : $this->getMainSpecial($event);
return $this->Application->IsTempMode($event->Prefix, $special);
}
/**
* Load item if id is available
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function LoadItem(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject();
$id = $this->getPassedID($event);
if ( $object->isLoaded() && !is_array($id) && ($object->GetID() == $id) ) {
// object is already loaded by same id
return ;
}
if ( $object->Load($id) ) {
/** @var Params $actions */
$actions = $this->Application->recallObject('kActions');
$actions->Set($event->getPrefixSpecial() . '_id', $object->GetID());
}
else {
$object->setID( is_array($id) ? false : $id );
}
}
/**
* Builds list
*
* Pattern: Prototype Manager
*
* @param kEvent $event
* @access protected
*/
protected function OnListBuild(kEvent $event)
{
/** @var kDBList $object */
$object = $event->getObject();
/*if ( $this->Application->isDebugMode() ) {
$event_params = http_build_query($event->getEventParams());
$this->Application->Debugger->appendHTML('InitList "<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);
/** @var Params $actions */
$actions = $this->Application->recallObject('kActions');
$actions->Set('remove_specials[' . $event->getPrefixSpecial() . ']', '0');
$actions->Set($event->getPrefixSpecial() . '_GoTab', '');
}
/**
* Returns special of main item for linking with sub-item
*
* @param kEvent $event
* @return string
* @access protected
*/
protected function getMainSpecial(kEvent $event)
{
$main_special = $event->getEventParam('main_special');
if ( $main_special === false ) {
// main item's special not passed
if ( substr($event->Special, -5) == '-item' ) {
// temp handler added "-item" to given special -> process that here
return substr($event->Special, 0, -5);
}
// by default subitem's special is used for main item searching
return $event->Special;
}
return $main_special;
}
/**
* Apply any custom changes to list's sql query
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetCustomQuery(kEvent $event)
{
}
/**
* Set's new per-page for grid
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSetPerPage(kEvent $event)
{
$per_page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_PerPage');
$event->SetRedirectParam($event->getPrefixSpecial() . '_PerPage', $per_page);
$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
if ( !$this->Application->isAdminUser ) {
/** @var ListHelper $list_helper */
$list_helper = $this->Application->recallObject('ListHelper');
$this->_passListParams($event, 'per_page');
}
}
/**
* Occurs when page is changed (only for hooking)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSetPage(kEvent $event)
{
$page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page');
$event->SetRedirectParam($event->getPrefixSpecial() . '_Page', $page);
$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
if ( !$this->Application->isAdminUser ) {
$this->_passListParams($event, 'page');
}
}
/**
* Passes through main list pagination and sorting
*
* @param kEvent $event
* @param string $skip_var
* @return void
* @access protected
*/
protected function _passListParams($event, $skip_var)
{
$param_names = array_diff(Array ('page', 'per_page', 'sort_by'), Array ($skip_var));
/** @var ListHelper $list_helper */
$list_helper = $this->Application->recallObject('ListHelper');
foreach ($param_names as $param_name) {
$value = $this->Application->GetVar($param_name);
switch ($param_name) {
case 'page':
if ( $value > 1 ) {
$event->SetRedirectParam('page', $value);
}
break;
case 'per_page':
if ( $value > 0 ) {
if ( $value != $list_helper->getDefaultPerPage($event->Prefix) ) {
$event->SetRedirectParam('per_page', $value);
}
}
break;
case 'sort_by':
$event->setPseudoClass('_List');
/** @var kDBList $object */
$object = $event->getObject(Array ('main_list' => 1));
if ( $list_helper->hasUserSorting($object) ) {
$event->SetRedirectParam('sort_by', $value);
}
break;
}
}
}
/**
* Set's correct page for list based on data provided with event
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetPagination(kEvent $event)
{
/** @var kDBList $object */
$object = $event->getObject();
// get PerPage (forced -> session -> config -> 10)
$object->SetPerPage($this->getPerPage($event));
// Main lists on Front-End have special get parameter for page.
if ( $object->isMainList() ) {
$page = $this->Application->GetVarFiltered('page', false, FILTER_VALIDATE_INT);
}
else {
$page = false;
}
if ( !$page ) {
// Page is given in "env" variable for given prefix.
$page = $this->Application->GetVarFiltered(
$event->getPrefixSpecial() . '_Page',
false,
FILTER_VALIDATE_INT
);
}
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->GetVarFiltered(
$event->getPrefixSpecial(true) . '_Page',
false,
FILTER_VALIDATE_INT
);
}
if ( !$object->isMainList() ) {
// main lists doesn't use session for page storing
$this->Application->StoreVarDefault($event->getPrefixSpecial() . '_Page', 1, true); // true for optional
if ( $page ) {
// page found in request -> store in session
$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional
}
else {
// page not found in request -> get from session
$page = $this->Application->RecallVar($event->getPrefixSpecial() . '_Page');
}
if ( !$event->getEventParam('skip_counting') ) {
// when stored page is larger, then maximal list page number
// (such case is also processed in kDBList::Query method)
$pages = $object->GetTotalPages();
if ( $page > $pages ) {
$page = 1;
$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', 1, true);
}
}
}
$object->SetPage($page);
}
/**
* Returns current per-page setting for list
*
* @param kEvent $event
* @return int
* @access protected
*/
protected function getPerPage(kEvent $event)
{
/** @var kDBList $object */
$object = $event->getObject();
$per_page = $event->getEventParam('per_page');
if ( $per_page ) {
// per-page is passed as tag parameter to PrintList, InitList, etc.
$config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping');
// 2. per-page setting is stored in configuration variable
if ( $config_mapping ) {
// such pseudo per-pages are only defined in templates directly
switch ($per_page) {
case 'short_list':
$per_page = $this->Application->ConfigValue($config_mapping['ShortListPerPage']);
break;
case 'default':
$per_page = $this->Application->ConfigValue($config_mapping['PerPage']);
break;
}
}
return $per_page;
}
if ( !$per_page && $object->isMainList() ) {
// Main lists on Front-End have special get parameter for per-page.
$per_page = $this->Application->GetVarFiltered(
'per_page',
false,
FILTER_VALIDATE_INT
);
}
if ( !$per_page ) {
// Per-page is given in "env" variable for given prefix.
$per_page = $this->Application->GetVarFiltered(
$event->getPrefixSpecial() . '_PerPage',
false,
FILTER_VALIDATE_INT
);
}
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->GetVarFiltered(
$event->getPrefixSpecial(true) . '_PerPage',
false,
FILTER_VALIDATE_INT
);
}
if ( !$object->isMainList() ) {
// per-page given in env and not in main list
$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
if ( $per_page ) {
// per-page found in request -> store in session and persistent session
$this->setListSetting($event, 'PerPage', $per_page);
}
else {
// per-page not found in request -> get from pesistent session (or session)
$per_page = $this->getListSetting($event, 'PerPage');
}
}
if ( !$per_page ) {
// per page wan't found in request/session/persistent session
/** @var ListHelper $list_helper */
$list_helper = $this->Application->recallObject('ListHelper');
// allow to override default per-page value from tag
$default_per_page = $event->getEventParam('default_per_page');
if ( !is_numeric($default_per_page) ) {
$default_per_page = $this->Application->ConfigValue('DefaultGridPerPage');
}
$per_page = $list_helper->getDefaultPerPage($event->Prefix, $default_per_page);
}
return $per_page;
}
/**
* Set's correct sorting for list based on data provided with event
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetSorting(kEvent $event)
{
$event->setPseudoClass('_List');
/** @var kDBList $object */
$object = $event->getObject();
if ( $object->isMainList() ) {
$sort_by = $this->Application->GetVarFiltered(
'sort_by',
false,
FILTER_CALLBACK,
array('options' => array($this, 'sortByFilterCallback'))
);
$cur_sort1 = $cur_sort1_dir = $cur_sort2 = $cur_sort2_dir = false;
if ( $sort_by ) {
$sortings = explode('|', $sort_by);
list ($cur_sort1, $cur_sort1_dir) = explode(',', $sortings[0]);
if ( isset($sortings[1]) ) {
list ($cur_sort2, $cur_sort2_dir) = explode(',', $sortings[1]);
}
}
}
else {
$sorting_settings = $this->getListSetting($event, 'Sortings');
$cur_sort1 = getArrayValue($sorting_settings, 'Sort1');
$cur_sort1_dir = getArrayValue($sorting_settings, 'Sort1_Dir');
$cur_sort2 = getArrayValue($sorting_settings, 'Sort2');
$cur_sort2_dir = getArrayValue($sorting_settings, 'Sort2_Dir');
}
$tag_sort_by = $event->getEventParam('sort_by');
if ( $tag_sort_by ) {
if ( $tag_sort_by == 'random' ) {
$object->AddOrderField('RAND()', '');
}
else {
// multiple sortings could be specified at once
$tag_sort_by = explode('|', $tag_sort_by);
foreach ($tag_sort_by as $sorting_element) {
list ($by, $dir) = explode(',', $sorting_element);
$object->AddOrderField($by, $dir);
}
}
}
$list_sortings = $this->_getDefaultSorting($event);
// use default if not specified in session
if ( !$cur_sort1 || !$cur_sort1_dir ) {
$sorting = getArrayValue($list_sortings, 'Sorting');
if ( $sorting ) {
reset($sorting);
$cur_sort1 = key($sorting);
$cur_sort1_dir = current($sorting);
if ( next($sorting) ) {
$cur_sort2 = key($sorting);
$cur_sort2_dir = current($sorting);
}
}
}
// always add forced sorting before any user sorting fields
/** @var Array $forced_sorting */
$forced_sorting = getArrayValue($list_sortings, 'ForcedSorting');
if ( $forced_sorting ) {
foreach ($forced_sorting as $field => $dir) {
$object->AddOrderField($field, $dir);
}
}
// add user sorting fields
if ( $cur_sort1 != '' && $cur_sort1_dir != '' ) {
$object->AddOrderField($cur_sort1, $cur_sort1_dir);
}
if ( $cur_sort2 != '' && $cur_sort2_dir != '' ) {
$object->AddOrderField($cur_sort2, $cur_sort2_dir);
}
}
/**
* Filters the "sort_by" request variable.
*
* @param string|boolean $value Value.
*
* @return string|boolean
*/
public function sortByFilterCallback($value)
{
if ( !$value ) {
return false;
}
$sortings = array_filter(
explode('|', $value),
function ($sorting) {
return preg_match('/^[a-z_][a-z0-9_]*,(asc|desc)$/i', $sorting);
}
);
return $sortings ? implode('|', $sortings) : false;
}
/**
* Returns default list sortings
*
* @param kEvent $event
* @return Array
* @access protected
*/
protected function _getDefaultSorting(kEvent $event)
{
$list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings', Array ());
$sorting_prefix = array_key_exists($event->Special, $list_sortings) ? $event->Special : '';
$sorting_configs = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping');
if ( $sorting_configs && array_key_exists('DefaultSorting1Field', $sorting_configs) ) {
// sorting defined in configuration variables overrides one from unit config
$list_sortings[$sorting_prefix]['Sorting'] = Array (
$this->Application->ConfigValue($sorting_configs['DefaultSorting1Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting1Dir']),
$this->Application->ConfigValue($sorting_configs['DefaultSorting2Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting2Dir']),
);
// TODO: lowercase configuration variable values in db, instead of here
$list_sortings[$sorting_prefix]['Sorting'] = array_map('strtolower', $list_sortings[$sorting_prefix]['Sorting']);
}
return isset($list_sortings[$sorting_prefix]) ? $list_sortings[$sorting_prefix] : Array ();
}
/**
* Gets list setting by name (persistent or real session)
*
* @param kEvent $event
* @param string $variable_name
* @return string|Array
* @access protected
*/
protected function getListSetting(kEvent $event, $variable_name)
{
$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
$storage_prefix = $event->getEventParam('same_special') ? $event->Prefix : $event->getPrefixSpecial();
// get sorting from persistent session
$default_value = $this->Application->isAdmin ? ALLOW_DEFAULT_SETTINGS : false;
$variable_value = $this->Application->RecallPersistentVar($storage_prefix . '_' . $variable_name . '.' . $view_name, $default_value);
/*if ( !$variable_value ) {
// get sorting from session
$variable_value = $this->Application->RecallVar($storage_prefix . '_' . $variable_name);
}*/
if ( kUtil::IsSerialized($variable_value) ) {
$variable_value = unserialize($variable_value);
}
return $variable_value;
}
/**
* Sets list setting by name (persistent and real session)
*
* @param kEvent $event
* @param string $variable_name
* @param string|Array $variable_value
* @return void
* @access protected
*/
protected function setListSetting(kEvent $event, $variable_name, $variable_value = NULL)
{
$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
// $this->Application->StoreVar($event->getPrefixSpecial() . '_' . $variable_name, $variable_value, true); //true for optional
if ( isset($variable_value) ) {
if ( is_array($variable_value) ) {
$variable_value = serialize($variable_value);
}
$this->Application->StorePersistentVar($event->getPrefixSpecial() . '_' . $variable_name . '.' . $view_name, $variable_value, true); //true for optional
}
else {
$this->Application->RemovePersistentVar($event->getPrefixSpecial() . '_' . $variable_name . '.' . $view_name);
}
}
/**
* Add filters found in session
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function AddFilters(kEvent $event)
{
/** @var kDBList $object */
$object = $event->getObject();
if ( $object->IsTempTable() ) {
$session_id = $this->Application->GetSID(Session::PURPOSE_REFERENCE);
$edit_mark = rtrim($session_id . '_' . $this->Application->GetTopmostWid($event->Prefix), '_');
}
else {
$edit_mark = '';
}
// 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) {
$field_options = current($field_options);
if ( isset($field_options['value']) && $field_options['value'] ) {
$filter_type = ($field_options['sql_filter_type'] == 'having') ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER;
$filter_value = str_replace(EDIT_MARK, $edit_mark, $field_options['value']);
$object->addFilter($field_name, $filter_value, $filter_type, kDBList::FLT_CUSTOM);
}
}
}
}
// add view filter
$view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter');
if ( $view_filter ) {
$view_filter = unserialize($view_filter);
/** @var kMultipleFilter $temp_filter */
$temp_filter = $this->Application->makeClass('kMultipleFilter');
$filter_menu = $this->Application->getUnitOption($event->Prefix, 'FilterMenu');
$group_key = 0;
$group_count = count($filter_menu['Groups']);
while ($group_key < $group_count) {
$group_info = $filter_menu['Groups'][$group_key];
$temp_filter->setType(constant('kDBList::FLT_TYPE_' . $group_info['mode']));
$temp_filter->clearFilters();
foreach ($group_info['filters'] as $flt_id) {
$sql_key = getArrayValue($view_filter, $flt_id) ? 'on_sql' : 'off_sql';
if ( $filter_menu['Filters'][$flt_id][$sql_key] != '' ) {
$temp_filter->addFilter('view_filter_' . $flt_id, $filter_menu['Filters'][$flt_id][$sql_key]);
}
}
$object->addFilter('view_group_' . $group_key, $temp_filter, $group_info['type'], kDBList::FLT_VIEW);
$group_key++;
}
}
// add item filter
if ( $object->isMainList() ) {
$this->applyItemFilters($event);
}
}
/**
* Applies item filters
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function applyItemFilters($event)
{
$filter_values = $this->Application->GetVar('filters', Array ());
if ( !$filter_values ) {
return;
}
/** @var kDBList $object */
$object = $event->getObject();
$where_clause = Array (
'ItemPrefix = ' . $this->Conn->qstr($object->Prefix),
'FilterField IN (' . implode(',', $this->Conn->qstrArray(array_keys($filter_values))) . ')',
'Enabled = 1',
);
$sql = 'SELECT *
FROM ' . $this->Application->getUnitOption('item-filter', 'TableName') . '
WHERE (' . implode(') AND (', $where_clause) . ')';
$filters = $this->Conn->Query($sql, 'FilterField');
foreach ($filters as $filter_field => $filter_data) {
$filter_value = $filter_values[$filter_field];
if ( "$filter_value" === '' ) {
// ListManager don't pass empty values, but check here just in case
continue;
}
$table_name = $object->isVirtualField($filter_field) ? '' : '%1$s.';
switch ($filter_data['FilterType']) {
case 'radio':
$filter_value = $table_name . '`' . $filter_field . '` = ' . $this->Conn->qstr($filter_value);
break;
case 'checkbox':
$filter_value = explode('|', substr($filter_value, 1, -1));
$filter_value = $this->Conn->qstrArray($filter_value, 'escape');
if ( $object->GetFieldOption($filter_field, 'multiple') ) {
$filter_value = $table_name . '`' . $filter_field . '` LIKE "%|' . implode('|%" OR ' . $table_name . '`' . $filter_field . '` LIKE "%|', $filter_value) . '|%"';
}
else {
$filter_value = $table_name . '`' . $filter_field . '` IN (' . implode(',', $filter_value) . ')';
}
break;
case 'range':
$filter_value = $this->Conn->qstrArray(explode('-', $filter_value));
$filter_value = $table_name . '`' . $filter_field . '` BETWEEN ' . $filter_value[0] . ' AND ' . $filter_value[1];
break;
}
$object->addFilter('item_filter_' . $filter_field, $filter_value, $object->isVirtualField($filter_field) ? kDBList::HAVING_FILTER : kDBList::WHERE_FILTER);
}
}
/**
* Set's new sorting for list
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSetSorting(kEvent $event)
{
$sorting_settings = $this->getListSetting($event, 'Sortings');
$cur_sort1 = getArrayValue($sorting_settings, 'Sort1');
$cur_sort1_dir = getArrayValue($sorting_settings, 'Sort1_Dir');
$use_double_sorting = $this->Application->ConfigValue('UseDoubleSorting');
if ( $use_double_sorting ) {
$cur_sort2 = getArrayValue($sorting_settings, 'Sort2');
$cur_sort2_dir = getArrayValue($sorting_settings, 'Sort2_Dir');
}
$passed_sort1 = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Sort1');
if ( $cur_sort1 == $passed_sort1 ) {
$cur_sort1_dir = $cur_sort1_dir == 'asc' ? 'desc' : 'asc';
}
else {
if ( $use_double_sorting ) {
$cur_sort2 = $cur_sort1;
$cur_sort2_dir = $cur_sort1_dir;
}
$cur_sort1 = $passed_sort1;
$cur_sort1_dir = 'asc';
}
$sorting_settings = Array ('Sort1' => $cur_sort1, 'Sort1_Dir' => $cur_sort1_dir);
if ( $use_double_sorting ) {
$sorting_settings['Sort2'] = $cur_sort2;
$sorting_settings['Sort2_Dir'] = $cur_sort2_dir;
}
$this->setListSetting($event, 'Sortings', $sorting_settings);
}
/**
* Set sorting directly to session (used for category item sorting (front-end), grid sorting (admin, view menu)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSetSortingDirect(kEvent $event)
{
// used on Front-End in category item lists
$prefix_special = $event->getPrefixSpecial();
$combined = $this->Application->GetVar($event->getPrefixSpecial(true) . '_CombinedSorting');
if ( $combined ) {
list ($field, $dir) = explode('|', $combined);
if ( $this->Application->isAdmin || !$this->Application->GetVar('main_list') ) {
$this->setListSetting($event, 'Sortings', Array ('Sort1' => $field, 'Sort1_Dir' => $dir));
}
else {
$event->setPseudoClass('_List');
$this->Application->SetVar('sort_by', $field . ',' . $dir);
/** @var kDBList $object */
$object = $event->getObject(Array ('main_list' => 1));
/** @var ListHelper $list_helper */
$list_helper = $this->Application->recallObject('ListHelper');
$this->_passListParams($event, 'sort_by');
if ( $list_helper->hasUserSorting($object) ) {
$event->SetRedirectParam('sort_by', $field . ',' . strtolower($dir));
}
$event->SetRedirectParam('pass', 'm');
}
return;
}
// used in "View Menu -> Sort" menu in administrative console
$field_pos = $this->Application->GetVar($event->getPrefixSpecial(true) . '_SortPos');
$this->Application->LinkVar($event->getPrefixSpecial(true) . '_Sort' . $field_pos, $prefix_special . '_Sort' . $field_pos);
$this->Application->LinkVar($event->getPrefixSpecial(true) . '_Sort' . $field_pos . '_Dir', $prefix_special . '_Sort' . $field_pos . '_Dir');
}
/**
* Reset grid sorting to default (from config)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnResetSorting(kEvent $event)
{
$this->setListSetting($event, 'Sortings');
}
/**
* Sets grid refresh interval
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSetAutoRefreshInterval(kEvent $event)
{
$refresh_interval = $this->Application->GetVar('refresh_interval');
$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
$this->Application->StorePersistentVar($event->getPrefixSpecial() . '_refresh_interval.' . $view_name, $refresh_interval);
}
/**
* Changes auto-refresh state for grid
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAutoRefreshToggle(kEvent $event)
{
$refresh_intervals = $this->Application->ConfigValue('AutoRefreshIntervals');
if ( !$refresh_intervals ) {
return;
}
$view_name = $this->Application->RecallVar($event->getPrefixSpecial() . '_current_view');
$auto_refresh = $this->Application->RecallPersistentVar($event->getPrefixSpecial() . '_auto_refresh.' . $view_name);
if ( $auto_refresh === false ) {
$refresh_intervals = explode(',', $refresh_intervals);
$this->Application->StorePersistentVar($event->getPrefixSpecial() . '_refresh_interval.' . $view_name, $refresh_intervals[0]);
}
$this->Application->StorePersistentVar($event->getPrefixSpecial() . '_auto_refresh.' . $view_name, $auto_refresh ? 0 : 1);
}
/**
* Creates needed sql query to load item,
* if no query is defined in config for
* special requested, then use list query
*
* @param kEvent $event
* @return string
* @access protected
*/
protected function ItemPrepareQuery(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject();
$sqls = $object->getFormOption('ItemSQLs', Array ());
$special = isset($sqls[$event->Special]) ? $event->Special : '';
// preferred special not found in ItemSQLs -> use analog from ListSQLs
return isset($sqls[$special]) ? $sqls[$special] : $this->ListPrepareQuery($event);
}
/**
* Creates needed sql query to load list,
* if no query is defined in config for
* special requested, then use default
* query
*
* @param kEvent $event
* @return string
* @access protected
*/
protected function ListPrepareQuery(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject();
$sqls = $object->getFormOption('ListSQLs', Array ());
return $sqls[array_key_exists($event->Special, $sqls) ? $event->Special : ''];
}
/**
* Apply custom processing to item
*
* @param kEvent $event
* @param string $type
* @return void
* @access protected
*/
protected function customProcessing(kEvent $event, $type)
{
}
/* Edit Events mostly used in Admin */
/**
* Creates new kDBItem
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCreate(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject(Array ('skip_autoload' => true));
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( !$items_info ) {
return;
}
$id = key($items_info);
$field_values = $items_info[$id];
$object->setID($id);
$object->SetFieldsFromHash($field_values);
$event->setEventParam('form_data', $field_values);
$this->customProcessing($event, 'before');
// look at kDBItem' Create for ForceCreateId description, it's rarely used and is NOT set by default
if ( $object->Create($event->getEventParam('ForceCreateId')) ) {
$this->customProcessing($event, 'after');
$event->SetRedirectParam('opener', 'u');
return;
}
$event->redirect = false;
$event->status = kEvent::erFAIL;
$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate');
}
/**
* Updates kDBItem
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnUpdate(kEvent $event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return;
}
$this->_update($event);
$event->SetRedirectParam('opener', 'u');
if ( $event->status == kEvent::erSUCCESS ) {
$this->saveChangesToLiveTable($event->Prefix);
}
}
/**
* Updates data in database based on request
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function _update(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject(Array ('skip_autoload' => true));
$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
if ( $items_info ) {
foreach ($items_info as $id => $field_values) {
$object->Load($id);
$object->SetFieldsFromHash($field_values);
$event->setEventParam('form_data', $field_values);
$this->customProcessing($event, 'before');
if ( $object->Update($id) ) {
$this->customProcessing($event, 'after');
$event->status = kEvent::erSUCCESS;
}
else {
$event->status = kEvent::erFAIL;
$event->redirect = false;
break;
}
}
}
}
/**
* Automatically saves data to live table after sub-item was updated in Content Mode.
*
* @param string $prefix Prefix.
*
* @return void
*/
protected function saveChangesToLiveTable($prefix)
{
$parent_prefix = $this->Application->getUnitOption($prefix, 'ParentPrefix');
if ( $parent_prefix === false ) {
return;
}
if ( $this->Application->GetVar('admin') && $this->Application->IsTempMode($parent_prefix) ) {
$this->Application->HandleEvent(new kEvent($parent_prefix . ':OnSave'));
}
}
/**
* Delete's kDBItem object
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnDelete(kEvent $event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return;
}
/** @var kTempTablesHandler $temp_handler */
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
$temp_handler->DeleteItems($event->Prefix, $event->Special, Array ($this->getPassedID($event)));
}
/**
* Deletes all records from table
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnDeleteAll(kEvent $event)
{
$sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . '
FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName');
$ids = $this->Conn->GetCol($sql);
if ( $ids ) {
/** @var kTempTablesHandler $temp_handler */
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
$temp_handler->DeleteItems($event->Prefix, $event->Special, $ids);
}
}
/**
* Prepares new kDBItem object
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnNew(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject(Array ('skip_autoload' => true));
$object->Clear(0);
$this->Application->SetVar($event->getPrefixSpecial() . '_SaveEvent', 'OnCreate');
if ( $event->getEventParam('top_prefix') != $event->Prefix ) {
// this is subitem prefix, so use main item special
$table_info = $object->getLinkedInfo($this->getMainSpecial($event));
}
else {
$table_info = $object->getLinkedInfo();
}
if ( $table_info !== false ) {
$object->SetDBField($table_info['ForeignKey'], $table_info['ParentId']);
}
$event->redirect = false;
}
/**
* Cancels kDBItem Editing/Creation
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCancel(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject(Array ('skip_autoload' => true));
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( $items_info ) {
$delete_ids = Array ();
/** @var kTempTablesHandler $temp_handler */
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
foreach ($items_info as $id => $field_values) {
$object->Load($id);
// record created for using with selector (e.g. Reviews->Select User), and not validated => Delete it
if ( $object->isLoaded() && !$object->Validate() && ($id <= 0) ) {
$delete_ids[] = $id;
}
}
if ( $delete_ids ) {
$temp_handler->DeleteItems($event->Prefix, $event->Special, $delete_ids);
}
}
$event->SetRedirectParam('opener', 'u');
}
/**
* Deletes all selected items.
* Automatically recurse into sub-items using temp handler, and deletes sub-items
* by calling its Delete method if sub-item has AutoDelete set to true in its config file
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnMassDelete(kEvent $event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return ;
}
/** @var kTempTablesHandler $temp_handler */
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
$ids = $this->StoreSelectedIDs($event);
$event->setEventParam('ids', $ids);
$this->customProcessing($event, 'before');
$ids = $event->getEventParam('ids');
if ( $ids ) {
$temp_handler->DeleteItems($event->Prefix, $event->Special, $ids);
}
$this->clearSelectedIDs($event);
}
/**
* Sets window id (of first opened edit window) to temp mark in uls
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function setTempWindowID(kEvent $event)
{
$prefixes = Array ($event->Prefix, $event->getPrefixSpecial(true));
foreach ($prefixes as $prefix) {
$mode = $this->Application->GetVar($prefix . '_mode');
if ($mode == 't') {
$wid = $this->Application->GetVar('m_wid');
$this->Application->SetVar(str_replace('_', '.', $prefix) . '_mode', 't' . $wid);
break;
}
}
}
/**
* Prepare temp tables and populate it
* with items selected in the grid
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnEdit(kEvent $event)
{
$this->setTempWindowID($event);
$ids = $this->StoreSelectedIDs($event);
/** @var kDBItem $object */
$object = $event->getObject(Array('skip_autoload' => true));
$object->setPendingActions(null, true);
- $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
- $this->Application->RemoveVar($changes_var_name);
+ /** @var ChangeLogHelper $change_log_helper */
+ $change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+ $change_log_helper->forgetChanges($this->Prefix);
/** @var kTempTablesHandler $temp_handler */
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
$temp_handler->PrepareEdit();
$event->SetRedirectParam('m_lang', $this->Application->GetDefaultLanguageId());
$event->SetRedirectParam($event->getPrefixSpecial() . '_id', array_shift($ids));
$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
$simultaneous_edit_message = $this->Application->GetVar('_simultaneous_edit_message');
if ( $simultaneous_edit_message ) {
$event->SetRedirectParam('_simultaneous_edit_message', $simultaneous_edit_message);
}
}
/**
* Saves content of temp table into live and
* redirects to event' default redirect (normally grid template)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSave(kEvent $event)
{
$event->CallSubEvent('OnPreSave');
if ( $event->status != kEvent::erSUCCESS ) {
return;
}
$skip_master = false;
/** @var kTempTablesHandler $temp_handler */
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
- $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
-
if ( !$this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$live_ids = $temp_handler->SaveEdit($event->getEventParam('master_ids') ? $event->getEventParam('master_ids') : Array ());
if ( $live_ids === false ) {
// coping from table failed, because we have another coping process to same table, that wasn't finished
$event->status = kEvent::erFAIL;
return;
}
if ( $live_ids ) {
// ensure, that newly created item ids are available as if they were selected from grid
// NOTE: only works if main item has sub-items !!!
$this->StoreSelectedIDs($event, $live_ids);
}
/** @var kDBItem $object */
$object = $event->getObject();
- $this->SaveLoggedChanges($changes_var_name, $object->ShouldLogChanges());
+ /** @var ChangeLogHelper $change_log_helper */
+ $change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+ $change_log_helper->saveLoggedChanges($this->Prefix, $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
+ * Saves changes made in the temporary table to log.
*
- * @param string $changes_var_name
- * @param bool $save
- * @return void
- * @access public
+ * @param string $changes_var_name Changes var name.
+ * @param boolean $save Save changes.
+ *
+ * @return void
+ * @deprecated 5.2.2-B3
+ * @see ChangeLogHelper::saveLoggedChanges()
*/
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);
+ kUtil::deprecatedMethod(__METHOD__, '5.2.2-B3', 'ChangeLogHelper::saveLoggedChanges');
- $this->Application->incrementCacheSerial('session-log');
+ /** @var ChangeLogHelper $change_log_helper */
+ $change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+ $change_log_helper->saveLoggedChanges($this->Prefix, $save);
}
/**
* Cancels edit
* Removes all temp tables and clears selected ids
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCancelEdit(kEvent $event)
{
/** @var kTempTablesHandler $temp_handler */
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
$temp_handler->CancelEdit();
$this->clearSelectedIDs($event);
$this->Application->RemoveVar($event->getPrefixSpecial() . '_modified');
- $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
- $this->Application->RemoveVar($changes_var_name);
+ /** @var ChangeLogHelper $change_log_helper */
+ $change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+ $change_log_helper->forgetChanges($this->Prefix);
$event->SetRedirectParam('opener', 'u');
}
/**
* Allows to determine if we are creating new item or editing already created item
*
* @param kEvent $event
* @return bool
* @access public
*/
public function isNewItemCreate(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject( Array ('raise_warnings' => 0) );
return !$object->isLoaded();
}
/**
* Saves edited item into temp table
* If there is no id, new item is created in temp table
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSave(kEvent $event)
{
// if there is no id - it means we need to create an item
if ( is_object($event->MasterEvent) ) {
$event->MasterEvent->setEventParam('IsNew', false);
}
if ( $this->isNewItemCreate($event) ) {
$event->CallSubEvent('OnPreSaveCreated');
if ( is_object($event->MasterEvent) ) {
$event->MasterEvent->setEventParam('IsNew', true);
}
return ;
}
// don't just call OnUpdate event here, since it maybe overwritten to Front-End specific behavior
$this->_update($event);
}
/**
* Analog of OnPreSave event for usage in AJAX request
*
* @param kEvent $event
*
* @return void
*/
protected function OnPreSaveAjax(kEvent $event)
{
/** @var AjaxFormHelper $ajax_form_helper */
$ajax_form_helper = $this->Application->recallObject('AjaxFormHelper');
$ajax_form_helper->transitEvent($event, 'OnPreSave');
}
/**
* [HOOK] Saves sub-item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSaveSubItem(kEvent $event)
{
$not_created = $this->isNewItemCreate($event);
$event->CallSubEvent($not_created ? 'OnCreate' : 'OnUpdate');
if ( $event->status == kEvent::erSUCCESS ) {
/** @var kDBItem $object */
$object = $event->getObject();
$this->Application->SetVar($event->getPrefixSpecial() . '_id', $object->GetID());
}
else {
$event->MasterEvent->status = $event->status;
}
$event->SetRedirectParam('opener', 's');
}
/**
* Saves edited item in temp table and loads
* item with passed id in current template
* Used in Prev/Next buttons
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSaveAndGo(kEvent $event)
{
$event->CallSubEvent('OnPreSave');
if ( $event->status == kEvent::erSUCCESS ) {
$id = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoId');
$event->SetRedirectParam($event->getPrefixSpecial() . '_id', $id);
}
}
/**
* Saves edited item in temp table and goes
* to passed tabs, by redirecting to it with OnPreSave event
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSaveAndGoToTab(kEvent $event)
{
$event->CallSubEvent('OnPreSave');
if ( $event->status == kEvent::erSUCCESS ) {
$event->redirect = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoTab');
}
}
/**
* Saves editable list and goes to passed tab,
* by redirecting to it with empty event
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnUpdateAndGoToTab(kEvent $event)
{
$event->setPseudoClass('_List');
$event->CallSubEvent('OnUpdate');
if ( $event->status == kEvent::erSUCCESS ) {
$event->redirect = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoTab');
}
}
/**
* Prepare temp tables for creating new item
* but does not create it. Actual create is
* done in OnPreSaveCreated
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreCreate(kEvent $event)
{
$this->setTempWindowID($event);
$this->clearSelectedIDs($event);
$this->Application->SetVar('m_lang', $this->Application->GetDefaultLanguageId());
/** @var kDBItem $object */
$object = $event->getObject(Array ('skip_autoload' => true));
/** @var kTempTablesHandler $temp_handler */
$temp_handler = $this->Application->recallObject($event->Prefix . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
$temp_handler->PrepareEdit();
$object->setID(0);
$this->Application->SetVar($event->getPrefixSpecial() . '_id', 0);
$this->Application->SetVar($event->getPrefixSpecial() . '_PreCreate', 1);
- $changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
- $this->Application->RemoveVar($changes_var_name);
+ /** @var ChangeLogHelper $change_log_helper */
+ $change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+ $change_log_helper->forgetChanges($this->Prefix);
$event->redirect = false;
}
/**
* Creates a new item in temp table and
* stores item id in App vars and Session on success
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSaveCreated(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject( Array('skip_autoload' => true) );
$object->setID(0);
$field_values = $this->getSubmittedFields($event);
$object->SetFieldsFromHash($field_values);
$event->setEventParam('form_data', $field_values);
$this->customProcessing($event, 'before');
if ( $object->Create() ) {
$this->customProcessing($event, 'after');
$event->SetRedirectParam($event->getPrefixSpecial(true) . '_id', $object->GetID());
}
else {
$event->status = kEvent::erFAIL;
$event->redirect = false;
}
}
/**
* Reloads form to loose all changes made during item editing
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnReset(kEvent $event)
{
//do nothing - should reset :)
if ( $this->isNewItemCreate($event) ) {
// just reset id to 0 in case it was create
/** @var kDBItem $object */
$object = $event->getObject( Array ('skip_autoload' => true) );
$object->setID(0);
$this->Application->SetVar($event->getPrefixSpecial() . '_id', 0);
}
}
/**
* Apply same processing to each item being selected in grid
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function iterateItems(kEvent $event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return ;
}
/** @var kDBItem $object */
$object = $event->getObject(Array ('skip_autoload' => true));
$ids = $this->StoreSelectedIDs($event);
if ( $ids ) {
$status_field = $object->getStatusField();
$order_field = $this->Application->getUnitOption($event->Prefix, 'OrderField');
if ( !$order_field ) {
$order_field = 'Priority';
}
foreach ($ids as $id) {
$object->Load($id);
switch ( $event->Name ) {
case 'OnMassApprove':
$object->SetDBField($status_field, 1);
break;
case 'OnMassDecline':
$object->SetDBField($status_field, 0);
break;
case 'OnMassMoveUp':
$object->SetDBField($order_field, $object->GetDBField($order_field) + 1);
break;
case 'OnMassMoveDown':
$object->SetDBField($order_field, $object->GetDBField($order_field) - 1);
break;
}
$object->Update();
}
}
$this->clearSelectedIDs($event);
}
/**
* Clones selected items in list
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnMassClone(kEvent $event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return;
}
/** @var kTempTablesHandler $temp_handler */
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
$ids = $this->StoreSelectedIDs($event);
if ( $ids ) {
$temp_handler->CloneItems($event->Prefix, $event->Special, $ids);
}
$this->clearSelectedIDs($event);
}
/**
* Checks if given value is present in given array
*
* @param Array $records
* @param string $field
* @param mixed $value
* @return bool
* @access protected
*/
protected function check_array($records, $field, $value)
{
foreach ($records as $record) {
if ($record[$field] == $value) {
return true;
}
}
return false;
}
/**
* Saves data from editing form to database without checking required fields
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSavePopup(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject();
$this->RemoveRequiredFields($object);
$event->CallSubEvent('OnPreSave');
$event->SetRedirectParam('opener', 'u');
}
/* End of Edit events */
// III. Events that allow to put some code before and after Update,Load,Create and Delete methods of item
/**
* Occurs before loading item, 'id' parameter
* allows to get id of item being loaded
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemLoad(kEvent $event)
{
}
/**
* Occurs after loading item, 'id' parameter
* allows to get id of item that was loaded
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemLoad(kEvent $event)
{
}
/**
* Occurs before creating item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemCreate(kEvent $event)
{
}
/**
* Occurs after creating item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemCreate(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject();
if ( !$object->IsTempTable() ) {
$this->_processPendingActions($event);
}
}
/**
* Occurs before updating item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemUpdate(kEvent $event)
{
}
/**
* Occurs after updating item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemUpdate(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject();
if ( !$object->IsTempTable() ) {
$this->_processPendingActions($event);
}
}
/**
* Occurs before deleting item, id of item being
* deleted is stored as 'id' event param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemDelete(kEvent $event)
{
}
/**
* Occurs after deleting item, id of deleted item
* is stored as 'id' param of event
*
* Also deletes subscriptions to that particual item once it's deleted
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemDelete(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject();
// 1. delete direct subscriptions to item, that was deleted
$this->_deleteSubscriptions($event->Prefix, 'ItemId', $object->GetID());
/** @var Array $sub_items */
$sub_items = $this->Application->getUnitOption($event->Prefix, 'SubItems', Array ());
// 2. delete this item sub-items subscriptions, that reference item, that was deleted
foreach ($sub_items as $sub_prefix) {
$this->_deleteSubscriptions($sub_prefix, 'ParentItemId', $object->GetID());
}
}
/**
* Deletes all subscriptions, associated with given item
*
* @param string $prefix
* @param string $field
* @param int $value
* @return void
* @access protected
*/
protected function _deleteSubscriptions($prefix, $field, $value)
{
$sql = 'SELECT TemplateId
FROM ' . $this->Application->getUnitOption('email-template', 'TableName') . '
WHERE BindToSystemEvent REGEXP "' . $this->Conn->escape($prefix) . '(\\\\.[^:]*:.*|:.*)"';
$email_template_ids = $this->Conn->GetCol($sql);
if ( !$email_template_ids ) {
return;
}
// e-mail events, connected to that unit prefix are found
$sql = 'SELECT SubscriptionId
FROM ' . TABLE_PREFIX . 'SystemEventSubscriptions
WHERE ' . $field . ' = ' . $value . ' AND EmailTemplateId IN (' . implode(',', $email_template_ids) . ')';
$ids = $this->Conn->GetCol($sql);
if ( !$ids ) {
return;
}
/** @var kTempTablesHandler $temp_handler */
$temp_handler = $this->Application->recallObject('system-event-subscription_TempHandler', 'kTempTablesHandler');
$temp_handler->DeleteItems('system-event-subscription', '', $ids);
}
/**
* Occurs before validation attempt
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemValidate(kEvent $event)
{
}
/**
* Occurs after successful item validation
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemValidate(kEvent $event)
{
}
/**
* Occurs after an item has been copied to temp
* Id of copied item is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterCopyToTemp(kEvent $event)
{
}
/**
* Occurs before an item is deleted from live table when copying from temp
* (temp handler deleted all items from live and then copy over all items from temp)
* Id of item being deleted is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeDeleteFromLive(kEvent $event)
{
}
/**
* Occurs before an item is copied to live table (after all foreign keys have been updated)
* Id of item being copied is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeCopyToLive(kEvent $event)
{
}
/**
* Occurs after an item has been copied to live table
* Id of copied item is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterCopyToLive(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject(array('skip_autoload' => true));
$object->SwitchToLive();
$object->Load($event->getEventParam('id'));
$this->_processPendingActions($event);
}
/**
* Processing file pending actions (e.g. delete scheduled files)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function _processPendingActions(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject();
$update_required = false;
$temp_id = $event->getEventParam('temp_id');
$id = $temp_id !== false ? $temp_id : $object->GetID();
foreach ($object->getPendingActions($id) as $data) {
switch ( $data['action'] ) {
case 'delete':
unlink($data['file']);
break;
case 'make_live':
/** @var FileHelper $file_helper */
$file_helper = $this->Application->recallObject('FileHelper');
if ( !file_exists($data['file']) ) {
// File removal was requested too.
break;
}
$old_name = basename($data['file']);
$new_name = $file_helper->ensureUniqueFilename(dirname($data['file']), kUtil::removeTempExtension($old_name));
rename($data['file'], dirname($data['file']) . '/' . $new_name);
$db_value = $object->GetDBField($data['field']);
$object->SetDBField($data['field'], str_replace($old_name, $new_name, $db_value));
$update_required = true;
break;
default:
trigger_error('Unsupported pending action "' . $data['action'] . '" for "' . $event->getPrefixSpecial() . '" unit', E_USER_WARNING);
break;
}
}
// remove pending actions before updating to prevent recursion
$object->setPendingActions();
if ( $update_required ) {
$object->Update();
}
}
/**
* Occurs before an item has been cloned
* Id of newly created item is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeClone(kEvent $event)
{
}
/**
* Occurs after an item has been cloned
* Id of newly created item is passed as event' 'id' param
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterClone(kEvent $event)
{
}
/**
* Occurs after list is queried
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterListQuery(kEvent $event)
{
}
/**
* Ensures that popup will be closed automatically
* and parent window will be refreshed with template
* passed
*
* @param kEvent $event
* @return void
* @access protected
* @deprecated
*/
protected function finalizePopup(kEvent $event)
{
$event->SetRedirectParam('opener', 'u');
}
/**
* Create search filters based on search query
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSearch(kEvent $event)
{
$event->setPseudoClass('_List');
/** @var kSearchHelper $search_helper */
$search_helper = $this->Application->recallObject('SearchHelper');
$search_helper->performSearch($event);
}
/**
* Clear search keywords
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSearchReset(kEvent $event)
{
/** @var kSearchHelper $search_helper */
$search_helper = $this->Application->recallObject('SearchHelper');
$search_helper->resetSearch($event);
}
/**
* Set's new filter value (filter_id meaning from config)
*
* @param kEvent $event
* @return void
* @access protected
* @deprecated
*/
protected function OnSetFilter(kEvent $event)
{
$filter_id = $this->Application->GetVar('filter_id');
$filter_value = $this->Application->GetVar('filter_value');
$view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter');
$view_filter = $view_filter ? unserialize($view_filter) : Array ();
$view_filter[$filter_id] = $filter_value;
$this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter));
}
/**
* Sets view filter based on request
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSetFilterPattern(kEvent $event)
{
$filters = $this->Application->GetVar($event->getPrefixSpecial(true) . '_filters');
if ( !$filters ) {
return;
}
$view_filter = $this->Application->RecallVar($event->getPrefixSpecial() . '_view_filter');
$view_filter = $view_filter ? unserialize($view_filter) : Array ();
$filters = explode(',', $filters);
foreach ($filters as $a_filter) {
list($id, $value) = explode('=', $a_filter);
$view_filter[$id] = $value;
}
$this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter));
$event->redirect = false;
}
/**
* Add/Remove all filters applied to list from "View" menu
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function FilterAction(kEvent $event)
{
$view_filter = Array ();
$filter_menu = $this->Application->getUnitOption($event->Prefix, 'FilterMenu');
switch ($event->Name) {
case 'OnRemoveFilters':
$filter_value = 1;
break;
case 'OnApplyFilters':
$filter_value = 0;
break;
default:
$filter_value = 0;
break;
}
foreach ($filter_menu['Filters'] as $filter_key => $filter_params) {
if ( !$filter_params ) {
continue;
}
$view_filter[$filter_key] = $filter_value;
}
$this->Application->StoreVar($event->getPrefixSpecial() . '_view_filter', serialize($view_filter));
}
/**
* Enter description here...
*
* @param kEvent $event
* @access protected
*/
protected function OnPreSaveAndOpenTranslator(kEvent $event)
{
$this->Application->SetVar('allow_translation', true);
/** @var kDBItem $object */
$object = $event->getObject();
$this->RemoveRequiredFields($object);
$event->CallSubEvent('OnPreSave');
if ( $event->status == kEvent::erSUCCESS ) {
$resource_id = $this->Application->GetVar('translator_resource_id');
if ( $resource_id ) {
$t_prefixes = explode(',', $this->Application->GetVar('translator_prefixes'));
/** @var kDBItem $cdata */
$cdata = $this->Application->recallObject($t_prefixes[1], NULL, Array ('skip_autoload' => true));
$cdata->Load($resource_id, 'ResourceId');
if ( !$cdata->isLoaded() ) {
$cdata->SetDBField('ResourceId', $resource_id);
$cdata->Create();
}
$this->Application->SetVar($cdata->getPrefixSpecial() . '_id', $cdata->GetID());
}
$event->redirect = $this->Application->GetVar('translator_t');
$redirect_params = Array (
'pass' => 'all,trans,' . $this->Application->GetVar('translator_prefixes'),
'opener' => 's',
$event->getPrefixSpecial(true) . '_id' => $object->GetID(),
'trans_event' => 'OnLoad',
'trans_prefix' => $this->Application->GetVar('translator_prefixes'),
'trans_field' => $this->Application->GetVar('translator_field'),
'trans_multi_line' => $this->Application->GetVar('translator_multi_line'),
);
$event->setRedirectParams($redirect_params);
// 1. SAVE LAST TEMPLATE TO SESSION (really needed here, because of tweaky redirect)
$last_template = $this->Application->RecallVar('last_template');
preg_match('/index4\.php\|' . $this->Application->GetSID() . '-(.*):/U', $last_template, $rets);
$this->Application->StoreVar('return_template', $this->Application->GetVar('t'));
}
}
/**
* Makes all fields non-required
*
* @param kDBItem $object
* @return void
* @access protected
*/
protected function RemoveRequiredFields(&$object)
{
// making all field non-required to achieve successful presave
$fields = array_keys( $object->getFields() );
foreach ($fields as $field) {
if ( $object->isRequired($field) ) {
$object->setRequired($field, false);
}
}
}
/**
* Saves selected user in needed field
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSelectUser(kEvent $event)
{
/** @var kDBItem $object */
$object = $event->getObject();
$items_info = $this->Application->GetVar('u');
if ( $items_info ) {
$user_id = key($items_info);
$this->RemoveRequiredFields($object);
$is_new = !$object->isLoaded();
$is_main = substr($this->Application->GetVar($event->Prefix . '_mode'), 0, 1) == 't';
if ( $is_new ) {
$new_event = $is_main ? 'OnPreCreate' : 'OnNew';
$event->CallSubEvent($new_event);
$event->redirect = true;
}
$object->SetDBField($this->Application->RecallVar('dst_field'), $user_id);
if ( $is_new ) {
$object->Create();
}
else {
$object->Update();
}
}
$event->SetRedirectParam($event->getPrefixSpecial() . '_id', $object->GetID());
$event->SetRedirectParam('opener', 'u');
}
/** EXPORT RELATED **/
/**
* Shows export dialog
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnExport(kEvent $event)
{
$selected_ids = $this->StoreSelectedIDs($event);
if ( implode(',', $selected_ids) == '' ) {
// K4 fix when no ids found bad selected ids array is formed
$selected_ids = false;
}
$this->Application->StoreVar($event->Prefix . '_export_ids', $selected_ids ? implode(',', $selected_ids) : '');
$this->Application->LinkVar('export_finish_t');
$this->Application->LinkVar('export_progress_t');
$this->Application->StoreVar('export_special', $event->Special);
$this->Application->StoreVar('export_grid', $this->Application->GetVar('grid', 'Default'));
$redirect_params = Array (
$this->Prefix . '.export_event' => 'OnNew',
'pass' => 'all,' . $this->Prefix . '.export'
);
$event->setRedirectParams($redirect_params);
}
/**
* Apply some special processing to object being
* recalled before using it in other events that
* call prepareObject
*
* @param kDBItem|kDBList $object
* @param kEvent $event
* @return void
* @access protected
*/
protected function prepareObject(&$object, kEvent $event)
{
if ( $event->Special == 'export' || $event->Special == 'import' ) {
/** @var kCatDBItemExportHelper $export_helper */
$export_helper = $this->Application->recallObject('CatItemExportHelper');
$export_helper->prepareExportColumns($event);
}
}
/**
* Returns specific to each item type columns only
*
* @param kEvent $event
* @return Array
* @access public
*/
public function getCustomExportColumns(kEvent $event)
{
return Array ();
}
/**
* Export form validation & processing
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnExportBegin(kEvent $event)
{
/** @var kCatDBItemExportHelper $export_helper */
$export_helper = $this->Application->recallObject('CatItemExportHelper');
$export_helper->OnExportBegin($event);
}
/**
* Enter description here...
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnExportCancel(kEvent $event)
{
$this->OnGoBack($event);
}
/**
* Allows configuring export options
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeExportBegin(kEvent $event)
{
}
/**
* Deletes export preset
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnDeleteExportPreset(kEvent $event)
{
$delete_preset_name = $this->Application->GetVar('delete_preset_name');
if ( !$delete_preset_name ) {
return;
}
$where_clause = array(
'ItemPrefix = ' . $this->Conn->qstr($event->Prefix),
'PortalUserId = ' . $this->Application->RecallVar('user_id'),
'PresetName = ' . $this->Conn->qstr($delete_preset_name),
);
$sql = 'DELETE
FROM ' . TABLE_PREFIX . 'ExportUserPresets
WHERE (' . implode(') AND (', $where_clause) . ')';
$this->Conn->Query($sql);
}
/**
* Saves changes & changes language
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreSaveAndChangeLanguage(kEvent $event)
{
if ( $this->UseTempTables($event) ) {
$event->CallSubEvent('OnPreSave');
}
if ( $event->status == kEvent::erSUCCESS ) {
$this->Application->SetVar('m_lang', $this->Application->GetVar('language'));
$data = $this->Application->GetVar('st_id');
if ( $data ) {
$event->SetRedirectParam('st_id', $data);
}
}
}
/**
* Used to save files uploaded via Plupload
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnUploadFile(kEvent $event)
{
$event->status = kEvent::erSTOP;
/** @var kUploadHelper $upload_helper */
$upload_helper = $this->Application->recallObject('kUploadHelper');
try {
$filename = $upload_helper->handle($event);
$response = array(
'jsonrpc' => '2.0',
'status' => 'success',
'result' => $filename,
);
}
catch ( kUploaderException $e ) {
$response = array(
'jsonrpc' => '2.0',
'status' => 'error',
'error' => array('code' => $e->getCode(), 'message' => $e->getMessage()),
);
}
echo json_encode($response);
}
/**
* Remembers, that file should be deleted on item's save from temp table
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnDeleteFile(kEvent $event)
{
$event->status = kEvent::erSTOP;
$field_id = $this->Application->GetVar('field_id');
if ( !preg_match_all('/\[([^\[\]]*)\]/', $field_id, $regs) ) {
return;
}
$field = $regs[1][1];
$record_id = $regs[1][0];
/** @var kUploadHelper $upload_helper */
$upload_helper = $this->Application->recallObject('kUploadHelper');
$object = $upload_helper->prepareUploadedFile($event, $field);
if ( !$object->GetDBField($field) ) {
return;
}
$pending_actions = $object->getPendingActions($record_id);
$pending_actions[] = Array (
'action' => 'delete',
'id' => $record_id,
'field' => $field,
'file' => $object->GetField($field, 'full_path'),
);
$object->setPendingActions($pending_actions, $record_id);
}
/**
* Returns url for viewing uploaded file
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnViewFile(kEvent $event)
{
$event->status = kEvent::erSTOP;
$field = $this->Application->GetVar('field');
/** @var kUploadHelper $upload_helper */
$upload_helper = $this->Application->recallObject('kUploadHelper');
$object = $upload_helper->prepareUploadedFile($event, $field);
if ( !$object->GetDBField($field) ) {
return;
}
// get url to uploaded file
if ( $this->Application->GetVar('thumb') ) {
$url = $object->GetField($field, $object->GetFieldOption($field, 'thumb_format'));
}
else {
$url = $object->GetField($field, 'raw_url');
}
/** @var FileHelper $file_helper */
$file_helper = $this->Application->recallObject('FileHelper');
$path = $file_helper->urlToPath($url);
if ( !file_exists($path) ) {
exit;
}
header('Content-Length: ' . filesize($path));
$this->Application->setContentType(kUtil::mimeContentType($path), false);
header('Content-Disposition: inline; filename="' . kUtil::removeTempExtension($object->GetDBField($field)) . '"');
readfile($path);
}
/**
* Validates MInput control fields
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnValidateMInputFields(kEvent $event)
{
/** @var MInputHelper $minput_helper */
$minput_helper = $this->Application->recallObject('MInputHelper');
$minput_helper->OnValidateMInputFields($event);
}
/**
* Validates individual object field and returns the result
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnValidateField(kEvent $event)
{
$event->status = kEvent::erSTOP;
$field = $this->Application->GetVar('field');
if ( ($this->Application->GetVar('ajax') != 'yes') || !$field ) {
return;
}
/** @var kDBItem $object */
$object = $event->getObject(Array ('skip_autoload' => true));
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( !$items_info ) {
return;
}
$id = key($items_info);
$field_values = $items_info[$id];
$object->Load($id);
$object->SetFieldsFromHash($field_values);
$event->setEventParam('form_data', $field_values);
$object->setID($id);
$response = Array ('status' => 'OK');
$event->CallSubEvent($object->isLoaded() ? 'OnBeforeItemUpdate' : 'OnBeforeItemCreate');
// validate all fields, since "Password_plain" field sets error to "Password" field, which is passed here
$error_field = $object->GetFieldOption($field, 'error_field', false, $field);
if ( !$object->Validate() && $object->GetErrorPseudo($error_field) ) {
$response['status'] = $object->GetErrorMsg($error_field, false);
}
/** @var AjaxFormHelper $ajax_form_helper */
$ajax_form_helper = $this->Application->recallObject('AjaxFormHelper');
$response['other_errors'] = $ajax_form_helper->getErrorMessages($object);
$response['uploader_info'] = $ajax_form_helper->getUploaderInfo($object, array_keys($field_values));
$event->status = kEvent::erSTOP; // since event's OnBefore... events can change this event status
echo json_encode($response);
}
/**
* Returns auto-complete values for ajax-dropdown
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSuggestValues(kEvent $event)
{
$event->status = kEvent::erSTOP;
$this->Application->XMLHeader();
$data = $this->getAutoCompleteSuggestions($event, $this->Application->GetVar('cur_value'));
$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
echo '<suggestions>';
if ( kUtil::isAssoc($data) ) {
foreach ($data as $key => $title) {
echo '<item value="' . kUtil::escape($key, kUtil::ESCAPE_HTML) . '">' . kUtil::escape($title, kUtil::ESCAPE_HTML) . '</item>';
}
}
else {
foreach ($data as $title) {
echo '<item>' . kUtil::escape($title, kUtil::ESCAPE_HTML) . '</item>';
}
}
echo '</suggestions>';
}
/**
* Returns auto-complete values for jQueryUI.AutoComplete
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSuggestValuesJSON(kEvent $event)
{
$event->status = kEvent::erSTOP;
$data = $this->getAutoCompleteSuggestions($event, $this->Application->GetVar('term'));
if ( kUtil::isAssoc($data) ) {
$transformed_data = array();
foreach ($data as $key => $title) {
$transformed_data[] = array('value' => $key, 'label' => $title);
}
$data = $transformed_data;
}
echo json_encode($data);
}
/**
* Prepares a suggestion list based on a given term.
*
* @param kEvent $event Event.
* @param string $term Term.
*
* @return Array
* @access protected
*/
protected function getAutoCompleteSuggestions(kEvent $event, $term)
{
/** @var kDBItem $object */
$object = $event->getObject(array('skip_autoload' => true));
$field = $this->Application->GetVar('field');
if ( !$field || !$term || !$object->isField($field) ) {
return array();
}
$limit = $this->Application->GetVar('limit');
if ( !$limit ) {
$limit = 20;
}
$sql = 'SELECT DISTINCT ' . $field . '
FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
WHERE ' . $field . ' LIKE ' . $this->Conn->qstr($term . '%') . '
ORDER BY ' . $field . '
LIMIT 0,' . $limit;
return $this->Conn->GetCol($sql);
}
/**
* Enter description here...
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSaveWidths(kEvent $event)
{
$event->status = kEvent::erSTOP;
// $this->Application->setContentType('text/xml');
$picker_helper = new kColumnPickerHelper(
$event->getPrefixSpecial(),
$this->Application->GetVar('grid_name')
);
$picker_helper->saveWidths($this->Application->GetVar('widths'));
echo 'OK';
}
/**
* Called from CSV import script after item fields
* are set and validated, but before actual item create/update.
* If event status is kEvent::erSUCCESS, line will be imported,
* else it will not be imported but added to skipped lines
* and displayed in the end of import.
* Event status is preset from import script.
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeCSVLineImport(kEvent $event)
{
// abstract, for hooking
}
/**
* [HOOK] Allows to add cloned subitem to given prefix
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCloneSubItem(kEvent $event)
{
$clones = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'Clones');
$subitem_prefix = $event->Prefix . '-' . preg_replace('/^#/', '', $event->MasterEvent->Prefix);
$clones[$subitem_prefix] = Array ('ParentPrefix' => $event->Prefix);
$this->Application->setUnitOption($event->MasterEvent->Prefix, 'Clones', $clones);
}
/**
* Returns constrain for priority calculations
*
* @param kEvent $event
* @return void
* @see PriorityEventHandler
* @access protected
*/
protected function OnGetConstrainInfo(kEvent $event)
{
$event->setEventParam('constrain_info', Array ('', ''));
}
}
Index: branches/5.2.x/core/kernel/db/dbitem.php
===================================================================
--- branches/5.2.x/core/kernel/db/dbitem.php (revision 16823)
+++ branches/5.2.x/core/kernel/db/dbitem.php (revision 16824)
@@ -1,1634 +1,1510 @@
<?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!');
/**
* DBItem
*
*/
class kDBItem extends kDBBase {
/**
* Description
*
* @var array Associative array of current item' field values
* @access protected
*/
protected $FieldValues = Array ();
/**
* Unformatted field values, before parse
*
* @var Array
* @access protected
*/
protected $DirtyFieldValues = Array ();
/**
* Holds item values after loading (not affected by submit)
*
* @var Array
* @access protected
*/
protected $OriginalFieldValues = Array ();
/**
* If set to true, Update will skip Validation before running
*
* @var array Associative array of current item' field values
* @access public
*/
public $IgnoreValidation = false;
/**
* Remembers if object was loaded
*
* @var bool
* @access protected
*/
protected $Loaded = false;
/**
* Holds item' primary key value
*
* @var int Value of primary key field for current item
* @access protected
*/
protected $ID;
/**
* This object is used in cloning operations
*
* @var bool
* @access public
*/
public $inCloning = false;
/**
* Validator object reference
*
* @var kValidator
*/
protected $validator = null;
/**
* Creates validator object, only when required
*
*/
public function initValidator()
{
if ( !is_object($this->validator) ) {
$validator_class = $this->Application->getUnitOption($this->Prefix, 'ValidatorClass', 'kValidator');
$this->validator = $this->Application->makeClass($validator_class);
}
$this->validator->setDataSource($this);
}
public function SetDirtyField($field_name, $field_value)
{
$this->DirtyFieldValues[$field_name] = $field_value;
}
public function GetDirtyField($field_name)
{
return $this->DirtyFieldValues[$field_name];
}
public function GetOriginalField($field_name, $formatted = false, $format=null)
{
if (array_key_exists($field_name, $this->OriginalFieldValues)) {
// item was loaded before
$value = $this->OriginalFieldValues[$field_name];
}
else {
// no original fields -> use default field value
$value = $this->Fields[$field_name]['default'];
}
if (!$formatted) {
return $value;
}
$res = $value;
$formatter = $this->GetFieldOption($field_name, 'formatter');
if ( $formatter ) {
$formatter = $this->getFormatter($formatter);
if ( $formatter instanceof kMultiLanguage && strpos((string)$format, 'no_default') === false ) {
$format = rtrim('no_default;' . $format, ';');
}
$res = $formatter->Format($value, $field_name, $this, $format);
}
return $res;
}
/**
* Sets original field value (useful for custom virtual fields)
*
* @param string $field_name
* @param string $field_value
*/
public function SetOriginalField($field_name, $field_value)
{
$this->OriginalFieldValues[$field_name] = $field_value;
}
/**
* Set's default values for all fields
*
* @access public
*/
public function SetDefaultValues()
{
parent::SetDefaultValues();
if ($this->populateMultiLangFields) {
$this->PopulateMultiLangFields();
}
foreach ($this->Fields as $field => $field_options) {
$default_value = isset($field_options['default']) ? $field_options['default'] : NULL;
$this->SetDBField($field, $default_value);
}
}
/**
* Sets current item field value
* (applies formatting)
*
* @access public
* @param string $name Name of the field
* @param mixed $value Value to set the field to
* @return void
*/
public function SetField($name,$value)
{
$options = $this->GetFieldOptions($name);
$parsed = $value;
if ($value == '') {
$parsed = NULL;
}
/*
* kFormatter is always used, to make sure, that numeric value is converted to normal representation
* according to regional format, even when formatter is not set (try setting format to 1.234,56 to
* understand why).
*/
$parsed = $this
->getFormatter(isset($options['formatter']) ? $options['formatter'] : 'kFormatter')
->Parse($value, $name, $this);
$this->SetDBField($name,$parsed);
}
/**
* Sets current item field value
* (doesn't apply formatting)
*
* @access public
* @param string $name Name of the field
* @param mixed $value Value to set the field to
* @return void
*/
public function SetDBField($name,$value)
{
$this->FieldValues[$name] = $value;
}
/**
* Set's field error, if pseudo passed not found then create it with message text supplied.
* Don't overwrite existing pseudo translation.
*
* @param string $field
* @param string $pseudo
* @param string $error_label
* @param Array $error_params
*
* @return bool
* @access public
*/
public function SetError($field, $pseudo, $error_label = null, $error_params = null)
{
$this->initValidator();
return $this->validator->SetError($field, $pseudo, $error_label, $error_params);
}
/**
* Removes error on field
*
* @param string $field
* @access public
*/
public function RemoveError($field)
{
if ( !is_object($this->validator) ) {
return ;
}
$this->validator->RemoveError($field);
}
/**
* Returns error pseudo
*
* @param string $field
* @return string
*/
public function GetErrorPseudo($field)
{
if ( !is_object($this->validator) ) {
return '';
}
return $this->validator->GetErrorPseudo($field);
}
/**
* Return current item' field value by field name
* (doesn't apply formatter)
*
* @param string $name field name to return
* @return mixed
* @access public
*/
public function GetDBField($name)
{
/*if (!array_key_exists($name, $this->FieldValues) && defined('DEBUG_MODE') && DEBUG_MODE) {
$this->Application->Debugger->appendTrace();
}*/
return $this->FieldValues[$name];
}
public function HasField($name)
{
return array_key_exists($name, $this->FieldValues);
}
public function GetFieldValues()
{
return $this->FieldValues;
}
/**
* Sets item' fields corresponding to elements in passed $hash values.
* The function sets current item fields to values passed in $hash, by matching $hash keys with field names
* of current item. If current item' fields are unknown {@link kDBItem::PrepareFields()} is called before actually setting the fields
*
* @param Array $hash Fields hash.
* @param Array $set_fields Optional param, field names in target object to set, other fields will be skipped
*
* @return void
*/
public function SetFieldsFromHash($hash, $set_fields = Array ())
{
if ( !$set_fields ) {
$set_fields = array_keys($hash);
}
$skip_fields = $this->getRequestProtectedFields($hash);
if ( $skip_fields ) {
$set_fields = array_diff($set_fields, $skip_fields);
}
$set_fields = array_intersect($set_fields, array_keys($this->Fields));
// used in formatter which work with multiple fields together
foreach ($set_fields as $field_name) {
$this->SetDirtyField($field_name, $hash[$field_name]);
}
// formats all fields using associated formatters
foreach ($set_fields as $field_name) {
$this->SetField($field_name, $hash[$field_name]);
}
}
/**
* Returns fields, that are not allowed to be changed from request.
*
* @param array $fields_hash Fields hash.
*
* @return array
*/
protected function getRequestProtectedFields(array $fields_hash)
{
// don't allow changing ID
$fields = Array ();
$fields[] = $this->Application->getUnitOption($this->Prefix, 'IDField');
$parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix');
if ( $parent_prefix && $this->isLoaded() && !$this->Application->isAdmin ) {
// don't allow changing foreign key of existing item from request
$foreign_key = $this->Application->getUnitOption($this->Prefix, 'ForeignKey');
$fields[] = is_array($foreign_key) ? $foreign_key[$parent_prefix] : $foreign_key;
}
return $fields;
}
/**
* Sets object fields from $hash array
* @param Array $hash
* @param Array|null $set_fields
* @return void
* @access public
*/
public function SetDBFieldsFromHash($hash, $set_fields = Array ())
{
if ( !$set_fields ) {
$set_fields = array_keys($hash);
}
$set_fields = array_intersect($set_fields, array_keys($this->Fields));
foreach ($set_fields as $field_name) {
$this->SetDBField($field_name, $hash[$field_name]);
}
}
/**
* Returns part of SQL WHERE clause identifying the record, ex. id = 25
*
* @param string $method Child class may want to know who called GetKeyClause, Load(), Update(), Delete() send its names as method
* @param Array $keys_hash alternative, then item id, keys hash to load item by
* @see kDBItem::Load()
* @see kDBItem::Update()
* @see kDBItem::Delete()
* @return string
* @access protected
*/
protected function GetKeyClause($method = null, $keys_hash = null)
{
if ( !isset($keys_hash) ) {
$keys_hash = Array ($this->IDField => $this->ID);
}
$ret = '';
foreach ($keys_hash as $field => $value) {
$value_part = is_null($value) ? ' IS NULL' : ' = ' . $this->Conn->qstr($value);
$ret .= '(' . (strpos($field, '.') === false ? '`' . $this->TableName . '`.' : '') . $field . $value_part . ') AND ';
}
return substr($ret, 0, -5);
}
/**
* Loads item from the database by given id
*
* @access public
* @param mixed $id item id of keys->values hash to load item by
* @param string $id_field_name Optional parameter to load item by given Id field
* @param bool $cachable cache this query result based on it's prefix serial
* @return bool True if item has been loaded, false otherwise
*/
public function Load($id, $id_field_name = null, $cachable = false)
{
$this->Clear();
if ( isset($id_field_name) ) {
$this->IDField = $id_field_name; // set new IDField
}
$keys_sql = '';
if (is_array($id)) {
$keys_sql = $this->GetKeyClause('load', $id);
}
else {
$this->setID($id);
$keys_sql = $this->GetKeyClause('load');
}
if ( isset($id_field_name) ) {
// restore original IDField from unit config
$this->IDField = $this->Application->getUnitOption($this->Prefix, 'IDField');
}
if (($id === false) || !$keys_sql) {
return false;
}
if (!$this->raiseEvent('OnBeforeItemLoad', $id)) {
return false;
}
$q = $this->GetSelectSQL() . ' WHERE ' . $keys_sql;
if ($cachable && $this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
$serial_name = $this->Application->incrementCacheSerial($this->Prefix == 'st' ? 'c' : $this->Prefix, isset($id_field_name) ? null : $id, false);
$cache_key = 'kDBItem::Load_' . crc32(serialize($id) . '-' . $this->IDField) . '[%' . $serial_name . '%]';
$field_values = $this->Application->getCache($cache_key, false);
if ($field_values === false) {
$field_values = $this->Conn->GetRow($q);
if ($field_values !== false) {
// only cache, when data was retrieved
$this->Application->setCache($cache_key, $field_values);
}
}
}
else {
$field_values = $this->Conn->GetRow($q);
}
if ($field_values) {
$this->FieldValues = array_merge($this->FieldValues, $field_values);
$this->OriginalFieldValues = $this->FieldValues;
$this->Loaded = true;
}
else {
return false;
}
if (is_array($id) || isset($id_field_name)) {
$this->setID($this->FieldValues[$this->IDField]);
}
$this->UpdateFormattersSubFields(); // used for updating separate virtual date/time fields from DB timestamp (for example)
$this->raiseEvent('OnAfterItemLoad', $this->GetID());
return true;
}
/**
* Loads object from hash (not db)
*
* @param Array $fields_hash
* @param string $id_field
*/
public function LoadFromHash($fields_hash, $id_field = null)
{
if (!isset($id_field)) {
$id_field = $this->IDField;
}
$this->Clear();
if (!$fields_hash || !array_key_exists($id_field, $fields_hash)) {
// no data OR id field missing
return false;
}
$id = $fields_hash[$id_field];
if ( !$this->raiseEvent('OnBeforeItemLoad', $id) ) {
return false;
}
$this->FieldValues = array_merge($this->FieldValues, $fields_hash);
$this->OriginalFieldValues = $this->FieldValues;
$this->setID($id);
$this->UpdateFormattersSubFields(); // used for updating separate virtual date/time fields from DB timestamp (for example)
$this->raiseEvent('OnAfterItemLoad', $id);
$this->Loaded = true;
return true;
}
/**
* Builds select sql, SELECT ... FROM parts only
*
* @access public
* @return string
*/
/**
* Returns SELECT part of list' query
*
* @param string $base_query
* @param bool $replace_table
* @return string
* @access public
*/
public function GetSelectSQL($base_query = null, $replace_table = true)
{
if (!isset($base_query)) {
$base_query = $this->SelectClause;
}
$base_query = $this->addCalculatedFields($base_query);
return parent::GetSelectSQL($base_query, $replace_table);
}
public function UpdateFormattersMasterFields()
{
$this->initValidator(); // used, when called not from kValidator::Validate method
foreach ($this->Fields as $field => $options) {
if ( isset($options['formatter']) ) {
$this
->getFormatter($options['formatter'])
->UpdateMasterFields($field, $this->GetDBField($field), $options, $this);
}
}
}
/**
* Returns variable name, used to store pending file actions
*
* @return string
* @access protected
*/
protected function _getPendingActionVariableName()
{
$window_id = $this->Application->GetTopmostWid($this->Prefix);
return $this->Prefix . '_file_pending_actions' . $window_id;
}
/**
* Returns pending actions
*
* @param mixed $id
* @return Array
* @access public
*/
public function getPendingActions($id = null)
{
if ( !isset($id) ) {
$id = $this->GetID();
}
$pending_actions = $this->Application->RecallVar($this->_getPendingActionVariableName());
$pending_actions = $pending_actions ? unserialize($pending_actions) : Array ();
if ( is_numeric($id) ) {
// filter by given/current id
$ret = Array ();
foreach ($pending_actions as $pending_action) {
if ( $pending_action['id'] == $id ) {
$ret[] = $pending_action;
}
}
return $ret;
}
return $pending_actions;
}
/**
* Sets new pending actions
*
* @param Array|null $new_pending_actions
* @param mixed $id
* @return void
* @access public
*/
public function setPendingActions($new_pending_actions = null, $id = null)
{
if ( !isset($new_pending_actions) ) {
$new_pending_actions = Array ();
}
if ( !isset($id) ) {
$id = $this->GetID();
}
$pending_actions = Array ();
$old_pending_actions = $this->getPendingActions(true);
if ( is_numeric($id) ) {
// remove old actions for this id
foreach ($old_pending_actions as $pending_action) {
if ( $pending_action['id'] != $id ) {
$pending_actions[] = $pending_action;
}
}
// add new actions for this id
$pending_actions = array_merge($pending_actions, $new_pending_actions);
}
else {
$pending_actions = $new_pending_actions;
}
// save changes
$var_name = $this->_getPendingActionVariableName();
if ( !$pending_actions ) {
$this->Application->RemoveVar($var_name);
}
else {
$this->Application->StoreVar($var_name, serialize($this->sortPendingActions($pending_actions)));
}
}
/**
* Sorts pending actions the way, that `delete` action will come before other actions.
*
* @param array $pending_actions Pending actions.
*
* @return array
*/
protected function sortPendingActions(array $pending_actions)
{
usort($pending_actions, array($this, 'comparePendingActions'));
return $pending_actions;
}
protected function comparePendingActions($pending_action_a, $pending_action_b)
{
if ( $pending_action_a['action'] == $pending_action_b['action'] ) {
return 0;
}
return $pending_action_a['action'] == 'delete' ? -1 : 1;
}
/**
* Allows to skip certain fields from getting into sql queries
*
* @param string $field_name
* @param mixed $force_id
* @return bool
*/
public function skipField($field_name, $force_id = false)
{
$skip = false;
// 1. skipping 'virtual' field
$skip = $skip || array_key_exists($field_name, $this->VirtualFields);
// 2. don't write empty field value to db, when "skip_empty" option is set
$field_value = array_key_exists($field_name, $this->FieldValues) ? $this->FieldValues[$field_name] : false;
if (array_key_exists($field_name, $this->Fields)) {
$skip_empty = array_key_exists('skip_empty', $this->Fields[$field_name]) ? $this->Fields[$field_name]['skip_empty'] : false;
}
else {
// field found in database, but not declared in unit config
$skip_empty = false;
}
$skip = $skip || (!$field_value && $skip_empty);
// 3. skipping field not in Fields (nor virtual, nor real)
$skip = $skip || !array_key_exists($field_name, $this->Fields);
return $skip;
}
/**
* Updates previously loaded record with current item' values
*
* @access public
* @param int $id Primary Key Id to update
* @param Array $update_fields
* @param bool $system_update
* @return bool
* @access public
*/
public function Update($id = null, $update_fields = null, $system_update = false)
{
if ( isset($id) ) {
$this->setID($id);
}
if ( !$this->raiseEvent('OnBeforeItemUpdate') ) {
return false;
}
if ( !isset($this->ID) ) {
// ID could be set inside OnBeforeItemUpdate event, so don't combine this check with previous one
return false;
}
// validate before updating
if ( !$this->Validate() ) {
return false;
}
if ( !$this->FieldValues ) {
// nothing to update
return true;
}
$sql = '';
if ( isset($update_fields) ) {
$set_fields = $update_fields;
$forgotten_fields = array_diff(array_keys($this->GetChangedFields()), $set_fields);
if ( $forgotten_fields ) {
trigger_error(
sprintf(
'These fields weren\'t updated for #%s record in "%s" unit: %s',
$this->GetID(),
$this->getPrefixSpecial(),
implode(', ', $forgotten_fields)
),
E_USER_WARNING
);
}
}
else {
$set_fields = array_keys($this->FieldValues);
}
foreach ($set_fields as $field_name) {
if ( $this->skipField($field_name) ) {
continue;
}
$field_value = $this->FieldValues[$field_name];
if ( is_null($field_value) ) {
if ( array_key_exists('not_null', $this->Fields[$field_name]) && $this->Fields[$field_name]['not_null'] ) {
// "kFormatter::Parse" methods converts empty values to NULL and for
// not-null fields they are replaced with default value here
$field_value = $this->Fields[$field_name]['default'];
}
}
$sql .= '`' . $field_name . '` = ' . $this->Conn->qstr($field_value) . ', ';
}
$sql = 'UPDATE ' . $this->TableName . '
SET ' . substr($sql, 0, -2) . '
WHERE ' . $this->GetKeyClause('update');
if ( $this->Conn->ChangeQuery($sql) === false ) {
// there was and sql error
$this->SetError($this->IDField, 'sql_error', '#' . $this->Conn->getErrorCode() . ': ' . $this->Conn->getErrorMsg());
return false;
}
$affected_rows = $this->Conn->getAffectedRows();
if ( !$system_update && ($affected_rows > 0) ) {
$this->setModifiedFlag(ChangeLog::UPDATE, $update_fields);
}
$this->saveCustomFields();
$this->raiseEvent('OnAfterItemUpdate');
// Preserve OriginalFieldValues during recursive Update() method calls.
$this->Loaded = true;
if ( !$this->IsTempTable() ) {
$this->Application->resetCounters($this->TableName);
}
return true;
}
/**
* Validates given field
*
* @param string $field
* @return bool
* @access public
*/
public function ValidateField($field)
{
$this->initValidator();
return $this->validator->ValidateField($field);
}
/**
* Validate all item fields based on
* constraints set in each field options
* in config
*
* @return bool
* @access private
*/
public function Validate()
{
if ( $this->IgnoreValidation ) {
return true;
}
$this->initValidator();
// will apply any custom validation to the item
$this->raiseEvent('OnBeforeItemValidate');
if ( $this->validator->Validate() ) {
// no validation errors
$this->raiseEvent('OnAfterItemValidate');
return true;
}
return false;
}
/**
* Check if item has errors
*
* @param Array $skip_fields fields to skip during error checking
* @return bool
*/
public function HasErrors($skip_fields = Array ())
{
if ( !is_object($this->validator) ) {
return false;
}
return $this->validator->HasErrors($skip_fields);
}
/**
* Check if value is set for required field
*
* @param string $field field name
* @param Array $params field options from config
* @return bool
* @access public
* @todo Find a way to get rid of direct call from kMultiLanguage::UpdateMasterFields method
*/
public function ValidateRequired($field, $params)
{
return $this->validator->ValidateRequired($field, $params);
}
/**
* Return error message for field
*
* @param string $field
* @param bool $force_escape
* @return string
* @access public
*/
public function GetErrorMsg($field, $force_escape = null)
{
if ( !is_object($this->validator) ) {
return '';
}
return $this->validator->GetErrorMsg($field, $force_escape);
}
/**
* Returns field errors
*
* @return Array
* @access public
*/
public function GetFieldErrors()
{
if ( !is_object($this->validator) ) {
return Array ();
}
return $this->validator->GetFieldErrors();
}
/**
* Creates a record in the database table with current item' values
*
* @param mixed $force_id Set to TRUE to force creating of item's own ID or to value to force creating of passed id. Do not pass 1 for true, pass exactly TRUE!
* @param bool $system_create
* @return bool
* @access public
*/
public function Create($force_id = false, $system_create = false)
{
if (!$this->raiseEvent('OnBeforeItemCreate')) {
return false;
}
// Validating fields before attempting to create record
if (!$this->Validate()) {
return false;
}
if (is_int($force_id)) {
$this->FieldValues[$this->IDField] = $force_id;
}
elseif (!$force_id || !is_bool($force_id)) {
$this->FieldValues[$this->IDField] = $this->generateID();
}
$fields_sql = '';
$values_sql = '';
foreach ($this->FieldValues as $field_name => $field_value) {
if ($this->skipField($field_name, $force_id)) {
continue;
}
if (is_null($field_value)) {
if (array_key_exists('not_null', $this->Fields[$field_name]) && $this->Fields[$field_name]['not_null']) {
// "kFormatter::Parse" methods converts empty values to NULL and for
// not-null fields they are replaced with default value here
$values_sql .= $this->Conn->qstr($this->Fields[$field_name]['default']);
}
else {
$values_sql .= $this->Conn->qstr($field_value);
}
}
else {
if (($field_name == $this->IDField) && ($field_value == 0) && !is_int($force_id)) {
// don't skip IDField in INSERT statement, just use DEFAULT keyword as it's value
$values_sql .= 'DEFAULT';
}
else {
$values_sql .= $this->Conn->qstr($field_value);
}
}
$fields_sql .= '`' . $field_name . '`, '; //Adding field name to fields block of Insert statement
$values_sql .= ', ';
}
$sql = 'INSERT INTO ' . $this->TableName . ' (' . substr($fields_sql, 0, -2) . ')
VALUES (' . substr($values_sql, 0, -2) . ')';
//Executing the query and checking the result
if ($this->Conn->ChangeQuery($sql) === false) {
$this->SetError($this->IDField, 'sql_error', '#' . $this->Conn->getErrorCode() . ': ' . $this->Conn->getErrorMsg());
return false;
}
$insert_id = $this->Conn->getInsertID();
if ($insert_id == 0) {
// insert into temp table (id is not auto-increment field)
$insert_id = $this->FieldValues[$this->IDField];
}
$temp_id = $this->GetID();
$this->setID($insert_id);
$this->OriginalFieldValues = $this->FieldValues;
if (!$system_create){
$this->setModifiedFlag(ChangeLog::CREATE);
}
$this->saveCustomFields();
if (!$this->IsTempTable()) {
$this->Application->resetCounters($this->TableName);
}
if ($this->IsTempTable() && ($this->Application->GetTopmostPrefix($this->Prefix) != $this->Prefix) && !is_int($force_id)) {
// temp table + subitem = set negative id
$this->setTempID();
}
$this->raiseEvent('OnAfterItemCreate', null, array('temp_id' => $temp_id));
$this->Loaded = true;
return true;
}
/**
* Deletes the record from database
*
* @param int $id
* @return bool
* @access public
*/
public function Delete($id = null)
{
if ( isset($id) ) {
$this->setID($id);
}
if ( !$this->raiseEvent('OnBeforeItemDelete') ) {
return false;
}
$sql = 'DELETE FROM ' . $this->TableName . '
WHERE ' . $this->GetKeyClause('Delete');
$ret = $this->Conn->ChangeQuery($sql);
$affected_rows = $this->Conn->getAffectedRows();
if ( $affected_rows > 0 ) {
$this->setModifiedFlag(ChangeLog::DELETE); // will change affected rows, so get it before this line
// something was actually deleted
$this->raiseEvent('OnAfterItemDelete');
}
if ( !$this->IsTempTable() ) {
$this->Application->resetCounters($this->TableName);
}
return $ret;
}
public function PopulateMultiLangFields()
{
foreach ($this->Fields as $field => $options) {
// master field is set only for CURRENT language
$formatter = array_key_exists('formatter', $options) ? $options['formatter'] : false;
if ( ($formatter == 'kMultiLanguage') && isset($options['master_field']) && isset($options['error_field']) ) {
// MuliLanguage formatter sets error_field to master_field, but in PopulateMlFields mode,
// we display ML fields directly so we set it back to itself, otherwise error won't be displayed
unset( $this->Fields[$field]['error_field'] );
}
}
}
/**
* Sets new name for item in case if it is being copied in same table
*
* @param array $master Table data from TempHandler
* @param int $foreign_key ForeignKey value to filter name check query by
* @param string $title_field FieldName to alter, by default - TitleField of the prefix
* @param string $format sprintf-style format of renaming pattern, by default Copy %1$s of %2$s which makes it Copy [Number] of Original Name
* @access public
*/
public function NameCopy($master=null, $foreign_key=null, $title_field=null, $format='Copy %1$s of %2$s')
{
if (!isset($title_field)) {
$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
if (!$title_field || isset($this->CalculatedFields[$title_field]) ) return;
}
$new_name = $this->GetDBField($title_field);
$original_checked = false;
do {
if ( preg_match('/'.sprintf($format, '([0-9]*) *', '(.*)').'/', $new_name, $regs) ) {
$new_name = sprintf($format, ($regs[1]+1), $regs[2]);
}
elseif ($original_checked) {
$new_name = sprintf($format, '', $new_name);
}
// if we are cloning in temp table this will look for names in temp table,
// since object' TableName contains correct TableName (for temp also!)
// if we are cloning live - look in live
$query = 'SELECT '.$title_field.' FROM '.$this->TableName.'
WHERE '.$title_field.' = '.$this->Conn->qstr($new_name);
$foreign_key_field = getArrayValue($master, 'ForeignKey');
$foreign_key_field = is_array($foreign_key_field) ? $foreign_key_field[ $master['ParentPrefix'] ] : $foreign_key_field;
if ($foreign_key_field && isset($foreign_key)) {
$query .= ' AND '.$foreign_key_field.' = '.$foreign_key;
}
$res = $this->Conn->GetOne($query);
/*// if not found in live table, check in temp table if applicable
if ($res === false && $object->Special == 'temp') {
$query = 'SELECT '.$name_field.' FROM '.$this->GetTempName($master['TableName']).'
WHERE '.$name_field.' = '.$this->Conn->qstr($new_name);
$res = $this->Conn->GetOne($query);
}*/
$original_checked = true;
} while ($res !== false);
$this->SetDBField($title_field, $new_name);
}
protected function raiseEvent($name, $id = null, $additional_params = Array())
{
$additional_params['id'] = isset($id) ? $id : $this->GetID();
$event = new kEvent($this->getPrefixSpecial() . ':' . $name, $additional_params);
if ( is_object($this->parentEvent) ) {
$event->MasterEvent = $this->parentEvent;
}
$this->Application->HandleEvent($event);
return $event->status == kEvent::erSUCCESS;
}
/**
* Set's new ID for item
*
* @param int $new_id
* @access public
*/
public function setID($new_id)
{
$this->ID = $new_id;
$this->SetDBField($this->IDField, $new_id);
}
/**
* Generate and set new temporary id
*
* @access private
*/
public function setTempID()
{
$new_id = (int)$this->Conn->GetOne('SELECT MIN('.$this->IDField.') FROM '.$this->TableName);
if($new_id > 0) $new_id = 0;
--$new_id;
$this->Conn->Query('UPDATE '.$this->TableName.' SET `'.$this->IDField.'` = '.$new_id.' WHERE `'.$this->IDField.'` = '.$this->GetID());
- if ($this->ShouldLogChanges(true)) {
- // Updating TempId in ChangesLog, if changes are disabled
- $ses_var_name = $this->Application->GetTopmostPrefix($this->Prefix) . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
- $changes = $this->Application->RecallVar($ses_var_name);
- $changes = $changes ? unserialize($changes) : Array ();
-
- if ($changes) {
- foreach ($changes as $key => $rec) {
- if ($rec['Prefix'] == $this->Prefix && $rec['ItemId'] == $this->GetID()) {
- // change log for record, that's ID was just updated -> update in change log record too
- $changes[$key]['ItemId'] = $new_id;
- }
-
- if ($rec['MasterPrefix'] == $this->Prefix && $rec['MasterId'] == $this->GetID()) {
- // master item id was changed
- $changes[$key]['MasterId'] = $new_id;
- }
-
- if (in_array($this->Prefix, $rec['ParentPrefix']) && $rec['ParentId'][$this->Prefix] == $this->GetID()) {
- // change log record of given item's sub item -> update changed id's in dependent fields
- $changes[$key]['ParentId'][$this->Prefix] = $new_id;
-
- if (array_key_exists('DependentFields', $rec)) {
- // these are fields from table of $rec['Prefix'] table!
- // when one of dependent fields goes into idfield of it's parent item, that was changed
- $parent_table_key = $this->Application->getUnitOption($rec['Prefix'], 'ParentTableKey');
- $parent_table_key = is_array($parent_table_key) ? $parent_table_key[$this->Prefix] : $parent_table_key;
-
- if ($parent_table_key == $this->IDField) {
- $foreign_key = $this->Application->getUnitOption($rec['Prefix'], 'ForeignKey');
- $foreign_key = is_array($foreign_key) ? $foreign_key[$this->Prefix] : $foreign_key;
-
- $changes[$key]['DependentFields'][$foreign_key] = $new_id;
- }
- }
- }
- }
- }
-
- $this->Application->StoreVar($ses_var_name, serialize($changes));
+ if ( $this->ShouldLogChanges(true) ) {
+ /** @var ChangeLogHelper $change_log_helper */
+ $change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+ $change_log_helper->updateForeignKeys(
+ array('Prefix' => $this->Prefix, 'IdField' => $this->IDField),
+ $new_id,
+ $this->GetID(),
+ $this->Application->GetTopmostPrefix($this->Prefix)
+ );
}
$old_id = $this->GetID();
$this->SetID($new_id);
$pending_actions = $this->getPendingActions($old_id);
foreach ( array_keys($pending_actions) as $key ) {
$pending_actions[$key]['id'] = $new_id;
}
$this->setPendingActions($pending_actions, $new_id);
}
/**
* Set's modification flag for main prefix of current prefix to true
*
* @param integer $mode Mode.
* @param array $update_fields Update fields.
*
* @return void
*/
public function setModifiedFlag($mode = null, array $update_fields = null)
{
$main_prefix = $this->Application->GetTopmostPrefix($this->Prefix);
$this->Application->StoreVar($main_prefix . '_modified', '1', true); // true for optional
if ( $this->ShouldLogChanges(true) ) {
- $this->LogChanges($main_prefix, $mode, $update_fields);
-
- if (!$this->IsTempTable()) {
- /** @var kDBEventHandler $handler */
- $handler = $this->Application->recallObject($this->Prefix . '_EventHandler');
+ /** @var ChangeLogHelper $change_log_helper */
+ $change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+ $change_log_helper->logChanges($this, $mode, $update_fields);
- $ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
- $handler->SaveLoggedChanges($ses_var_name, $this->ShouldLogChanges());
+ if ( !$this->IsTempTable() ) {
+ $change_log_helper->saveLoggedChanges($this->Prefix, $this->ShouldLogChanges());
}
}
}
/**
* Determines, that changes made to this item should be written to change log
*
* @param bool $log_changes
* @return bool
*/
public function ShouldLogChanges($log_changes = null)
{
if (!isset($log_changes)) {
// specific logging mode no forced -> use global logging settings
$log_changes = $this->Application->getUnitOption($this->Prefix, 'LogChanges') || $this->Application->ConfigValue('UseChangeLog');
}
return $log_changes && !$this->Application->getUnitOption($this->Prefix, 'ForceDontLogChanges');
}
- protected function LogChanges($main_prefix, $mode, $update_fields = null)
- {
- if ( !$mode ) {
- return ;
- }
-
- $ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
- $changes = $this->Application->RecallVar($ses_var_name);
- $changes = $changes ? unserialize($changes) : Array ();
-
- $fields_hash = Array (
- 'Prefix' => $this->Prefix,
- 'ItemId' => $this->GetID(),
- 'OccuredOn' => adodb_mktime(),
- 'MasterPrefix' => $main_prefix,
- 'Action' => $mode,
- );
-
- if ( $this->Prefix == $main_prefix ) {
- // main item
- $fields_hash['MasterId'] = $this->GetID();
- $fields_hash['ParentPrefix'] = Array ($main_prefix);
- $fields_hash['ParentId'] = Array ($main_prefix => $this->GetID());
- }
- else {
- // sub item
- // collect foreign key values (for serial reset)
- $foreign_keys = $this->Application->getUnitOption($this->Prefix, 'ForeignKey', Array ());
- $dependent_fields = $fields_hash['ParentId'] = $fields_hash['ParentPrefix'] = Array ();
- /** @var Array $foreign_keys */
-
- if ( is_array($foreign_keys) ) {
- foreach ($foreign_keys as $prefix => $field_name) {
- $dependent_fields[$field_name] = $this->GetDBField($field_name);
- $fields_hash['ParentPrefix'][] = $prefix;
- $fields_hash['ParentId'][$prefix] = $this->getParentId($prefix);
- }
- }
- else {
- $dependent_fields[$foreign_keys] = $this->GetDBField($foreign_keys);
- $fields_hash['ParentPrefix'] = Array ( $this->Application->getUnitOption($this->Prefix, 'ParentPrefix') );
- $fields_hash['ParentId'][ $fields_hash['ParentPrefix'][0] ] = $this->getParentId('auto');
- }
-
- $fields_hash['DependentFields'] = $dependent_fields;
-
-
- // works only, when main item is present in url, when sub-item is changed
- $master_id = $this->Application->GetVar($main_prefix . '_id');
-
- if ( $master_id === false ) {
- // works in case of we are not editing topmost item, when sub-item is created/updated/deleted
- $master_id = $this->getParentId('auto', true);
- }
-
- $fields_hash['MasterId'] = $master_id;
- }
-
- switch ( $mode ) {
- case ChangeLog::UPDATE:
- $changed_fields = $this->GetChangedFields();
-
- if ( $update_fields ) {
- $changed_fields = array_intersect_key(
- $changed_fields,
- array_combine($update_fields, $update_fields)
- );
- }
-
- $to_save = array_merge($this->GetTitleField(), $changed_fields);
- break;
-
- case ChangeLog::CREATE:
- $to_save = $this->GetTitleField();
- break;
-
- case ChangeLog::DELETE:
- $to_save = array_merge($this->GetTitleField(), $this->GetRealFields());
- break;
-
- default:
- $to_save = Array ();
- break;
- }
-
- $fields_hash['Changes'] = serialize($to_save);
- $changes[] = $fields_hash;
-
- $this->Application->StoreVar($ses_var_name, serialize($changes));
- }
-
/**
* Returns current item parent's ID
*
* @param string $parent_prefix
* @param bool $top_most return topmost parent, when used
* @return int
* @access public
*/
public function getParentId($parent_prefix, $top_most = false)
{
$current_id = $this->GetID();
$current_prefix = $this->Prefix;
if ($parent_prefix == 'auto') {
$parent_prefix = $this->Application->getUnitOption($current_prefix, 'ParentPrefix');
}
if (!$parent_prefix) {
return $current_id;
}
do {
// field in this table
$foreign_key = $this->Application->getUnitOption($current_prefix, 'ForeignKey');
$foreign_key = is_array($foreign_key) ? $foreign_key[$parent_prefix] : $foreign_key;
// get foreign key value for $current_prefix
if ($current_prefix == $this->Prefix) {
$foreign_key_value = $this->GetDBField($foreign_key);
}
else {
$id_field = $this->Application->getUnitOption($current_prefix, 'IDField');
$table_name = $this->Application->getUnitOption($current_prefix, 'TableName');
if ($this->IsTempTable()) {
$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $current_prefix);
}
$sql = 'SELECT ' . $foreign_key . '
FROM ' . $table_name . '
WHERE ' . $id_field . ' = ' . $current_id;
$foreign_key_value = $this->Conn->GetOne($sql);
}
// field in parent table
$parent_table_key = $this->Application->getUnitOption($current_prefix, 'ParentTableKey');
$parent_table_key = is_array($parent_table_key) ? $parent_table_key[$parent_prefix] : $parent_table_key;
$parent_id_field = $this->Application->getUnitOption($parent_prefix, 'IDField');
$parent_table_name = $this->Application->getUnitOption($parent_prefix, 'TableName');
if ($this->IsTempTable()) {
$parent_table_name = $this->Application->GetTempName($parent_table_name, 'prefix:' . $current_prefix);
}
if ($parent_id_field == $parent_table_key) {
// sub-item is related by parent item idfield
$current_id = $foreign_key_value;
}
else {
// sub-item is related by other parent item field
$sql = 'SELECT ' . $parent_id_field . '
FROM ' . $parent_table_name . '
WHERE ' . $parent_table_key . ' = ' . $foreign_key_value;
$current_id = $this->Conn->GetOne($sql);
}
$current_prefix = $parent_prefix;
if (!$top_most) {
break;
}
} while ( $parent_prefix = $this->Application->getUnitOption($current_prefix, 'ParentPrefix') );
return $current_id;
}
/**
* Returns title field (if any)
*
* @return Array
*/
public function GetTitleField()
{
$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
if ($title_field) {
$value = $this->GetField($title_field);
return $value ? Array ($title_field => $value) : Array ();
}
return Array ();
}
/**
* Returns only fields, that are present in database (no virtual and no calculated fields)
*
* @return Array
*/
public function GetRealFields()
{
return array_diff_key($this->FieldValues, $this->VirtualFields, $this->CalculatedFields);
}
/**
* Returns only changed database field
*
* @param bool $include_virtual_fields
* @return Array
*/
public function GetChangedFields($include_virtual_fields = false)
{
$changes = Array ();
$fields = $include_virtual_fields ? $this->FieldValues : $this->GetRealFields();
$diff = array_diff_assoc($fields, $this->OriginalFieldValues);
foreach ($diff as $field => $new_value) {
$old_value = $this->GetOriginalField($field, true);
$new_value = $this->GetField($field);
if ($old_value != $new_value) {
// "0.00" and "0.0000" are stored as strings and will differ. Double check to prevent that.
$changes[$field] = Array ('old' => $old_value, 'new' => $new_value);
}
}
return $changes;
}
/**
* Returns ID of currently processed record
*
* @return int
* @access public
*/
public function GetID()
{
return $this->ID;
}
/**
* Generates ID for new items before inserting into database
*
* @return int
* @access private
*/
protected function generateID()
{
return 0;
}
/**
* Returns true if item was loaded successfully by Load method
*
* @return bool
*/
public function isLoaded()
{
return $this->Loaded;
}
/**
* Checks if field is required
*
* @param string $field
* @return bool
*/
public function isRequired($field)
{
return isset($this->Fields[$field]['required']) && $this->Fields[$field]['required'];
}
/**
* Sets new required flag to field
*
* @param mixed $fields
* @param bool $is_required
*/
public function setRequired($fields, $is_required = true)
{
if ( !is_array($fields) ) {
$fields = explode(',', $fields);
}
foreach ($fields as $field) {
$this->Fields[$field]['required'] = $is_required;
}
}
/**
* Removes all data from an object
*
* @param int $new_id
* @return bool
* @access public
*/
public function Clear($new_id = null)
{
$this->Loaded = false;
$this->FieldValues = $this->OriginalFieldValues = Array ();
$this->SetDefaultValues(); // will wear off kDBItem::setID effect, so set it later
if ( is_object($this->validator) ) {
$this->validator->reset();
}
$this->setID($new_id);
return $this->Loaded;
}
public function Query($force = false)
{
throw new Exception('<b>Query</b> method is called in class <strong>' . get_class($this) . '</strong> for prefix <strong>' . $this->getPrefixSpecial() . '</strong>');
}
protected function saveCustomFields()
{
if ( !$this->customFields || $this->inCloning ) {
return true;
}
$cdata_key = rtrim($this->Prefix . '-cdata.' . $this->Special, '.');
/** @var kDBItem $cdata */
$cdata = $this->Application->recallObject($cdata_key, null, Array ('skip_autoload' => true));
$resource_id = $this->GetDBField('ResourceId');
$cdata->Load($resource_id, 'ResourceId');
$cdata->SetDBField('ResourceId', $resource_id);
/** @var kMultiLanguage $ml_formatter */
$ml_formatter = $this->getFormatter('kMultiLanguage');
/** @var kMultiLanguageHelper $ml_helper */
$ml_helper = $this->Application->recallObject('kMultiLanguageHelper');
$languages = $ml_helper->getLanguages();
foreach ($this->customFields as $custom_id => $custom_name) {
$force_primary = $cdata->GetFieldOption('cust_' . $custom_id, 'force_primary');
if ( $force_primary ) {
$cdata->SetDBField($ml_formatter->LangFieldName('cust_' . $custom_id, true), $this->GetDBField('cust_' . $custom_name));
}
else {
foreach ($languages as $language_id) {
$cdata->SetDBField('l' . $language_id . '_cust_' . $custom_id, $this->GetDBField('l' . $language_id . '_cust_' . $custom_name));
}
}
}
return $cdata->isLoaded() ? $cdata->Update() : $cdata->Create();
}
/**
* Returns specified field value from all selected rows.
* Don't affect current record index
*
* @param string $field
* @param bool $formatted
* @param string $format
* @return Array
*/
public function GetCol($field, $formatted = false, $format = null)
{
if ($formatted) {
return Array (0 => $this->GetField($field, $format));
}
return Array (0 => $this->GetDBField($field));
}
/**
* Set's loaded status of object
*
* @param bool $is_loaded
* @access public
* @todo remove this method, since item can't be marked as loaded externally
*/
public function setLoaded($is_loaded = true)
{
$this->Loaded = $is_loaded;
}
/**
* Returns item's first status field
*
* @return string
* @access public
*/
public function getStatusField()
{
$status_fields = $this->Application->getUnitOption($this->Prefix, 'StatusField');
return array_shift($status_fields);
}
}
Index: branches/5.2.x/core/kernel/utility/temp_handler.php
===================================================================
--- branches/5.2.x/core/kernel/utility/temp_handler.php (revision 16823)
+++ branches/5.2.x/core/kernel/utility/temp_handler.php (revision 16824)
@@ -1,1139 +1,1110 @@
<?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 kTempTablesHandler extends kBase {
var $Tables = Array();
/**
* Master table name for temp handler
*
* @var string
* @access private
*/
var $MasterTable = '';
/**
* IDs from master table
*
* @var Array
* @access private
*/
var $MasterIDs = Array();
var $AlreadyProcessed = Array();
var $DroppedTables = Array();
var $FinalRefs = Array();
var $TableIdCounter = 0;
var $CopiedTables = Array();
/**
* Foreign key cache
*
* @var Array
*/
var $FKeysCache = Array ();
/**
* IDs of newly cloned items (key - prefix.special, value - array of ids)
*
* @var Array
*/
var $savedIDs = Array();
/**
* Window ID of current window
*
* @var mixed
*/
var $WindowID = '';
/**
* Event, that was used to create this object
*
* @var kEvent
* @access protected
*/
protected $parentEvent = null;
/**
+ * Change Log helper.
+ *
+ * @var $changeLogHelper
+ */
+ protected $changeLogHelper;
+
+ /**
+ * Creates class instance.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+
+ $this->changeLogHelper = $this->Application->recallObject('ChangeLogHelper');
+ }
+
+ /**
* Sets new parent event to the object
*
* @param kEvent $event
* @return void
* @access public
*/
public function setParentEvent($event)
{
$this->parentEvent = $event;
}
function SetTables($tables)
{
// set table name as key for tables array
$this->Tables = $tables;
$this->MasterTable = $tables['TableName'];
}
function saveID($prefix, $special = '', $id = null)
{
if (!isset($this->savedIDs[$prefix.($special ? '.' : '').$special])) {
$this->savedIDs[$prefix.($special ? '.' : '').$special] = array();
}
if (is_array($id)) {
foreach ($id as $tmp_id => $live_id) {
$this->savedIDs[$prefix.($special ? '.' : '').$special][$tmp_id] = $live_id;
}
}
else {
$this->savedIDs[$prefix.($special ? '.' : '').$special][] = $id;
}
}
/**
* Get temp table name
*
* @param string $table
* @return string
*/
function GetTempName($table)
{
return $this->Application->GetTempName($table, $this->WindowID);
}
function GetTempTablePrefix()
{
return $this->Application->GetTempTablePrefix($this->WindowID);
}
/**
* Return live table name based on temp table name
*
* @param string $temp_table
* @return string
*/
function GetLiveName($temp_table)
{
return $this->Application->GetLiveName($temp_table);
}
function IsTempTable($table)
{
return $this->Application->IsTempTable($table);
}
/**
* Return temporary table name for master table
*
* @return string
* @access public
*/
function GetMasterTempName()
{
return $this->GetTempName($this->MasterTable);
}
function CreateTempTable($table)
{
$sql = 'CREATE TABLE ' . $this->GetTempName($table) . ' ENGINE = ' . $this->getTableEngine($table) . '
SELECT *
FROM ' . $table . '
WHERE 0';
$this->Conn->Query($sql);
}
/**
* Returns an engine of a given table.
*
* @param string $table Table.
*
* @return string
*/
protected function getTableEngine($table)
{
static $cache;
if ( $cache === null ) {
$sql = 'SHOW TABLE STATUS WHERE `Name` LIKE ' . $this->Conn->qstr(TABLE_PREFIX . '%');
$table_statuses = $this->Conn->GetIterator($sql, 'Name');
$cache = array();
foreach ( $table_statuses as $table_status_name => $table_status_data ) {
$cache[$table_status_name] = $table_status_data['Engine'];
}
}
return isset($cache[$table]) ? $cache[$table] : null;
}
function BuildTables($prefix, $ids)
{
$this->WindowID = $this->Application->GetVar('m_wid');
$this->TableIdCounter = 0;
$tables = Array(
'TableName' => $this->Application->getUnitOption($prefix, 'TableName'),
'IdField' => $this->Application->getUnitOption($prefix, 'IDField'),
'IDs' => $ids,
'Prefix' => $prefix,
'TableId' => $this->TableIdCounter++,
);
/*$parent_prefix = $this->Application->getUnitOption($prefix, 'ParentPrefix');
if ($parent_prefix) {
$tables['ForeignKey'] = $this->Application->getUnitOption($prefix, 'ForeignKey');
$tables['ParentPrefix'] = $parent_prefix;
$tables['ParentTableKey'] = $this->Application->getUnitOption($prefix, 'ParentTableKey');
}*/
$this->FinalRefs[ $tables['TableName'] ] = $tables['TableId']; // don't forget to add main table to FinalRefs too
/** @var Array $sub_items */
$sub_items = $this->Application->getUnitOption($prefix, 'SubItems', Array ());
if ( is_array($sub_items) ) {
foreach ($sub_items as $prefix) {
$this->AddTables($prefix, $tables);
}
}
$this->SetTables($tables);
}
/**
* Searches through TempHandler tables info for required prefix
*
* @param string $prefix
* @param Array $master
* @return mixed
*/
function SearchTable($prefix, $master = null)
{
if (is_null($master)) {
$master = $this->Tables;
}
if ($master['Prefix'] == $prefix) {
return $master;
}
if (isset($master['SubTables'])) {
foreach ($master['SubTables'] as $sub_table) {
$found = $this->SearchTable($prefix, $sub_table);
if ($found !== false) {
return $found;
}
}
}
return false;
}
function AddTables($prefix, &$tables)
{
if ( !$this->Application->prefixRegistred($prefix) ) {
// allows to skip subitem processing if subitem module not enabled/installed
return ;
}
$tmp = Array(
'TableName' => $this->Application->getUnitOption($prefix,'TableName'),
'IdField' => $this->Application->getUnitOption($prefix,'IDField'),
'ForeignKey' => $this->Application->getUnitOption($prefix,'ForeignKey'),
'ParentPrefix' => $this->Application->getUnitOption($prefix, 'ParentPrefix'),
'ParentTableKey' => $this->Application->getUnitOption($prefix,'ParentTableKey'),
'Prefix' => $prefix,
'AutoClone' => $this->Application->getUnitOption($prefix,'AutoClone'),
'AutoDelete' => $this->Application->getUnitOption($prefix,'AutoDelete'),
'TableId' => $this->TableIdCounter++,
);
$this->FinalRefs[ $tmp['TableName'] ] = $tmp['TableId'];
$constrain = $this->Application->getUnitOption($prefix, 'Constrain');
if ( $constrain ) {
$tmp['Constrain'] = $constrain;
$this->FinalRefs[ $tmp['TableName'] . $tmp['Constrain'] ] = $tmp['TableId'];
}
/** @var Array $sub_items */
$sub_items = $this->Application->getUnitOption($prefix, 'SubItems', Array ());
if ( is_array($sub_items) ) {
foreach ($sub_items as $prefix) {
$this->AddTables($prefix, $tmp);
}
}
if ( !is_array(getArrayValue($tables, 'SubTables')) ) {
$tables['SubTables'] = Array ();
}
$tables['SubTables'][] = $tmp;
}
function CloneItems($prefix, $special, $ids, $master = null, $foreign_key = null, $parent_prefix = null, $skip_filenames = false)
{
if (!isset($master)) $master = $this->Tables;
// recalling by different name, because we may get kDBList, if we recall just by prefix
if (!preg_match('/(.*)-item$/', $special)) {
$special .= '-item';
}
/** @var kCatDBItem $object */
$object = $this->Application->recallObject($prefix.'.'.$special, $prefix, Array('skip_autoload' => true, 'parent_event' => $this->parentEvent));
$object->PopulateMultiLangFields();
foreach ($ids as $id) {
$mode = 'create';
$cloned_ids = getArrayValue($this->AlreadyProcessed, $master['TableName']);
if ( $cloned_ids ) {
// if we have already cloned the id, replace it with cloned id and set mode to update
// update mode is needed to update second ForeignKey for items cloned by first ForeignKey
if ( getArrayValue($cloned_ids, $id) ) {
$id = $cloned_ids[$id];
$mode = 'update';
}
}
$object->Load($id);
$original_values = $object->GetFieldValues();
if (!$skip_filenames) {
$object->NameCopy($master, $foreign_key);
}
elseif ($master['TableName'] == $this->MasterTable) {
// kCatDBItem class only has this attribute
$object->useFilenames = false;
}
if (isset($foreign_key)) {
$master_foreign_key_field = is_array($master['ForeignKey']) ? $master['ForeignKey'][$parent_prefix] : $master['ForeignKey'];
$object->SetDBField($master_foreign_key_field, $foreign_key);
}
if ($mode == 'create') {
$this->RaiseEvent('OnBeforeClone', $master['Prefix'], $special, Array($object->GetId()), $foreign_key);
}
$object->inCloning = true;
$res = $mode == 'update' ? $object->Update() : $object->Create();
$object->inCloning = false;
if ($res)
{
if ( $mode == 'create' && is_array( getArrayValue($master, 'ForeignKey')) ) {
// remember original => clone mapping for dual ForeignKey updating
$this->AlreadyProcessed[$master['TableName']][$id] = $object->GetId();
}
if ($mode == 'create') {
$this->RaiseEvent('OnAfterClone', $master['Prefix'], $special, Array($object->GetId()), $foreign_key, array('original_id' => $id) );
$this->saveID($master['Prefix'], $special, $object->GetID());
}
if ( is_array(getArrayValue($master, 'SubTables')) ) {
foreach($master['SubTables'] as $sub_table) {
if (!getArrayValue($sub_table, 'AutoClone')) continue;
$sub_TableName = $object->IsTempTable() ? $this->GetTempName($sub_table['TableName']) : $sub_table['TableName'];
$foreign_key_field = is_array($sub_table['ForeignKey']) ? $sub_table['ForeignKey'][$master['Prefix']] : $sub_table['ForeignKey'];
$parent_key_field = is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$master['Prefix']] : $sub_table['ParentTableKey'];
if (!$foreign_key_field || !$parent_key_field) continue;
$query = 'SELECT '.$sub_table['IdField'].' FROM '.$sub_TableName.'
WHERE '.$foreign_key_field.' = '.$original_values[$parent_key_field];
if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain'];
$sub_ids = $this->Conn->GetCol($query);
if ( is_array(getArrayValue($sub_table, 'ForeignKey')) ) {
// $sub_ids could containt newly cloned items, we need to remove it here
// to escape double cloning
$cloned_ids = getArrayValue($this->AlreadyProcessed, $sub_table['TableName']);
if ( !$cloned_ids ) $cloned_ids = Array();
$new_ids = array_values($cloned_ids);
$sub_ids = array_diff($sub_ids, $new_ids);
}
$parent_key = $object->GetDBField($parent_key_field);
$this->CloneItems($sub_table['Prefix'], $special, $sub_ids, $sub_table, $parent_key, $master['Prefix']);
}
}
}
}
if (!$ids) {
$this->savedIDs[$prefix.($special ? '.' : '').$special] = Array();
}
return $this->savedIDs[$prefix.($special ? '.' : '').$special];
}
function DeleteItems($prefix, $special, $ids, $master=null, $foreign_key=null)
{
if ( !$ids ) {
return;
}
if ( !isset($master) ) {
$master = $this->Tables;
}
if ( strpos($prefix, '.') !== false ) {
list($prefix, $special) = explode('.', $prefix, 2);
}
$prefix_special = rtrim($prefix . '.' . $special, '.');
//recalling by different name, because we may get kDBList, if we recall just by prefix
$recall_prefix = $prefix_special . ($special ? '' : '.') . '-item';
/** @var kDBItem $object */
$object = $this->Application->recallObject($recall_prefix, $prefix, Array ('skip_autoload' => true, 'parent_event' => $this->parentEvent));
foreach ($ids as $id) {
$object->Load($id);
$original_values = $object->GetFieldValues();
if ( !$object->Delete($id) ) {
continue;
}
if ( is_array(getArrayValue($master, 'SubTables')) ) {
foreach ($master['SubTables'] as $sub_table) {
if ( !getArrayValue($sub_table, 'AutoDelete') ) {
continue;
}
$sub_TableName = $object->IsTempTable() ? $this->GetTempName($sub_table['TableName']) : $sub_table['TableName'];
$foreign_key_field = is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey'];
$parent_key_field = is_array($sub_table['ParentTableKey']) ? getArrayValue($sub_table, 'ParentTableKey', $master['Prefix']) : $sub_table['ParentTableKey'];
if ( !$foreign_key_field || !$parent_key_field ) {
continue;
}
$sql = 'SELECT ' . $sub_table['IdField'] . '
FROM ' . $sub_TableName . '
WHERE ' . $foreign_key_field . ' = ' . $original_values[$parent_key_field];
$sub_ids = $this->Conn->GetCol($sql);
$parent_key = $object->GetDBField(is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$prefix] : $sub_table['ParentTableKey']);
$this->DeleteItems($sub_table['Prefix'], $special, $sub_ids, $sub_table, $parent_key);
}
}
}
}
function DoCopyLiveToTemp($master, $ids, $parent_prefix=null)
{
// when two tables refers the same table as sub-sub-table, and ForeignKey and ParentTableKey are arrays
// the table will be first copied by first sub-table, then dropped and copied over by last ForeignKey in the array
// this should not do any problems :)
if ( !preg_match("/.*\.[0-9]+/", $master['Prefix']) ) {
if( $this->DropTempTable($master['TableName']) )
{
$this->CreateTempTable($master['TableName']);
}
}
if (is_array($ids)) {
$ids = join(',', $ids);
}
$table_sig = $master['TableName'].(isset($master['Constrain']) ? $master['Constrain'] : '');
if ($ids != '' && !in_array($table_sig, $this->CopiedTables)) {
if ( getArrayValue($master, 'ForeignKey') ) {
if ( is_array($master['ForeignKey']) ) {
$key_field = $master['ForeignKey'][$parent_prefix];
}
else {
$key_field = $master['ForeignKey'];
}
}
else {
$key_field = $master['IdField'];
}
$query = 'INSERT INTO '.$this->GetTempName($master['TableName']).'
SELECT * FROM '.$master['TableName'].'
WHERE '.$key_field.' IN ('.$ids.')';
if (isset($master['Constrain'])) $query .= ' AND '.$master['Constrain'];
$this->Conn->Query($query);
$this->CopiedTables[] = $table_sig;
$query = 'SELECT '.$master['IdField'].' FROM '.$master['TableName'].'
WHERE '.$key_field.' IN ('.$ids.')';
if (isset($master['Constrain'])) $query .= ' AND '.$master['Constrain'];
$this->RaiseEvent( 'OnAfterCopyToTemp', $master['Prefix'], '', $this->Conn->GetCol($query) );
}
if ( getArrayValue($master, 'SubTables') ) {
foreach ($master['SubTables'] as $sub_table) {
$parent_key = is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$master['Prefix']] : $sub_table['ParentTableKey'];
if (!$parent_key) continue;
if ( $ids != '' && $parent_key != $key_field ) {
$query = 'SELECT '.$parent_key.' FROM '.$master['TableName'].'
WHERE '.$key_field.' IN ('.$ids.')';
$sub_foreign_keys = join(',', $this->Conn->GetCol($query));
}
else {
$sub_foreign_keys = $ids;
}
$this->DoCopyLiveToTemp($sub_table, $sub_foreign_keys, $master['Prefix']);
}
}
}
function GetForeignKeys($master, $sub_table, $live_id, $temp_id=null)
{
$mode = 1; //multi
if (!is_array($live_id)) {
$live_id = Array($live_id);
$mode = 2; //single
}
if (isset($temp_id) && !is_array($temp_id)) $temp_id = Array($temp_id);
if ( isset($sub_table['ParentTableKey']) ) {
if ( is_array($sub_table['ParentTableKey']) ) {
$parent_key_field = $sub_table['ParentTableKey'][$master['Prefix']];
}
else {
$parent_key_field = $sub_table['ParentTableKey'];
}
}
else {
$parent_key_field = $master['IdField'];
}
$cached = getArrayValue($this->FKeysCache, $master['TableName'].'.'.$parent_key_field);
if ( $cached ) {
if ( array_key_exists(serialize($live_id), $cached) ) {
list($live_foreign_key, $temp_foreign_key) = $cached[serialize($live_id)];
if ($mode == 1) {
return $live_foreign_key;
}
else {
return Array($live_foreign_key[0], $temp_foreign_key[0]);
}
}
}
if ($parent_key_field != $master['IdField']) {
$query = 'SELECT '.$parent_key_field.' FROM '.$master['TableName'].'
WHERE '.$master['IdField'].' IN ('.join(',', $live_id).')';
$live_foreign_key = $this->Conn->GetCol($query);
if (isset($temp_id)) {
// because DoCopyTempToOriginal resets negative IDs to 0 in temp table (one by one) before copying to live
$temp_key = $temp_id < 0 ? 0 : $temp_id;
$query = 'SELECT '.$parent_key_field.' FROM '.$this->GetTempName($master['TableName']).'
WHERE '.$master['IdField'].' IN ('.join(',', $temp_key).')';
$temp_foreign_key = $this->Conn->GetCol($query);
}
else {
$temp_foreign_key = Array();
}
}
else {
$live_foreign_key = $live_id;
$temp_foreign_key = $temp_id;
}
$this->FKeysCache[$master['TableName'].'.'.$parent_key_field][serialize($live_id)] = Array($live_foreign_key, $temp_foreign_key);
if ($mode == 1) {
return $live_foreign_key;
}
else {
return Array($live_foreign_key[0], $temp_foreign_key[0]);
}
}
/**
* Copies data from temp to live table and returns IDs of copied records
*
* @param Array $master
* @param string $parent_prefix
* @param Array $current_ids
* @return Array
* @access public
*/
public function DoCopyTempToOriginal($master, $parent_prefix = null, $current_ids = Array())
{
$current_ids = $this->getMainIDs($master, $current_ids);
$table_sig = $master['TableName'] . (isset($master['Constrain']) ? $master['Constrain'] : '');
if ($current_ids) {
// delete all ids from live table - for MasterTable ONLY!
// because items from Sub Tables get deteleted in CopySubTablesToLive !BY ForeignKey!
if ( $master['TableName'] == $this->MasterTable ) {
$this->RaiseEvent('OnBeforeDeleteFromLive', $master['Prefix'], '', $current_ids);
$query = 'DELETE FROM ' . $master['TableName'] . ' WHERE ' . $master['IdField'] . ' IN (' . join(',', $current_ids) . ')';
$this->Conn->Query($query);
}
if ( getArrayValue($master, 'SubTables') ) {
if ( in_array($table_sig, $this->CopiedTables) || $this->FinalRefs[$table_sig] != $master['TableId'] ) {
return Array ();
}
foreach ($current_ids AS $id) {
$this->RaiseEvent('OnBeforeCopyToLive', $master['Prefix'], '', Array ($id));
//reset negative ids to 0, so autoincrement in live table works fine
if ( $id < 0 ) {
$query = ' UPDATE ' . $this->GetTempName($master['TableName']) . '
SET ' . $master['IdField'] . ' = 0
WHERE ' . $master['IdField'] . ' = ' . $id;
if ( isset($master['Constrain']) ) {
$query .= ' AND ' . $master['Constrain'];
}
$this->Conn->Query($query);
$id_to_copy = 0;
}
else {
$id_to_copy = $id;
}
//copy current id_to_copy (0 for new or real id) to live table
$query = ' INSERT INTO ' . $master['TableName'] . '
SELECT * FROM ' . $this->GetTempName($master['TableName']) . '
WHERE ' . $master['IdField'] . ' = ' . $id_to_copy;
$this->Conn->Query($query);
$insert_id = $id_to_copy == 0 ? $this->Conn->getInsertID() : $id_to_copy;
$this->saveID($master['Prefix'], '', array ($id => $insert_id));
$this->RaiseEvent('OnAfterCopyToLive', $master['Prefix'], '', Array ($insert_id), null, Array ('temp_id' => $id));
$this->UpdateForeignKeys($master, $insert_id, $id);
//delete already copied record from master temp table
$query = ' DELETE FROM ' . $this->GetTempName($master['TableName']) . '
WHERE ' . $master['IdField'] . ' = ' . $id_to_copy;
if ( isset($master['Constrain']) ) {
$query .= ' AND ' . $master['Constrain'];
}
$this->Conn->Query($query);
}
$this->CopiedTables[] = $table_sig;
// when all of ids in current master has been processed, copy all sub-tables data
$this->CopySubTablesToLive($master, $current_ids);
}
elseif ( !in_array($table_sig, $this->CopiedTables) && ($this->FinalRefs[$table_sig] == $master['TableId']) ) { //If current master doesn't have sub-tables - we could use mass operations
// We don't need to delete items from live here, as it get deleted in the beginning of the method for MasterTable
// or in parent table processing for sub-tables
$live_ids = Array ();
$this->RaiseEvent('OnBeforeCopyToLive', $master['Prefix'], '', $current_ids);
foreach ($current_ids as $an_id) {
if ( $an_id > 0 ) {
$live_ids[$an_id] = $an_id;
// positive (already live) IDs will be copied in on query all togather below,
// so we just store it here
continue;
}
else { // zero or negative ids should be copied one by one to get their InsertId
// resetting to 0 so it get inserted into live table with autoincrement
$query = ' UPDATE ' . $this->GetTempName($master['TableName']) . '
SET ' . $master['IdField'] . ' = 0
WHERE ' . $master['IdField'] . ' = ' . $an_id;
// constrain is not needed here because ID is already unique
$this->Conn->Query($query);
// copying
$query = ' INSERT INTO ' . $master['TableName'] . '
SELECT * FROM ' . $this->GetTempName($master['TableName']) . '
WHERE ' . $master['IdField'] . ' = 0';
$this->Conn->Query($query);
$live_ids[$an_id] = $this->Conn->getInsertID(); //storing newly created live id
//delete already copied record from master temp table
$query = ' DELETE FROM ' . $this->GetTempName($master['TableName']) . '
WHERE ' . $master['IdField'] . ' = 0';
$this->Conn->Query($query);
- $this->UpdateChangeLogForeignKeys($master, $live_ids[$an_id], $an_id);
+ $this->changeLogHelper->updateForeignKeys($master, $live_ids[$an_id], $an_id, $this->Prefix);
}
}
// copy ALL records to live table
$query = ' INSERT INTO ' . $master['TableName'] . '
SELECT * FROM ' . $this->GetTempName($master['TableName']);
if ( isset($master['Constrain']) ) {
$query .= ' WHERE ' . $master['Constrain'];
}
$this->Conn->Query($query);
$this->CopiedTables[] = $table_sig;
$this->RaiseEvent('OnAfterCopyToLive', $master['Prefix'], '', $live_ids);
$this->saveID($master['Prefix'], '', $live_ids);
// no need to clear temp table - it will be dropped by next statement
}
}
if ( $this->FinalRefs[ $master['TableName'] ] != $master['TableId'] ) {
return Array ();
}
/*if ( is_array(getArrayValue($master, 'ForeignKey')) ) { //if multiple ForeignKeys
if ( $master['ForeignKey'][$parent_prefix] != end($master['ForeignKey']) ) {
return; // Do not delete temp table if not all ForeignKeys have been processed (current is not the last)
}
}*/
$this->DropTempTable($master['TableName']);
$this->Application->resetCounters($master['TableName']);
if ( !isset($this->savedIDs[ $master['Prefix'] ]) ) {
$this->savedIDs[ $master['Prefix'] ] = Array ();
}
return $this->savedIDs[ $master['Prefix'] ];
}
/**
* Returns IDs from main temp table.
*
* @param array $master Master table configuration.
* @param array $current_ids User provided IDs.
*
* @return array
*/
protected function getMainIDs(array $master, array $current_ids = array())
{
if ( $current_ids ) {
return $current_ids;
}
$sql = 'SELECT ' . $master['IdField'] . '
FROM ' . $this->GetTempName($master['TableName']);
if ( isset($master['Constrain']) ) {
$sql .= ' WHERE ' . $master['Constrain'];
}
return $this->Conn->GetCol($sql);
}
/**
* Create separate connection for locking purposes
*
* @return kDBConnection
*/
function &_getSeparateConnection()
{
static $connection = null;
if (!isset($connection)) {
/** @var kDBConnection $connection */
$connection = $this->Application->makeClass( 'kDBConnection', Array (SQL_TYPE, Array ($this->Application, 'handleSQLError')) );
$connection->debugMode = $this->Application->isDebugMode();
$connection->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB);
}
return $connection;
}
- function UpdateChangeLogForeignKeys($master, $live_id, $temp_id)
- {
- if ($live_id == $temp_id) {
- return ;
- }
-
- $prefix = $master['Prefix'];
- $main_prefix = $this->Application->GetTopmostPrefix($prefix);
- $ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
- $changes = $this->Application->RecallVar($ses_var_name);
- $changes = $changes ? unserialize($changes) : Array ();
-
- foreach ($changes as $key => $rec) {
- if ($rec['Prefix'] == $prefix && $rec['ItemId'] == $temp_id) {
- // main item change log record
- $changes[$key]['ItemId'] = $live_id;
- }
-
- if ($rec['MasterPrefix'] == $prefix && $rec['MasterId'] == $temp_id) {
- // sub item change log record
- $changes[$key]['MasterId'] = $live_id;
- }
-
- if (in_array($prefix, $rec['ParentPrefix']) && $rec['ParentId'][$prefix] == $temp_id) {
- // parent item change log record
- $changes[$key]['ParentId'][$prefix] = $live_id;
-
- if (array_key_exists('DependentFields', $rec)) {
- // these are fields from table of $rec['Prefix'] table!
- // when one of dependent fields goes into idfield of it's parent item, that was changed
- $parent_table_key = $this->Application->getUnitOption($rec['Prefix'], 'ParentTableKey');
- $parent_table_key = is_array($parent_table_key) ? $parent_table_key[$prefix] : $parent_table_key;
-
- if ($parent_table_key == $master['IdField']) {
- $foreign_key = $this->Application->getUnitOption($rec['Prefix'], 'ForeignKey');
- $foreign_key = is_array($foreign_key) ? $foreign_key[$prefix] : $foreign_key;
-
- $changes[$key]['DependentFields'][$foreign_key] = $live_id;
- }
- }
- }
- }
-
- $this->Application->StoreVar($ses_var_name, serialize($changes));
- }
-
function UpdateForeignKeys($master, $live_id, $temp_id)
{
- $this->UpdateChangeLogForeignKeys($master, $live_id, $temp_id);
+ $this->changeLogHelper->updateForeignKeys($master, $live_id, $temp_id, $this->Prefix);
foreach ($master['SubTables'] as $sub_table) {
$foreign_key_field = is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey'];
if (!$foreign_key_field) {
continue;
}
list ($live_foreign_key, $temp_foreign_key) = $this->GetForeignKeys($master, $sub_table, $live_id, $temp_id);
//Update ForeignKey in sub TEMP table
if ($live_foreign_key != $temp_foreign_key) {
$query = 'UPDATE '.$this->GetTempName($sub_table['TableName']).'
SET '.$foreign_key_field.' = '.$live_foreign_key.'
WHERE '.$foreign_key_field.' = '.$temp_foreign_key;
if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain'];
$this->Conn->Query($query);
}
}
}
function CopySubTablesToLive($master, $current_ids) {
foreach ($master['SubTables'] as $sub_table) {
$table_sig = $sub_table['TableName'].(isset($sub_table['Constrain']) ? $sub_table['Constrain'] : '');
// delete records from live table by foreign key, so that records deleted from temp table
// get deleted from live
if (count($current_ids) > 0 && !in_array($table_sig, $this->CopiedTables) ) {
$foreign_key_field = is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey'];
if (!$foreign_key_field) continue;
$foreign_keys = $this->GetForeignKeys($master, $sub_table, $current_ids);
if (count($foreign_keys) > 0) {
$query = 'SELECT '.$sub_table['IdField'].' FROM '.$sub_table['TableName'].'
WHERE '.$foreign_key_field.' IN ('.join(',', $foreign_keys).')';
if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain'];
if ( $this->RaiseEvent( 'OnBeforeDeleteFromLive', $sub_table['Prefix'], '', $this->Conn->GetCol($query), $foreign_keys ) ){
$query = 'DELETE FROM '.$sub_table['TableName'].'
WHERE '.$foreign_key_field.' IN ('.join(',', $foreign_keys).')';
if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain'];
$this->Conn->Query($query);
}
}
}
//sub_table passed here becomes master in the method, and recursively updated and copy its sub tables
$this->DoCopyTempToOriginal($sub_table, $master['Prefix']);
}
}
/**
* Raises event using IDs, that are currently being processed in temp handler
*
* @param string $name
* @param string $prefix
* @param string $special
* @param Array $ids
* @param string $foreign_key
* @param Array $add_params
* @return bool
* @access protected
*/
protected function RaiseEvent($name, $prefix, $special, $ids, $foreign_key = null, $add_params = null)
{
if ( !is_array($ids) ) {
return true;
}
$event_key = $prefix . ($special ? '.' : '') . $special . ':' . $name;
$event = new kEvent($event_key);
$event->MasterEvent = $this->parentEvent;
if ( isset($foreign_key) ) {
$event->setEventParam('foreign_key', $foreign_key);
}
$set_temp_id = ($name == 'OnAfterCopyToLive') && (!is_array($add_params) || !array_key_exists('temp_id', $add_params));
foreach ($ids as $index => $id) {
$event->setEventParam('id', $id);
if ( $set_temp_id ) {
$event->setEventParam('temp_id', $index);
}
if ( is_array($add_params) ) {
foreach ($add_params as $name => $val) {
$event->setEventParam($name, $val);
}
}
$this->Application->HandleEvent($event);
}
return $event->status == kEvent::erSUCCESS;
}
function DropTempTable($table)
{
if ( in_array($table, $this->DroppedTables) ) {
return false;
}
$query = 'DROP TABLE IF EXISTS ' . $this->GetTempName($table);
array_push($this->DroppedTables, $table);
$this->DroppedTables = array_unique($this->DroppedTables);
$this->Conn->Query($query);
return true;
}
function PrepareEdit()
{
$this->DoCopyLiveToTemp($this->Tables, $this->Tables['IDs']);
if ($this->Application->getUnitOption($this->Tables['Prefix'],'CheckSimulatniousEdit')) {
$this->CheckSimultaniousEdit();
}
}
function SaveEdit($master_ids = Array())
{
// SessionId field is required for deleting records from expired sessions.
$conn =& $this->_getSeparateConnection();
$sleep_count = 0;
$master_ids = $this->getMainIDs($this->Tables, $master_ids);
do {
// acquire lock
$conn->ChangeQuery('LOCK TABLES '.TABLE_PREFIX.'Semaphores WRITE');
$another_coping_active = false;
$sql = 'SELECT MainIDs
FROM ' . TABLE_PREFIX . 'Semaphores
WHERE MainPrefix = ' . $conn->qstr($this->Tables['Prefix']) . ' AND MainIDs <> "0"';
$other_semaphores_main_ids = $conn->GetCol($sql);
foreach ( $other_semaphores_main_ids as $other_semaphore_main_ids ) {
$other_semaphore_main_ids = explode(',', $other_semaphore_main_ids);
if ( array_intersect($master_ids, $other_semaphore_main_ids) ) {
$another_coping_active = true;
break;
}
}
if ($another_coping_active) {
// another user is coping data from temp table to live -> release lock and try again after 1 second
$conn->ChangeQuery('UNLOCK TABLES');
$sleep_count++;
sleep(1);
}
} while ($another_coping_active && ($sleep_count <= 30));
if ($sleep_count > 30) {
// Another coping process failed to finished in 30 seconds.
$error_message = $this->Application->Phrase('la_error_TemporaryTableCopyingFailed');
$this->Application->SetVar('_temp_table_message', $error_message);
$log = $this->Application->log('Parallel item saving attempt detected');
$log->addTrace();
$log->setLogLevel(kLogger::LL_ERROR);
$log->write();
return false;
}
/*
* Mark, that we are coping from temp to live right now,
* so other similar attempt (from another script) will fail.
*/
$semaphore_id = $this->createSemaphore($conn, $master_ids);
// unlock table now to prevent permanent lock in case, when coping will end with SQL error in the middle
$conn->ChangeQuery('UNLOCK TABLES');
$ids = $this->DoCopyTempToOriginal($this->Tables, null, $master_ids);
// remove mark, that we are coping from temp to live
$conn->Query('LOCK TABLES '.TABLE_PREFIX.'Semaphores WRITE');
$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Semaphores
WHERE SemaphoreId = ' . $semaphore_id;
$conn->ChangeQuery($sql);
$conn->ChangeQuery('UNLOCK TABLES');
return $ids;
}
/**
* Creates a semaphore.
*
* @param IDBConnection $conn Database connection.
* @param array $master_ids Master record IDs.
*
* @return integer
*/
protected function createSemaphore(IDBConnection $conn, array $master_ids)
{
$fields_hash = array(
'SessionKey' => $this->Application->GetSID(Session::PURPOSE_STORAGE),
'Timestamp' => adodb_mktime(),
'MainPrefix' => $this->Tables['Prefix'],
'MainIDs' => implode(',', $master_ids),
'UserId' => $this->Application->RecallVar('user_id'),
'IPAddress' => $this->Application->getClientIp(),
'Hostname' => $_SERVER['HTTP_HOST'],
'RequestURI' => $_SERVER['REQUEST_URI'],
'Backtrace' => serialize(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)),
);
$conn->doInsert($fields_hash, TABLE_PREFIX . 'Semaphores');
return $conn->getInsertID();
}
function CancelEdit($master=null)
{
if (!isset($master)) $master = $this->Tables;
$this->DropTempTable($master['TableName']);
if ( getArrayValue($master, 'SubTables') ) {
foreach ($master['SubTables'] as $sub_table) {
$this->CancelEdit($sub_table);
}
}
}
/**
* Checks, that someone is editing selected records and returns true, when no one.
*
* @param Array $ids
*
* @return bool
*/
function CheckSimultaniousEdit($ids = null)
{
$tables = $this->Conn->GetCol('SHOW TABLES');
$mask_edit_table = '/' . TABLE_PREFIX . 'ses_(.*)_edit_' . $this->MasterTable . '$/';
$my_sid = $this->Application->GetSID(Session::PURPOSE_REFERENCE);
$my_wid = $this->Application->GetVar('m_wid');
$ids = implode(',', isset($ids) ? $ids : $this->Tables['IDs']);
$sids = Array ();
if (!$ids) {
return true;
}
foreach ($tables as $table) {
if ( preg_match($mask_edit_table, $table, $rets) ) {
$sid = preg_replace('/(.*)_(.*)/', '\\1', $rets[1]); // remove popup's wid from sid
if ($sid == $my_sid) {
if ($my_wid) {
// using popups for editing
if (preg_replace('/(.*)_(.*)/', '\\2', $rets[1]) == $my_wid) {
// don't count window, that is being opened right now
continue;
}
}
else {
// not using popups for editing -> don't count my session tables
continue;
}
}
$sql = 'SELECT COUNT(' . $this->Tables['IdField'] . ')
FROM ' . $table . '
WHERE ' . $this->Tables['IdField'] . ' IN (' . $ids . ')';
$found = $this->Conn->GetOne($sql);
if (!$found || in_array($sid, $sids)) {
continue;
}
$sids[] = $sid;
}
}
if ($sids) {
// detect who is it
$sql = 'SELECT
CONCAT(IF (s.PortalUserId = ' . USER_ROOT . ', \'root\',
IF (s.PortalUserId = ' . USER_GUEST . ', \'Guest\',
CONCAT(u.FirstName, \' \', u.LastName, \' (\', u.Username, \')\')
)
), \' IP: \', s.IpAddress, \'\') FROM ' . TABLE_PREFIX . 'UserSessions AS s
LEFT JOIN ' . TABLE_PREFIX . 'Users AS u
ON u.PortalUserId = s.PortalUserId
WHERE s.SessionId IN (' . implode(',', $sids) . ')';
$users = $this->Conn->GetCol($sql);
if ($users) {
$this->Application->SetVar('_simultaneous_edit_message',
sprintf($this->Application->Phrase('la_record_being_edited_by'), join(",\n", $users))
);
return false;
}
}
return true;
}
}
Index: branches/5.2.x/core/units/helpers/ChangeLogHelper.php
===================================================================
--- branches/5.2.x/core/units/helpers/ChangeLogHelper.php (nonexistent)
+++ branches/5.2.x/core/units/helpers/ChangeLogHelper.php (revision 16824)
@@ -0,0 +1,316 @@
+<?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.
+*/
+
+class ChangeLogHelper extends kHelper
+{
+
+ /**
+ * Forgets changes.
+ *
+ * @param string $main_prefix Main prefix.
+ *
+ * @return void
+ */
+ public function forgetChanges($main_prefix)
+ {
+ $ses_var_name = $this->getSessionVariableName($main_prefix, $main_prefix);
+ $this->Application->RemoveVar($ses_var_name);
+ }
+
+ /**
+ * Updates the foreign keys in the changelog for a given table based on the provided identifiers.
+ *
+ * This method ensures that all references to the temporary ID in the changelog are updated to use
+ * the live ID, while considering dependencies and relationships between items.
+ *
+ * @param array $table_info Table info.
+ * @param integer $live_id Live ID.
+ * @param integer $temp_id Temp ID.
+ * @param string $caller_prefix Caller prefix.
+ *
+ * @return void
+ */
+ public function updateForeignKeys(array $table_info, $live_id, $temp_id, $caller_prefix)
+ {
+ if ( $live_id == $temp_id ) {
+ return;
+ }
+
+ $prefix = $table_info['Prefix'];
+ $main_prefix = $this->Application->GetTopmostPrefix($prefix);
+
+ $ses_var_name = $this->getSessionVariableName($main_prefix, $caller_prefix);
+ $changes = $this->Application->RecallVar($ses_var_name);
+ $changes = $changes ? unserialize($changes) : array();
+
+ if ( !$changes ) {
+ return;
+ }
+
+ foreach ( $changes as $key => $rec ) {
+ // The main item changelog record.
+ if ( $rec['Prefix'] == $prefix && $rec['ItemId'] == $temp_id ) {
+ $changes[$key]['ItemId'] = $live_id;
+ }
+
+ // The subitem changelog record.
+ if ( $rec['MasterPrefix'] == $prefix && $rec['MasterId'] == $temp_id ) {
+ $changes[$key]['MasterId'] = $live_id;
+ }
+
+ // The parent item changelog record.
+ if ( in_array($prefix, $rec['ParentPrefix']) && $rec['ParentId'][$prefix] == $temp_id ) {
+ $changes[$key]['ParentId'][$prefix] = $live_id;
+
+ if ( array_key_exists('DependentFields', $rec) ) {
+ /*
+ * These are fields from the $rec['Prefix'] table!
+ * Used, when one of the dependent fields uses IdField of its changed parent item.
+ */
+ $parent_table_key = $this->Application->getUnitOption($rec['Prefix'], 'ParentTableKey');
+ $parent_table_key = is_array($parent_table_key) ? $parent_table_key[$prefix] : $parent_table_key;
+
+ if ( $parent_table_key == $table_info['IdField'] ) {
+ $foreign_key = $this->Application->getUnitOption($rec['Prefix'], 'ForeignKey');
+ $foreign_key = is_array($foreign_key) ? $foreign_key[$prefix] : $foreign_key;
+
+ $changes[$key]['DependentFields'][$foreign_key] = $live_id;
+ }
+ }
+ }
+ }
+
+ $this->Application->StoreVar($ses_var_name, serialize($changes));
+ }
+
+ /**
+ * Logs changes for a database item, tracking relevant details for create, update, or delete actions.
+ *
+ * @param kDBItem $object Object.
+ * @param integer $mode Mode (e.g., create, update, delete).
+ * @param array|null $update_fields Update fields.
+ *
+ * @return void
+ */
+ public function logChanges(kDBItem $object, $mode, array $update_fields = null)
+ {
+ if ( !$mode ) {
+ return;
+ }
+
+ $prefix = $object->Prefix;
+ $main_prefix = $this->Application->GetTopmostPrefix($prefix);
+
+ $ses_var_name = $this->getSessionVariableName($main_prefix, $prefix);
+ $changes = $this->Application->RecallVar($ses_var_name);
+ $changes = $changes ? unserialize($changes) : array();
+
+ $fields_hash = array(
+ 'Prefix' => $prefix,
+ 'ItemId' => $object->GetID(),
+ 'OccuredOn' => adodb_mktime(),
+ 'MasterPrefix' => $main_prefix,
+ 'Action' => $mode,
+ );
+
+ if ( $prefix == $main_prefix ) {
+ // Main item.
+ $fields_hash['MasterId'] = $object->GetID();
+ $fields_hash['ParentPrefix'] = array($main_prefix);
+ $fields_hash['ParentId'] = array($main_prefix => $object->GetID());
+ }
+ else {
+ /*
+ * Subitem.
+ * Collect foreign key values (for serial reset).
+ */
+ $foreign_keys = $this->Application->getUnitOption($prefix, 'ForeignKey', array());
+ $dependent_fields = $fields_hash['ParentId'] = $fields_hash['ParentPrefix'] = array();
+ /** @var Array $foreign_keys */
+
+ if ( is_array($foreign_keys) ) {
+ foreach ( $foreign_keys as $foreign_key_prefix => $foreign_key_field_name ) {
+ $dependent_fields[$foreign_key_field_name] = $object->GetDBField($foreign_key_field_name);
+ $fields_hash['ParentPrefix'][] = $foreign_key_prefix;
+ $fields_hash['ParentId'][$foreign_key_prefix] = $object->getParentId($foreign_key_prefix);
+ }
+ }
+ else {
+ $dependent_fields[$foreign_keys] = $object->GetDBField($foreign_keys);
+ $fields_hash['ParentPrefix'] = array(
+ $this->Application->getUnitOption($prefix, 'ParentPrefix'),
+ );
+ $fields_hash['ParentId'][$fields_hash['ParentPrefix'][0]] = $object->getParentId('auto');
+ }
+
+ $fields_hash['DependentFields'] = $dependent_fields;
+
+ // Works only when the main item is present in url when a subitem is changed.
+ $master_id = $this->Application->GetVar($main_prefix . '_id');
+
+ if ( $master_id === false ) {
+ // Works in case when we're not editing topmost item, when subitem is created/updated/deleted.
+ $master_id = $object->getParentId('auto', true);
+ }
+
+ $fields_hash['MasterId'] = $master_id;
+ }
+
+ switch ( $mode ) {
+ case ChangeLog::UPDATE:
+ $changed_fields = $object->GetChangedFields();
+
+ if ( $update_fields ) {
+ $changed_fields = array_intersect_key(
+ $changed_fields,
+ array_combine($update_fields, $update_fields)
+ );
+ }
+
+ $to_save = array_merge($object->GetTitleField(), $changed_fields);
+ break;
+
+ case ChangeLog::CREATE:
+ $to_save = $object->GetTitleField();
+ break;
+
+ case ChangeLog::DELETE:
+ $to_save = array_merge($object->GetTitleField(), $object->GetRealFields());
+ break;
+
+ default:
+ $to_save = array();
+ break;
+ }
+
+ $fields_hash['Changes'] = serialize($to_save);
+ $changes[] = $fields_hash;
+
+ $this->Application->StoreVar($ses_var_name, serialize($changes));
+ }
+
+ /**
+ * Saves changes made in the temporary table to log.
+ *
+ * @param string $prefix Prefix.
+ * @param boolean $save Save changes.
+ *
+ * @return void
+ */
+ public function saveLoggedChanges($prefix, $save = true)
+ {
+ $main_prefix = $this->Application->GetTopmostPrefix($prefix);
+
+ // 1. get changes that were made.
+ $ses_var_name = $this->getSessionVariableName($main_prefix, $prefix);
+ $changes = $this->Application->RecallVar($ses_var_name);
+ $changes = $changes ? unserialize($changes) : array();
+ $this->Application->RemoveVar($ses_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']);
+ }
+
+ // Remove keys that don't have corresponding columns in the "ChangeLogs" database table.
+ 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($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 a database.
+ $session_log_id = $this->Application->RecallVar('_SessionLogId_');
+
+ if ( !$save || !$session_log_id ) {
+ // Saving changes to database disabled OR related session log missing.
+ return;
+ }
+
+ $add_fields = array(
+ 'PortalUserId' => $this->Application->RecallVar('user_id'),
+ 'SessionLogId' => $session_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 = ' . $session_log_id;
+ $this->Conn->Query($sql);
+
+ $this->Application->incrementCacheSerial('session-log');
+ }
+
+ /**
+ * Returns change log session variable name.
+ *
+ * @param string $main_prefix Main prefix.
+ * @param string $prefix Prefix.
+ *
+ * @return string
+ */
+ protected function getSessionVariableName($main_prefix, $prefix)
+ {
+ return $main_prefix . '_changes_' . $this->Application->GetTopmostWid($prefix);
+ }
+
+}
Property changes on: branches/5.2.x/core/units/helpers/ChangeLogHelper.php
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+LF
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+Id
\ No newline at end of property
Index: branches/5.2.x/core/units/helpers/helpers_config.php
===================================================================
--- branches/5.2.x/core/units/helpers/helpers_config.php (revision 16823)
+++ branches/5.2.x/core/units/helpers/helpers_config.php (revision 16824)
@@ -1,82 +1,83 @@
<?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!');
$config = Array (
'Prefix' => 'helpers',
'EventHandlerClass' => Array ('class' => 'kEventHandler', 'file' => '', 'build_event' => 'OnBuild'),
'RegisterClasses' => Array (
Array ('pseudo' => 'kMultiLanguageHelper', 'class' => 'kMultiLanguageHelper', 'file' => 'multilanguage_helper.php', 'build_event' => ''),
Array ('pseudo' => 'SearchHelper', 'class' => 'kSearchHelper', 'file' => 'search_helper.php', 'build_event' => ''),
Array ('pseudo' => 'SectionsHelper', 'class' => 'kSectionsHelper', 'file' => 'sections_helper.php', 'build_event' => ''),
Array ('pseudo' => 'PermissionsHelper', 'class' => 'kPermissionsHelper', 'file' => 'permissions_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ModulesHelper', 'class' => 'kModulesHelper', 'file' => 'modules_helper.php', 'build_event' => ''),
Array ('pseudo' => 'CategoryItemRewrite', 'class' => 'CategoryItemRewrite', 'file' => 'mod_rewrite_helper.php', 'build_event' => ''),
Array ('pseudo' => 'RecursiveHelper', 'class' => 'kRecursiveHelper', 'file' => 'recursive_helper.php', 'build_event' => ''),
Array ('pseudo' => 'FilenamesHelper', 'class' => 'kFilenamesHelper', 'file' => 'filenames_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ClipboardHelper', 'class' => 'kClipboardHelper', 'file' => 'clipboard_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ColumnPickerHelper', 'class' => 'kColumnPickerHelper', 'file' => 'col_picker_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ColumnSet', 'class' => 'ColumnSet', 'file' => 'col_picker_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ThemesHelper', 'class' => 'kThemesHelper', 'file' => 'themes_helper.php', 'build_event' => ''),
Array ('pseudo' => 'CaptchaHelper', 'class' => 'kCaptchaHelper', 'file' => 'captcha_helper.php', 'build_event' => ''),
Array ('pseudo' => 'PriorityHelper', 'class' => 'kPriorityHelper', 'file' => 'priority_helper.php', 'build_event' => ''),
Array ('pseudo' => 'CurlHelper', 'class' => 'kCurlHelper', 'file' => 'curl_helper.php', 'build_event' => ''),
Array ('pseudo' => 'CountHelper', 'class' => 'kCountHelper', 'file' => 'count_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ImageHelper', 'class' => 'ImageHelper', 'file' => 'image_helper.php', 'build_event' => ''),
Array ('pseudo' => 'FileHelper', 'class' => 'FileHelper', 'file' => 'file_helper.php', 'build_event' => ''),
Array ('pseudo' => 'CategoryHelper', 'class' => 'CategoryHelper', 'file' => 'category_helper.php', 'build_event' => ''),
Array ('pseudo' => 'kNavigationBar', 'class' => 'kNavigationBar', 'file' => 'navigation_bar.php', 'build_event' => ''),
Array ('pseudo' => 'CSVHelper', 'class' => 'kCSVHelper', 'file' => 'csv_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ChartHelper', 'class' => 'kChartHelper', 'file' => 'chart_helper.php', 'build_event' => ''),
Array ('pseudo' => 'RatingHelper', 'class' => 'RatingHelper', 'file' => 'rating_helper.php', 'build_event' => ''),
Array ('pseudo' => 'FCKHelper', 'class' => 'fckFCKHelper', 'file' => 'fck_helper.php', 'build_event' => ''),
Array ('pseudo' => 'SpamHelper', 'class' => 'SpamHelper', 'file' => 'spam_helper.php', 'build_event' => ''),
Array ('pseudo' => 'TemplateHelper', 'class' => 'TemplateHelper', 'file' => 'template_helper.php', 'build_event' => ''),
Array ('pseudo' => 'MailingListHelper', 'class' => 'MailingListHelper', 'file' => 'mailing_list_helper.php', 'build_event' => ''),
Array ('pseudo' => 'JSONHelper', 'class' => 'JSONHelper', 'file' => 'json_helper.php', 'build_event' => ''),
Array ('pseudo' => 'LanguageImportHelper', 'class' => 'LanguageImportHelper', 'file' => 'language_import_helper.php', 'build_event' => ''),
Array ('pseudo' => 'SkinHelper', 'class' => 'SkinHelper', 'file' => 'skin_helper.php', 'build_event' => ''),
Array ('pseudo' => 'SiteConfigHelper', 'class' => 'SiteConfigHelper', 'file' => 'site_config_helper.php', 'build_event' => ''),
Array ('pseudo' => 'MenuHelper', 'class' => 'MenuHelper', 'file' => 'menu_helper.php', 'build_event' => ''),
Array ('pseudo' => 'InpCustomFieldsHelper', 'class' => 'InpCustomFieldsHelper', 'file' => 'custom_fields_helper.php', 'build_event' => ''),
Array ('pseudo' => 'CountryStatesHelper', 'class' => 'kCountryStatesHelper', 'file' => 'country_states_helper.php', 'build_event' => ''),
Array ('pseudo' => 'BracketsHelper', 'class' => 'kBracketsHelper', 'file' => 'brackets_helper.php', 'build_event' => ''),
Array ('pseudo' => 'kXMLHelper', 'class' => 'kXMLHelper', 'file' => 'xml_helper.php', 'build_event' => ''),
Array ('pseudo' => 'kXMLNode', 'class' => 'kXMLNode', 'file' => 'xml_helper.php', 'build_event' => ''),
Array ('pseudo' => 'XMLIterator', 'class' => 'XMLIterator', 'file' => 'xml_helper5.php', 'build_event' => ''),
Array ('pseudo' => 'kXMLNode5', 'class' => 'kXMLNode5', 'file' => 'xml_helper5.php', 'build_event' => '', 'require_classes' => 'kXMLNode'),
Array ('pseudo' => 'CatItemExportHelper', 'class' => 'kCatDBItemExportHelper', 'file' => 'cat_dbitem_export_helper.php', 'build_event' => ''),
Array ('pseudo' => 'kEmailTemplateHelper', 'class' => 'kEmailTemplateHelper', 'file' => 'email_template_helper.php', 'build_event' => ''),
Array ('pseudo' => 'ListHelper', 'class' => 'ListHelper', 'file' => 'list_helper.php', 'build_event' => ''),
Array ('pseudo' => 'FormSubmissionHelper', 'class' => 'FormSubmissionHelper', 'file' => 'form_submission_helper.php', 'build_event' => ''),
Array ('pseudo' => 'MailboxHelper', 'class' => 'MailboxHelper', 'file' => 'mailbox_helper.php', 'build_event' => ''),
Array ('pseudo' => 'POP3Helper', 'class' => 'POP3Helper', 'file' => 'pop3_helper.php', 'build_event' => ''),
Array ('pseudo' => 'MimeDecodeHelper', 'class' => 'MimeDecodeHelper', 'file' => 'mime_decode_helper.php', 'build_event' => ''),
Array ('pseudo' => 'UserHelper', 'class' => 'UserHelper', 'file' => 'user_helper.php', 'build_event' => ''),
Array ('pseudo' => 'SiteHelper', 'class' => 'SiteHelper', 'file' => 'site_helper.php', 'build_event' => ''),
Array ('pseudo' => 'DeploymentHelper', 'class' => 'DeploymentHelper', 'file' => 'deployment_helper.php', 'build_event' => ''),
Array ('pseudo' => 'PageHelper', 'class' => 'PageHelper', 'file' => 'page_helper.php', 'build_event' => ''),
Array ('pseudo' => 'BackupHelper', 'class' => 'BackupHelper', 'file' => 'backup_helper.php', 'build_event' => ''),
Array ('pseudo' => 'AjaxFormHelper', 'class' => 'AjaxFormHelper', 'file' => 'ajax_form_helper.php', 'build_event' => ''),
Array ('pseudo' => 'kCronHelper', 'class' => 'kCronHelper', 'file' => 'cron_helper.php', 'build_event' => ''),
Array ('pseudo' => 'kUploadHelper', 'class' => 'kUploadHelper', 'file' => 'upload_helper.php', 'build_event' => ''),
+ Array ('pseudo' => 'ChangeLogHelper', 'class' => 'ChangeLogHelper', 'file' => 'ChangeLogHelper.php', 'build_event' => ''),
),
);

Event Timeline