Page MenuHomeIn-Portal Phabricator

in-portal
No OneTemporary

File Metadata

Created
Sat, Feb 22, 12:06 AM

in-portal

Index: branches/5.2.x/core/kernel/db/cat_event_handler.php
===================================================================
--- branches/5.2.x/core/kernel/db/cat_event_handler.php (revision 15780)
+++ branches/5.2.x/core/kernel/db/cat_event_handler.php (revision 15781)
@@ -1,3080 +1,3083 @@
<?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 kCatDBEventHandler extends kDBEventHandler {
/**
* Allows to override standard permission mapping
*
* @return void
* @access protected
* @see kEventHandler::$permMapping
*/
protected function mapPermissions()
{
parent::mapPermissions();
$permissions = Array(
'OnSaveSettings' => Array ('self' => 'add|edit|advanced:import'),
'OnResetSettings' => Array ('self' => 'add|edit|advanced:import'),
'OnBeforeDeleteOriginal' => Array ('self' => 'edit|advanced:approve'),
'OnAfterDeleteOriginal' => Array ('self' => 'edit|advanced:approve'),
'OnCopy' => Array ('self' => true),
'OnDownloadFile' => Array ('self' => 'view'),
'OnCancelAction' => Array ('self' => true),
'OnItemBuild' => Array ('self' => true),
'OnMakeVote' => Array ('self' => true),
'OnReviewHelpful' => Array ('self' => true),
);
$this->permMapping = array_merge($this->permMapping, $permissions);
}
/**
* Load item if id is available
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function LoadItem(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$id = $this->getPassedID($event);
if ( $object->Load($id) ) {
$actions = $this->Application->recallObject('kActions');
/* @var $actions Params */
$actions->Set($event->getPrefixSpecial() . '_id', $object->GetID());
$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
if ( $use_pending_editing && $event->Special != 'original' ) {
$this->Application->SetVar($event->Prefix . '.original_id', $object->GetDBField('OrgId'));
}
}
else {
$object->setID($id);
}
}
/**
* Checks user permission to execute given $event
*
* @param kEvent $event
* @return bool
* @access public
*/
public function CheckPermission(kEvent $event)
{
if ( !$this->Application->isAdmin ) {
if ( $event->Name == 'OnSetSortingDirect' ) {
// allow sorting on front event without view permission
return true;
}
}
if ( $event->Name == 'OnExport' ) {
// save category_id before doing export
$this->Application->LinkVar('m_cat_id');
}
if ( in_array($event->Name, $this->_getMassPermissionEvents()) ) {
$items = $this->_getPermissionCheckInfo($event);
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
if ( ($event->Name == 'OnSave') && array_key_exists(0, $items) ) {
// adding new item (ID = 0)
$perm_value = $perm_helper->AddCheckPermission($items[0]['CategoryId'], $event->Prefix) > 0;
}
else {
// leave only items, that can be edited
$ids = Array ();
$check_method = in_array($event->Name, Array ('OnMassDelete', 'OnCut')) ? 'DeleteCheckPermission' : 'ModifyCheckPermission';
foreach ($items as $item_id => $item_data) {
if ( $perm_helper->$check_method($item_data['CreatedById'], $item_data['CategoryId'], $event->Prefix) > 0 ) {
$ids[] = $item_id;
}
}
if ( !$ids ) {
// no items left for editing -> no permission
return $perm_helper->finalizePermissionCheck($event, false);
}
$perm_value = true;
$event->setEventParam('ids', $ids); // will be used later by "kDBEventHandler::StoreSelectedIDs" method
}
return $perm_helper->finalizePermissionCheck($event, $perm_value);
}
$export_events = Array ('OnSaveSettings', 'OnResetSettings', 'OnExportBegin');
if ( in_array($event->Name, $export_events) ) {
// when import settings before selecting target import category
return $this->Application->CheckPermission('in-portal:main_import.view');
}
if ( $event->Name == 'OnProcessSelected' ) {
if ( $this->Application->RecallVar('dst_field') == 'ImportCategory' ) {
// when selecting target import category
return $this->Application->CheckPermission('in-portal:main_import.view');
}
}
return parent::CheckPermission($event);
}
/**
* Returns events, that require item-based (not just event-name based) permission check
*
* @return Array
*/
function _getMassPermissionEvents()
{
return Array (
'OnEdit', 'OnSave', 'OnMassDelete', 'OnMassApprove',
'OnMassDecline', 'OnMassMoveUp', 'OnMassMoveDown',
'OnCut',
);
}
/**
* Returns category item IDs, that require permission checking
*
* @param kEvent $event
* @return string
*/
function _getPermissionCheckIDs($event)
{
if ($event->Name == 'OnSave') {
$selected_ids = implode(',', $this->getSelectedIDs($event, true));
if (!$selected_ids) {
$selected_ids = 0; // when saving newly created item (OnPreCreate -> OnPreSave -> OnSave)
}
}
else {
// OnEdit, OnMassDelete events, when items are checked in grid
$selected_ids = implode(',', $this->StoreSelectedIDs($event));
}
return $selected_ids;
}
/**
* Returns information used in permission checking
*
* @param kEvent $event
* @return Array
*/
function _getPermissionCheckInfo($event)
{
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
// when saving data from temp table to live table check by data from temp table
$item_ids = $this->_getPermissionCheckIDs($event);
$items = $perm_helper->GetCategoryItemData($event->Prefix, $item_ids, $event->Name == 'OnSave');
if (!$items) {
// when item not present in temp table, then permission is not checked, because there are no data in db to check
$items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
list ($id, $fields_hash) = each($items_info);
if (array_key_exists('CategoryId', $fields_hash)) {
$item_category = $fields_hash['CategoryId'];
}
else {
$item_category = $this->Application->GetVar('m_cat_id');
}
$items[$id] = Array (
'CreatedById' => $this->Application->RecallVar('use_id'),
'CategoryId' => $item_category,
);
}
return $items;
}
/**
* Add selected items to clipboard with mode = COPY (CLONE)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCopy($event)
{
$this->Application->RemoveVar('clipboard');
$clipboard_helper = $this->Application->recallObject('ClipboardHelper');
/* @var $clipboard_helper kClipboardHelper */
$clipboard_helper->setClipboard($event, 'copy', $this->StoreSelectedIDs($event));
$this->clearSelectedIDs($event);
}
/**
* Add selected items to clipboard with mode = CUT
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCut($event)
{
$this->Application->RemoveVar('clipboard');
$clipboard_helper = $this->Application->recallObject('ClipboardHelper');
/* @var $clipboard_helper kClipboardHelper */
$clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event));
$this->clearSelectedIDs($event);
}
/**
* Checks permission for OnPaste event
*
* @param kEvent $event
* @return bool
*/
function _checkPastePermission($event)
{
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
$category_id = $this->Application->GetVar('m_cat_id');
if ($perm_helper->AddCheckPermission($category_id, $event->Prefix) == 0) {
// no items left for editing -> no permission
return $perm_helper->finalizePermissionCheck($event, false);
}
return true;
}
/**
* Performs category item paste
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPaste($event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event) ) {
$event->status = kEvent::erFAIL;
return;
}
$clipboard_data = $event->getEventParam('clipboard_data');
if ( !$clipboard_data['cut'] && !$clipboard_data['copy'] ) {
return;
}
if ( $clipboard_data['copy'] ) {
$temp = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
/* @var $temp kTempTablesHandler */
$this->Application->SetVar('ResetCatBeforeClone', 1); // used in "kCatDBEventHandler::OnBeforeClone"
$temp->CloneItems($event->Prefix, $event->Special, $clipboard_data['copy']);
}
if ( $clipboard_data['cut'] ) {
$object = $this->Application->recallObject($event->getPrefixSpecial() . '.item', $event->Prefix, Array ('skip_autoload' => true));
/* @var $object kCatDBItem */
foreach ($clipboard_data['cut'] as $id) {
$object->Load($id);
$object->MoveToCat();
}
}
}
/**
* 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;
}
$ids = $this->StoreSelectedIDs($event);
$to_delete = Array ();
$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
if ( $recycle_bin ) {
$rb = $this->Application->recallObject('c.recycle', NULL, array ('skip_autoload' => true));
/* @var $rb CategoriesItem */
$rb->Load($recycle_bin);
$object = $this->Application->recallObject($event->Prefix . '.recycleitem', NULL, Array ('skip_autoload' => true));
/* @var $object kCatDBItem */
foreach ($ids as $id) {
$object->Load($id);
if ( preg_match('/^' . preg_quote($rb->GetDBField('ParentPath'), '/') . '/', $object->GetDBField('ParentPath')) ) {
$to_delete[] = $id;
continue;
}
$object->MoveToCat($recycle_bin);
}
$ids = $to_delete;
}
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
/* @var $temp_handler kTempTablesHandler */
$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);
}
/**
* Return type clauses for list bulding on front
*
* @param kEvent $event
* @return Array
*/
function getTypeClauses($event)
{
$types = $event->getEventParam('types');
$types = $types ? explode(',', $types) : Array ();
$except_types = $event->getEventParam('except');
$except_types = $except_types ? explode(',', $except_types) : Array ();
$type_clauses = Array();
$user_id = $this->Application->RecallVar('user_id');
$owner_field = $this->getOwnerField($event->Prefix);
$type_clauses['my_items']['include'] = '%1$s.'.$owner_field.' = '.$user_id;
$type_clauses['my_items']['except'] = '%1$s.'.$owner_field.' <> '.$user_id;
$type_clauses['my_items']['having_filter'] = false;
$type_clauses['pick']['include'] = '%1$s.EditorsPick = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
$type_clauses['pick']['except'] = '%1$s.EditorsPick! = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1';
$type_clauses['pick']['having_filter'] = false;
$type_clauses['hot']['include'] = '`IsHot` = 1 AND PrimaryCat = 1';
$type_clauses['hot']['except'] = '`IsHot`! = 1 AND PrimaryCat = 1';
$type_clauses['hot']['having_filter'] = true;
$type_clauses['pop']['include'] = '`IsPop` = 1 AND PrimaryCat = 1';
$type_clauses['pop']['except'] = '`IsPop`! = 1 AND PrimaryCat = 1';
$type_clauses['pop']['having_filter'] = true;
$type_clauses['new']['include'] = '`IsNew` = 1 AND PrimaryCat = 1';
$type_clauses['new']['except'] = '`IsNew`! = 1 AND PrimaryCat = 1';
$type_clauses['new']['having_filter'] = true;
$type_clauses['displayed']['include'] = '';
$displayed = $this->Application->GetVar($event->Prefix.'_displayed_ids');
if ($displayed) {
$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
$type_clauses['displayed']['except'] = '%1$s.'.$id_field.' NOT IN ('.$displayed.')';
}
else {
$type_clauses['displayed']['except'] = '';
}
$type_clauses['displayed']['having_filter'] = false;
if (in_array('search', $types) || in_array('search', $except_types)) {
$event_mapping = Array (
'simple' => 'OnSimpleSearch',
'subsearch' => 'OnSubSearch',
'advanced' => 'OnAdvancedSearch'
);
$keywords = $event->getEventParam('keyword_string');
$type = $this->Application->GetVar('search_type', 'simple');
if ( $keywords ) {
// processing keyword_string param of ListProducts tag
$this->Application->SetVar('keywords', $keywords);
$type = 'simple';
}
$search_event = $event_mapping[$type];
$this->$search_event($event);
$object = $event->getObject();
/* @var $object kDBList */
$search_sql = ' FROM ' . TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search
search_result JOIN %1$s ON %1$s.ResourceId = search_result.ResourceId';
$sql = str_replace('FROM %1$s', $search_sql, $object->GetPlainSelectSQL());
$object->SetSelectSQL($sql);
$object->addCalculatedField('Relevance', 'search_result.Relevance');
$type_clauses['search']['include'] = 'PrimaryCat = 1 AND ('.TABLE_PREFIX.'Categories.Status = '.STATUS_ACTIVE.')';
$type_clauses['search']['except'] = 'PrimaryCat = 1 AND ('.TABLE_PREFIX.'Categories.Status = '.STATUS_ACTIVE.')';
$type_clauses['search']['having_filter'] = false;
}
if (in_array('related', $types) || in_array('related', $except_types)) {
$related_to = $event->getEventParam('related_to');
if (!$related_to) {
$related_prefix = $event->Prefix;
}
else {
$sql = 'SELECT Prefix
FROM '.TABLE_PREFIX.'ItemTypes
WHERE ItemName = '.$this->Conn->qstr($related_to);
$related_prefix = $this->Conn->GetOne($sql);
}
$rel_table = $this->Application->getUnitOption('rel', 'TableName');
$item_type = (int)$this->Application->getUnitOption($event->Prefix, 'ItemType');
if ($item_type == 0) {
trigger_error('<strong>ItemType</strong> not defined for prefix <strong>' . $event->Prefix . '</strong>', E_USER_WARNING);
}
// process case, then this list is called inside another list
$prefix_special = $event->getEventParam('PrefixSpecial');
if (!$prefix_special) {
$prefix_special = $this->Application->Parser->GetParam('PrefixSpecial');
}
$id = false;
if ($prefix_special !== false) {
$processed_prefix = $this->Application->processPrefix($prefix_special);
if ($processed_prefix['prefix'] == $related_prefix) {
// printing related categories within list of items (not on details page)
$list = $this->Application->recallObject($prefix_special);
/* @var $list kDBList */
$id = $list->GetID();
}
}
if ($id === false) {
// printing related categories for single item (possibly on details page)
if ($related_prefix == 'c') {
$id = $this->Application->GetVar('m_cat_id');
}
else {
$id = $this->Application->GetVar($related_prefix . '_id');
}
}
$p_item = $this->Application->recallObject($related_prefix.'.current', NULL, Array('skip_autoload' => true));
/* @var $p_item kCatDBItem */
$p_item->Load( (int)$id );
$p_resource_id = $p_item->GetDBField('ResourceId');
$sql = 'SELECT SourceId, TargetId FROM '.$rel_table.'
WHERE
(Enabled = 1)
AND (
(Type = 0 AND SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
OR
(Type = 1
AND (
(SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.')
OR
(TargetId = '.$p_resource_id.' AND SourceType = '.$item_type.')
)
)
)';
$related_ids_array = $this->Conn->Query($sql);
$related_ids = Array();
foreach ($related_ids_array as $record) {
$related_ids[] = $record[ $record['SourceId'] == $p_resource_id ? 'TargetId' : 'SourceId' ];
}
if (count($related_ids) > 0) {
$type_clauses['related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_ids).') AND PrimaryCat = 1';
$type_clauses['related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_ids).') AND PrimaryCat = 1';
}
else {
$type_clauses['related']['include'] = '0';
$type_clauses['related']['except'] = '1';
}
$type_clauses['related']['having_filter'] = false;
}
if (in_array('favorites', $types) || in_array('favorites', $except_types)) {
$sql = 'SELECT ResourceId
FROM '.$this->Application->getUnitOption('fav', 'TableName').'
WHERE PortalUserId = '.$this->Application->RecallVar('user_id');
$favorite_ids = $this->Conn->GetCol($sql);
if ($favorite_ids) {
$type_clauses['favorites']['include'] = '%1$s.ResourceId IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1';
$type_clauses['favorites']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1';
}
else {
$type_clauses['favorites']['include'] = 0;
$type_clauses['favorites']['except'] = 1;
}
$type_clauses['favorites']['having_filter'] = false;
}
return $type_clauses;
}
/**
* Returns SQL clause, that will help to select only data from specified category & it's children
*
* @param int $category_id
* @return string
*/
function getCategoryLimitClause($category_id)
{
if (!$category_id) {
return false;
}
$tree_indexes = $this->Application->getTreeIndex($category_id);
if (!$tree_indexes) {
// id of non-existing category was given
return 'FALSE';
}
return TABLE_PREFIX.'Categories.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight'];
}
/**
* Apply any custom changes to list's sql query
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetCustomQuery(kEvent $event)
{
parent::SetCustomQuery($event);
$object = $event->getObject();
/* @var $object kCatDBList */
// add category filter if needed
if ($event->Special != 'showall' && $event->Special != 'user') {
if ( (string)$event->getEventParam('parent_cat_id') !== '' ) {
$parent_cat_id = $event->getEventParam('parent_cat_id');
}
else {
$parent_cat_id = $this->Application->GetVar('c_id');
if (!$parent_cat_id) {
$parent_cat_id = $this->Application->GetVar('m_cat_id');
}
if (!$parent_cat_id) {
$parent_cat_id = 0;
}
}
if ("$parent_cat_id" == '0') {
// replace "0" category with "Content" category id (this way template
$parent_cat_id = $this->Application->getBaseCategory();
}
if ((string)$parent_cat_id != 'any') {
if ($event->getEventParam('recursive')) {
$filter_clause = $this->getCategoryLimitClause($parent_cat_id);
if ($filter_clause !== false) {
$object->addFilter('category_filter', $filter_clause);
}
$object->addFilter('primary_filter', 'PrimaryCat = 1');
}
else {
$object->addFilter('category_filter', TABLE_PREFIX.'CategoryItems.CategoryId = '.$parent_cat_id );
}
}
else {
$object->addFilter('primary_filter', 'PrimaryCat = 1');
}
}
else {
$object->addFilter('primary_filter', 'PrimaryCat = 1');
// if using recycle bin don't show items from there
$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
if ($recycle_bin) {
$object->addFilter('recyclebin_filter', TABLE_PREFIX.'CategoryItems.CategoryId <> '.$recycle_bin);
}
}
if ($event->Special == 'user') {
$editable_user = $this->Application->GetVar('u_id');
$object->addFilter('owner_filter', '%1$s.'.$this->getOwnerField($event->Prefix).' = '.$editable_user);
}
$this->applyViewPermissionFilter($object);
$types = $event->getEventParam('types');
$this->applyItemStatusFilter($object, $types);
$except_types = $event->getEventParam('except');
$type_clauses = $this->getTypeClauses($event);
$search_helper = $this->Application->recallObject('SearchHelper');
/* @var $search_helper kSearchHelper */
$search_helper->SetComplexFilter($event, $type_clauses, $types, $except_types);
}
/**
* Adds filter, that uses *.VIEW permissions to determine if an item should be shown to a user.
*
* @param kCatDBList $object Object.
*
* @return void
* @access protected
*/
protected function applyViewPermissionFilter(kCatDBList $object)
{
if ( !$this->Application->ConfigValue('CheckViewPermissionsInCatalog') ) {
return;
}
if ( $this->Application->RecallVar('user_id') == USER_ROOT ) {
// for "root" CATEGORY.VIEW permission is checked for items lists too
$view_perm = 1;
}
else {
// for any real user item list view permission is checked instead of CATEGORY.VIEW
$count_helper = $this->Application->recallObject('CountHelper');
/* @var $count_helper kCountHelper */
list ($view_perm, $view_filter) = $count_helper->GetPermissionClause($object->Prefix, 'perm');
$object->addFilter('perm_filter2', $view_filter);
}
$object->addFilter('perm_filter', 'perm.PermId = ' . $view_perm);
}
/**
* Adds filter that filters out items with non-required statuses
*
* @param kDBList $object
* @param string $types
*/
function applyItemStatusFilter(&$object, $types)
{
// Link1 (before modifications) [Status = 1, OrgId = NULL], Link2 (after modifications) [Status = -2, OrgId = Link1_ID]
$pending_editing = $this->Application->getUnitOption($object->Prefix, 'UsePendingEditing');
if (!$this->Application->isAdminUser) {
$types = explode(',', $types);
if (in_array('my_items', $types)) {
$allow_statuses = Array (STATUS_ACTIVE, STATUS_PENDING, STATUS_PENDING_EDITING);
$object->addFilter('status_filter', '%1$s.Status IN ('.implode(',', $allow_statuses).')');
if ($pending_editing) {
$user_id = $this->Application->RecallVar('user_id');
$this->applyPendingEditingFilter($object, $user_id);
}
}
else {
$object->addFilter('status_filter', '(%1$s.Status = ' . STATUS_ACTIVE . ') AND (' . TABLE_PREFIX . 'Categories.Status = ' . STATUS_ACTIVE . ')');
if ($pending_editing) {
// if category item uses pending editing abilities, then in no cases show pending copies on front
$object->addFilter('original_filter', '%1$s.OrgId = 0 OR %1$s.OrgId IS NULL');
}
}
}
else {
if ($pending_editing) {
$this->applyPendingEditingFilter($object);
}
}
}
/**
* Adds filter, that removes live items if they have pending editing copies
*
* @param kDBList $object
* @param int $user_id
*/
function applyPendingEditingFilter(&$object, $user_id = NULL)
{
$sql = 'SELECT OrgId
FROM '.$object->TableName.'
WHERE Status = '.STATUS_PENDING_EDITING.' AND OrgId IS NOT NULL';
if (isset($user_id)) {
$owner_field = $this->getOwnerField($object->Prefix);
$sql .= ' AND '.$owner_field.' = '.$user_id;
}
$pending_ids = $this->Conn->GetCol($sql);
if ($pending_ids) {
$object->addFilter('no_original_filter', '%1$s.'.$object->IDField.' NOT IN ('.implode(',', $pending_ids).')');
}
}
/**
* Adds calculates fields for item statuses
*
* @param kDBItem|kDBList $object
* @param kEvent $event
* @return void
* @access protected
*/
protected function prepareObject(&$object, kEvent $event)
{
$this->prepareItemStatuses($event);
$object->addCalculatedField('CachedNavbar', 'l' . $this->Application->GetVar('m_lang') . '_CachedNavbar');
if ( $event->Special == 'export' || $event->Special == 'import' ) {
$export_helper = $this->Application->recallObject('CatItemExportHelper');
/* @var $export_helper kCatDBItemExportHelper */
$export_helper->prepareExportColumns($event);
}
}
/**
* Creates calculated fields for all item statuses based on config settings
*
* @param kEvent $event
*/
function prepareItemStatuses($event)
{
$object = $event->getObject( Array('skip_autoload' => true) );
$property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings');
if (!$property_map) {
return ;
}
// new items
$object->addCalculatedField('IsNew', ' IF(%1$s.NewItem = 2,
IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - '.
$this->Application->ConfigValue($property_map['NewDays']).
'*3600*24), 1, 0),
%1$s.NewItem
)');
// hot items (cache updated every hour)
if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) {
$serial_name = $this->Application->incrementCacheSerial($event->Prefix, NULL, false);
$hot_limit = $this->Application->getCache($property_map['HotLimit'] . '[%' . $serial_name . '%]');
}
else {
$hot_limit = $this->Application->getDBCache($property_map['HotLimit']);
}
if ($hot_limit === false) {
$hot_limit = $this->CalculateHotLimit($event);
}
$object->addCalculatedField('IsHot', ' IF(%1$s.HotItem = 2,
IF(%1$s.'.$property_map['ClickField'].' >= '.$hot_limit.', 1, 0),
%1$s.HotItem
)');
// popular items
$object->addCalculatedField('IsPop', ' IF(%1$s.PopItem = 2,
IF(%1$s.CachedVotesQty >= '.
$this->Application->ConfigValue($property_map['MinPopVotes']).
' AND %1$s.CachedRating >= '.
$this->Application->ConfigValue($property_map['MinPopRating']).
', 1, 0),
%1$s.PopItem)');
}
/**
* Calculates hot limit for current item's table
*
* @param kEvent $event
* @return float
* @access protected
*/
protected function CalculateHotLimit($event)
{
$property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings');
if ( !$property_map ) {
return 0.00;
}
$click_field = $property_map['ClickField'];
$last_hot = $this->Application->ConfigValue($property_map['MaxHotNumber']) - 1;
$sql = 'SELECT ' . $click_field . '
FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
ORDER BY ' . $click_field . ' DESC
LIMIT ' . $last_hot . ', 1';
$res = $this->Conn->GetCol($sql);
$hot_limit = (double)array_shift($res);
if ( $this->Application->isCachingType(CACHING_TYPE_MEMORY) ) {
$serial_name = $this->Application->incrementCacheSerial($event->Prefix, NULL, false);
$this->Application->setCache($property_map['HotLimit'] . '[%' . $serial_name . '%]', $hot_limit);
}
else {
$this->Application->setDBCache($property_map['HotLimit'], $hot_limit, 3600);
}
return $hot_limit;
}
/**
* Moves item to preferred category, updates item hits
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemUpdate(kEvent $event)
{
parent::OnBeforeItemUpdate($event);
$object = $event->getObject();
/* @var $object kCatDBItem */
// update hits field
$property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings');
if ( $property_map ) {
$click_field = $property_map['ClickField'];
if ( $this->Application->isAdminUser && ($this->Application->GetVar($click_field . '_original') !== false) && floor($this->Application->GetVar($click_field . '_original')) != $object->GetDBField($click_field) ) {
$sql = 'SELECT MAX(' . $click_field . ')
FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
WHERE FLOOR(' . $click_field . ') = ' . $object->GetDBField($click_field);
$hits = ($res = $this->Conn->GetOne($sql)) ? $res + 0.000001 : $object->GetDBField($click_field);
$object->SetDBField($click_field, $hits);
}
}
// change category
$target_category = $object->GetDBField('CategoryId');
if ( $object->GetOriginalField('CategoryId') != $target_category ) {
$object->MoveToCat($target_category);
}
}
/**
* 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)
{
parent::OnAfterItemLoad($event);
$special = substr($event->Special, -6);
$object = $event->getObject();
/* @var $object kCatDBItem */
if ( $special == 'import' || $special == 'export' ) {
$image_data = $object->getPrimaryImageData();
if ( $image_data ) {
$thumbnail_image = $image_data[$image_data['LocalThumb'] ? 'ThumbPath' : 'ThumbUrl'];
if ( $image_data['SameImages'] ) {
$full_image = '';
}
else {
$full_image = $image_data[$image_data['LocalImage'] ? 'LocalPath' : 'Url'];
}
$object->SetDBField('ThumbnailImage', $thumbnail_image);
$object->SetDBField('FullImage', $full_image);
$object->SetDBField('ImageAlt', $image_data['AltName']);
}
}
// substituting pending status value for pending editing
if ( $object->HasField('OrgId') && $object->GetDBField('OrgId') > 0 && $object->GetDBField('Status') == -2 ) {
$new_options = Array ();
$options = $object->GetFieldOption('Status', 'options', false, Array ());
foreach ($options as $key => $val) {
if ( $key == 2 ) {
$key = -2;
}
$new_options[$key] = $val;
}
$object->SetFieldOption('Status', 'options', $new_options);
}
if ( !$this->Application->isAdmin ) {
// linking existing images for item with virtual fields
$image_helper = $this->Application->recallObject('ImageHelper');
/* @var $image_helper ImageHelper */
$image_helper->LoadItemImages($object);
// linking existing files for item with virtual fields
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
$file_helper->LoadItemFiles($object);
}
if ( $object->isVirtualField('MoreCategories') ) {
// set item's additional categories to virtual field (used in editing)
$item_categories = $this->getItemCategories($object->GetDBField('ResourceId'));
$object->SetDBField('MoreCategories', $item_categories ? '|' . implode('|', $item_categories) . '|' : '');
}
}
/**
* Occurs after updating item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemUpdate(kEvent $event)
{
parent::OnAfterItemUpdate($event);
$this->CalculateHotLimit($event);
if ( substr($event->Special, -6) == 'import' ) {
$this->setCustomExportColumns($event);
}
$object = $event->getObject();
/* @var $object kCatDBItem */
if ( !$this->Application->isAdmin ) {
$image_helper = $this->Application->recallObject('ImageHelper');
/* @var $image_helper ImageHelper */
// process image upload in virtual fields
$image_helper->SaveItemImages($object);
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
// process file upload in virtual fields
$file_helper->SaveItemFiles($object);
if ( $event->Special != '-item' ) {
// don't touch categories during cloning
$this->processAdditionalCategories($object, 'update');
}
}
$recycle_bin = $this->Application->ConfigValue('RecycleBinFolder');
if ( $this->Application->isAdminUser && $recycle_bin ) {
$sql = 'SELECT CategoryId
FROM ' . $this->Application->getUnitOption('ci', 'TableName') . '
WHERE ItemResourceId = ' . $object->GetDBField('ResourceId') . ' AND PrimaryCat = 1';
$primary_category = $this->Conn->GetOne($sql);
if ( $primary_category == $recycle_bin ) {
$event->CallSubEvent('OnAfterItemDelete');
}
}
if ( $object->GetChangedFields() ) {
$now = adodb_mktime();
$object->SetDBField('Modified_date', $now);
$object->SetDBField('Modified_time', $now);
$object->SetDBField('ModifiedById', $this->Application->RecallVar('user_id'));
}
}
/**
* Sets values for import process
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemCreate(kEvent $event)
{
parent::OnAfterItemCreate($event);
$object = $event->getObject();
/* @var $object kCatDBItem */
if ( substr($event->Special, -6) == 'import' ) {
$this->setCustomExportColumns($event);
}
$object->assignPrimaryCategory();
if ( !$this->Application->isAdmin ) {
$image_helper = $this->Application->recallObject('ImageHelper');
/* @var $image_helper ImageHelper */
// process image upload in virtual fields
$image_helper->SaveItemImages($object);
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
// process file upload in virtual fields
$file_helper->SaveItemFiles($object);
if ( $event->Special != '-item' ) {
// don't touch categories during cloning
$this->processAdditionalCategories($object, 'create');
}
}
}
/**
* Make record to search log
*
* @param string $keywords
* @param int $search_type 0 - simple search, 1 - advanced search
*/
function saveToSearchLog($keywords, $search_type = 0)
{
// don't save keywords for each module separately, just one time
// static variable can't help here, because each module uses it's own class instance !
if (!$this->Application->GetVar('search_logged')) {
$sql = 'UPDATE '.TABLE_PREFIX.'SearchLogs
SET Indices = Indices + 1
WHERE Keyword = '.$this->Conn->qstr($keywords).' AND SearchType = '.$search_type; // 0 - simple search, 1 - advanced search
$this->Conn->Query($sql);
if ($this->Conn->getAffectedRows() == 0) {
$fields_hash = Array('Keyword' => $keywords, 'Indices' => 1, 'SearchType' => $search_type);
$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'SearchLogs');
}
$this->Application->SetVar('search_logged', 1);
}
}
/**
* Makes simple search for category items
* based on keywords string
*
* @param kEvent $event
*/
function OnSimpleSearch($event)
{
$event->redirect = false;
$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
$keywords = htmlspecialchars_decode( trim($this->Application->GetVar('keywords')) );
$query_object = $this->Application->recallObject('HTTPQuery');
/* @var $query_object kHTTPQuery */
$sql = 'SHOW TABLES LIKE "'.$search_table.'"';
if(!isset($query_object->Get['keywords']) &&
!isset($query_object->Post['keywords']) &&
$this->Conn->Query($sql))
{
return; // used when navigating by pages or changing sorting in search results
}
if(!$keywords || strlen($keywords) < $this->Application->ConfigValue('Search_MinKeyword_Length'))
{
$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table);
$this->Application->SetVar('keywords_too_short', 1);
return; // if no or too short keyword entered, doing nothing
}
$this->Application->StoreVar('keywords', $keywords);
$this->saveToSearchLog($keywords, 0); // 0 - simple search, 1 - advanced search
$event->setPseudoClass('_List');
$object = $event->getObject();
/* @var $object kDBList */
$this->Application->SetVar($event->getPrefixSpecial().'_Page', 1);
$lang = $this->Application->GetVar('m_lang');
$items_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
$module_name = $this->Application->findModule('Var', $event->Prefix, 'Name');
$sql = 'SELECT *
FROM ' . $this->Application->getUnitOption('confs', 'TableName') . '
WHERE ModuleName = ' . $this->Conn->qstr($module_name) . ' AND SimpleSearch = 1';
$search_config = $this->Conn->Query($sql, 'FieldName');
$field_list = array_keys($search_config);
$join_clauses = Array();
// field processing
$weight_sum = 0;
$alias_counter = 0;
$custom_fields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
if ($custom_fields) {
$custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
$join_clauses[] = ' LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId';
}
// what field in search config becomes what field in sql (key - new field, value - old field (from searchconfig table))
$search_config_map = Array();
foreach ($field_list as $key => $field) {
$local_table = TABLE_PREFIX.$search_config[$field]['TableName'];
$weight_sum += $search_config[$field]['Priority']; // counting weight sum; used when making relevance clause
// processing multilingual fields
if ( !$search_config[$field]['CustomFieldId'] && $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) {
$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
$field_list[$key] = 'l'.$lang.'_'.$field;
if (!isset($search_config[$field]['ForeignField'])) {
$field_list[$key.'_primary'] = $local_table.'.'.$field_list[$key.'_primary'];
$search_config_map[ $field_list[$key.'_primary'] ] = $field;
}
}
// processing fields from other tables
$foreign_field = $search_config[$field]['ForeignField'];
if ( $foreign_field ) {
$exploded = explode(':', $foreign_field, 2);
if ($exploded[0] == 'CALC') {
// ignoring having type clauses in simple search
unset($field_list[$key]);
continue;
}
else {
$multi_lingual = false;
if ($exploded[0] == 'MULTI') {
$multi_lingual = true;
$foreign_field = $exploded[1];
}
$exploded = explode('.', $foreign_field); // format: table.field_name
$foreign_table = TABLE_PREFIX.$exploded[0];
$alias_counter++;
$alias = 't'.$alias_counter;
if ($multi_lingual) {
$field_list[$key] = $alias.'.'.'l'.$lang.'_'.$exploded[1];
$field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field;
$search_config_map[ $field_list[$key] ] = $field;
$search_config_map[ $field_list[$key.'_primary'] ] = $field;
}
else {
$field_list[$key] = $alias.'.'.$exploded[1];
$search_config_map[ $field_list[$key] ] = $field;
}
$join_clause = str_replace('{ForeignTable}', $alias, $search_config[$field]['JoinClause']);
$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
$join_clauses[] = ' LEFT JOIN '.$foreign_table.' '.$alias.'
ON '.$join_clause;
}
}
else {
// processing fields from local table
if ($search_config[$field]['CustomFieldId']) {
$local_table = 'custom_data';
// search by custom field value on current language
$custom_field_id = array_search($field_list[$key], $custom_fields);
$field_list[$key] = 'l'.$lang.'_cust_'.$custom_field_id;
// search by custom field value on primary language
$field_list[$key.'_primary'] = $local_table.'.l'.$this->Application->GetDefaultLanguageId().'_cust_'.$custom_field_id;
$search_config_map[ $field_list[$key.'_primary'] ] = $field;
}
$field_list[$key] = $local_table.'.'.$field_list[$key];
$search_config_map[ $field_list[$key] ] = $field;
}
}
// keyword string processing
$search_helper = $this->Application->recallObject('SearchHelper');
/* @var $search_helper kSearchHelper */
$where_clause = Array ();
foreach ($field_list as $field) {
if (preg_match('/^' . preg_quote($items_table, '/') . '\.(.*)/', $field, $regs)) {
// local real field
$filter_data = $search_helper->getSearchClause($object, $regs[1], $keywords, false);
if ($filter_data) {
$where_clause[] = $filter_data['value'];
}
}
elseif (preg_match('/^custom_data\.(.*)/', $field, $regs)) {
$custom_field_name = 'cust_' . $search_config_map[$field];
$filter_data = $search_helper->getSearchClause($object, $custom_field_name, $keywords, false);
if ($filter_data) {
$where_clause[] = str_replace('`' . $custom_field_name . '`', $field, $filter_data['value']);
}
}
else {
$where_clause[] = $search_helper->buildWhereClause($keywords, Array ($field));
}
}
$where_clause = '((' . implode(') OR (', $where_clause) . '))'; // 2 braces for next clauses, see below!
$search_scope = $this->Application->GetVar('search_scope');
if ($search_scope == 'category') {
$category_id = $this->Application->GetVar('m_cat_id');
$category_filter = $this->getCategoryLimitClause($category_id);
if ($category_filter !== false) {
$join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'CategoryItems ON '.TABLE_PREFIX.'CategoryItems.ItemResourceId = '.$items_table.'.ResourceId';
$join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'Categories ON '.TABLE_PREFIX.'Categories.CategoryId = '.TABLE_PREFIX.'CategoryItems.CategoryId';
$where_clause = '('.$this->getCategoryLimitClause($category_id).') AND '.$where_clause;
}
}
$where_clause = $where_clause . ' AND (' . $items_table . '.Status = ' . STATUS_ACTIVE . ')';
if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') {
$sub_search_ids = $event->MasterEvent->getEventParam('ResultIds');
if ( $sub_search_ids !== false ) {
if ( $sub_search_ids ) {
$where_clause .= 'AND (' . $items_table . '.ResourceId IN (' . implode(',', $sub_search_ids) . '))';
}
else {
$where_clause .= 'AND FALSE';
}
}
}
// making relevance clause
$positive_words = $search_helper->getPositiveKeywords($keywords);
$this->Application->StoreVar('highlight_keywords', serialize($positive_words));
$revelance_parts = Array();
reset($search_config);
foreach ($positive_words as $keyword_index => $positive_word) {
$positive_word = $search_helper->transformWildcards($positive_word);
$positive_words[$keyword_index] = $this->Conn->escape($positive_word);
}
foreach ($field_list as $field) {
if (!array_key_exists($field, $search_config_map)) {
$map_key = $search_config_map[$items_table . '.' . $field];
}
else {
$map_key = $search_config_map[$field];
}
$config_elem = $search_config[ $map_key ];
$weight = $config_elem['Priority'];
// search by whole words only ([[:<:]] - word boundary)
/*$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.implode(' ', $positive_words).')[[:>:]]", '.$weight.', 0)';
foreach ($positive_words as $keyword) {
$revelance_parts[] = 'IF('.$field.' REGEXP "[[:<:]]('.$keyword.')[[:>:]]", '.$weight.', 0)';
}*/
// search by partial word matches too
$revelance_parts[] = 'IF('.$field.' LIKE "%'.implode(' ', $positive_words).'%", '.$weight_sum.', 0)';
foreach ($positive_words as $keyword) {
$revelance_parts[] = 'IF('.$field.' LIKE "%'.$keyword.'%", '.$weight.', 0)';
}
}
$revelance_parts = array_unique($revelance_parts);
$conf_postfix = $this->Application->getUnitOption($event->Prefix, 'SearchConfigPostfix');
$rel_keywords = $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix) / 100;
$rel_pop = $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix) / 100;
$rel_rating = $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix) / 100;
$relevance_clause = '('.implode(' + ', $revelance_parts).') / '.$weight_sum.' * '.$rel_keywords;
if ($rel_pop && $object->isField('Hits')) {
$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
}
if ($rel_rating && $object->isField('CachedRating')) {
$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
}
// building final search query
if (!$this->Application->GetVar('do_not_drop_search_table')) {
$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); // erase old search table if clean k4 event
$this->Application->SetVar('do_not_drop_search_table', true);
}
$search_table_exists = $this->Conn->Query('SHOW TABLES LIKE "'.$search_table.'"');
if ($search_table_exists) {
$select_intro = 'INSERT INTO '.$search_table.' (Relevance, ItemId, ResourceId, ItemType, EdPick) ';
}
else {
$select_intro = 'CREATE TABLE '.$search_table.' AS ';
}
$edpick_clause = $this->Application->getUnitOption($event->Prefix.'.EditorsPick', 'Fields') ? $items_table.'.EditorsPick' : '0';
$sql = $select_intro.' SELECT '.$relevance_clause.' AS Relevance,
'.$items_table.'.'.$this->Application->getUnitOption($event->Prefix, 'IDField').' AS ItemId,
'.$items_table.'.ResourceId,
'.$this->Application->getUnitOption($event->Prefix, 'ItemType').' AS ItemType,
'.$edpick_clause.' AS EdPick
FROM '.$object->TableName.'
'.implode(' ', $join_clauses).'
WHERE '.$where_clause.'
GROUP BY '.$items_table.'.'.$this->Application->getUnitOption($event->Prefix, 'IDField').' ORDER BY Relevance DESC';
$this->Conn->Query($sql);
if ( !$search_table_exists ) {
$sql = 'ALTER TABLE ' . $search_table . '
ADD INDEX (ResourceId),
ADD INDEX (Relevance)';
$this->Conn->Query($sql);
}
}
/**
* Enter description here...
*
* @param kEvent $event
*/
function OnSubSearch($event)
{
// keep search results from other items after doing a sub-search on current item type
$this->Application->SetVar('do_not_drop_search_table', true);
$ids = Array ();
$search_table = TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search';
$sql = 'SHOW TABLES LIKE "' . $search_table . '"';
if ( $this->Conn->Query($sql) ) {
$item_type = $this->Application->getUnitOption($event->Prefix, 'ItemType');
// 1. get ids to be used as search bounds
$sql = 'SELECT DISTINCT ResourceId
FROM ' . $search_table . '
WHERE ItemType = ' . $item_type;
$ids = $this->Conn->GetCol($sql);
// 2. delete previously found ids
$sql = 'DELETE FROM ' . $search_table . '
WHERE ItemType = ' . $item_type;
$this->Conn->Query($sql);
}
$event->setEventParam('ResultIds', $ids);
$event->CallSubEvent('OnSimpleSearch');
}
/**
* Enter description here...
*
* @param kEvent $event
* @todo Change all hardcoded Products table & In-Commerce module usage to dynamic usage from item config !!!
*/
function OnAdvancedSearch($event)
{
$query_object = $this->Application->recallObject('HTTPQuery');
/* @var $query_object kHTTPQuery */
if ( !isset($query_object->Post['andor']) ) {
// used when navigating by pages or changing sorting in search results
return;
}
$this->Application->RemoveVar('keywords');
$this->Application->RemoveVar('Search_Keywords');
$module_name = $this->Application->findModule('Var', $event->Prefix, 'Name');
$sql = 'SELECT *
FROM '.$this->Application->getUnitOption('confs', 'TableName').'
WHERE (ModuleName = '.$this->Conn->qstr($module_name).') AND (AdvancedSearch = 1)';
$search_config = $this->Conn->Query($sql);
$lang = $this->Application->GetVar('m_lang');
$object = $event->getObject();
/* @var $object kDBList */
$object->SetPage(1);
$items_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
$search_keywords = $this->Application->GetVar('value'); // will not be changed
$keywords = $this->Application->GetVar('value'); // will be changed down there
$verbs = $this->Application->GetVar('verb');
$glues = $this->Application->GetVar('andor');
$and_conditions = Array();
$or_conditions = Array();
$and_having_conditions = Array();
$or_having_conditions = Array();
$join_clauses = Array();
$highlight_keywords = Array();
$relevance_parts = Array();
$alias_counter = 0;
$custom_fields = $this->Application->getUnitOption($event->Prefix, 'CustomFields');
if ($custom_fields) {
$custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName');
$join_clauses[] = ' LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId';
}
$search_log = '';
$weight_sum = 0;
// processing fields and preparing conditions
foreach ($search_config as $record) {
$field = $record['FieldName'];
$join_clause = '';
$condition_mode = 'WHERE';
// field processing
$local_table = TABLE_PREFIX.$record['TableName'];
$weight_sum += $record['Priority']; // counting weight sum; used when making relevance clause
// processing multilingual fields
if ( $object->GetFieldOption($field, 'formatter') == 'kMultiLanguage' ) {
$field_name = 'l'.$lang.'_'.$field;
}
else {
$field_name = $field;
}
// processing fields from other tables
$foreign_field = $record['ForeignField'];
if ( $foreign_field ) {
$exploded = explode(':', $foreign_field, 2);
if($exploded[0] == 'CALC')
{
$user_groups = $this->Application->RecallVar('UserGroups');
$field_name = str_replace('{PREFIX}', TABLE_PREFIX, $exploded[1]);
$join_clause = str_replace('{PREFIX}', TABLE_PREFIX, $record['JoinClause']);
$join_clause = str_replace('{USER_GROUPS}', $user_groups, $join_clause);
$join_clause = ' LEFT JOIN '.$join_clause;
$condition_mode = 'HAVING';
}
else {
$exploded = explode('.', $foreign_field);
$foreign_table = TABLE_PREFIX.$exploded[0];
if($record['CustomFieldId']) {
$exploded[1] = 'l'.$lang.'_'.$exploded[1];
}
$alias_counter++;
$alias = 't'.$alias_counter;
$field_name = $alias.'.'.$exploded[1];
$join_clause = str_replace('{ForeignTable}', $alias, $record['JoinClause']);
$join_clause = str_replace('{LocalTable}', $items_table, $join_clause);
if($record['CustomFieldId'])
{
$join_clause .= ' AND '.$alias.'.CustomFieldId='.$record['CustomFieldId'];
}
$join_clause = ' LEFT JOIN '.$foreign_table.' '.$alias.'
ON '.$join_clause;
}
}
else
{
// processing fields from local table
if ($record['CustomFieldId']) {
$local_table = 'custom_data';
$field_name = 'l'.$lang.'_cust_'.array_search($field_name, $custom_fields);
}
$field_name = $local_table.'.'.$field_name;
}
$condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords);
if ($record['CustomFieldId'] && strlen($condition)) {
// search in primary value of custom field + value in current language
$field_name = $local_table.'.'.'l'.$this->Application->GetDefaultLanguageId().'_cust_'.array_search($field, $custom_fields);
$primary_condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords);
$condition = '('.$condition.' OR '.$primary_condition.')';
}
if ($condition) {
if ($join_clause) {
$join_clauses[] = $join_clause;
}
$relevance_parts[] = 'IF('.$condition.', '.$record['Priority'].', 0)';
if ($glues[$field] == 1) { // and
if ($condition_mode == 'WHERE') {
$and_conditions[] = $condition;
}
else {
$and_having_conditions[] = $condition;
}
}
else { // or
if ($condition_mode == 'WHERE') {
$or_conditions[] = $condition;
}
else {
$or_having_conditions[] = $condition;
}
}
// create search log record
$search_log_data = Array('search_config' => $record, 'verb' => getArrayValue($verbs, $field), 'value' => ($record['FieldType'] == 'range') ? $search_keywords[$field.'_from'].'|'.$search_keywords[$field.'_to'] : $search_keywords[$field]);
$search_log[] = $this->Application->Phrase('la_Field').' "'.$this->getHuman('Field', $search_log_data).'" '.$this->getHuman('Verb', $search_log_data).' '.$this->Application->Phrase('la_Value').' '.$this->getHuman('Value', $search_log_data).' '.$this->Application->Phrase($glues[$field] == 1 ? 'lu_And' : 'lu_Or');
}
}
if ($search_log) {
$search_log = implode('<br />', $search_log);
$search_log = preg_replace('/(.*) '.preg_quote($this->Application->Phrase('lu_and'), '/').'|'.preg_quote($this->Application->Phrase('lu_or'), '/').'$/is', '\\1', $search_log);
$this->saveToSearchLog($search_log, 1); // advanced search
}
$this->Application->StoreVar('highlight_keywords', serialize($highlight_keywords));
// making relevance clause
if($relevance_parts)
{
$conf_postfix = $this->Application->getUnitOption($event->Prefix, 'SearchConfigPostfix');
$rel_keywords = $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix) / 100;
$rel_pop = $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix) / 100;
$rel_rating = $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix) / 100;
$relevance_clause = '('.implode(' + ', $relevance_parts).') / '.$weight_sum.' * '.$rel_keywords;
$relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop;
$relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating;
}
else
{
$relevance_clause = '0';
}
// building having clause
if($or_having_conditions)
{
$and_having_conditions[] = '('.implode(' OR ', $or_having_conditions).')';
}
$having_clause = implode(' AND ', $and_having_conditions);
$having_clause = $having_clause ? ' HAVING '.$having_clause : '';
// building where clause
if($or_conditions)
{
$and_conditions[] = '('.implode(' OR ', $or_conditions).')';
}
// $and_conditions[] = $items_table.'.Status = 1';
$where_clause = implode(' AND ', $and_conditions);
if(!$where_clause)
{
if($having_clause)
{
$where_clause = '1';
}
else
{
$where_clause = '0';
$this->Application->SetVar('adv_search_error', 1);
}
}
$where_clause .= ' AND '.$items_table.'.Status = 1';
// building final search query
$search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search';
$this->Conn->Query('DROP TABLE IF EXISTS '.$search_table);
$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
$pick_field = isset($fields['EditorsPick']) ? $items_table.'.EditorsPick' : '0';
$sql = ' CREATE TABLE '.$search_table.'
SELECT '.$relevance_clause.' AS Relevance,
'.$items_table.'.'.$id_field.' AS ItemId,
'.$items_table.'.ResourceId AS ResourceId,
11 AS ItemType,
'.$pick_field.' AS EdPick
FROM '.$items_table.'
'.implode(' ', $join_clauses).'
WHERE '.$where_clause.'
GROUP BY '.$items_table.'.'.$id_field.
$having_clause;
$res = $this->Conn->Query($sql);
}
function getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, &$highlight_keywords)
{
$field = $record['FieldName'];
$condition_patterns = Array (
'any' => '%s LIKE %s',
'contains' => '%s LIKE %s',
'notcontains' => '(NOT (%1$s LIKE %2$s) OR %1$s IS NULL)',
'is' => '%s = %s',
'isnot' => '(%1$s != %2$s OR %1$s IS NULL)'
);
$condition = '';
switch ($record['FieldType']) {
case 'select':
$keywords[$field] = htmlspecialchars_decode( $keywords[$field] );
if ($keywords[$field]) {
$condition = sprintf($condition_patterns['is'], $field_name, $this->Conn->qstr( $keywords[$field] ));
}
break;
case 'multiselect':
$keywords[$field] = htmlspecialchars_decode( $keywords[$field] );
if ($keywords[$field]) {
$condition = Array ();
$values = explode('|', substr($keywords[$field], 1, -1));
foreach ($values as $selected_value) {
$condition[] = sprintf($condition_patterns['contains'], $field_name, $this->Conn->qstr('%|'.$selected_value.'|%'));
}
$condition = '('.implode(' OR ', $condition).')';
}
break;
case 'text':
$keywords[$field] = htmlspecialchars_decode( $keywords[$field] );
if (mb_strlen($keywords[$field]) >= $this->Application->ConfigValue('Search_MinKeyword_Length')) {
$highlight_keywords[] = $keywords[$field];
if (in_array($verbs[$field], Array('any', 'contains', 'notcontains'))) {
$keywords[$field] = '%'.strtr($keywords[$field], Array('%' => '\\%', '_' => '\\_')).'%';
}
$condition = sprintf($condition_patterns[$verbs[$field]], $field_name, $this->Conn->qstr( $keywords[$field] ));
}
break;
case 'boolean':
if ($keywords[$field] != -1) {
$property_mappings = $this->Application->getUnitOption($this->Prefix, 'ItemPropertyMappings');
$items_table = $this->Application->getUnitOption($this->Prefix, 'TableName');
switch ($field) {
case 'HotItem':
$hot_limit_var = getArrayValue($property_mappings, 'HotLimit');
if ($hot_limit_var) {
$hot_limit = (int)$this->Application->getDBCache($hot_limit_var);
$condition = 'IF('.$items_table.'.HotItem = 2,
IF('.$items_table.'.Hits >= '.
$hot_limit.
', 1, 0), '.$items_table.'.HotItem) = '.$keywords[$field];
}
break;
case 'PopItem':
$votes2pop_var = getArrayValue($property_mappings, 'VotesToPop');
$rating2pop_var = getArrayValue($property_mappings, 'RatingToPop');
if ($votes2pop_var && $rating2pop_var) {
$condition = 'IF('.$items_table.'.PopItem = 2, IF('.$items_table.'.CachedVotesQty >= '.
$this->Application->ConfigValue($votes2pop_var).
' AND '.$items_table.'.CachedRating >= '.
$this->Application->ConfigValue($rating2pop_var).
', 1, 0), '.$items_table.'.PopItem) = '.$keywords[$field];
}
break;
case 'NewItem':
$new_days_var = getArrayValue($property_mappings, 'NewDays');
if ($new_days_var) {
$condition = 'IF('.$items_table.'.NewItem = 2,
IF('.$items_table.'.CreatedOn >= (UNIX_TIMESTAMP() - '.
$this->Application->ConfigValue($new_days_var).
'*3600*24), 1, 0), '.$items_table.'.NewItem) = '.$keywords[$field];
}
break;
case 'EditorsPick':
$condition = $items_table.'.EditorsPick = '.$keywords[$field];
break;
}
}
break;
case 'range':
$range_conditions = Array();
if ($keywords[$field.'_from'] && !preg_match("/[^0-9]/i", $keywords[$field.'_from'])) {
$range_conditions[] = $field_name.' >= '.$keywords[$field.'_from'];
}
if ($keywords[$field.'_to'] && !preg_match("/[^0-9]/i", $keywords[$field.'_to'])) {
$range_conditions[] = $field_name.' <= '.$keywords[$field.'_to'];
}
if ($range_conditions) {
$condition = implode(' AND ', $range_conditions);
}
break;
case 'date':
if ($keywords[$field]) {
if (in_array($keywords[$field], Array('today', 'yesterday'))) {
$current_time = getdate();
$day_begin = adodb_mktime(0, 0, 0, $current_time['mon'], $current_time['mday'], $current_time['year']);
$time_mapping = Array('today' => $day_begin, 'yesterday' => ($day_begin - 86400));
$min_time = $time_mapping[$keywords[$field]];
}
else {
$time_mapping = Array (
'last_week' => 604800, 'last_month' => 2628000, 'last_3_months' => 7884000,
'last_6_months' => 15768000, 'last_year' => 31536000,
);
$min_time = adodb_mktime() - $time_mapping[$keywords[$field]];
}
$condition = $field_name.' > '.$min_time;
}
break;
}
return $condition;
}
/**
* Returns human readable representation of searched data to be placed in search log
* @param string $type
* @param Array $search_data
* @return string
* @access protected
*/
protected function getHuman($type, $search_data)
{
// all 3 variables are retrieved from $search_data array
/* @var $search_config Array */
/* @var $verb string */
/* @var $value string */
$type = ucfirst(strtolower($type));
extract($search_data);
switch ($type) {
case 'Field':
return $this->Application->Phrase($search_config['DisplayName']);
break;
case 'Verb':
return $verb ? $this->Application->Phrase('lu_advsearch_'.$verb) : '';
break;
case 'Value':
switch ($search_config['FieldType']) {
case 'date':
$values = Array(0 => 'lu_comm_Any', 'today' => 'lu_comm_Today',
'yesterday' => 'lu_comm_Yesterday', 'last_week' => 'lu_comm_LastWeek',
'last_month' => 'lu_comm_LastMonth', 'last_3_months' => 'lu_comm_Last3Months',
'last_6_months' => 'lu_comm_Last6Months', 'last_year' => 'lu_comm_LastYear');
$ret = $this->Application->Phrase($values[$value]);
break;
case 'range':
$value = explode('|', $value);
return $this->Application->Phrase('lu_comm_From').' "'.$value[0].'" '.$this->Application->Phrase('lu_comm_To').' "'.$value[1].'"';
break;
case 'boolean':
$values = Array(1 => 'lu_comm_Yes', 0 => 'lu_comm_No', -1 => 'lu_comm_Both');
$ret = $this->Application->Phrase($values[$value]);
break;
default:
$ret = $value;
break;
}
return '"'.$ret.'"';
break;
}
return '';
}
/**
* Set's correct page for list based on data provided with event
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetPagination(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBList */
// get PerPage (forced -> session -> config -> 10)
$object->SetPerPage($this->getPerPage($event));
// main lists on Front-End have special get parameter for page
$page = $object->isMainList() ? $this->Application->GetVar('page') : false;
if ( !$page ) {
// page is given in "env" variable for given prefix
$page = $this->Application->GetVar($event->getPrefixSpecial() . '_Page');
}
if ( !$page && $event->Special ) {
// when not part of env, then variables like "prefix.special_Page" are
// replaced (by PHP) with "prefix_special_Page", so check for that too
$page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page');
}
if ( !$object->isMainList() ) {
// main lists doesn't use session for page storing
$this->Application->StoreVarDefault($event->getPrefixSpecial() . '_Page', 1, true); // true for optional
if ( !$page ) {
if ( $this->Application->RewriteURLs() ) {
// when page not found by prefix+special, then try to search it without special at all
$page = $this->Application->GetVar($event->Prefix . '_Page');
if ( !$page ) {
// page not found in request -> get from session
$page = $this->Application->RecallVar($event->Prefix . '_Page');
}
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');
}
}
else {
// page found in request -> store in session
$this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional
}
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);
}
/* === RELATED TO IMPORT/EXPORT: BEGIN === */
/**
* 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;
}
$selected_cats_ids = $this->Application->GetVar('export_categories');
$this->Application->StoreVar($event->Prefix . '_export_ids', $selected_ids ? implode(',', $selected_ids) : '');
$this->Application->StoreVar($event->Prefix . '_export_cats_ids', $selected_cats_ids);
$export_helper = $this->Application->recallObject('CatItemExportHelper');
/* @var $export_helper kCatDBItemExportHelper */
$redirect_params = Array (
$this->Prefix . '.export_event' => 'OnNew',
'pass' => 'all,' . $this->Prefix . '.export'
);
$event->setRedirectParams($redirect_params);
}
/**
* Performs each export step & displays progress percent
*
* @param kEvent $event
*/
function OnExportProgress($event)
{
$export_object = $this->Application->recallObject('CatItemExportHelper');
/* @var $export_object kCatDBItemExportHelper */
$event = new kEvent($event->getPrefixSpecial().':OnDummy');
$action_method = 'perform'.ucfirst($event->Special);
$field_values = $export_object->$action_method($event);
// finish code is done from JS now
if ($field_values['start_from'] == $field_values['total_records']) {
if ($event->Special == 'import') {
$this->Application->StoreVar('PermCache_UpdateRequired', 1);
$event->SetRedirectParam('m_cat_id', $this->Application->RecallVar('ImportCategory'));
$event->SetRedirectParam('anchor', 'tab-' . $event->Prefix);
$event->redirect = 'catalog/catalog';
}
elseif ($event->Special == 'export') {
$event->redirect = $export_object->getModuleName($event) . '/' . $event->Special . '_finish';
$event->SetRedirectParam('pass', 'all');
}
return ;
}
$export_options = $export_object->loadOptions($event);
echo $export_options['start_from'] * 100 / $export_options['total_records'];
$event->status = kEvent::erSTOP;
}
/**
* Returns specific to each item type columns only
*
* @param kEvent $event
* @return Array
* @access protected
*/
public function getCustomExportColumns(kEvent $event)
{
return Array (
'__VIRTUAL__ThumbnailImage' => 'ThumbnailImage',
'__VIRTUAL__FullImage' => 'FullImage',
'__VIRTUAL__ImageAlt' => 'ImageAlt'
);
}
/**
* Sets non standart virtual fields (e.g. to other tables)
*
* @param kEvent $event
*/
function setCustomExportColumns($event)
{
$this->restorePrimaryImage($event);
}
/**
* Create/Update primary image record in info found in imported data
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function restorePrimaryImage($event)
{
$object = $event->getObject();
/* @var $object kCatDBItem */
$has_image_info = $object->GetDBField('ImageAlt') && ($object->GetDBField('ThumbnailImage') || $object->GetDBField('FullImage'));
if ( !$has_image_info ) {
return ;
}
$image_data = $object->getPrimaryImageData();
$image = $this->Application->recallObject('img', NULL, Array ('skip_autoload' => true));
/* @var $image kDBItem */
if ( $image_data ) {
$image->Load($image_data['ImageId']);
}
else {
$image->Clear();
$image->SetDBField('Name', 'main');
$image->SetDBField('DefaultImg', 1);
$image->SetDBField('ResourceId', $object->GetDBField('ResourceId'));
}
$image->SetDBField('AltName', $object->GetDBField('ImageAlt'));
if ( $object->GetDBField('ThumbnailImage') ) {
$thumbnail_field = $this->isURL($object->GetDBField('ThumbnailImage')) ? 'ThumbUrl' : 'ThumbPath';
$image->SetDBField($thumbnail_field, $object->GetDBField('ThumbnailImage'));
$image->SetDBField('LocalThumb', $thumbnail_field == 'ThumbPath' ? 1 : 0);
}
if ( !$object->GetDBField('FullImage') ) {
$image->SetDBField('SameImages', 1);
}
else {
$image->SetDBField('SameImages', 0);
$full_field = $this->isURL($object->GetDBField('FullImage')) ? 'Url' : 'LocalPath';
$image->SetDBField($full_field, $object->GetDBField('FullImage'));
$image->SetDBField('LocalImage', $full_field == 'LocalPath' ? 1 : 0);
}
if ( $image->isLoaded() ) {
$image->Update();
}
else {
$image->Create();
}
}
/**
* Detects if image url is specified in a given path (instead of path on disk)
*
* @param string $path
* @return bool
* @access protected
*/
protected function isURL($path)
{
return preg_match('#(http|https)://(.*)#', $path);
}
/**
* Prepares item for import/export operations
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnNew(kEvent $event)
{
parent::OnNew($event);
if ( $event->Special == 'import' || $event->Special == 'export' ) {
$export_helper = $this->Application->recallObject('CatItemExportHelper');
/* @var $export_helper kCatDBItemExportHelper */
$export_helper->setRequiredFields($event);
}
}
/**
* Process items selected in item_selector
*
* @param kEvent $event
*/
function OnProcessSelected($event)
{
$dst_field = $this->Application->RecallVar('dst_field');
$selected_ids = $this->Application->GetVar('selected_ids');
if ( $dst_field == 'ItemCategory' ) {
// Item Edit -> Categories Tab -> New Categories
$object = $event->getObject();
/* @var $object kCatDBItem */
$category_ids = explode(',', $selected_ids['c']);
foreach ($category_ids as $category_id) {
$object->assignToCategory($category_id);
}
}
if ($dst_field == 'ImportCategory') {
// Tools -> Import -> Item Import -> Select Import Category
$this->Application->StoreVar('ImportCategory', $selected_ids['c']);
$event->SetRedirectParam($event->getPrefixSpecial() . '_id', 0);
$event->SetRedirectParam($event->getPrefixSpecial() . '_event', 'OnExportBegin');
}
$event->SetRedirectParam('opener', 'u');
}
/**
* Saves Import/Export settings to session
*
* @param kEvent $event
*/
function OnSaveSettings($event)
{
$event->redirect = false;
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( $items_info ) {
list($id, $field_values) = each($items_info);
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object kDBItem */
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
$field_values['ImportFilename'] = $object->GetDBField('ImportFilename'); //if upload formatter has renamed the file during moving !!!
$field_values['ImportSource'] = 2;
$field_values['ImportLocalFilename'] = $object->GetDBField('ImportFilename');
$items_info[$id] = $field_values;
$this->Application->StoreVar($event->getPrefixSpecial() . '_ItemsInfo', serialize($items_info));
}
}
/**
* Saves Import/Export settings to session
*
* @param kEvent $event
*/
function OnResetSettings($event)
{
$this->Application->StoreVar('ImportCategory', $this->Application->getBaseCategory());
}
/**
* Cancels item editing
* @param kEvent $event
* @return void
* @todo Used?
*/
function OnCancelAction($event)
{
$event->redirect = $this->Application->GetVar('cancel_template');
$event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial());
}
/* === RELATED TO IMPORT/EXPORT: END === */
/**
* Stores item's owner login into separate field together with id
*
* @param kEvent $event
* @param string $id_field
* @param string $cached_field
*/
function cacheItemOwner($event, $id_field, $cached_field)
{
$object = $event->getObject();
/* @var $object kDBItem */
$object->SetDBField($cached_field, $object->GetField($id_field));
}
/**
* 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)
{
parent::OnPreSave($event);
$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
if ( $event->status == kEvent::erSUCCESS && $use_pending_editing ) {
// decision: clone or not clone
$object = $event->getObject();
/* @var $object kCatDBItem */
if ( $object->GetID() == 0 || $object->GetDBField('OrgId') > 0 ) {
// new items or cloned items shouldn't be cloned again
return ;
}
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
$owner_field = $this->getOwnerField($event->Prefix);
if ( $perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix) == 2 ) {
// 1. clone original item
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler');
/* @var $temp_handler kTempTablesHandler */
$cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array ($object->GetID()), NULL, NULL, NULL, true);
$ci_table = $this->Application->GetTempName(TABLE_PREFIX . 'CategoryItems');
// 2. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem
$sql = 'SELECT ResourceId
FROM ' . $object->TableName . '
WHERE ' . $object->IDField . ' = ' . $cloned_ids[0];
$clone_resource_id = $this->Conn->GetOne($sql);
$sql = 'DELETE FROM ' . $ci_table . '
WHERE ItemResourceId = ' . $clone_resource_id . ' AND PrimaryCat = 1';
$this->Conn->Query($sql);
// 3. copy main item categoryitems to cloned item
$sql = ' INSERT INTO ' . $ci_table . ' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename)
SELECT CategoryId, ' . $clone_resource_id . ' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename
FROM ' . $ci_table . '
WHERE ItemResourceId = ' . $object->GetDBField('ResourceId');
$this->Conn->Query($sql);
// 4. put cloned id to OrgId field of item being cloned
$sql = 'UPDATE ' . $object->TableName . '
SET OrgId = ' . $object->GetID() . '
WHERE ' . $object->IDField . ' = ' . $cloned_ids[0];
$this->Conn->Query($sql);
// 5. substitute id of item being cloned with clone id
$this->Application->SetVar($event->getPrefixSpecial() . '_id', $cloned_ids[0]);
$selected_ids = $this->getSelectedIDs($event, true);
$selected_ids[ array_search($object->GetID(), $selected_ids) ] = $cloned_ids[0];
$this->StoreSelectedIDs($event, $selected_ids);
// 6. delete original item from temp table
$temp_handler->DeleteItems($event->Prefix, $event->Special, Array ($object->GetID()));
}
}
}
/**
* Sets item's owner field
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreCreate(kEvent $event)
{
parent::OnPreCreate($event);
if ( $event->status != kEvent::erSUCCESS ) {
return ;
}
$object = $event->getObject();
/* @var $object kDBItem */
$owner_field = $this->getOwnerField($event->Prefix);
$object->SetDBField($owner_field, $this->Application->RecallVar('user_id'));
}
/**
* Occurs before original item of item in pending editing got deleted (for hooking only)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeDeleteOriginal(kEvent $event)
{
}
/**
* Occurs after original item of item in pending editing got deleted (for hooking only)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterDeleteOriginal(kEvent $event)
{
}
/**
* 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)
{
parent::OnBeforeClone($event);
$object = $event->getObject();
/* @var $object kDBItem */
$object->SetDBField('ResourceId', 0); // this will reset it
if ( $this->Application->GetVar('ResetCatBeforeClone') ) {
$object->SetDBField('CategoryId', NULL);
}
}
/**
* Set status for new category item based on user permission in category
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemCreate(kEvent $event)
{
parent::OnBeforeItemCreate($event);
$object = $event->getObject();
/* @var $object kCatDBItem */
$is_admin = $this->Application->isAdminUser;
$owner_field = $this->getOwnerField($event->Prefix);
if ( (!$object->IsTempTable() && !$is_admin) || ($is_admin && !$object->GetDBField($owner_field)) ) {
// Front-end OR owner not specified -> set to currently logged-in user
$object->SetDBField($owner_field, $this->Application->RecallVar('user_id'));
}
if ( !$this->Application->isAdmin ) {
$this->setItemStatusByPermission($event);
}
}
/**
* Sets category item status based on user permissions (only on Front-end)
*
* @param kEvent $event
*/
function setItemStatusByPermission($event)
{
$use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
if (!$use_pending_editing) {
return ;
}
$object = $event->getObject();
/* @var $object kCatDBItem */
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
$primary_category = $object->GetDBField('CategoryId') > 0 ? $object->GetDBField('CategoryId') : $this->Application->GetVar('m_cat_id');
$item_status = $perm_helper->AddCheckPermission($primary_category, $event->Prefix);
if ($item_status == STATUS_DISABLED) {
$event->status = kEvent::erFAIL;
}
else {
$object->SetDBField('Status', $item_status);
}
}
/**
* Creates category item & redirects to confirmation template (front-end only)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCreate(kEvent $event)
{
parent::OnCreate($event);
$this->SetFrontRedirectTemplate($event, 'suggest');
}
/**
* Returns item's categories (allows to exclude primary category)
*
* @param int $resource_id
* @param bool $with_primary
* @return Array
*/
function getItemCategories($resource_id, $with_primary = false)
{
$sql = 'SELECT CategoryId
FROM '.TABLE_PREFIX.'CategoryItems
WHERE (ItemResourceId = '.$resource_id.')';
if (!$with_primary) {
$sql .= ' AND (PrimaryCat = 0)';
}
return $this->Conn->GetCol($sql);
}
/**
* Adds new and removes old additional categories from category item
*
* @param kCatDBItem $object
* @param int $mode
*/
function processAdditionalCategories(&$object, $mode)
{
if ( !$object->isVirtualField('MoreCategories') ) {
// given category item doesn't require such type of processing
return ;
}
$process_categories = $object->GetDBField('MoreCategories');
if ($process_categories === '') {
// field was not in submit & have default value (when no categories submitted, then value is null)
return ;
}
if ($mode == 'create') {
// prevents first additional category to become primary
$object->assignPrimaryCategory();
}
$process_categories = $process_categories ? explode('|', substr($process_categories, 1, -1)) : Array ();
$existing_categories = $this->getItemCategories($object->GetDBField('ResourceId'));
$add_categories = array_diff($process_categories, $existing_categories);
foreach ($add_categories as $category_id) {
$object->assignToCategory($category_id);
}
$remove_categories = array_diff($existing_categories, $process_categories);
foreach ($remove_categories as $category_id) {
$object->removeFromCategory($category_id);
}
}
/**
* Creates category item & redirects to confirmation template (front-end only)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnUpdate(kEvent $event)
{
$use_pending = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing');
if ($this->Application->isAdminUser || !$use_pending) {
parent::OnUpdate($event);
$this->SetFrontRedirectTemplate($event, 'modify');
return ;
}
$object = $event->getObject(Array('skip_autoload' => true));
/* @var $object kCatDBItem */
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ($items_info) {
$perm_helper = $this->Application->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
$temp_handler = $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler');
/* @var $temp_handler kTempTablesHandler */
$owner_field = $this->getOwnerField($event->Prefix);
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
foreach ($items_info as $id => $field_values) {
$object->Load($id);
$edit_perm = $perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix);
if ($use_pending && !$object->GetDBField('OrgId') && ($edit_perm == STATUS_PENDING)) {
// pending editing enabled + not pending copy -> get/create pending copy & save changes to it
$original_id = $object->GetID();
$original_resource_id = $object->GetDBField('ResourceId');
$file_helper->PreserveItemFiles($field_values);
$object->Load($original_id, 'OrgId');
if (!$object->isLoaded()) {
// 1. user has no pending copy of live item -> clone live item
$cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array($original_id), NULL, NULL, NULL, true);
$object->Load($cloned_ids[0]);
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
+ $event->setEventParam('form_data', $field_values);
// 1a. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem
$ci_table = $this->Application->getUnitOption('ci', 'TableName');
$sql = 'DELETE FROM '.$ci_table.'
WHERE ItemResourceId = '.$object->GetDBField('ResourceId').' AND PrimaryCat = 1';
$this->Conn->Query($sql);
// 1b. copy main item categoryitems to cloned item
$sql = 'INSERT INTO '.$ci_table.' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename)
SELECT CategoryId, '.$object->GetDBField('ResourceId').' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename
FROM '.$ci_table.'
WHERE ItemResourceId = '.$original_resource_id;
$this->Conn->Query($sql);
// 1c. put cloned id to OrgId field of item being cloned
$object->SetDBField('Status', STATUS_PENDING_EDITING);
$object->SetDBField('OrgId', $original_id);
}
else {
// 2. user has pending copy of live item -> just update field values
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
+ $event->setEventParam('form_data', $field_values);
}
// update id in request (used for redirect in mod-rewrite mode)
$this->Application->SetVar($event->getPrefixSpecial().'_id', $object->GetID());
}
else {
// 3. already editing pending copy -> just update field values
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
+ $event->setEventParam('form_data', $field_values);
}
if ($object->Update()) {
$event->status = kEvent::erSUCCESS;
}
else {
$event->status = kEvent::erFAIL;
$event->redirect = false;
break;
}
}
}
$this->SetFrontRedirectTemplate($event, 'modify');
}
/**
* Sets next template to one required for front-end after adding/modifying item
*
* @param kEvent $event
* @param string $template_key - {suggest,modify}
*/
function SetFrontRedirectTemplate($event, $template_key)
{
if ( $this->Application->isAdmin || $event->status != kEvent::erSUCCESS ) {
return;
}
// prepare redirect template
$object = $event->getObject();
/* @var $object kDBItem */
$is_active = ($object->GetDBField('Status') == STATUS_ACTIVE);
$next_template = $is_active ? 'confirm_template' : 'pending_confirm_template';
$event->redirect = $this->Application->GetVar($template_key . '_' . $next_template);
$event->SetRedirectParam('opener', 's');
// send email events
$perm_prefix = $this->Application->getUnitOption($event->Prefix, 'PermItemPrefix');
$owner_field = $this->getOwnerField($event->Prefix);
$owner_id = $object->GetDBField($owner_field);
switch ( $event->Name ) {
case 'OnCreate':
$event_suffix = $is_active ? 'ADD' : 'ADD.PENDING';
$this->Application->emailAdmin($perm_prefix . '.' . $event_suffix); // there are no ADD.PENDING event for admin :(
$this->Application->emailUser($perm_prefix . '.' . $event_suffix, $owner_id);
break;
case 'OnUpdate':
$event_suffix = $is_active ? 'MODIFY' : 'MODIFY.PENDING';
$user_id = is_numeric($object->GetDBField('ModifiedById')) ? $object->GetDBField('ModifiedById') : $owner_id;
$this->Application->emailAdmin($perm_prefix . '.' . $event_suffix); // there are no ADD.PENDING event for admin :(
$this->Application->emailUser($perm_prefix . '.' . $event_suffix, $user_id);
break;
}
}
/**
* Apply same processing to each item being selected in grid
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function iterateItems(kEvent $event)
{
if ( $event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline' ) {
parent::iterateItems($event);
}
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return ;
}
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object kCatDBItem */
$ids = $this->StoreSelectedIDs($event);
if ( $ids ) {
foreach ($ids as $id) {
$ret = true;
$object->Load($id);
switch ( $event->Name ) {
case 'OnMassApprove':
$ret = $object->ApproveChanges();
break;
case 'OnMassDecline':
$ret = $object->DeclineChanges();
break;
}
if ( !$ret ) {
$event->status = kEvent::erFAIL;
$event->redirect = false;
break;
}
}
}
$this->clearSelectedIDs($event);
}
/**
* Deletes items & preserves clean env
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnDelete(kEvent $event)
{
parent::OnDelete($event);
if ( $event->status == kEvent::erSUCCESS && !$this->Application->isAdmin ) {
$event->SetRedirectParam('pass', 'm');
$event->SetRedirectParam('m_cat_id', 0);
}
}
/**
* Checks, that currently loaded item is allowed for viewing (non permission-based)
*
* @param kEvent $event
* @return bool
* @access protected
*/
protected function checkItemStatus(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
if ( !$object->isLoaded() ) {
if ( $event->Special != 'previous' && $event->Special != 'next' ) {
$this->_errorNotFound($event);
}
return true;
}
$status = $object->GetDBField('Status');
$user_id = $this->Application->RecallVar('user_id');
$owner_field = $this->getOwnerField($event->Prefix);
if ( ($status == STATUS_PENDING_EDITING || $status == STATUS_PENDING) && ($object->GetDBField($owner_field) == $user_id) ) {
return true;
}
return $status == STATUS_ACTIVE;
}
/**
* 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)
{
if ( !$this->Application->isAdmin ) {
$event->setEventParam('same_special', true);
}
$types = $event->getEventParam('types');
$types = $types ? explode(',', $types) : Array ();
if ( in_array('search', $types) ) {
$event->setPseudoClass('_List');
$object = $event->getObject();
/* @var $object kDBList */
// 1. no user sorting - sort by relevance
$default_sortings = parent::_getDefaultSorting($event);
$default_sorting = key($default_sortings['Sorting']) . ',' . current($default_sortings['Sorting']);
if ( $object->isMainList() ) {
$sort_by = $this->Application->GetVar('sort_by', '');
if ( !$sort_by ) {
$this->Application->SetVar('sort_by', 'Relevance,desc|' . $default_sorting);
}
elseif ( strpos($sort_by, 'Relevance,') !== false ) {
$this->Application->SetVar('sort_by', $sort_by . '|' . $default_sorting);
}
}
else {
$sorting_settings = $this->getListSetting($event, 'Sortings');
$sort_by = trim(getArrayValue($sorting_settings, 'Sort1') . ',' . getArrayValue($sorting_settings, 'Sort1_Dir'), ',');
if ( !$sort_by ) {
$event->setEventParam('sort_by', 'Relevance,desc|' . $default_sorting);
}
elseif ( strpos($sort_by, 'Relevance,') !== false ) {
$event->setEventParam('sort_by', $sort_by . '|' . $default_sorting);
}
}
$this->_removeForcedSortings($event);
}
parent::SetSorting($event);
}
/**
* Removes forced sortings
*
* @param kEvent $event
*/
protected function _removeForcedSortings(kEvent $event)
{
$list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings', Array ());
/* @var $list_sortings Array */
foreach ($list_sortings as $special => $sortings) {
unset($list_sortings[$special]['ForcedSorting']);
}
$this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings);
}
/**
* Default sorting in search results only comes from relevance field
*
* @param kEvent $event
* @return Array
* @access protected
*/
protected function _getDefaultSorting(kEvent $event)
{
$types = $event->getEventParam('types');
$types = $types ? explode(',', $types) : Array ();
return in_array('search', $types) ? Array () : parent::_getDefaultSorting($event);
}
/**
* Returns current per-page setting for list
*
* @param kEvent $event
* @return int
* @access protected
*/
protected function getPerPage(kEvent $event)
{
if ( !$this->Application->isAdmin ) {
$event->setEventParam('same_special', true);
}
return parent::getPerPage($event);
}
/**
* Returns owner field for given prefix
*
* @param $prefix
* @return string
* @access protected
*/
protected function getOwnerField($prefix)
{
return $this->Application->getUnitOption($prefix, 'OwnerField', 'CreatedById');
}
/**
* Creates virtual image fields for item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterConfigRead(kEvent $event)
{
parent::OnAfterConfigRead($event);
if (defined('IS_INSTALL') && IS_INSTALL) {
$this->addViewPermissionJoin($event);
return ;
}
if ( !$this->Application->isAdmin ) {
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
$file_helper->createItemFiles($event->Prefix, true); // create image fields
$file_helper->createItemFiles($event->Prefix, false); // create file fields
}
$this->changeSortings($event)->addViewPermissionJoin($event);
// add grids for advanced view (with primary category column)
$grids = $this->Application->getUnitOption($this->Prefix, 'Grids');
$process_grids = Array ('Default', 'Radio');
foreach ($process_grids as $process_grid) {
$grid_data = $grids[$process_grid];
$grid_data['Fields']['CachedNavbar'] = Array ('title' => 'la_col_Path', 'data_block' => 'grid_primary_category_td', 'filter_block' => 'grid_like_filter');
$grids[$process_grid . 'ShowAll'] = $grid_data;
}
$this->Application->setUnitOption($this->Prefix, 'Grids', $grids);
// add options for CategoryId field (quick way to select item's primary category)
$category_helper = $this->Application->recallObject('CategoryHelper');
/* @var $category_helper CategoryHelper */
$virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields');
$virtual_fields['CategoryId']['default'] = (int)$this->Application->GetVar('m_cat_id');
$virtual_fields['CategoryId']['options'] = $category_helper->getStructureTreeAsOptions();
$this->Application->setUnitOption($event->Prefix, 'VirtualFields', $virtual_fields);
}
/**
* Changes default sorting according to system settings.
*
* @param kEvent $event Event.
*
* @return self
* @access protected
*/
protected function changeSortings(kEvent $event)
{
$remove_sortings = Array ();
if ( !$this->Application->isAdmin ) {
// remove Pick sorting on Front-end, when not required
$config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping', Array ());
if ( !isset($config_mapping['ForceEditorPick']) || !$this->Application->ConfigValue($config_mapping['ForceEditorPick']) ) {
$remove_sortings[] = 'EditorsPick';
}
}
else {
// remove all forced sortings in Admin Console
$remove_sortings = array_merge($remove_sortings, Array ('Priority', 'EditorsPick'));
}
if ( !$remove_sortings ) {
return $this;
}
$list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings', Array ());
/* @var $list_sortings Array */
foreach ($list_sortings as $special => $sorting_fields) {
foreach ($remove_sortings as $sorting_field) {
unset($list_sortings[$special]['ForcedSorting'][$sorting_field]);
}
}
$this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings);
return $this;
}
/**
* Adds permission table table JOIN clause only, when advanced catalog view permissions enabled.
*
* @param kEvent $event Event.
*
* @return self
* @access protected
*/
protected function addViewPermissionJoin(kEvent $event)
{
if ( $this->Application->ConfigValue('CheckViewPermissionsInCatalog') ) {
$join_clause = 'LEFT JOIN ' . TABLE_PREFIX . 'CategoryPermissionsCache perm ON perm.CategoryId = ' . TABLE_PREFIX . '%3$sCategoryItems.CategoryId';
}
else {
$join_clause = '';
}
$list_sqls = $this->Application->getUnitOption($event->Prefix, 'ListSQLs');
/* @var $list_sqls array */
foreach ($list_sqls as $special => $list_sql) {
$list_sqls[$special] = str_replace('{PERM_JOIN}', $join_clause, $list_sql);
}
$this->Application->setUnitOption($event->Prefix, 'ListSQLs', $list_sqls);
return $this;
}
/**
* Returns file contents associated with item
*
* @param kEvent $event
*/
function OnDownloadFile($event)
{
$object = $event->getObject();
/* @var $object kCatDBItem */
$event->status = kEvent::erSTOP;
$field = $this->Application->GetVar('field');
if (!preg_match('/^File([\d]+)/', $field)) {
return ;
}
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
$filename = $object->GetField($field, 'full_path');
$file_helper->DownloadFile($filename);
}
/**
* Saves user's vote
*
* @param kEvent $event
*/
function OnMakeVote($event)
{
$event->status = kEvent::erSTOP;
if ($this->Application->GetVar('ajax') != 'yes') {
// this is supposed to call from AJAX only
return ;
}
$rating_helper = $this->Application->recallObject('RatingHelper');
/* @var $rating_helper RatingHelper */
$object = $event->getObject( Array ('skip_autoload' => true) );
/* @var $object kCatDBItem */
$object->Load( $this->Application->GetVar('id') );
echo $rating_helper->makeVote($object);
}
/**
* Marks review as useful
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnReviewHelpful($event)
{
if ( $this->Application->GetVar('ajax') == 'yes' ) {
$event->status = kEvent::erSTOP;
}
$review_id = (int)$this->Application->GetVar('review_id');
if ( !$review_id ) {
return;
}
$spam_helper = $this->Application->recallObject('SpamHelper');
/* @var $spam_helper SpamHelper */
$spam_helper->InitHelper($review_id, 'ReviewHelpful', strtotime('+1 month') - strtotime('now'));
$field = (int)$this->Application->GetVar('helpful') ? 'HelpfulCount' : 'NotHelpfulCount';
$sql = 'SELECT ' . $field . '
FROM ' . $this->Application->getUnitOption('rev', 'TableName') . '
WHERE ' . $this->Application->getUnitOption('rev', 'IDField') . ' = ' . $review_id;
$count = $this->Conn->GetOne($sql);
if ( $spam_helper->InSpamControl() ) {
if ( $this->Application->GetVar('ajax') == 'yes' ) {
echo $count;
}
return;
}
$sql = 'UPDATE ' . $this->Application->getUnitOption('rev', 'TableName') . '
SET ' . $field . ' = ' . $field . ' + 1
WHERE ' . $this->Application->getUnitOption('rev', 'IDField') . ' = ' . $review_id;
$this->Conn->Query($sql);
if ( $this->Conn->getAffectedRows() ) {
// db was changed -> review with such ID exists
$spam_helper->AddToSpamControl();
}
if ( $this->Application->GetVar('ajax') == 'yes' ) {
echo $count + 1;
}
}
/**
* [HOOK] Allows to add cloned subitem to given prefix
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCloneSubItem(kEvent $event)
{
parent::OnCloneSubItem($event);
if ( $event->MasterEvent->Prefix == 'fav' ) {
$clones = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'Clones');
$subitem_prefix = $event->Prefix . '-' . $event->MasterEvent->Prefix;
$clones[$subitem_prefix]['ParentTableKey'] = 'ResourceId';
$clones[$subitem_prefix]['ForeignKey'] = 'ResourceId';
$this->Application->setUnitOption($event->MasterEvent->Prefix, 'Clones', $clones);
}
}
/**
* Set's new unique resource id to user
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemValidate(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$resource_id = $object->GetDBField('ResourceId');
if ( !$resource_id ) {
$object->SetDBField('ResourceId', $this->Application->NextResourceId());
}
}
}
\ No newline at end of file
Index: branches/5.2.x/core/units/translator/translator_event_handler.php
===================================================================
--- branches/5.2.x/core/units/translator/translator_event_handler.php (revision 15780)
+++ branches/5.2.x/core/units/translator/translator_event_handler.php (revision 15781)
@@ -1,181 +1,182 @@
<?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 TranslatorEventHandler extends kDBEventHandler
{
/**
* Allows to override standard permission mapping
*
* @return void
* @access protected
* @see kEventHandler::$permMapping
*/
protected function mapPermissions()
{
parent::mapPermissions();
$permissions = Array(
'OnChangeLanguage' => Array('subitem' => 'add|edit'),
'OnSaveAndClose' => Array('subitem' => 'add|edit'),
);
$this->permMapping = array_merge($this->permMapping, $permissions);
}
/**
* Check permission of item, that being translated
*
* @param kEvent $event
* @return bool
* @access public
*/
public function CheckPermission(kEvent $event)
{
list($prefix, ) = $this->getPrefixAndField($event);
$top_prefix = $this->Application->GetTopmostPrefix($prefix, true);
$event->setEventParam('top_prefix', $top_prefix);
return parent::CheckPermission($event);
}
/**
* Returns prefix and field being translated
*
* @param kEvent $event
*/
function getPrefixAndField($event)
{
$field = $this->Application->GetVar($event->getPrefixSpecial(true).'_field');
if (strpos($field,':') !== false) {
list($prefix, $field) = explode(':', $field);
}
else {
$prefix = $this->Application->GetVar($event->getPrefixSpecial(true).'_prefix');
}
return Array($prefix, $field);
}
/**
* Loads record to be translated
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnLoad($event)
{
list($obj_prefix, $field) = $this->getPrefixAndField($event);
$object = $this->Application->recallObject($obj_prefix);
/* @var $object kDBItem */
$translator = $event->getObject();
/* @var $translator kDBItem */
$def_lang = $this->Application->GetDefaultLanguageId();
$current_lang = $translator->GetDBField('Language');
if (!$current_lang) $current_lang = $this->Application->RecallVar('trans_lang');
if (!$current_lang) $current_lang = $this->Application->GetVar('m_lang');
/*if ($current_lang == $def_lang) {
$current_lang = $def_lang + 1;
}*/
$this->Application->StoreVar('trans_lang', $current_lang); //remember translation language for user friendlyness
$translator->SetID(1);
$translator->SetDBField('Original', $object->GetDBField('l'.$this->Application->GetVar('m_lang').'_'.$field));
$translator->SetDBField('Language', $current_lang);
$translator->SetDBField('SwitchLanguage', $current_lang);
$translator->SetDBField('Translation', $object->GetDBField('l'.$current_lang.'_'.$field));
$cur_lang = $this->Application->recallObject('lang.current');
/* @var $cur_lang LanguagesItem */
$cur_lang->Load($current_lang);
$translator->SetDBField('Charset', CHARSET);
$event->redirect = false;
}
/**
* Saves changes into temporary table and closes editing window
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSaveAndClose($event)
{
$event->CallSubEvent('OnPreSave');
$event->SetRedirectParam('opener', 'u');
}
/**
* 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)
{
$translator = $event->getObject();
/* @var $translator kDBItem */
$field_values = $this->getSubmittedFields($event);
$translator->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
+ $event->setEventParam('form_data', $field_values);
list($obj_prefix, $field) = $this->getPrefixAndField($event);
$object = $this->Application->recallObject($obj_prefix);
/* @var $object kDBItem */
$lang = $translator->GetDBField('Language');
$object->SetFieldOptions('l' . $lang . '_' . $field, Array ());
$object->SetDBField('l' . $lang . '_' . $field, $translator->GetDBField('Translation'));
$this->RemoveRequiredFields($object);
$object->Update();
}
/**
* Changes current language in translation popup
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnChangeLanguage($event)
{
$event->CallSubEvent('OnPreSave');
$object = $event->getObject();
/* @var $object kDBItem */
$object->SetDBField('Language', $object->GetDBField('SwitchLanguage'));
$event->CallSubEvent('OnLoad');
$event->redirect = false;
}
}
\ No newline at end of file
Index: branches/5.2.x/core/units/users/users_event_handler.php
===================================================================
--- branches/5.2.x/core/units/users/users_event_handler.php (revision 15780)
+++ branches/5.2.x/core/units/users/users_event_handler.php (revision 15781)
@@ -1,1935 +1,1936 @@
<?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 UsersEventHandler extends kDBEventHandler
{
/**
* Allows to override standard permission mapping
*
* @return void
* @access protected
* @see kEventHandler::$permMapping
*/
protected function mapPermissions()
{
parent::mapPermissions();
$permissions = Array (
// admin
'OnSetPersistantVariable' => Array('self' => 'view'), // because setting to logged in user only
'OnUpdatePassword' => Array('self' => true),
'OnSaveSelected' => Array ('self' => 'view'),
'OnGeneratePassword' => Array ('self' => 'view'),
// front
'OnRefreshForm' => Array('self' => true),
'OnForgotPassword' => Array('self' => true),
'OnSubscribeQuery' => Array('self' => true),
'OnSubscribeUser' => Array('self' => true),
'OnRecommend' => Array('self' => true),
'OnItemBuild' => Array('self' => true),
'OnMassResetSettings' => Array('self' => 'edit'),
'OnMassCloneUsers' => Array('self' => 'add'),
);
$this->permMapping = array_merge($this->permMapping, $permissions);
}
/**
* Returns fields, that are not allowed to be changed from request
*
* @param Array $hash
* @return Array
* @access protected
*/
protected function getRequestProtectedFields($hash)
{
$fields = parent::getRequestProtectedFields($hash);
$fields = array_merge($fields, Array ('PrevEmails', 'ResourceId', 'IPAddress', 'IsBanned', 'PwResetConfirm', 'PwRequestTime', 'OldStyleLogin'));
if ( !$this->Application->isAdmin ) {
$fields = array_merge($fields, Array ('UserType', 'Status', 'EmailVerified', 'IsBanned'));
}
return $fields;
}
/**
* Builds item (loads if needed)
*
* Pattern: Prototype Manager
*
* @param kEvent $event
* @access protected
*/
protected function OnItemBuild(kEvent $event)
{
parent::OnItemBuild($event);
$object = $event->getObject();
/* @var $object kDBItem */
if ( $event->Special == 'forgot' || $object->getFormName() == 'registration' ) {
$this->_makePasswordRequired($event);
}
}
/**
* Shows only admins when required
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetCustomQuery(kEvent $event)
{
parent::SetCustomQuery($event);
$object = $event->getObject();
/* @var $object kDBList */
if ( $event->Special == 'regular' ) {
$object->addFilter('primary_filter', '%1$s.UserType = ' . UserType::USER);
}
if ( $event->Special == 'admins' ) {
$object->addFilter('primary_filter', '%1$s.UserType = ' . UserType::ADMIN);
}
if ( !$this->Application->isAdminUser ) {
$object->addFilter('status_filter', '%1$s.Status = ' . STATUS_ACTIVE);
}
if ( $event->Special == 'online' ) {
$object->addFilter('online_users_filter', 's.PortalUserId IS NOT NULL');
}
if ( $event->Special == 'group' ) {
$group_id = $this->Application->GetVar('g_id');
if ( $group_id !== false ) {
// show only users, that user doesn't belong to current group
$sql = 'SELECT PortalUserId
FROM ' . $this->Application->GetTempName(TABLE_PREFIX . 'UserGroupRelations', 'prefix:g') . '
WHERE GroupId = ' . (int)$group_id;
$user_ids = $this->Conn->GetCol($sql);
if ( $user_ids ) {
$object->addFilter('already_member_filter', '%1$s.PortalUserId NOT IN (' . implode(',', $user_ids) . ')');
}
}
}
}
/**
* Checks user permission to execute given $event
*
* @param kEvent $event
* @return bool
* @access public
*/
public function CheckPermission(kEvent $event)
{
if ( $event->Name == 'OnLogin' || $event->Name == 'OnLoginAjax' || $event->Name == 'OnLogout' ) {
// permission is checked in OnLogin event directly
return true;
}
if ( $event->Name == 'OnResetRootPassword' ) {
return defined('DBG_RESET_ROOT') && DBG_RESET_ROOT;
}
if ( $event->Name == 'OnLoginAs' ) {
$admin_session = $this->Application->recallObject('Session.admin');
/* @var $admin_session Session */
return $admin_session->LoggedIn();
}
if ( !$this->Application->isAdminUser ) {
$user_id = $this->Application->RecallVar('user_id');
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( ($event->Name == 'OnCreate' || $event->Name == 'OnRegisterAjax') && $user_id == USER_GUEST ) {
// "Guest" can create new users
return true;
}
if ( $event->Name == 'OnUpdate' && $user_id > 0 ) {
$user_dummy = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true));
/* @var $user_dummy UsersItem */
foreach ($items_info as $id => $field_values) {
if ( $id != $user_id ) {
// registered users can update their record only
return false;
}
$user_dummy->Load($id);
$status_field = $user_dummy->getStatusField();
if ( $user_dummy->GetDBField($status_field) != STATUS_ACTIVE ) {
// not active user is not allowed to update his record (he could not activate himself manually)
return false;
}
if ( isset($field_values[$status_field]) && $user_dummy->GetDBField($status_field) != $field_values[$status_field] ) {
// user can't change status by himself
return false;
}
}
return true;
}
if ( $event->Name == 'OnResetLostPassword' && $event->Special == 'forgot' && $user_id == USER_GUEST ) {
// non-logged in users can reset their password, when reset code is valid
return is_numeric($this->getPassedID($event));
}
if ( $event->Name == 'OnUpdate' && $user_id <= 0 ) {
// guests are not allowed to update their record, because they don't have it :)
return false;
}
}
return parent::CheckPermission($event);
}
/**
* Handles session expiration (redirects to valid template)
*
* @param kEvent $event
*/
function OnSessionExpire($event)
{
$this->Application->resetCounters('UserSessions');
// place 2 of 2 (also in kHTTPQuery::getRedirectParams)
$admin_url_params = Array (
'm_cat_id' => 0, // category means nothing on admin login screen
'm_wid' => '', // remove wid, otherwise parent window may add wid to its name breaking all the frameset (for <a> targets)
'pass' => 'm', // don't pass any other (except "m") prefixes to admin session expiration template
'expired' => 1, // expiration mark to show special error on login screen
'no_pass_through' => 1, // this way kApplication::HREF won't add them again
);
if ($this->Application->isAdmin) {
$this->Application->Redirect('index', $admin_url_params, '', 'index.php');
}
if ($this->Application->GetVar('admin') == 1) {
// Front-End showed in admin's right frame
$session_admin = $this->Application->recallObject('Session.admin');
/* @var $session_admin Session */
if (!$session_admin->LoggedIn()) {
// front-end session created from admin session & both expired
$this->Application->DeleteVar('admin');
$this->Application->Redirect('index', $admin_url_params, '', 'admin/index.php');
}
}
// Front-End session expiration
$get = $this->Application->HttpQuery->getRedirectParams();
$t = $this->Application->GetVar('t');
$get['js_redirect'] = $this->Application->ConfigValue('UseJSRedirect');
$this->Application->Redirect($t ? $t : 'index', $get);
}
/**
* [SCHEDULED TASK] Deletes expired sessions
*
* @param kEvent $event
*/
function OnDeleteExpiredSessions($event)
{
if (defined('IS_INSTALL') && IS_INSTALL) {
return ;
}
$this->Application->Session->DeleteExpired();
}
/**
* Checks user data and logs it in if allowed
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnLogin($event)
{
$object = $event->getObject( Array ('form_name' => 'login') );
/* @var $object kDBItem */
$field_values = $this->getSubmittedFields($event);
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
$username = $object->GetDBField('UserLogin');
$password = $object->GetDBField('UserPassword');
$remember_login = $object->GetDBField('UserRememberLogin') == 1;
/* @var $user_helper UserHelper */
$user_helper = $this->Application->recallObject('UserHelper');
$user_helper->event =& $event;
$result = $user_helper->loginUser($username, $password, false, $remember_login);
if ($result != LoginResult::OK) {
$event->status = kEvent::erFAIL;
$object->SetError('UserLogin', $result == LoginResult::NO_PERMISSION ? 'no_permission' : 'invalid_password');
}
if ( is_object($event->MasterEvent) && ($event->MasterEvent->Name == 'OnLoginAjax') ) {
// used to insert just logged-in user e-mail on "One Step Checkout" form in "Modern Store" theme
$user =& $user_helper->getUserObject();
$event->SetRedirectParam('user_email', $user->GetDBField('Email'));
}
}
/**
* Performs user login from ajax request
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnLoginAjax($event)
{
$ajax_form_helper = $this->Application->recallObject('AjaxFormHelper');
/* @var $ajax_form_helper AjaxFormHelper */
$ajax_form_helper->transitEvent($event, 'OnLogin'); //, Array ('do_refresh' => 1));
}
/**
* [HOOK] Auto-Logins Front-End user when "Remember Login" cookie is found
*
* @param kEvent $event
*/
function OnAutoLoginUser($event)
{
$remember_login_cookie = $this->Application->GetVar('remember_login');
if (!$remember_login_cookie || $this->Application->isAdmin || $this->Application->LoggedIn()) {
return ;
}
/* @var $user_helper UserHelper */
$user_helper = $this->Application->recallObject('UserHelper');
$user_helper->loginUser('', '', false, false, $remember_login_cookie);
}
/**
* Called when user logs in using old in-portal
*
* @param kEvent $event
*/
function OnInpLogin($event)
{
$sync_manager = $this->Application->recallObject('UsersSyncronizeManager', null, Array(), Array ('InPortalSyncronize'));
/* @var $sync_manager UsersSyncronizeManager */
$sync_manager->performAction('LoginUser', $event->getEventParam('user'), $event->getEventParam('pass') );
if ($event->redirect && is_string($event->redirect)) {
// some real template specified instead of true
$this->Application->Redirect($event->redirect, $event->getRedirectParams());
}
}
/**
* Called when user logs in using old in-portal
*
* @param kEvent $event
*/
function OnInpLogout($event)
{
$sync_manager = $this->Application->recallObject('UsersSyncronizeManager', null, Array(), Array ('InPortalSyncronize'));
/* @var $sync_manager UsersSyncronizeManager */
$sync_manager->performAction('LogoutUser');
}
/**
* Performs user logout
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnLogout($event)
{
/* @var $user_helper UserHelper */
$user_helper = $this->Application->recallObject('UserHelper');
$user_helper->event =& $event;
$user_helper->logoutUser();
}
/**
* Redirects user after successful registration to confirmation template (on Front only)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemCreate(kEvent $event)
{
parent::OnAfterItemCreate($event);
$this->afterItemChanged($event);
$this->assignToPrimaryGroup($event);
}
/**
* Performs user registration
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnCreate(kEvent $event)
{
if ( $this->Application->isAdmin ) {
parent::OnCreate($event);
return ;
}
$object = $event->getObject( Array('form_name' => 'registration') );
/* @var $object UsersItem */
$field_values = $this->getSubmittedFields($event);
$user_email = getArrayValue($field_values, 'Email');
$subscriber_id = $user_email ? $this->getSubscriberByEmail($user_email) : false;
if ( $subscriber_id ) {
// update existing subscriber
$object->Load($subscriber_id);
$object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_NewGroup'));
$this->Application->SetVar($event->getPrefixSpecial(true), Array ($object->GetID() => $field_values));
}
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
+ $event->setEventParam('form_data', $field_values);
$status = $object->isLoaded() ? $object->Update() : $object->Create();
if ( !$status ) {
$event->status = kEvent::erFAIL;
$event->redirect = false;
$object->setID( (int)$object->GetID() );
}
$this->setNextTemplate($event, true);
if ( ($event->status == kEvent::erSUCCESS) && $event->redirect ) {
$this->assignToPrimaryGroup($event);
$object->sendEmails();
$this->autoLoginUser($event);
}
}
/**
* Processes user registration from ajax request
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnRegisterAjax(kEvent $event)
{
$ajax_form_helper = $this->Application->recallObject('AjaxFormHelper');
/* @var $ajax_form_helper AjaxFormHelper */
$ajax_form_helper->transitEvent($event, 'OnCreate', Array ('do_refresh' => 1));
}
/**
* Returns subscribed user ID by given e-mail address
*
* @param string $email
* @return int|bool
* @access protected
*/
protected function getSubscriberByEmail($email)
{
$verify_user = $this->Application->recallObject('u.verify', null, Array ('skip_autoload' => true));
/* @var $verify_user UsersItem */
$verify_user->Load($email, 'Email');
return $verify_user->isLoaded() && $verify_user->isSubscriberOnly() ? $verify_user->GetID() : false;
}
/**
* Login user if possible, if not then redirect to corresponding template
*
* @param kEvent $event
*/
function autoLoginUser($event)
{
$object = $event->getObject();
/* @var $object UsersItem */
if ( $object->GetDBField('Status') == STATUS_ACTIVE ) {
/* @var $user_helper UserHelper */
$user_helper = $this->Application->recallObject('UserHelper');
$user =& $user_helper->getUserObject();
$user->Load($object->GetID());
if ( $user_helper->checkLoginPermission() ) {
$user_helper->loginUserById( $user->GetID() );
}
}
}
/**
* Set's new unique resource id to user
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemCreate(kEvent $event)
{
parent::OnBeforeItemCreate($event);
$this->beforeItemChanged($event);
$cs_helper = $this->Application->recallObject('CountryStatesHelper');
/* @var $cs_helper kCountryStatesHelper */
$object = $event->getObject();
/* @var $object UsersItem */
if ( !$object->isSubscriberOnly() ) {
// don't check state-to-country relations for subscribers
$cs_helper->CheckStateField($event, 'State', 'Country');
}
if ( $object->getFormName() != 'login' ) {
$this->_makePasswordRequired($event);
}
$cs_helper->PopulateStates($event, 'State', 'Country');
$this->setUserGroup($object);
/* @var $user_helper UserHelper */
$user_helper = $this->Application->recallObject('UserHelper');
if ( !$user_helper->checkBanRules($object) ) {
$object->SetError('Username', 'banned');
}
$object->SetDBField('IPAddress', $this->Application->getClientIp());
if ( !$this->Application->isAdmin ) {
$object->SetDBField('FrontLanguage', $this->Application->GetVar('m_lang'));
}
}
/**
* Sets primary group of the user
*
* @param kDBItem $object
*/
protected function setUserGroup(&$object)
{
if ($object->Special == 'subscriber') {
$object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_SubscriberGroup'));
return ;
}
// set primary group to user
if ( !$this->Application->isAdminUser ) {
$group_id = $object->GetDBField('PrimaryGroupId');
if ($group_id) {
// check, that group is allowed for Front-End
$sql = 'SELECT GroupId
FROM ' . TABLE_PREFIX . 'UserGroups
WHERE GroupId = ' . (int)$group_id . ' AND FrontRegistration = 1';
$group_id = $this->Conn->GetOne($sql);
}
if (!$group_id) {
// when group not selected OR not allowed -> use default group
$object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_NewGroup'));
}
}
}
/**
* Assigns a user to it's primary group
*
* @param kEvent $event
*/
protected function assignToPrimaryGroup($event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$primary_group_id = $object->GetDBField('PrimaryGroupId');
if ($primary_group_id) {
$ug_table = TABLE_PREFIX . 'UserGroupRelations';
if ( $object->IsTempTable() ) {
$ug_table = $this->Application->GetTempName($ug_table, 'prefix:' . $event->Prefix);
}
$fields_hash = Array (
'PortalUserId' => $object->GetID(),
'GroupId' => $primary_group_id,
);
$this->Conn->doInsert($fields_hash, $ug_table, 'REPLACE');
}
}
/**
* Set's new unique resource id to user
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemValidate(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$resource_id = $object->GetDBField('ResourceId');
if ( !$resource_id ) {
$object->SetDBField('ResourceId', $this->Application->NextResourceId());
}
}
/**
* Enter description here...
*
* @param kEvent $event
*/
function OnRecommend($event)
{
$object = $event->getObject( Array ('form_name' => 'recommend') );
/* @var $object kDBItem */
$field_values = $this->getSubmittedFields($event);
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
if ( !$object->ValidateField('RecommendEmail') ) {
$event->status = kEvent::erFAIL;
return ;
}
$send_params = Array (
'to_email' => $object->GetDBField('RecommendEmail'),
'to_name' => $object->GetDBField('RecommendEmail'),
);
$user_id = $this->Application->RecallVar('user_id');
$email_sent = $this->Application->emailUser('USER.SUGGEST', $user_id, $send_params);
$this->Application->emailAdmin('USER.SUGGEST');
if ( $email_sent ) {
$event->SetRedirectParam('pass', 'all');
$event->redirect = $this->Application->GetVar('template_success');
}
else {
$event->status = kEvent::erFAIL;
$object->SetError('RecommendEmail', 'send_error');
}
}
/**
* Saves address changes and mades no redirect
*
* @param kEvent $event
*/
function OnUpdateAddress($event)
{
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object kDBItem */
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( $items_info ) {
list ($id, $field_values) = each($items_info);
if ( $id > 0 ) {
$object->Load($id);
}
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
$object->setID($id);
$object->Validate();
}
$cs_helper = $this->Application->recallObject('CountryStatesHelper');
/* @var $cs_helper kCountryStatesHelper */
$cs_helper->PopulateStates($event, 'State', 'Country');
$event->redirect = false;
}
/**
* Validate subscriber's email & store it to session -> redirect to confirmation template
*
* @param kEvent $event
*/
function OnSubscribeQuery($event)
{
$object = $event->getObject( Array ('form_name' => 'subscription') );
/* @var $object UsersItem */
$field_values = $this->getSubmittedFields($event);
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
if ( !$object->ValidateField('SubscriberEmail') ) {
$event->status = kEvent::erFAIL;
return ;
}
$user_email = $object->GetDBField('SubscriberEmail');
$object->Load($user_email, 'Email');
$event->SetRedirectParam('subscriber_email', $user_email);
if ( $object->isLoaded() && $object->isSubscribed() ) {
$event->redirect = $this->Application->GetVar('unsubscribe_template');
}
else {
$event->redirect = $this->Application->GetVar('subscribe_template');
}
$event->SetRedirectParam('pass', 'm');
}
/**
* Subscribe/Unsubscribe user based on email stored in previous step
*
* @param kEvent $event
*/
function OnSubscribeUser($event)
{
$object = $event->getObject( Array ('form_name' => 'subscription') );
/* @var $object UsersItem */
$user_email = $this->Application->GetVar('subscriber_email');
$object->SetDBField('SubscriberEmail', $user_email);
if ( !$object->ValidateField('SubscriberEmail') ) {
$event->status = kEvent::erFAIL;
return ;
}
$username_required = $object->isRequired('Username');
$this->RemoveRequiredFields($object);
$object->Load($user_email, 'Email');
if ( $object->isLoaded() ) {
if ( $object->isSubscribed() ) {
if ( $event->getEventParam('no_unsubscribe') ) {
// for customization code from FormsEventHandler
return ;
}
if ( $object->isSubscriberOnly() ) {
$temp_handler = $this->Application->recallObject($event->Prefix . '_TempHandler', 'kTempTablesHandler');
/* @var $temp_handler kTempTablesHandler */
$temp_handler->DeleteItems($event->Prefix, '', Array($object->GetID()));
}
else {
$this->RemoveSubscriberGroup( $object->GetID() );
}
$event->redirect = $this->Application->GetVar('unsubscribe_ok_template');
}
else {
$this->AddSubscriberGroup($object);
$event->redirect = $this->Application->GetVar('subscribe_ok_template');
}
}
else {
$object->generatePassword();
$object->SetDBField('Email', $user_email);
if ( $username_required ) {
$object->SetDBField('Username', str_replace('@', '_at_', $user_email));
}
$object->SetDBField('Status', STATUS_ACTIVE); // make user subscriber Active by default
if ( $object->Create() ) {
$this->AddSubscriberGroup($object);
$event->redirect = $this->Application->GetVar('subscribe_ok_template');
}
}
}
/**
* Adding user to subscribers group
*
* @param UsersItem $object
*/
function AddSubscriberGroup(&$object)
{
if ( !$object->isSubscriberOnly() ) {
$fields_hash = Array (
'PortalUserId' => $object->GetID(),
'GroupId' => $this->Application->ConfigValue('User_SubscriberGroup'),
);
$this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'UserGroupRelations');
}
$this->Application->emailAdmin('USER.SUBSCRIBE');
$this->Application->emailUser('USER.SUBSCRIBE', $object->GetID());
}
/**
* Removing user from subscribers group
*
* @param int $user_id
*/
function RemoveSubscriberGroup($user_id)
{
$group_id = $this->Application->ConfigValue('User_SubscriberGroup');
$sql = 'DELETE FROM ' . TABLE_PREFIX . 'UserGroupRelations
WHERE PortalUserId = ' . $user_id . ' AND GroupId = ' . $group_id;
$this->Conn->Query($sql);
$this->Application->emailAdmin('USER.UNSUBSCRIBE');
$this->Application->emailUser('USER.UNSUBSCRIBE', $user_id);
}
/**
* Validates forgot password form and sends password reset confirmation e-mail
*
* @param kEvent $event
* @return void
*/
function OnForgotPassword($event)
{
$object = $event->getObject( Array ('form_name' => 'forgot_password') );
/* @var $object kDBItem */
$field_values = $this->getSubmittedFields($event);
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
$user = $this->Application->recallObject('u.tmp', null, Array ('skip_autoload' => true));
/* @var $user UsersItem */
$found = $allow_reset = false;
$email_or_username = $object->GetDBField('ForgotLogin');
$is_email = strpos($email_or_username, '@') !== false;
if ( strlen($email_or_username) ) {
$user->Load($email_or_username, $is_email ? 'Email' : 'Username');
}
if ( $user->isLoaded() ) {
$min_pwd_reset_delay = $this->Application->ConfigValue('Users_AllowReset');
$found = ($user->GetDBField('Status') == STATUS_ACTIVE) && strlen($user->GetDBField('Password'));
if ( !$user->GetDBField('PwResetConfirm') ) {
// no reset made -> allow
$allow_reset = true;
}
else {
// reset made -> wait N minutes, then allow
$allow_reset = TIMENOW > $user->GetDBField('PwRequestTime') + $min_pwd_reset_delay;
}
}
if ( $found && $allow_reset ) {
$this->Application->emailUser('USER.PSWDC', $user->GetID());
$event->redirect = $this->Application->GetVar('template_success');
return;
}
if ( strlen($email_or_username) ) {
$object->SetError('ForgotLogin', $found ? 'reset_denied' : ($is_email ? 'unknown_email' : 'unknown_username'));
}
if ( !$object->ValidateField('ForgotLogin') ) {
$event->status = kEvent::erFAIL;
}
}
/**
* Updates kDBItem
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnUpdate(kEvent $event)
{
parent::OnUpdate($event);
if ( !$this->Application->isAdmin ) {
$this->setNextTemplate($event);
}
}
/**
* Checks state against country
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemUpdate(kEvent $event)
{
parent::OnBeforeItemUpdate($event);
$this->beforeItemChanged($event);
$cs_helper = $this->Application->recallObject('CountryStatesHelper');
/* @var $cs_helper kCountryStatesHelper */
$cs_helper->CheckStateField($event, 'State', 'Country');
$cs_helper->PopulateStates($event, 'State', 'Country');
$object = $event->getObject();
/* @var $object kDBItem */
if ( $event->Special == 'forgot' ) {
$object->SetDBField('PwResetConfirm', '');
$object->SetDBField('PwRequestTime_date', NULL);
$object->SetDBField('PwRequestTime_time', NULL);
}
$changed_fields = array_keys($object->GetChangedFields());
if ( $changed_fields && !in_array('Modified', $changed_fields) ) {
$object->SetDBField('Modified_date', adodb_mktime());
$object->SetDBField('Modified_time', adodb_mktime());
}
if ( !$this->Application->isAdmin && in_array('Email', $changed_fields) && ($event->Special != 'email-restore') ) {
$object->SetDBField('EmailVerified', 0);
}
}
/**
* Occurs before item is changed
*
* @param kEvent $event
*/
function beforeItemChanged($event)
{
$object = $event->getObject();
/* @var $object UsersItem */
if ( !$this->Application->isAdmin && $object->getFormName() == 'registration' ) {
// sets new user's status based on config options
$status_map = Array (1 => STATUS_ACTIVE, 2 => STATUS_DISABLED, 3 => STATUS_PENDING, 4 => STATUS_PENDING);
$object->SetDBField('Status', $status_map[ $this->Application->ConfigValue('User_Allow_New') ]);
if ( $this->Application->ConfigValue('User_Password_Auto') ) {
$object->generatePassword( rand(5, 8) );
}
if ( $this->Application->ConfigValue('RegistrationCaptcha') ) {
$captcha_helper = $this->Application->recallObject('CaptchaHelper');
/* @var $captcha_helper kCaptchaHelper */
$captcha_helper->validateCode($event, false);
}
if ( $event->Name == 'OnBeforeItemUpdate' ) {
// when a subscriber-only users performs normal registration, then assign him to Member group
$this->setUserGroup($object);
}
}
}
/**
* Sets redirect template based on user status & user request contents
*
* @param kEvent $event
* @param bool $for_registration
*/
function setNextTemplate($event, $for_registration = false)
{
$event->SetRedirectParam('opener', 's');
$object = $event->getObject();
/* @var $object UsersItem */
$next_template = false;
if ( $object->GetDBField('Status') == STATUS_ACTIVE && $this->Application->GetVar('next_template') ) {
$next_template = $this->Application->GetVar('next_template');
}
elseif ( $for_registration ) {
switch ( $this->Application->ConfigValue('User_Allow_New') ) {
case 1: // Immediate
$next_template = $this->Application->GetVar('registration_confirm_template');
break;
case 3: // Upon Approval
case 4: // Email Activation
$next_template = $this->Application->GetVar('registration_confirm_pending_template');
break;
}
}
if ($next_template) {
$event->redirect = $next_template;
}
}
/**
* Delete users from groups if their membership is expired
*
* @param kEvent $event
*/
function OnCheckExpiredMembership($event)
{
// send pre-expiration reminders: begin
$pre_expiration = adodb_mktime() + $this->Application->ConfigValue('User_MembershipExpirationReminder') * 3600 * 24;
$sql = 'SELECT PortalUserId, GroupId
FROM '.TABLE_PREFIX.'UserGroupRelations
WHERE (MembershipExpires IS NOT NULL) AND (ExpirationReminderSent = 0) AND (MembershipExpires < '.$pre_expiration.')';
$skip_clause = $event->getEventParam('skip_clause');
if ($skip_clause) {
$sql .= ' AND !('.implode(') AND !(', $skip_clause).')';
}
$records = $this->Conn->Query($sql);
if ($records) {
$conditions = Array();
foreach ($records as $record) {
$this->Application->emailUser('USER.MEMBERSHIP.EXPIRATION.NOTICE', $record['PortalUserId']);
$this->Application->emailAdmin('USER.MEMBERSHIP.EXPIRATION.NOTICE');
$conditions[] = '(PortalUserId = '.$record['PortalUserId'].' AND GroupId = '.$record['GroupId'].')';
}
$sql = 'UPDATE '.TABLE_PREFIX.'UserGroupRelations
SET ExpirationReminderSent = 1
WHERE '.implode(' OR ', $conditions);
$this->Conn->Query($sql);
}
// send pre-expiration reminders: end
// remove users from groups with expired membership: begin
$sql = 'SELECT PortalUserId
FROM '.TABLE_PREFIX.'UserGroupRelations
WHERE (MembershipExpires IS NOT NULL) AND (MembershipExpires < '.adodb_mktime().')';
$user_ids = $this->Conn->GetCol($sql);
if ($user_ids) {
foreach ($user_ids as $id) {
$this->Application->emailUser('USER.MEMBERSHIP.EXPIRED', $id);
$this->Application->emailAdmin('USER.MEMBERSHIP.EXPIRED');
}
}
$sql = 'DELETE FROM '.TABLE_PREFIX.'UserGroupRelations
WHERE (MembershipExpires IS NOT NULL) AND (MembershipExpires < '.adodb_mktime().')';
$this->Conn->Query($sql);
// remove users from groups with expired membership: end
}
/**
* Used to keep user registration form data, while showing affiliate registration form fields
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnRefreshForm($event)
{
$event->redirect = false;
$item_info = $this->Application->GetVar( $event->getPrefixSpecial(true) );
list($id, $field_values) = each($item_info);
$object = $event->getObject( Array ('skip_autoload' => true) );
/* @var $object kDBItem */
$object->setID($id);
$object->IgnoreValidation = true;
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
}
/**
* Sets persistant variable
*
* @param kEvent $event
*/
function OnSetPersistantVariable($event)
{
$field = $this->Application->GetVar('field');
$value = $this->Application->GetVar('value');
$this->Application->StorePersistentVar($field, $value);
$force_tab = $this->Application->GetVar('SetTab');
if ($force_tab) {
$this->Application->StoreVar('force_tab', $force_tab);
}
}
/**
* Return user from order by special .ord
*
* @param kEvent $event
* @return int
* @access public
*/
public function getPassedID(kEvent $event)
{
switch ($event->Special) {
case 'ord':
$order = $this->Application->recallObject('ord');
/* @var $order OrdersItem */
return $order->GetDBField('PortalUserId');
break;
case 'profile':
$id = $this->Application->GetVar('user_id');
if ( !$id ) {
// if none user_id given use current user id
$id = $this->Application->RecallVar('user_id');
}
return $id;
break;
case 'forgot':
/* @var $user_helper UserHelper */
$user_helper = $this->Application->recallObject('UserHelper');
$id = $user_helper->validateUserCode($this->Application->GetVar('user_key'), 'forgot_password');
if ( is_numeric($id) ) {
return $id;
}
break;
}
if ( preg_match('/^(login|register|recommend|subscribe|forgot)/', $event->Special) ) {
// this way we can have 2+ objects stating with same special, e.g. "u.login-sidebox" and "u.login-main"
return USER_GUEST;
}
return parent::getPassedID($event);
}
/**
* Allows to change root password
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnUpdatePassword($event)
{
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
if ( !$items_info ) {
return;
}
list ($id, $field_values) = each($items_info);
$user_id = $this->Application->RecallVar('user_id');
if ( $id == $user_id && ($user_id > 0 || $user_id == USER_ROOT) ) {
$user_dummy = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true));
/* @var $user_dummy kDBItem */
$user_dummy->Load($id);
$status_field = $user_dummy->getStatusField();
if ( $user_dummy->GetDBField($status_field) != STATUS_ACTIVE ) {
// not active user is not allowed to update his record (he could not activate himself manually)
return ;
}
}
if ( $user_id == USER_ROOT ) {
$object = $event->getObject(Array ('skip_autoload' => true));
/* @var $object UsersItem */
// this is internal hack to allow root/root passwords for dev
if ( $this->Application->isDebugMode() && $field_values['RootPassword'] == 'root' ) {
$object->SetFieldOption('RootPassword', 'min_length', 4);
}
$this->RemoveRequiredFields($object);
$object->SetDBField('RootPassword', $this->Application->ConfigValue('RootPass'));
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
$object->setID(-1);
if ( $object->Validate() ) {
// validation on, password match too
$fields_hash = Array ('VariableValue' => $object->GetDBField('RootPassword'));
$conf_table = $this->Application->getUnitOption('conf', 'TableName');
$this->Conn->doUpdate($fields_hash, $conf_table, 'VariableName = "RootPass"');
$event->SetRedirectParam('opener', 'u');
}
else {
$event->status = kEvent::erFAIL;
$event->redirect = false;
return ;
}
}
else {
$object = $event->getObject();
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
if ( !$object->Update() ) {
$event->status = kEvent::erFAIL;
$event->redirect = false;
}
}
$event->SetRedirectParam('opener', 'u');
}
/**
* Resets grid settings, remembered in each user record
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnMassResetSettings($event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return;
}
$ids = $this->StoreSelectedIDs($event);
$default_user_id = $this->Application->ConfigValue('DefaultSettingsUserId');
if ( in_array($default_user_id, $ids) ) {
array_splice($ids, array_search($default_user_id, $ids), 1);
}
if ( $ids ) {
$q = 'DELETE FROM ' . TABLE_PREFIX . 'UserPersistentSessionData WHERE PortalUserId IN (' . join(',', $ids) . ') AND
(VariableName LIKE "%_columns_%"
OR
VariableName LIKE "%_filter%"
OR
VariableName LIKE "%_PerPage%")';
$this->Conn->Query($q);
}
$this->clearSelectedIDs($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)
{
$object = $event->getObject();
/* @var $object kDBItem */
if ( !$object->isLoaded() ) {
return true;
}
$virtual_users = Array (USER_ROOT, USER_GUEST);
return ($object->GetDBField('Status') == STATUS_ACTIVE) || in_array($object->GetID(), $virtual_users);
}
/**
* Sends approved/declined email event on user status change
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemUpdate(kEvent $event)
{
parent::OnAfterItemUpdate($event);
$this->afterItemChanged($event);
$object = $event->getObject();
/* @var $object UsersItem */
if ( !$this->Application->isAdmin && ($event->Special != 'email-restore') ) {
$this->sendEmailChangeEvent($event);
}
if ( !$this->Application->isAdmin || $object->IsTempTable() ) {
return;
}
$this->sendStatusChangeEvent($object->GetID(), $object->GetOriginalField('Status'), $object->GetDBField('Status'));
}
/**
* Occurs, after item is changed
*
* @param kEvent $event
*/
protected function afterItemChanged($event)
{
$this->saveUserImages($event);
$object = $event->getObject();
/* @var $object UsersItem */
if ( $object->GetDBField('EmailPassword') && $object->GetDBField('Password_plain') ) {
$email_passwords = $this->Application->RecallVar('email_passwords');
$email_passwords = $email_passwords ? unserialize($email_passwords) : Array ();
$email_passwords[ $object->GetID() ] = $object->GetDBField('Password_plain');
$this->Application->StoreVar('email_passwords', serialize($email_passwords));
}
// update user subscription status (via my profile or new user registration)
if ( !$this->Application->isAdmin && !$object->isSubscriberOnly() ) {
if ( $object->GetDBField('SubscribeToMailing') && !$object->isSubscribed() ) {
$this->AddSubscriberGroup($object);
}
elseif ( !$object->GetDBField('SubscribeToMailing') && $object->isSubscribed() ) {
$this->RemoveSubscriberGroup( $object->GetID() );
}
}
}
/**
* Stores user's original Status before overwriting with data from temp table
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeDeleteFromLive(kEvent $event)
{
parent::OnBeforeDeleteFromLive($event);
$user_id = $event->getEventParam('id');
$user_status = $this->Application->GetVar('user_status', Array ());
if ( $user_id > 0 ) {
$user_status[$user_id] = $this->getUserStatus($user_id);
$this->Application->SetVar('user_status', $user_status);
}
}
/**
* Sends approved/declined email event on user status change (in temp tables during editing)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterCopyToLive(kEvent $event)
{
parent::OnAfterCopyToLive($event);
$temp_id = $event->getEventParam('temp_id');
$email_passwords = $this->Application->RecallVar('email_passwords');
if ( $email_passwords ) {
$email_passwords = unserialize($email_passwords);
if ( isset($email_passwords[$temp_id]) ) {
$object = $event->getObject();
/* @var $object kDBItem */
$object->SwitchToLive();
$object->Load( $event->getEventParam('id') );
$object->SetField('Password', $email_passwords[$temp_id]);
$object->SetField('VerifyPassword', $email_passwords[$temp_id]);
$this->Application->emailUser($temp_id > 0 ? 'USER.NEW.PASSWORD': 'USER.ADD.BYADMIN', $object->GetID());
unset($email_passwords[$temp_id]);
$this->Application->StoreVar('email_passwords', serialize($email_passwords));
}
}
if ( $temp_id > 0 ) {
// only send status change e-mail on user update
$new_status = $this->getUserStatus($temp_id);
$user_status = $this->Application->GetVar('user_status');
$this->sendStatusChangeEvent($temp_id, $user_status[$temp_id], $new_status);
}
}
/**
* Returns user status (active, pending, disabled) based on ID and temp mode setting
*
* @param int $user_id
* @return int
*/
function getUserStatus($user_id)
{
$id_field = $this->Application->getUnitOption($this->Prefix, 'IDField');
$table_name = $this->Application->getUnitOption($this->Prefix, 'TableName');
$sql = 'SELECT Status
FROM '.$table_name.'
WHERE '.$id_field.' = '.$user_id;
return $this->Conn->GetOne($sql);
}
/**
* Sends approved/declined email event on user status change
*
* @param int $user_id
* @param int $prev_status
* @param int $new_status
*/
function sendStatusChangeEvent($user_id, $prev_status, $new_status)
{
$status_events = Array (
STATUS_ACTIVE => 'USER.APPROVE',
STATUS_DISABLED => 'USER.DENY',
);
$email_event = isset($status_events[$new_status]) ? $status_events[$new_status] : false;
if (($prev_status != $new_status) && $email_event) {
$this->Application->emailUser($email_event, $user_id);
$this->Application->emailAdmin($email_event);
}
// deletes sessions from users, that are no longer active
if (($prev_status != $new_status) && ($new_status != STATUS_ACTIVE)) {
$sql = 'SELECT SessionKey
FROM ' . TABLE_PREFIX . 'UserSessions
WHERE PortalUserId = ' . $user_id;
$session_ids = $this->Conn->GetCol($sql);
$this->Application->Session->DeleteSessions($session_ids);
}
}
/**
* Sends restore/validation email event on user email change
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function sendEmailChangeEvent(kEvent $event)
{
$object = $event->getObject();
/* @var $object UsersItem */
$new_email = $object->GetDBField('Email');
$prev_email = $object->GetOriginalField('Email');
if ( !$new_email || ($prev_email == $new_email) ) {
return;
}
$prev_emails = $object->GetDBField('PrevEmails');
$prev_emails = $prev_emails ? unserialize($prev_emails) : Array ();
$fields_hash = Array (
'PrevEmails' => serialize($prev_emails),
'EmailVerified' => 0,
);
$user_id = $object->GetID();
if ( $prev_email ) {
$hash = md5(TIMENOW + $user_id);
$prev_emails[$hash] = $prev_email;
$fields_hash['PrevEmails'] = serialize($prev_emails);
$send_params = Array (
'hash' => $hash,
'to_email' => $prev_email,
'to_name' => trim($object->GetDBField('FirstName') . ' ' . $object->GetDBField('LastName')),
);
$this->Application->emailUser('USER.EMAIL.CHANGE.UNDO', null, $send_params);
}
if ( $new_email ) {
$this->Application->emailUser('USER.EMAIL.CHANGE.VERIFY', $user_id);
}
// direct DB update, since USER.EMAIL.CHANGE.VERIFY puts verification code in user record, that we don't want to loose
$this->Conn->doUpdate($fields_hash, $object->TableName, 'PortalUserId = ' . $user_id);
}
/**
* OnAfterConfigRead for users
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterConfigRead(kEvent $event)
{
parent::OnAfterConfigRead($event);
$forms = $this->Application->getUnitOption($event->Prefix, 'Forms');
$form_fields =& $forms['default']['Fields'];
// 1. arrange user registration countries
$site_helper = $this->Application->recallObject('SiteHelper');
/* @var $site_helper SiteHelper */
$first_country = $site_helper->getDefaultCountry('', false);
if ($first_country === false) {
$first_country = $this->Application->ConfigValue('User_Default_Registration_Country');
}
if ($first_country) {
// update user country dropdown sql
$form_fields['Country']['options_sql'] = preg_replace('/ORDER BY (.*)/', 'ORDER BY IF (CountryStateId = '.$first_country.', 1, 0) DESC, \\1', $form_fields['Country']['options_sql']);
}
// 2. set default user registration group
$form_fields['PrimaryGroupId']['default'] = $this->Application->ConfigValue('User_NewGroup');
// 3. allow avatar upload on Front-End
$file_helper = $this->Application->recallObject('FileHelper');
/* @var $file_helper FileHelper */
$file_helper->createItemFiles($event->Prefix, true); // create image fields
if ($this->Application->isAdminUser) {
// 4. when in administrative console, then create all users with Active status
$form_fields['Status']['default'] = STATUS_ACTIVE;
// 5. remove groups tab on editing forms when AdvancedUserManagement config variable not set
if (!$this->Application->ConfigValue('AdvancedUserManagement')) {
$edit_tab_presets = $this->Application->getUnitOption($event->Prefix, 'EditTabPresets');
foreach ($edit_tab_presets as $preset_name => $preset_tabs) {
if (array_key_exists('groups', $preset_tabs)) {
unset($edit_tab_presets[$preset_name]['groups']);
if (count($edit_tab_presets[$preset_name]) == 1) {
// only 1 tab left -> remove it too
$edit_tab_presets[$preset_name] = Array ();
}
}
}
$this->Application->setUnitOption($event->Prefix, 'EditTabPresets', $edit_tab_presets);
}
}
if ( $this->Application->ConfigValue('RegistrationUsernameRequired') ) {
// Username becomes required only, when it's used in registration process
$max_username = $this->Application->ConfigValue('MaxUserName');
$form_fields['Username']['required'] = 1;
$form_fields['Username']['min_len'] = $this->Application->ConfigValue('Min_UserName');
$form_fields['Username']['max_len'] = $max_username ? $max_username : 255;
}
$this->Application->setUnitOption($event->Prefix, 'Forms', $forms);
}
/**
* OnMassCloneUsers
*
* @param kEvent $event
*/
function OnMassCloneUsers($event)
{
if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) {
$event->status = kEvent::erFAIL;
return;
}
$temp_handler = $this->Application->recallObject($event->Prefix.'_TempHandler', 'kTempTablesHandler');
/* @var $temp_handler kTempTablesHandler */
$ids = $this->StoreSelectedIDs($event);
$temp_handler->CloneItems($event->Prefix, '', $ids);
$this->clearSelectedIDs($event);
}
/**
* When cloning users, reset password (set random)
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeClone(kEvent $event)
{
parent::OnBeforeClone($event);
$object = $event->getObject();
/* @var $object UsersItem */
$object->generatePassword();
$object->SetDBField('ResourceId', 0); // this will reset it
// change email because it should be unique
$object->NameCopy(Array (), $object->GetID(), 'Email', 'copy%1$s.%2$s');
}
/**
* Saves selected ids to session
*
* @param kEvent $event
*/
function OnSaveSelected($event)
{
$this->StoreSelectedIDs($event);
// remove current ID, otherwise group selector will use it in filters
$this->Application->DeleteVar($event->getPrefixSpecial(true) . '_id');
}
/**
* Sets primary group of selected users
*
* @param kEvent $event
*/
function OnProcessSelected($event)
{
$event->SetRedirectParam('opener', 'u');
$user_ids = $this->getSelectedIDs($event, true);
$this->clearSelectedIDs($event);
$dst_field = $this->Application->RecallVar('dst_field');
if ( $dst_field != 'PrimaryGroupId' ) {
return;
}
$group_ids = array_keys($this->Application->GetVar('g'));
$primary_group_id = $group_ids ? array_shift($group_ids) : false;
if ( !$user_ids || !$primary_group_id ) {
return;
}
$table_name = $this->Application->getUnitOption('ug', 'TableName');
// 1. mark group as primary
$sql = 'UPDATE ' . TABLE_PREFIX . 'Users
SET PrimaryGroupId = ' . $primary_group_id . '
WHERE PortalUserId IN (' . implode(',', $user_ids) . ')';
$this->Conn->Query($sql);
$sql = 'SELECT PortalUserId
FROM ' . $table_name . '
WHERE (GroupId = ' . $primary_group_id . ') AND (PortalUserId IN (' . implode(',', $user_ids) . '))';
$existing_members = $this->Conn->GetCol($sql);
// 2. add new members to a group
$new_members = array_diff($user_ids, $existing_members);
foreach ($new_members as $user_id) {
$fields_hash = Array (
'GroupId' => $primary_group_id,
'PortalUserId' => $user_id,
);
$this->Conn->doInsert($fields_hash, $table_name);
}
}
/**
* Loads user images
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemLoad(kEvent $event)
{
parent::OnAfterItemLoad($event);
// linking existing images for item with virtual fields
$image_helper = $this->Application->recallObject('ImageHelper');
/* @var $image_helper ImageHelper */
$object = $event->getObject();
/* @var $object UsersItem */
$image_helper->LoadItemImages($object);
$cs_helper = $this->Application->recallObject('CountryStatesHelper');
/* @var $cs_helper kCountryStatesHelper */
$cs_helper->PopulateStates($event, 'State', 'Country');
// get user subscription status
$object->SetDBField('SubscribeToMailing', $object->isSubscribed() ? 1 : 0);
if ( !$this->Application->isAdmin ) {
$object->SetFieldOption('FrontLanguage', 'options', $this->getEnabledLanguages());
}
}
/**
* Returns list of enabled languages with their names
*
* @return Array
* @access protected
*/
protected function getEnabledLanguages()
{
$cache_key = 'user_languages[%LangSerial%]';
$ret = $this->Application->getCache($cache_key);
if ( $ret === false ) {
$languages = $this->Application->recallObject('lang.enabled', 'lang_List');
/* @var $languages kDBList */
$ret = Array ();
foreach ($languages as $language_info) {
$ret[$languages->GetID()] = $language_info['LocalName'];
}
$this->Application->setCache($cache_key, $ret);
}
return $ret;
}
/**
* Save user images
*
* @param kEvent $event
*/
function saveUserImages($event)
{
if (!$this->Application->isAdmin) {
$image_helper = $this->Application->recallObject('ImageHelper');
/* @var $image_helper ImageHelper */
$object = $event->getObject();
/* @var $object kDBItem */
// process image upload in virtual fields
$image_helper->SaveItemImages($object);
}
}
/**
* Makes password required for new users
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreCreate(kEvent $event)
{
parent::OnPreCreate($event);
if ( $event->status != kEvent::erSUCCESS ) {
return;
}
$object = $event->getObject();
/* @var $object kDBItem */
$user_type = $this->Application->GetVar('user_type');
if ( $user_type ) {
$object->SetDBField('UserType', $user_type);
if ( $user_type == UserType::ADMIN ) {
$object->SetDBField('PrimaryGroupId', $this->Application->ConfigValue('User_AdminGroup'));
}
}
if ( $this->Application->ConfigValue('User_Password_Auto') ) {
$object->SetDBField('EmailPassword', 1);
}
$this->_makePasswordRequired($event);
}
/**
* Makes password required for new users
*
* @param kEvent $event
*/
function _makePasswordRequired($event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$required_fields = Array ('Password', 'Password_plain', 'VerifyPassword', 'VerifyPassword_plain');
$object->setRequired($required_fields);
}
/**
* Load item if id is available
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function LoadItem(kEvent $event)
{
$id = $this->getPassedID($event);
if ( $id < 0 ) {
// when root, guest and so on
$object = $event->getObject();
/* @var $object kDBItem */
$object->Clear($id);
return;
}
parent::LoadItem($event);
}
/**
* Occurs just after login (for hooking)
*
* @param kEvent $event
*/
function OnAfterLogin($event)
{
if ( is_object($event->MasterEvent) && !$this->Application->isAdmin ) {
$event->MasterEvent->SetRedirectParam('login', 1);
}
}
/**
* Occurs just before logout (for hooking)
*
* @param kEvent $event
*/
function OnBeforeLogout($event)
{
if ( is_object($event->MasterEvent) && !$this->Application->isAdmin ) {
$event->MasterEvent->SetRedirectParam('logout', 1);
}
}
/**
* Generates password
*
* @param kEvent $event
*/
function OnGeneratePassword($event)
{
$event->status = kEvent::erSTOP;
if ( $this->Application->isAdminUser ) {
echo kUtil::generatePassword();
}
}
/**
* Changes user's password and logges him in
*
* @param kEvent $event
*/
function OnResetLostPassword($event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$event->CallSubEvent('OnUpdate');
if ( $event->status == kEvent::erSUCCESS ) {
/* @var $user_helper UserHelper */
$user_helper = $this->Application->recallObject('UserHelper');
$user =& $user_helper->getUserObject();
$user->Load( $object->GetID() );
if ( $user_helper->checkLoginPermission() ) {
$user_helper->loginUserById( $user->GetID() );
}
}
}
/**
* Generates new Root password and email it
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnResetRootPassword($event)
{
$password_formatter = $this->Application->recallObject('kPasswordFormatter');
/* @var $password_formatter kPasswordFormatter */
$new_root_password = kUtil::generatePassword();
$this->Application->SetConfigValue('RootPass', $password_formatter->hashPassword($new_root_password));
$this->Application->emailAdmin('ROOT.RESET.PASSWORD', null, Array ('password' => $new_root_password));
$event->SetRedirectParam('reset', 1);
$event->SetRedirectParam('pass', 'm');
}
/**
* Perform login of user, selected in Admin Console, on Front-End in a separate window
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnLoginAs(kEvent $event)
{
/* @var $user_helper UserHelper */
$user_helper = $this->Application->recallObject('UserHelper');
$user =& $user_helper->getUserObject();
$user->Load( $this->Application->GetVar('user_id') );
if ( !$user->isLoaded() ) {
return ;
}
if ( $user_helper->checkLoginPermission() ) {
$user_helper->loginUserById( $user->GetID() );
}
}
}
Index: branches/5.2.x/core/units/configuration/configuration_event_handler.php
===================================================================
--- branches/5.2.x/core/units/configuration/configuration_event_handler.php (revision 15780)
+++ branches/5.2.x/core/units/configuration/configuration_event_handler.php (revision 15781)
@@ -1,567 +1,568 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class ConfigurationEventHandler extends kDBEventHandler {
/**
* Allows to override standard permission mapping
*
* @return void
* @access protected
* @see kEventHandler::$permMapping
*/
protected function mapPermissions()
{
parent::mapPermissions();
$permissions = Array (
'OnGenerateMaintenancePage' => Array ('self' => 'add|edit'),
);
$this->permMapping = array_merge($this->permMapping, $permissions);
}
/**
* Changes permission section to one from REQUEST, not from config
*
* @param kEvent $event
* @return bool
* @access public
*/
public function CheckPermission(kEvent $event)
{
$event->setEventParam('PermSection', $this->Application->GetVar('section'));
return parent::CheckPermission($event);
}
/**
* Apply any custom changes to list's sql query
*
* @param kEvent $event
* @return void
* @access protected
* @see kDBEventHandler::OnListBuild()
*/
protected function SetCustomQuery(kEvent $event)
{
parent::SetCustomQuery($event);
$object = $event->getObject();
/* @var $object kDBList */
$module = $this->Application->GetVar('module');
$section = $this->Application->GetVar('section');
$object->addFilter('module_filter', '%1$s.ModuleOwner = ' . $this->Conn->qstr($module));
$object->addFilter('section_filter', '%1$s.Section = ' . $this->Conn->qstr($section));
$can_change = $this->Application->ConfigValue('AllowAdminConsoleInterfaceChange');
if ( !$can_change && !$this->Application->isDebugMode() ) {
$object->addFilter('interface_change_filter', '%1$s.VariableName NOT IN ("AdminConsoleInterface", "AllowAdminConsoleInterfaceChange")');
}
if ( defined('IS_INSTALL') && IS_INSTALL ) {
$object->addFilter('install_filter', '%1$s.Install = 1');
}
$object->addFilter('visible_filter', '%1$s.Heading <> ""');
}
/**
* Presets new system setting fields
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnPreCreate(kEvent $event)
{
parent::OnPreCreate($event);
$object = $event->getObject();
/* @var $object kDBItem */
$object->SetDBField('Section', $this->Application->GetVar('section'));
$object->SetDBField('ModuleOwner', $this->Application->GetVar('module'));
}
/**
* Sets custom validation
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemLoad(kEvent $event)
{
static $default_field_options = null;
parent::OnAfterItemLoad($event);
$object = $event->getObject();
/* @var $object kDBItem */
// ability to validate each configuration variable separately
if ( !isset($default_field_options) ) {
$default_field_options = $object->GetFieldOptions('VariableValue');
}
$new_field_options = $default_field_options;
$validation = $object->GetDBField('Validation');
if ( $validation ) {
$new_field_options = array_merge($new_field_options, unserialize($validation));
}
$object->SetFieldOptions('VariableValue', $new_field_options);
}
/**
* Performs custom validation
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemValidate(kEvent $event)
{
parent::OnBeforeItemValidate($event);
$object = $event->getObject();
/* @var $object kDBItem */
// if password field is empty, then don't update
if ( $object->GetDBField('ElementType') == 'password' ) {
if ( trim($object->GetDBField('VariableValue')) != '' ) {
$password_formatter = $this->Application->recallObject('kPasswordFormatter');
/* @var $password_formatter kPasswordFormatter */
$object->SetDBField('VariableValue', $password_formatter->hashPassword($object->GetDBField('VariableValue')));
}
}
$this->_processCountryState($event);
$variable_name = $object->GetDBField('VariableName');
$new_value = $object->GetDBField('VariableValue');
if ( $variable_name == 'AdminConsoleInterface' ) {
$can_change = $this->Application->ConfigValue('AllowAdminConsoleInterfaceChange');
if ( ($new_value != $object->GetOriginalField('VariableValue')) && !$can_change ) {
$object->SetError('VariableValue', 'not_allowed', 'la_error_OperationNotAllowed');
}
}
elseif ( $variable_name == 'HardMaintenanceTemplate' ) {
$compile = $event->MasterEvent->getEventParam('compile_maintenance_template');
$compile = $compile || $new_value != $object->GetOriginalField('VariableValue');
if ( $compile && !$this->_generateMaintenancePage($new_value) ) {
$object->SetError('VariableValue', 'template_file_missing', 'la_error_TemplateFileMissing');
}
}
elseif ( $variable_name == 'DefaultEmailRecipients' ) {
$email_event_data = $this->Application->GetVar('email-template_' . $event->Prefix);
$object->SetDBField('VariableValue', $email_event_data[0]['Recipients']);
}
$sections_helper = $this->Application->recallObject('SectionsHelper');
/* @var $sections_helper kSectionsHelper */
$section = $object->GetDBField('Section');
if ( $section && !$sections_helper->getSectionData($section) ) {
$object->SetError('Section', 'unknown_section');
}
}
/**
* Checks, that state belongs to selected country
*
* @param kEvent $event
* @access protected
*/
protected function _processCountryState(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$country_iso = $this->_getCountryByState($event);
$state_name = $object->GetDBField('VariableValue');
if ( !$country_iso || !$state_name ) {
return;
}
$cs_helper = $this->Application->recallObject('CountryStatesHelper');
/* @var $cs_helper kCountryStatesHelper */
$state_iso = $cs_helper->getStateIso($state_name, $country_iso);
if ( $state_iso !== false ) {
$object->SetDBField('VariableValue', $state_iso);
}
else {
// selected state doesn't belong to selected country
$object->SetError('VariableValue', 'invalid_state', 'la_InvalidState');
}
}
/**
* Returns country iso code, that matches current state variable name
*
* @param kEvent $event
* @return bool
* @access protected
*/
protected function _getCountryByState(kEvent $event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$variable_name = $object->GetDBField('VariableName');
$state_country_hash = Array (
'Comm_State' => 'Comm_Country',
'Comm_Shipping_State' => 'Comm_Shipping_Country'
);
if ( !array_key_exists($variable_name, $state_country_hash) ) {
return false;
}
$field_values = $this->Application->GetVar($event->getPrefixSpecial(true));
$sql = 'SELECT VariableId
FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
WHERE VariableName = ' . $this->Conn->qstr($state_country_hash[$variable_name]);
$country_variable_id = $this->Conn->GetOne($sql);
return $field_values[$country_variable_id]['VariableValue'];
}
/**
* Does custom password setting processong
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemUpdate(kEvent $event)
{
parent::OnBeforeItemUpdate($event);
$object = $event->getObject();
/* @var $object kDBItem */
// if password field is empty, then don't update
if ( $object->GetDBField('ElementType') == 'password' && trim($object->GetDBField('VariableValue')) == '' ) {
$object->SetFieldOption('VariableValue', 'skip_empty', 1);
}
}
/**
* Occurs after updating item
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterItemUpdate(kEvent $event)
{
static $skin_deleted = false;
parent::OnAfterItemUpdate($event);
$object = $event->getObject();
/* @var $object kDBItem */
if ( $object->GetDBField('ElementType') == 'password' && trim($object->GetDBField('VariableValue')) == '' ) {
$object->SetFieldOption('VariableValue', 'skip_empty', 0);
}
// allows to check if variable's value was changed now
$variable_name = $object->GetDBField('VariableName');
$changed = $this->Application->GetVar($event->getPrefixSpecial() . '_changed', Array ());
if ( $object->GetDBField('VariableValue') != $object->GetOriginalField('VariableValue') ) {
$changed[] = $variable_name;
$this->Application->SetVar($event->getPrefixSpecial() . '_changed', $changed);
// update value in cache, so other code (during this script run) would use new value
$this->Application->SetConfigValue($variable_name, $object->GetDBField('VariableValue'), true);
}
if ( $variable_name == 'Require_AdminSSL' || $variable_name == 'AdminSSL_URL' ) {
// when administrative console is moved to SSL mode, then delete skin
if ( in_array($variable_name, $changed) && !$skin_deleted ) {
$skin_helper = $this->Application->recallObject('SkinHelper');
/* @var $skin_helper SkinHelper */
$skin_file = $skin_helper->getSkinPath();
if ( file_exists($skin_file) ) {
unlink($skin_file);
}
$skin_deleted = true;
}
}
}
/**
* Updates kDBItem
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnUpdate(kEvent $event)
{
if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
$event->status = kEvent::erFAIL;
return;
}
$items_info = $this->Application->GetVar($event->getPrefixSpecial(true));
// 1. save user selected module root category
$new_category_id = getArrayValue($items_info, 'ModuleRootCategory', 'VariableValue');
if ( $new_category_id !== false ) {
unset($items_info['ModuleRootCategory']);
}
$object = $event->getObject( Array('skip_autoload' => true) );
/* @var $object kDBItem */
if ( $items_info ) {
$has_error = false;
foreach ($items_info as $id => $field_values) {
$object->Clear(); // clear validation errors from previous variable
$object->Load($id);
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
+ $event->setEventParam('form_data', $field_values);
if ( !$object->Update($id) ) {
// don't stop when error found !
$has_error = true;
}
}
$event->status = $has_error ? kEvent::erFAIL : kEvent::erSUCCESS;
}
if ( $event->status == kEvent::erSUCCESS ) {
$event->SetRedirectParam('action_completed', 1);
if ( $new_category_id !== false ) {
// root category was submitted
$module = $this->Application->GetVar('module');
$root_category_id = $this->Application->findModule('Name', $module, 'RootCat');
if ( $root_category_id != $new_category_id ) {
// root category differs from one in db
$fields_hash = Array ('RootCat' => $new_category_id);
$this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Modules', 'Name = ' . $this->Conn->qstr($module));
}
}
// reset cache
$changed = $this->Application->GetVar($event->getPrefixSpecial() . '_changed', Array ());
$require_refresh = Array ('AdvancedUserManagement', 'Site_Name', 'AdminConsoleInterface', 'UsePopups');
$refresh_sections = array_intersect($require_refresh, $changed);
$require_full_refresh = Array ('Site_Name', 'AdminConsoleInterface');
if ( array_intersect($require_full_refresh, $changed) ) {
$event->SetRedirectParam('refresh_all', 1);
}
elseif ( $refresh_sections ) {
$event->SetRedirectParam('refresh_tree', 1);
}
if ( $refresh_sections ) {
// reset sections too, because of AdvancedUserManagement
$this->Application->DeleteSectionCache();
}
$this->Application->DeleteUnitCache($changed);
}
else{
$errors = $this->Application->GetVar('errors_' . $event->getPrefixSpecial());
if ( $errors ) {
// because we have list out there, and this is item
$this->Application->SetVar('first_error', key($errors));
$this->Application->removeObject($event->getPrefixSpecial());
}
}
// keeps module and section in REQUEST to ensure, that last admin template will work
$event->SetRedirectParam('module', $this->Application->GetVar('module'));
$event->SetRedirectParam('section', $this->Application->GetVar('section'));
}
/**
* Process items from selector (selected_ids var, key - prefix, value - comma separated ids)
*
* @param kEvent $event
*/
function OnProcessSelected($event)
{
$selected_ids = $this->Application->GetVar('selected_ids');
$this->Application->StoreVar('ModuleRootCategory', $selected_ids['c']);
$event->SetRedirectParam('opener', 'u');
}
/**
* Generates maintenance page
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnGenerateMaintenancePage(kEvent $event)
{
$event->setEventParam('compile_maintenance_template', 1);
$event->CallSubEvent('OnUpdate');
}
/**
* Generates HTML version of hard maintenance template
*
* @param string $template
* @return bool
* @access protected
*/
protected function _generateMaintenancePage($template = null)
{
if ( !isset($template) ) {
$template = $this->Application->ConfigValue('HardMaintenanceTemplate');
}
$curl_helper = $this->Application->recallObject('CurlHelper');
/* @var $curl_helper kCurlHelper */
$html = $curl_helper->Send($this->Application->BaseURL() . '?t=' . $template);
if ( $curl_helper->isGoodResponseCode() ) {
file_put_contents(WRITEABLE . DIRECTORY_SEPARATOR . 'maintenance.html', $html);
return true;
}
return false;
}
/**
* Returns auto-complete values for ajax-dropdown
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnSuggestValues(kEvent $event)
{
if ( !$this->Application->isAdminUser ) {
// very careful here, because this event allows to
// view every object field -> limit only to logged-in admins
return;
}
$event->status = kEvent::erSTOP;
$field = $this->Application->GetVar('field');
$cur_value = $this->Application->GetVar('cur_value');
$object = $event->getObject();
/* @var $object kDBItem */
if ( !$field || !$cur_value || !$object->isField($field) ) {
return;
}
$limit = $this->Application->GetVar('limit');
if ( !$limit ) {
$limit = 20;
}
$sql = 'SELECT DISTINCT ' . $field . ', ModuleOwner
FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . '
WHERE ' . $field . ' LIKE ' . $this->Conn->qstr('%' . $cur_value . '%') . '
ORDER BY ' . $field . ' ASC';
$raw_suggestions = $this->Conn->Query($sql);
$suggestions = Array ();
$this->Application->XMLHeader();
foreach ($raw_suggestions as $raw_suggestion) {
$suggestion = $raw_suggestion[$field];
if ( !isset($suggestions[$suggestion]) ) {
$suggestions[$suggestion] = Array ();
}
$suggestions[$suggestion][] = $raw_suggestion['ModuleOwner'];
}
array_splice($suggestions, $limit);
echo '<suggestions>';
$of_label = $this->Application->Phrase('la_From', false);
foreach ($suggestions as $suggestion_value => $suggestion_modules) {
$suggestion_module = in_array('In-Portal', $suggestion_modules) ? 'In-Portal' : implode(', ', $suggestion_modules);
$suggestion_title = $suggestion_value . ' <em style="color: grey;">' . $of_label . ' ' . $suggestion_module . '</em>';
echo '<item value="' . htmlspecialchars($suggestion_value, null, CHARSET) . '">' . htmlspecialchars($suggestion_title, null, CHARSET) . '</item>';
}
echo '</suggestions>';
}
/**
* Prefills module dropdown
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnAfterConfigRead(kEvent $event)
{
parent::OnAfterConfigRead($event);
$options = Array ();
foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
if ( $module_name == 'Core' ) {
continue;
}
$options[$module_name] = $module_name;
if ( $module_name == 'In-Portal' ) {
$options['In-Portal:Users'] = 'In-Portal:Users';
}
}
$fields = $this->Application->getUnitOption($event->Prefix, 'Fields');
$fields['ModuleOwner']['options'] = $options;
$this->Application->setUnitOption($event->Prefix, 'Fields', $fields);
}
}
\ No newline at end of file
Index: branches/5.2.x/core/units/forms/forms/forms_eh.php
===================================================================
--- branches/5.2.x/core/units/forms/forms/forms_eh.php (revision 15780)
+++ branches/5.2.x/core/units/forms/forms/forms_eh.php (revision 15781)
@@ -1,626 +1,627 @@
<?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 FormsEventHandler extends kDBEventHandler {
/**
* Allows to override standard permission mapping
*
* @return void
* @access protected
* @see kEventHandler::$permMapping
*/
protected function mapPermissions()
{
parent::mapPermissions();
$permissions = Array (
// user can view any form on front-end
'OnItemBuild' => Array ('self' => true),
);
$this->permMapping = array_merge($this->permMapping, $permissions);
}
function OnCreateSubmissionNodes($event)
{
if (defined('IS_INSTALL') && IS_INSTALL) {
// skip any processing, because Forms table doesn't exists until install is finished
return ;
}
$forms = $this->getForms();
if (!$forms) {
return ;
}
$form_subsection = Array(
'parent' => 'in-portal:forms',
'icon' => 'form_submission',
'label' => '',
'url' => Array('t' => 'submissions/submissions_list', 'pass' => 'm,form'),
'permissions' => Array('view', 'add', 'edit', 'delete'),
'priority' => 1,
'type' => stTREE,
);
$priority = 1;
$sections = $this->Application->getUnitOption($event->Prefix, 'Sections');
foreach ($forms as $form_id => $form_name) {
$this->Application->Phrases->AddCachedPhrase('form_sub_label_'.$form_id, $form_name);
$this->Application->Phrases->AddCachedPhrase('la_description_in-portal:submissions:'.$form_id, $form_name.' Submissions');
$form_subsection['label'] = 'form_sub_label_'.$form_id;
$form_subsection['url']['form_id'] = $form_id;
$form_subsection['priority'] = $priority++;
$sections['in-portal:submissions:'.$form_id] = $form_subsection;
}
$this->Application->setUnitOption($event->Prefix, 'Sections', $sections);
}
function getForms()
{
$cache_key = 'forms[%FormSerial%]';
$forms = $this->Application->getCache($cache_key);
if ($forms === false) {
$this->Conn->nextQueryCachable = true;
$sql = 'SELECT Title, FormId
FROM ' . TABLE_PREFIX . 'Forms
ORDER BY Title ASC';
$forms = $this->Conn->GetCol($sql, 'FormId');
$this->Application->setCache($cache_key, $forms);
}
return $forms;
}
/**
* 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)
{
parent::OnSave($event);
if ( $event->status == kEvent::erSUCCESS ) {
$this->OnCreateFormFields($event);
$this->_deleteSectionCache();
}
}
/**
* 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)
{
parent::OnMassDelete($event);
if ( $event->status == kEvent::erSUCCESS ) {
$this->_deleteSectionCache();
}
}
function _deleteSectionCache()
{
$this->Application->HandleEvent(new kEvent('adm:OnResetSections'));
$this->Application->StoreVar('RefreshStructureTree', 1);
}
/**
* Dynamically fills custom data config
*
* @param kEvent $event
*/
function OnCreateFormFields($event)
{
$cur_fields = $this->Conn->Query('DESCRIBE '.TABLE_PREFIX.'FormSubmissions', 'Field');
$cur_fields = array_keys($cur_fields);
// keep all fields, that are not created on the fly (includes ones, that are added during customizations)
foreach ($cur_fields as $field_index => $field_name) {
if (!preg_match('/^fld_[\d]+/', $field_name)) {
unset($cur_fields[$field_index]);
}
}
$desired_fields = $this->Conn->GetCol('SELECT CONCAT(\'fld_\', FormFieldId) FROM '.TABLE_PREFIX.'FormFields ORDER BY FormFieldId');
$sql = array();
$fields_to_add = array_diff($desired_fields, $cur_fields);
foreach ($fields_to_add as $field) {
$field_expression = $field.' Text NULL';
$sql[] = 'ADD COLUMN '.$field_expression;
}
$fields_to_drop = array_diff($cur_fields, $desired_fields);
foreach ($fields_to_drop as $field) {
$sql[] = 'DROP COLUMN '.$field;
}
if ($sql) {
$query = 'ALTER TABLE '.TABLE_PREFIX.'FormSubmissions '.implode(', ', $sql);
$this->Conn->Query($query);
}
}
/**
* Enter description here...
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnFormSubmit($event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$fields = explode(',',$this->Application->GetVar('fields'));
$required_fields = explode(',', $this->Application->GetVar('required_fields'));
$fields_params = $this->Application->GetVar('fields_params');
$virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields');
foreach ($fields as $field) {
$virtual_fields[$field] = Array ();
if ( in_array($field, $required_fields) ) {
$virtual_fields[$field]['required'] = 1;
}
$params = getArrayValue($fields_params, $field);
if ( $params !== false ) {
if ( getArrayValue($params, 'Type') == 'email' ) {
$virtual_fields[$field]['formatter'] = 'kFormatter';
$virtual_fields[$field]['regexp'] = '/^(' . REGEX_EMAIL_USER . '@' . REGEX_EMAIL_DOMAIN . ')$/i';
$virtual_fields[$field]['error_msgs'] = Array ('invalid_format' => '!la_invalid_email!');
}
if ( getArrayValue($params, 'Type') == 'file' ) {
$virtual_fields[$field]['formatter'] = 'kUploadFormatter';
$virtual_fields[$field]['upload_dir'] = '/uploads/sketches/';
}
}
}
$object->SetVirtualFields($virtual_fields);
$field_values = $this->getSubmittedFields($event);
$checkboxes = explode(',', $this->Application->GetVar('checkbox_fields')); // MailingList,In-Link,In-Newz,In-Bulletin
foreach ($checkboxes as $checkbox) {
if (isset($field_values[$checkbox])) {
$field_values[$checkbox] = 1;
}
else {
$field_values[$checkbox] = '0';
}
}
$object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values));
+ $event->setEventParam('form_data', $field_values);
if ( $object->Validate() ) {
$event->redirect = $this->Application->GetVar('success_template');
$this->Application->emailAdmin($this->Application->GetVar('email_event'));
$send_params = Array (
'to_email' => $field_values[$this->Application->GetVar('email_field')],
'to_name' => $field_values[$this->Application->GetVar('name_field')]
);
$this->Application->emailUser($this->Application->GetVar('email_event'), null, $send_params);
if ( $field_values['MailingList'] ) {
$this->Application->StoreVar('SubscriberEmail', $field_values['Email']);
$this->Application->HandleEvent(new kEvent('u:OnSubscribeUser', Array ('no_unsubscribe' => 1)));
}
}
else {
$event->status = kEvent::erFAIL;
}
}
/**
* Don't use security image, when form requires login
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemCreate(kEvent $event)
{
parent::OnBeforeItemCreate($event);
$this->itemChanged($event);
}
/**
* Don't use security image, when form requires login
*
* @param kEvent $event
* @return void
* @access protected
*/
protected function OnBeforeItemUpdate(kEvent $event)
{
parent::OnBeforeItemUpdate($event);
$this->itemChanged($event);
}
/**
* Occurs before item is changed
*
* @param kEvent $event
*/
function itemChanged($event)
{
$this->_validatePopSettings($event);
$this->_disableSecurityImage($event);
$this->_setRequired($event);
}
/**
* Validates POP3 settings (performs test connect)
*
* @param kEvent $event
*/
function _validatePopSettings($event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$modes = Array ('Reply', 'Bounce');
$fields = Array ('Server', 'Port', 'Username', 'Password');
$changed_fields = array_keys( $object->GetChangedFields() );
foreach ($modes as $mode) {
$set = true;
$changed = false;
foreach ($fields as $field) {
$value = $object->GetDBField($mode . $field);
if (strlen( trim($value) ) == 0) {
$set = false;
break;
}
if (!$changed && in_array($mode . $field, $changed_fields)) {
$changed = true;
}
}
if ($set && $changed) {
// fields are set and at least on of them is changed
$connection_info = Array ();
foreach ($fields as $field) {
$connection_info[ strtolower($field) ] = $object->GetDBField($mode . $field);
}
$pop3_helper = $this->Application->makeClass('POP3Helper', Array ($connection_info, 10));
/* @var $pop3_helper POP3Helper */
switch ( $pop3_helper->initMailbox(true) ) {
case 'socket':
$object->SetError($mode . 'Server', 'connection_failed');
break;
case 'login':
$object->SetError($mode . 'Username', 'login_failed');
break;
case 'list':
$object->SetError($mode . 'Server', 'message_listing_failed');
break;
}
}
}
}
/**
* Makes email communication fields required, when form uses email communication
*
* @param kEvent $event
*/
function _setRequired($event)
{
$object = $event->getObject();
/* @var $object kDBItem */
$required = $object->GetDBField('EnableEmailCommunication');
$fields = Array (
'ReplyFromName', 'ReplyFromEmail', 'ReplyServer', 'ReplyPort', 'ReplyUsername', 'ReplyPassword',
);
if ($required && $object->GetDBField('BounceEmail')) {
$bounce_fields = Array ('BounceEmail', 'BounceServer', 'BouncePort', 'BounceUsername', 'BouncePassword');
$fields = array_merge($fields, $bounce_fields);
}
$object->setRequired($fields, $required);
}
/**
* Don't use security image, when form requires login
*
* @param kEvent $event
*/
function _disableSecurityImage($event)
{
$object = $event->getObject();
/* @var $object kDBItem */
if ($object->GetDBField('RequireLogin')) {
$object->SetDBField('UseSecurityImage', 0);
}
}
/**
* Queries pop3 server about new incoming mail
*
* @param kEvent $event
*/
function OnProcessReplies($event)
{
$this->_processMailbox($event, false);
}
/**
* Queries pop3 server about new incoming mail
*
* @param kEvent $event
*/
function OnProcessBouncedReplies($event)
{
$this->_processMailbox($event, true);
}
/**
* Queries pop3 server about new incoming mail
*
* @param kEvent $event
* @param bool $bounce_mode
*/
function _processMailbox($event, $bounce_mode = false)
{
$this->Application->SetVar('client_mode', 1);
$id_field = $this->Application->getUnitOption($event->Prefix, 'IDField');
$table_name = $this->Application->getUnitOption($event->Prefix, 'TableName');
$sql = 'SELECT *
FROM ' . $table_name . '
WHERE EnableEmailCommunication = 1';
$forms = $this->Conn->Query($sql, $id_field);
$mailbox_helper = $this->Application->recallObject('MailboxHelper');
/* @var $mailbox_helper MailboxHelper */
$field_prefix = $bounce_mode ? 'Bounce' : 'Reply';
foreach ($forms as $form_id => $form_info) {
$recipient_email = $bounce_mode ? $form_info['BounceEmail'] : $form_info['ReplyFromEmail'];
if (!$recipient_email) {
continue;
}
$mailbox_helper->process(
Array (
'server' => $form_info[$field_prefix . 'Server'],
'port' => $form_info[$field_prefix . 'Port'],
'username' => $form_info[$field_prefix . 'Username'],
'password' => $form_info[$field_prefix . 'Password']
),
Array (&$this, 'isValidRecipient'),
Array (&$this, 'processEmail'),
Array (
'recipient_email' => $recipient_email,
'bounce_mode' => $bounce_mode,
'form_info' => $form_info,
)
);
}
}
function isValidRecipient($params)
{
$mailbox_helper = $this->Application->recallObject('MailboxHelper');
/* @var $mailbox_helper MailboxHelper */
$recipients = $mailbox_helper->getRecipients();
$recipient_email = $params['recipient_email'];
$emails_found = preg_match_all('/((' . REGEX_EMAIL_USER . ')(@' . REGEX_EMAIL_DOMAIN . '))/i', $recipients, $all_emails);
if (is_array($all_emails)) {
for ($i = 0; $i < $emails_found; $i++) {
if ($all_emails[1][$i] == $recipient_email) {
// only read messages, that are addresses to submission reply email
return true;
}
}
}
// If this is a forwarded message - we drop all the other aliases and deliver only to the x-forward to address;
if (preg_match('/((' . REGEX_EMAIL_USER . ')(@' . REGEX_EMAIL_DOMAIN . '))/i', $mailbox_helper->headers['x-forward-to'], $get_to_email)) {
if ($get_to_email[1] == $recipient_email) {
// only read messages, that are addresses to submission reply email
return true;
}
}
return false;
}
function processEmail($params, &$fields_hash)
{
if ($params['bounce_mode']) {
// mark original message as bounced
$mailbox_helper = $this->Application->recallObject('MailboxHelper');
/* @var $mailbox_helper MailboxHelper */
if (!array_key_exists('attachments', $mailbox_helper->parsedMessage)) {
// for now only parse bounces based on attachments, skip other bounce types
return false;
}
for ($i = 0; $i < count($mailbox_helper->parsedMessage['attachments']); $i++) {
$attachment =& $mailbox_helper->parsedMessage['attachments'][$i];
switch ($attachment['headers']['content-type']) {
case 'message/delivery-status':
// save as BounceInfo
$mime_decode_helper = $this->Application->recallObject('MimeDecodeHelper');
/* @var $mime_decode_helper MimeDecodeHelper */
$charset = $mailbox_helper->parsedMessage[ $fields_hash['MessageType'] ][0]['charset'];
$fields_hash['Message'] = $mime_decode_helper->convertEncoding($charset, $attachment['data']);
break;
case 'message/rfc822':
// undelivered message
$fields_hash['Subject'] = $attachment['filename2'] ? $attachment['filename2'] : $attachment['filename'];
break;
}
}
}
if (!preg_match('/^(.*) #verify(.*)$/', $fields_hash['Subject'], $regs)) {
// incorrect subject, no verification code
$form_info = $params['form_info'];
if ($form_info['ProcessUnmatchedEmails'] && ($fields_hash['FromEmail'] != $params['recipient_email'])) {
// it's requested to convert unmatched emails to new submissions
$form_id = $form_info['FormId'];
$this->Application->SetVar('form_id', $form_id);
$sql = 'SELECT ' . $this->Application->getUnitOption('formsubs', 'IDField') . '
FROM ' . $this->Application->getUnitOption('formsubs', 'TableName') . '
WHERE MessageId = ' . $this->Conn->qstr($fields_hash['MessageId']);
$found = $this->Conn->GetOne($sql);
if ($found) {
// don't process same message twice
return false;
}
$sql = 'SELECT *
FROM ' . TABLE_PREFIX . 'FormFields
WHERE (FormId = ' . $form_info['FormId'] . ') AND (EmailCommunicationRole > 0)';
$form_fields = $this->Conn->Query($sql, 'EmailCommunicationRole');
// what roles are filled from what fields
$role_mapping = Array (
SubmissionFormField::COMMUNICATION_ROLE_EMAIL => 'FromEmail',
SubmissionFormField::COMMUNICATION_ROLE_NAME => 'FromName',
SubmissionFormField::COMMUNICATION_ROLE_SUBJECT => 'Subject',
SubmissionFormField::COMMUNICATION_ROLE_BODY => 'Message',
);
$submission_fields = Array ();
foreach ($role_mapping as $role => $email_field) {
if (array_key_exists($role, $form_fields)) {
$submission_fields[ 'fld_' . $form_fields[$role]['FormFieldId'] ] = $fields_hash[$email_field];
}
}
if ($submission_fields) {
// remove object, because it's linked to single form upon creation forever
$this->Application->removeObject('formsubs.-item');
$form_submission = $this->Application->recallObject('formsubs.-item', null, Array ('skip_autoload' => true));
/* @var $form_submission kDBItem */
// in case that other non-role mapped fields are required
$form_submission->IgnoreValidation = true;
$form_submission->SetDBFieldsFromHash($submission_fields);
$form_submission->SetDBField('FormId', $form_id);
$form_submission->SetDBField('MessageId', $fields_hash['MessageId']);
$form_submission->SetDBField('SubmissionTime_date', adodb_mktime());
$form_submission->SetDBField('SubmissionTime_time', adodb_mktime());
$form_submission->SetDBField('ReferrerURL', $this->Application->Phrase('la_Text_Email'));
return $form_submission->Create();
}
}
return false;
}
$sql = 'SELECT ' . $this->Application->getUnitOption('submission-log', 'IDField') . '
FROM ' . $this->Application->getUnitOption('submission-log', 'TableName') . '
WHERE MessageId = ' . $this->Conn->qstr($fields_hash['MessageId']);
$found = $this->Conn->GetOne($sql);
if ($found) {
// don't process same message twice
return false;
}
$reply_to = $this->Application->recallObject('submission-log.-reply-to', null, Array ('skip_autoload' => true));
/* @var $reply_to kDBItem */
$reply_to->Load($regs[2], 'VerifyCode');
if (!$reply_to->isLoaded()) {
// fake verification code OR feedback, containing submission log was deleted
return false;
}
if ($params['bounce_mode']) {
// mark original message as bounced
$reply_to->SetDBField('BounceInfo', $fields_hash['Message']);
$reply_to->SetDBField('BounceDate_date', TIMENOW);
$reply_to->SetDBField('BounceDate_time', TIMENOW);
$reply_to->SetDBField('SentStatus', SUBMISSION_LOG_BOUNCE);
$reply_to->Update();
return true;
}
$reply = $this->Application->recallObject('submission-log.-reply', null, Array ('skip_autoload' => true));
/* @var $reply kDBItem */
$reply->SetDBFieldsFromHash($fields_hash);
$reply->SetDBField('ReplyTo', $reply_to->GetID());
$reply->SetDBField('FormSubmissionId', $reply_to->GetDBField('FormSubmissionId'));
$reply->SetDBField('ToEmail', $params['recipient_email']);
$reply->SetDBField('Subject', $regs[1]); // save subject without verification code
$reply->SetDBField('SentStatus', SUBMISSION_LOG_SENT);
return $reply->Create();
}
}
\ No newline at end of file

Event Timeline