Index: branches/5.2.x/core/kernel/db/cat_dbitem.php =================================================================== --- branches/5.2.x/core/kernel/db/cat_dbitem.php (revision 14718) +++ branches/5.2.x/core/kernel/db/cat_dbitem.php (revision 14719) @@ -1,645 +1,621 @@ <?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 kCatDBItem extends kDBItem { /** * Category path, needed for import * * @var Array */ var $CategoryPath = Array(); /** * Use automatic filename generation * * @var bool */ var $useFilenames = true; /** * Use pending editing abilities during item (delegated by permissions) * * @var bool */ var $usePendingEditing = false; - function Clear($new_id = null) + /** + * Removes all data from an object + * + * @param int $new_id + * @return bool + * @access public + */ + public function Clear($new_id = null) { parent::Clear($new_id); $this->CategoryPath = Array(); } /** * Set's prefix and special * * @param string $prefix * @param string $special * @access public */ function Init($prefix, $special) { parent::Init($prefix, $special); $this->usePendingEditing = $this->Application->getUnitOption($this->Prefix, 'UsePendingEditing'); } /** - * Creates a record in the database table with current item' values - * - * @param mixed $force_id Set to TRUE to force creating of item's own ID or to value to force creating of passed id. Do not pass 1 for true, pass exactly TRUE! - * @param bool $system_create - * @return bool - * @access public - */ - public function Create($force_id = false, $system_create = false) - { - $ret = parent::Create($force_id, $system_create); - - if ($ret) { - // TODO: move to OnAfterItemCreate method - $this->assignPrimaryCategory(); - } - - return $ret; - } - - /** * Assigns primary category for the item * * @access public */ - function assignPrimaryCategory() + public function assignPrimaryCategory() { if ( $this->GetDBField('CategoryId') <= 0 ) { // set primary category in item object $this->SetDBField('CategoryId', $this->Application->GetVar('m_cat_id')); } $this->assignToCategory($this->GetDBField('CategoryId'), true); } /** * Updates previously loaded record with current item' values * * @access public * @param int $id Primary Key Id to update * @param bool $system_update * @return bool * @access public */ public function Update($id = null, $system_update = false) { - $this->VirtualFields['ResourceId'] = Array (); - - if ( $this->GetChangedFields() ) { - $now = adodb_mktime(); - $this->SetDBField('Modified_date', $now); - $this->SetDBField('Modified_time', $now); - $this->SetDBField('ModifiedById', $this->Application->RecallVar('user_id')); - } - if ( $this->useFilenames ) { $this->checkFilename(); $this->generateFilename(); } $ret = parent::Update($id, $system_update); if ( $ret ) { $filename = $this->useFilenames ? (string)$this->GetDBField('Filename') : ''; $sql = 'UPDATE ' . $this->CategoryItemsTable() . ' SET Filename = ' . $this->Conn->qstr($filename) . ' WHERE ItemResourceId = ' . $this->GetDBField('ResourceId'); $this->Conn->Query($sql); } - unset($this->VirtualFields['ResourceId']); - return $ret; } /** * Returns CategoryItems table based on current item mode (temp/live) * * @return string */ function CategoryItemsTable() { $table = TABLE_PREFIX.'CategoryItems'; if ($this->Application->IsTempTable($this->TableName)) { $table = $this->Application->GetTempName($table, 'prefix:'.$this->Prefix); } return $table; } function checkFilename() { if( !$this->GetDBField('AutomaticFilename') ) { $filename = $this->GetDBField('Filename'); $this->SetDBField('Filename', $this->stripDisallowed($filename) ); } } function Copy($cat_id=null) { if (!isset($cat_id)) $cat_id = $this->Application->GetVar('m_cat_id'); $this->NameCopy($cat_id); return $this->Create($cat_id); } /** * Sets new name for item in case if it is being copied in same table * * @param array $master Table data from TempHandler * @param int $foreign_key ForeignKey value to filter name check query by * @param string $title_field FieldName to alter, by default - TitleField of the prefix * @param string $format sprintf-style format of renaming pattern, by default Copy %1$s of %2$s which makes it Copy [Number] of Original Name * @access public */ public function NameCopy($master=null, $foreign_key=null, $title_field=null, $format='Copy %1$s of %2$s') { $title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField'); if (!$title_field) return; $new_name = $this->GetDBField($title_field); $cat_id = (int)$this->Application->GetVar('m_cat_id'); $original_checked = false; do { if ( preg_match('/Copy ([0-9]*) *of (.*)/', $new_name, $regs) ) { $new_name = 'Copy '.( (int)$regs[1] + 1 ).' of '.$regs[2]; } elseif ($original_checked) { $new_name = 'Copy of '.$new_name; } $query = 'SELECT '.$title_field.' FROM '.$this->TableName.' LEFT JOIN '.TABLE_PREFIX.'CategoryItems ON ('.TABLE_PREFIX.'CategoryItems.ItemResourceId = '.$this->TableName.'.ResourceId) WHERE ('.TABLE_PREFIX.'CategoryItems.CategoryId = '.$cat_id.') AND '. $title_field.' = '.$this->Conn->qstr($new_name); $res = $this->Conn->GetOne($query); $original_checked = true; } while ($res !== false); $this->SetDBField($title_field, $new_name); // this is needed, because Create will create items in its own CategoryId (if it's set), // but we need to create it in target Paste category @see{kCatDBItem::Create} and its primary_category detection $this->SetDBField('CategoryId', $cat_id); } /** * Changes item primary category to given/current category * * @param int $category_id */ function MoveToCat($category_id = null) { // $this->NameCopy(); if (!isset($category_id)) { $category_id = $this->Application->GetVar('m_cat_id'); } $table_name = TABLE_PREFIX . 'CategoryItems'; if ($this->IsTempTable()) { $table_name = $this->Application->GetTempName($table_name, 'prefix:' . $this->Prefix); } // check if the item already exists in destination category $sql = 'SELECT PrimaryCat FROM ' . $table_name . ' WHERE (CategoryId = ' . (int)$category_id . ') AND (ItemResourceId = ' . $this->GetDBField('ResourceId') . ')'; $is_primary = $this->Conn->GetOne($sql); // if it's not found is_primary will be FALSE, if it's found but not primary it will be int 0 $exists = $is_primary !== false; if ($exists) { // if the item already exists in destination category if ($is_primary) { // do nothing when we paste to primary return ; } // if it's not primary - delete it from destination category, as we will move it from current primary below $sql = 'DELETE FROM ' . $table_name . ' WHERE (CategoryId = ' . (int)$category_id . ') AND (ItemResourceId = ' . $this->GetDBField('ResourceId') . ')'; $this->Conn->Query($sql); } // change category id in existing primary category record $sql = 'UPDATE ' . $table_name . ' SET CategoryId = ' . (int)$category_id . ' WHERE (ItemResourceId = ' . $this->GetDBField('ResourceId') . ') AND (PrimaryCat = 1)'; $this->Conn->Query($sql); $this->Update(); } /** * When item is deleted, then also delete it from all categories * * @param int $id * @return bool * @access public */ public function Delete($id = null) { if ( isset($id) ) { $this->setID($id); } $this->Load($this->GetID()); $ret = parent::Delete(); if ( $ret ) { // TODO: move to OnAfterItemDelete method $query = ' DELETE FROM ' . $this->CategoryItemsTable() . ' WHERE ItemResourceId = ' . $this->GetDBField('ResourceId'); $this->Conn->Query($query); } return $ret; } /** * Deletes item from categories * * @param Array $delete_category_ids * @author Alex */ function DeleteFromCategories($delete_category_ids) { $id_field = $this->Application->getUnitOption($this->Prefix, 'IDField'); // because item was loaded before by ResourceId $ci_table = $this->Application->getUnitOption($this->Prefix . '-ci', 'TableName'); $resource_id = $this->GetDBField('ResourceId'); $item_cats_sql = ' SELECT CategoryId FROM %s WHERE ItemResourceId = %s'; $delete_category_items_sql = ' DELETE FROM %s WHERE ItemResourceId = %s AND CategoryId IN (%s)'; $category_ids = $this->Conn->GetCol( sprintf($item_cats_sql, $ci_table, $resource_id) ); $cats_left = array_diff($category_ids, $delete_category_ids); if ( !$cats_left ) { $sql = 'SELECT %s FROM %s WHERE ResourceId = %s'; $ids = $this->Conn->GetCol(sprintf($sql, $id_field, $this->TableName, $resource_id)); $temp_handler =& $this->Application->recallObject($this->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler'); /* @var $temp_handler kTempTablesHandler */ $temp_handler->DeleteItems($this->Prefix, $this->Special, $ids); } else { $this->Conn->Query( sprintf($delete_category_items_sql, $ci_table, $resource_id, implode(',', $delete_category_ids)) ); $sql = 'SELECT CategoryId FROM %s WHERE PrimaryCat = 1 AND ItemResourceId = %s'; $primary_cat_id = $this->Conn->GetCol(sprintf($sql, $ci_table, $resource_id)); if ( count($primary_cat_id) == 0 ) { $sql = 'UPDATE %s SET PrimaryCat = 1 WHERE (CategoryId = %s) AND (ItemResourceId = %s)'; $this->Conn->Query( sprintf($sql, $ci_table, reset($cats_left), $resource_id) ); } } } /** * replace not allowed symbols with "_" chars + remove duplicate "_" chars in result * * @param string $filename * @return string */ function stripDisallowed($filename) { $filenames_helper =& $this->Application->recallObject('FilenamesHelper'); /* @var $filenames_helper kFilenamesHelper */ $table = $this->IsTempTable() ? $this->Application->GetTempName(TABLE_PREFIX.'CategoryItems', 'prefix:'.$this->Prefix) : TABLE_PREFIX.'CategoryItems'; return $filenames_helper->stripDisallowed($table, 'ItemResourceId', $this->GetDBField('ResourceId'), $filename); } /* commented out because it's called only from stripDisallowed body, which is moved to helper function checkAutoFilename($filename) { $filenames_helper =& $this->Application->recallObject('FilenamesHelper'); return $filenames_helper->checkAutoFilename($this->TableName, $this->IDField, $this->GetID(), $filename); }*/ /** * Generate item's filename based on it's title field value * * @return void * @access protected */ protected function generateFilename() { if ( !$this->GetDBField('AutomaticFilename') && $this->GetDBField('Filename') ) { return ; } $title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField'); if ( preg_match('/l([\d]+)_(.*)/', $title_field, $regs) ) { // if title field is multilingual, then use it's name from primary language $title_field = 'l' . $this->Application->GetDefaultLanguageId() . '_' . $regs[2]; } $name = $this->stripDisallowed( $this->GetDBField($title_field) ); if ( $name != $this->GetDBField('Filename') ) { $this->SetDBField('Filename', $name); } } /** * Adds item to other category * * @param int $category_id * @param bool $is_primary * @return void * @access public */ public function assignToCategory($category_id, $is_primary = false) { $table = $this->CategoryItemsTable(); $key_clause = '(ItemResourceId = ' . $this->GetDBField('ResourceId') . ')'; // get all categories, where item is in $sql = 'SELECT PrimaryCat, CategoryId FROM ' . $table . ' WHERE ' . $key_clause; $item_categories = $this->Conn->GetCol($sql, 'CategoryId'); $primary_found = $item_category_id = false; if ( $item_categories ) { // find primary category foreach ($item_categories as $item_category_id => $primary_found) { if ( $primary_found ) { break; } } } if ( $primary_found && ($item_category_id == $category_id) && !$is_primary ) { // want to make primary category as non-primary :( return; } elseif ( !$primary_found ) { $is_primary = true; } if ( $is_primary && $item_categories ) { // reset primary mark from all other categories $sql = 'UPDATE ' . $table . ' SET PrimaryCat = 0 WHERE ' . $key_clause; $this->Conn->Query($sql); } // UPDATE & INSERT instead of REPLACE because CategoryItems table has no primary key defined in database if ( isset($item_categories[$category_id]) ) { $sql = 'UPDATE ' . $table . ' SET PrimaryCat = ' . ($is_primary ? 1 : 0) . ' WHERE ' . $key_clause . ' AND (CategoryId = ' . $category_id . ')'; $this->Conn->Query($sql); } else { $fields_hash = Array( 'CategoryId' => $category_id, 'ItemResourceId' => $this->GetField('ResourceId'), 'PrimaryCat' => $is_primary ? 1 : 0, 'ItemPrefix' => $this->Prefix, 'Filename' => $this->useFilenames ? (string)$this->GetDBField('Filename') : '', // because some prefixes does not use filenames, ); $this->Conn->doInsert($fields_hash, $table); } // to ensure filename update after adding to another category // this is critical since there may be an item with same filename in newly added category! $this->Update(); } /** * Removes item from category specified * * @param int $category_id */ function removeFromCategory($category_id) { $sql = 'DELETE FROM '.TABLE_PREFIX.'CategoryItems WHERE (CategoryId = %s) AND (ItemResourceId = %s)'; $this->Conn->Query( sprintf($sql, $category_id, $this->GetDBField('ResourceId')) ); } /** * Returns list of columns, that could exist in imported file * * @return Array */ function getPossibleExportColumns() { static $columns = null; if (!is_array($columns)) { $columns = array_merge($this->Fields['AvailableColumns']['options'], $this->Fields['ExportColumns']['options']); } return $columns; } /** * Returns item's primary image data * * @return Array */ function getPrimaryImageData() { $sql = 'SELECT * FROM '.TABLE_PREFIX.'Images WHERE (ResourceId = '.$this->GetDBField('ResourceId').') AND (DefaultImg = 1)'; $image_data = $this->Conn->GetRow($sql); if (!$image_data) { // 2. no primary image, then get image with name "main" $sql = 'SELECT * FROM '.TABLE_PREFIX.'Images WHERE (ResourceId = '.$this->GetDBField('ResourceId').') AND (Name = "main")'; $image_data = $this->Conn->GetRow($sql); } return $image_data; } function ChangeStatus($new_status, $pending_editing = false) { $status_field = array_shift( $this->Application->getUnitOption($this->Prefix,'StatusField') ); if ($new_status != $this->GetDBField($status_field)) { // status was changed $this->sendEmailEvents($new_status, $pending_editing); } $this->SetDBField($status_field, $new_status); return $this->Update(); } function sendEmailEvents($new_status, $pending_editing = false) { $owner_field = $this->Application->getUnitOption($this->Prefix, 'OwnerField'); if (!$owner_field) { $owner_field = 'CreatedById'; } $event_name = $this->Application->getUnitOption($this->Prefix, 'PermItemPrefix'); if ($pending_editing) { $event_name .= '.MODIFY'; } $event_name .= $new_status == STATUS_ACTIVE ? '.APPROVE' : '.DENY'; $this->Application->EmailEventUser($event_name, $this->GetDBField($owner_field)); } /** * Approves changes made to category item * * @return bool */ function ApproveChanges() { $original_id = $this->GetDBField('OrgId'); if ( !($this->usePendingEditing && $original_id) ) { // non-pending copy of original link return $this->ChangeStatus(STATUS_ACTIVE); } if ( $this->raiseEvent('OnBeforeDeleteOriginal', null, Array ('original_id' => $original_id)) ) { // delete original item, because changes made in pending copy (this item) got to be approved in this method $temp_handler =& $this->Application->recallObject($this->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler'); /* @var $temp_handler kTempTablesHandler */ $temp_handler->DeleteItems($this->Prefix, $this->Special, Array ($original_id)); $this->SetDBField('OrgId', 0); return $this->ChangeStatus(STATUS_ACTIVE, true); } return false; } /** * Decline changes made to category item * * @return bool */ function DeclineChanges() { $original_id = $this->GetDBField('OrgId'); if ( !($this->usePendingEditing && $original_id) ) { // non-pending copy of original link return $this->ChangeStatus(STATUS_DISABLED); } // delete this item, because changes made in pending copy (this item) will be declined in this method $temp_handler =& $this->Application->recallObject($this->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler'); /* @var $temp_handler kTempTablesHandler */ $temp_handler->DeleteItems($this->Prefix, $this->Special, Array ($this->GetID())); $this->sendEmailEvents(STATUS_DISABLED, true); // original item is not changed here, because it is already enabled (thrus pending copy is visible to item's owner or admin with permission) return true; } function RegisterHit() { $already_viewed = $this->Application->RecallVar($this->getPrefixSpecial().'_already_viewed'); $already_viewed = $already_viewed ? unserialize($already_viewed) : Array (); $id = $this->GetID(); if (!in_array($id, $already_viewed)) { $property_map = $this->Application->getUnitOption($this->Prefix, 'ItemPropertyMappings'); if (!$property_map) { return ; } $hits_field = $property_map['ClickField']; $new_hits = $this->GetDBField($hits_field) + 1; $sql = 'SELECT MAX('.$hits_field.') FROM '.$this->TableName.' WHERE FLOOR('.$hits_field.') = '.$new_hits; $max_hits = $this->Conn->GetOne($sql); if ($max_hits) { $new_hits = $max_hits + 0.000001; } $fields_hash = Array ( $hits_field => $new_hits, ); $this->Conn->doUpdate($fields_hash, $this->TableName, $this->IDField.' = '.$id); array_push($already_viewed, $id); $this->Application->StoreVar($this->getPrefixSpecial().'_already_viewed', serialize($already_viewed)); } } /** * Returns part of SQL WHERE clause identifying the record, ex. id = 25 * * @param string $method Child class may want to know who called GetKeyClause, Load(), Update(), Delete() send its names as method * @param Array $keys_hash alternative, then item id, keys hash to load item by * @see kDBItem::Load() * @see kDBItem::Update() * @see kDBItem::Delete() * @return string * @access protected */ protected function GetKeyClause($method = null, $keys_hash = null) { if ($method == 'load') { // for item with many categories makes primary to load $ci_table = TABLE_PREFIX . 'CategoryItems'; if ($this->IsTempTable()) { $ci_table = $this->Application->GetTempName($ci_table, 'prefix:' . $this->Prefix); } $primary_category_clause = Array ('`' . $ci_table . '`.`PrimaryCat`' => 1); if (!isset($keys_hash)) { $keys_hash = Array ($this->IDField => $this->ID); } // merge primary category clause in any case to be sure, that // CategoryId field will always contain primary category of item $keys_hash = array_merge($keys_hash, $primary_category_clause); } return parent::GetKeyClause($method, $keys_hash); } } \ No newline at end of file Index: branches/5.2.x/core/kernel/db/cat_event_handler.php =================================================================== --- branches/5.2.x/core/kernel/db/cat_event_handler.php (revision 14718) +++ branches/5.2.x/core/kernel/db/cat_event_handler.php (revision 14719) @@ -1,2868 +1,2892 @@ <?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 * */ 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'), '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 */ function LoadItem(&$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(&$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(&$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 LEFT 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'); $object->AddOrderField('search_result.Relevance', 'desc', true); $type_clauses['search']['include'] = 'PrimaryCat = 1 AND ('.TABLE_PREFIX.'Category.Status = '.STATUS_ACTIVE.')'; $type_clauses['search']['except'] = 'PrimaryCat = 1 AND ('.TABLE_PREFIX.'Category.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.'Category.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(&$event) { parent::SetCustomQuery($event); $object =& $event->getObject(); /* @var $object kDBList */ // add category filter if needed if ($event->Special != 'showall' && $event->Special != 'user') { if ($event->getEventParam('parent_cat_id') !== false) { $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); } // add permission filter 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 itemlist 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($event->Prefix, 'perm'); $object->addFilter('perm_filter2', $view_filter); } $object->addFilter('perm_filter', 'perm.PermId = '.$view_perm); $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 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 . 'Category.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, &$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(&$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(&$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 * @access public */ function 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(&$event) { parent::OnAfterItemCreate($event); + $object =& $event->getObject(); + /* @var $object kCatDBItem */ + if ( substr($event->Special, -6) == 'import' ) { $this->setCustomExportColumns($event); } - if ( !$this->Application->isAdmin ) { - $object =& $event->getObject(); - /* @var $object kCatDBItem */ + $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.'SearchLog 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.'SearchLog'); } $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 = kUtil::unhtmlentities( 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.'Category ON '.TABLE_PREFIX.'Category.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') { if ($event->MasterEvent->getEventParam('ResultIds')) { $where_clause .= ' AND '.$items_table.'.ResourceId IN ('.implode(',', $event->MasterEvent->getEventParam('ResultIds')).')'; } } // 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) { $ids = Array (); $search_table = TABLE_PREFIX . 'ses_' . $this->Application->GetSID() . '_' . TABLE_PREFIX . 'Search'; $sql = 'SHOW TABLES LIKE "' . $search_table . '"'; if ( $this->Conn->Query($sql) ) { $sql = 'SELECT DISTINCT ResourceId FROM ' . $search_table; $ids = $this->Conn->GetCol($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] = kUtil::unhtmlentities( $keywords[$field] ); if ($keywords[$field]) { $condition = sprintf($condition_patterns['is'], $field_name, $this->Conn->qstr( $keywords[$field] )); } break; case 'multiselect': $keywords[$field] = kUtil::unhtmlentities( $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] = kUtil::unhtmlentities( $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 * @access private * @see OnListBuild */ function SetPagination(&$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 */ function OnExport(&$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 */ function getCustomExportColumns(&$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 */ function OnNew(&$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); $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->setRedirectParams(Array ('pass' => 'all,' . $event->getPrefixSpecial()), true); $event->redirect = $this->Application->GetVar('cancel_template'); } /* === 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 */ $user_id = $object->GetDBField($id_field); $options = $object->GetFieldOptions($id_field); if ( isset($options['options'][$user_id]) ) { $object->SetDBField($cached_field, $options['options'][$user_id]); } else { $id_field = $this->Application->getUnitOption('u', 'IDField'); $table_name = $this->Application->getUnitOption('u', 'TableName'); $sql = 'SELECT Login FROM ' . $table_name . ' WHERE ' . $id_field . ' = ' . $user_id; $object->SetDBField($cached_field, $this->Conn->GetOne($sql)); } } /** * 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(&$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(&$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')); } /** * Occures before original item of item in pending editing got deleted (for hooking only) * * @param kEvent $event */ function OnBeforeDeleteOriginal(&$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(&$event) { parent::OnBeforeClone($event); if ( $this->Application->GetVar('ResetCatBeforeClone') ) { $object =& $event->getObject(); /* @var $object kDBItem */ $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(&$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 ( $object->Validate() ) { - $object->SetDBField('ResourceId', $this->Application->NextResourceId()); - } - 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 */ function OnCreate(&$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 */ function OnUpdate(&$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); // 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); } // 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); } 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->isAdminUser || $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->EmailEventAdmin($perm_prefix . '.' . $event_suffix); // there are no ADD.PENDING event for admin :( $this->Application->EmailEventUser($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->EmailEventAdmin($perm_prefix . '.' . $event_suffix); // there are no ADD.PENDING event for admin :( $this->Application->EmailEventUser($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(&$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 */ function OnDelete(&$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 */ function checkItemStatus(&$event) { $object =& $event->getObject(); /* @var $object kDBItem */ if ( !$object->isLoaded() ) { $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 * @access private * @see OnListBuild */ function SetSorting(&$event) { if (!$this->Application->isAdmin) { $event->setEventParam('same_special', true); } parent::SetSorting($event); } /** * Returns current per-page setting for list * * @param kEvent $event * @return int */ function getPerPage(&$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 */ function OnAfterConfigRead(&$event) { parent::OnAfterConfigRead($event); if (defined('IS_INSTALL') && IS_INSTALL) { 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); // 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); } function changeSortings(&$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; } $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); } /** * 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) { $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')); if ( $spam_helper->InSpamControl() ) { return; } $field = (int)$this->Application->GetVar('helpful') ? 'HelpfulCount' : 'NotHelpfulCount'; $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(); } } /** * [HOOK] Allows to add cloned subitem to given prefix * * @param kEvent $event */ function OnCloneSubItem(&$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/categories/categories_event_handler.php =================================================================== --- branches/5.2.x/core/units/categories/categories_event_handler.php (revision 14718) +++ branches/5.2.x/core/units/categories/categories_event_handler.php (revision 14719) @@ -1,2788 +1,2846 @@ <?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 CategoriesEventHandler extends kDBEventHandler { /** * Allows to override standard permission mapping * */ function mapPermissions() { parent::mapPermissions(); $permissions = Array ( 'OnRebuildCache' => Array ('self' => 'add|edit'), 'OnCopy' => Array ('self' => true), 'OnCut' => Array ('self' => 'edit'), 'OnPasteClipboard' => Array ('self' => true), 'OnPaste' => Array ('self' => 'add|edit', 'subitem' => 'edit'), 'OnRecalculatePriorities' => Array ('self' => 'add|edit'), // category ordering 'OnItemBuild' => Array ('self' => true), // always allow to view individual categories (regardless of CATEGORY.VIEW right) 'OnUpdatePreviewBlock' => Array ('self' => true), // for FCKEditor integration ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Categories are sorted using special sorting event * */ function mapEvents() { parent::mapEvents(); $events_map = Array ( 'OnMassMoveUp' => 'OnChangePriority', 'OnMassMoveDown' => 'OnChangePriority', ); $this->eventMethods = array_merge($this->eventMethods, $events_map); } /** * Checks user permission to execute given $event * * @param kEvent $event * @return bool * @access public */ public function CheckPermission(&$event) { if ($event->Name == 'OnResetCMSMenuCache') { // events from "Tools -> System Tools" section are controlled via that section "edit" permission $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $perm_value = $this->Application->CheckPermission('in-portal:service.edit'); return $perm_helper->finalizePermissionCheck($event, $perm_value); } if (!$this->Application->isAdmin) { if ($event->Name == 'OnSetSortingDirect') { // allow sorting on front event without view permission return true; } if ($event->Name == 'OnItemBuild') { $category_id = $this->getPassedID($event); if ($category_id == 0) { return true; } } } 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]['ParentId'], $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['ParentId'], $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); } if ($event->Name == 'OnRecalculatePriorities') { $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $category_id = $this->Application->GetVar('m_cat_id'); return $perm_helper->AddCheckPermission($category_id, $event->Prefix) || $perm_helper->ModifyCheckPermission(0, $category_id, $event->Prefix); } if ($event->Name == 'OnPasteClipboard') { // forces permission check to work by current category for "Paste In Category" operation $category_id = $this->Application->GetVar('m_cat_id'); $this->Application->SetVar('c_id', $category_id); } 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) { // when saving data from temp table to live table check by data from temp table $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); $table_name = $this->Application->getUnitOption($event->Prefix, 'TableName'); if ($event->Name == 'OnSave') { $table_name = $this->Application->GetTempName($table_name, 'prefix:' . $event->Prefix); } $sql = 'SELECT ' . $id_field . ', CreatedById, ParentId FROM ' . $table_name . ' WHERE ' . $id_field . ' IN (' . $this->_getPermissionCheckIDs($event) . ')'; $items = $this->Conn->Query($sql, $id_field); if (!$items) { // when creating new category, then no IDs are stored in session $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); list ($id, $fields_hash) = each($items_info); if (array_key_exists('ParentId', $fields_hash)) { $item_category = $fields_hash['ParentId']; } else { $item_category = $this->Application->RecallVar('m_cat_id'); // saved in c:OnPreCreate event permission checking } $items[$id] = Array ( 'CreatedById' => $this->Application->RecallVar('user_id'), 'ParentId' => $item_category, ); } return $items; } /** * Set's mark, that root category is edited * * @param kEvent $event */ function OnEdit(&$event) { $category_id = $this->Application->GetVar($event->getPrefixSpecial() . '_id'); $home_category = $this->Application->getBaseCategory(); $this->Application->StoreVar('IsRootCategory_'.$this->Application->GetVar('m_wid'), ($category_id === '0') || ($category_id == $home_category)); parent::OnEdit($event); if ($event->status == kEvent::erSUCCESS) { // keep "Section Properties" link (in browse modes) clean $this->Application->DeleteVar('admin'); } } /** * Adds selected link to listing * * @param kEvent $event */ function OnProcessSelected(&$event) { $object =& $event->getObject(); /* @var $object kDBItem */ $selected_ids = $this->Application->GetVar('selected_ids'); $this->RemoveRequiredFields($object); $object->SetDBField($this->Application->RecallVar('dst_field'), $selected_ids['c']); $object->Update(); $event->SetRedirectParam('opener', 'u'); } /** * Apply system filter to categories list * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetCustomQuery(&$event) { parent::SetCustomQuery($event); $object =& $event->getObject(); /* @var $object kDBList */ // don't show "Content" category in advanced view $object->addFilter('system_categories', '%1$s.Status <> 4'); // show system templates from current theme only + all virtual templates $object->addFilter('theme_filter', '%1$s.ThemeId = ' . $this->_getCurrentThemeId() . ' OR %1$s.ThemeId = 0'); if ($event->Special == 'showall') { // if using recycle bin don't show categories from there $recycle_bin = $this->Application->ConfigValue('RecycleBinFolder'); if ($recycle_bin) { $sql = 'SELECT TreeLeft, TreeRight FROM '.TABLE_PREFIX.'Category WHERE CategoryId = '.$recycle_bin; $tree_indexes = $this->Conn->GetRow($sql); $object->addFilter('recyclebin_filter', '%1$s.TreeLeft < '.$tree_indexes['TreeLeft'].' OR %1$s.TreeLeft > '.$tree_indexes['TreeRight']); } } if ($event->getEventParam('parent_cat_id') !== false) { $parent_cat_id = $event->getEventParam('parent_cat_id'); if ("$parent_cat_id" == 'Root') { $module_name = $event->getEventParam('module') ? $event->getEventParam('module') : 'In-Commerce'; $parent_cat_id = $this->Application->findModule('Name', $module_name, 'RootCat'); } } 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 ("$parent_cat_id" != 'any') { if ($event->getEventParam('recursive')) { if ($parent_cat_id > 0) { // not "Home" category $tree_indexes = $this->Application->getTreeIndex($parent_cat_id); $object->addFilter('parent_filter', TABLE_PREFIX.'Category.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight']); } } else { $object->addFilter('parent_filter', '%1$s.ParentId = '.$parent_cat_id); } } $object->addFilter('perm_filter', TABLE_PREFIX . 'PermCache.PermId = 1'); // check for CATEGORY.VIEW permission if ($this->Application->RecallVar('user_id') != USER_ROOT) { // apply permission filters to all users except "root" $view_filters = Array (); $groups = explode(',',$this->Application->RecallVar('UserGroups')); foreach ($groups as $group) { $view_filters[] = 'FIND_IN_SET('.$group.', ' . TABLE_PREFIX . 'PermCache.ACL)'; } $view_filter = implode(' OR ', $view_filters); $object->addFilter('perm_filter2', $view_filter); } if (!$this->Application->isAdminUser) { // apply status filter only on front $object->addFilter('status_filter', $object->TableName.'.Status = 1'); } // process "types" and "except" parameters $type_clauses = Array(); $types = $event->getEventParam('types'); $types = $types ? explode(',', $types) : Array (); $except_types = $event->getEventParam('except'); $except_types = $except_types ? explode(',', $except_types) : Array (); 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 $key => $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).')'; $type_clauses['related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_ids).')'; } else { $type_clauses['related']['include'] = '0'; $type_clauses['related']['except'] = '1'; } $type_clauses['related']['having_filter'] = false; } if (in_array('category_related', $type_clauses)) { $object->removeFilter('parent_filter'); $resource_id = $this->Conn->GetOne(' SELECT ResourceId FROM '.$this->Application->getUnitOption($event->Prefix, 'TableName').' WHERE CategoryId = '.$parent_cat_id ); $sql = 'SELECT DISTINCT(TargetId) FROM '.TABLE_PREFIX.'Relationship WHERE SourceId = '.$resource_id.' AND SourceType = 1'; $related_cats = $this->Conn->GetCol($sql); $related_cats = is_array($related_cats) ? $related_cats : Array(); $sql = 'SELECT DISTINCT(SourceId) FROM '.TABLE_PREFIX.'Relationship WHERE TargetId = '.$resource_id.' AND TargetType = 1 AND Type = 1'; $related_cats2 = $this->Conn->GetCol($sql); $related_cats2 = is_array($related_cats2) ? $related_cats2 : Array(); $related_cats = array_unique( array_merge( $related_cats2, $related_cats ) ); if ($related_cats) { $type_clauses['category_related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_cats).')'; $type_clauses['category_related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_cats).')'; } else { $type_clauses['category_related']['include'] = '0'; $type_clauses['category_related']['except'] = '1'; } $type_clauses['category_related']['having_filter'] = false; } if (in_array('product_related', $types)) { $object->removeFilter('parent_filter'); $product_id = $event->getEventParam('product_id') ? $event->getEventParam('product_id') : $this->Application->GetVar('p_id'); $resource_id = $this->Conn->GetOne(' SELECT ResourceId FROM '.$this->Application->getUnitOption('p', 'TableName').' WHERE ProductId = '.$product_id ); $sql = 'SELECT DISTINCT(TargetId) FROM '.TABLE_PREFIX.'Relationship WHERE SourceId = '.$resource_id.' AND TargetType = 1'; $related_cats = $this->Conn->GetCol($sql); $related_cats = is_array($related_cats) ? $related_cats : Array(); $sql = 'SELECT DISTINCT(SourceId) FROM '.TABLE_PREFIX.'Relationship WHERE TargetId = '.$resource_id.' AND SourceType = 1 AND Type = 1'; $related_cats2 = $this->Conn->GetCol($sql); $related_cats2 = is_array($related_cats2) ? $related_cats2 : Array(); $related_cats = array_unique( array_merge( $related_cats2, $related_cats ) ); if ($related_cats) { $type_clauses['product_related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_cats).')'; $type_clauses['product_related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_cats).')'; } else { $type_clauses['product_related']['include'] = '0'; $type_clauses['product_related']['except'] = '1'; } $type_clauses['product_related']['having_filter'] = false; } $type_clauses['menu']['include'] = '%1$s.IsMenu = 1'; $type_clauses['menu']['except'] = '%1$s.IsMenu = 0'; $type_clauses['menu']['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 LEFT 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'); $object->AddOrderField('search_result.Relevance', 'desc', true); $type_clauses['search']['include'] = '1'; $type_clauses['search']['except'] = '0'; $type_clauses['search']['having_filter'] = false; } $search_helper =& $this->Application->recallObject('SearchHelper'); /* @var $search_helper kSearchHelper */ $search_helper->SetComplexFilter($event, $type_clauses, implode(',', $types), implode(',', $except_types)); } /** * Returns current theme id * * @return int */ function _getCurrentThemeId() { $themes_helper =& $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ return (int)$themes_helper->getCurrentThemeId(); } /** * Enter description here... * * @param kEvent $event * @return int */ function getPassedID(&$event) { if (($event->Special == 'page') || ($event->Special == '-virtual') || ($event->Prefix == 'st')) { return $this->_getPassedStructureID($event); } if ($this->Application->isAdmin) { return parent::getPassedID($event); } return $this->Application->GetVar('m_cat_id'); } /** * Enter description here... * * @param kEvent $event * @return int */ function _getPassedStructureID(&$event) { static $page_by_template = Array (); if ($event->Special == 'current') { return $this->Application->GetVar('m_cat_id'); } $event->setEventParam('raise_warnings', 0); $page_id = parent::getPassedID($event); if ($page_id === false) { $template = $event->getEventParam('page'); if (!$template) { $template = $this->Application->GetVar('t'); } // bug: when template contains "-" symbols (or others, that stripDisallowed will replace) it's not found if (!array_key_exists($template, $page_by_template)) { $sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . ' FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' WHERE ( (NamedParentPath = ' . $this->Conn->qstr($template) . ') OR (NamedParentPath = ' . $this->Conn->qstr('Content/' . $template) . ') OR (`Type` = ' . PAGE_TYPE_TEMPLATE . ' AND CachedTemplate = ' . $this->Conn->qstr($template) . ') ) AND (ThemeId = ' . $this->_getCurrentThemeId() . ' OR ThemeId = 0)'; $page_id = $this->Conn->GetOne($sql); } else { $page_id = $page_by_template[$template]; } if ($page_id === false && EDITING_MODE) { // create missing pages, when in editing mode $object =& $this->Application->recallObject($this->Prefix . '.rebuild', null, Array('skip_autoload' => true)); /* @var $object CategoriesItem */ $created = $this->_prepareAutoPage($object, $template, null, SMS_MODE_AUTO); // create virtual (not system!) page if ($created) { if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild') || !$this->Application->isAdmin) { $updater =& $this->Application->makeClass('kPermCacheUpdater'); /* @var $updater kPermCacheUpdater */ $updater->OneStepRun(); } $this->_resetMenuCache(); $this->Application->RemoveVar('PermCache_UpdateRequired'); $page_id = $object->GetID(); $this->Application->SetVar('m_cat_id', $page_id); } } if ($page_id) { $page_by_template[$template] = $page_id; } } if (!$page_id && !$this->Application->isAdmin) { $page_id = $this->Application->GetVar('m_cat_id'); } return $page_id; } function ParentGetPassedID(&$event) { return parent::getPassedID($event); } /** * Adds calculates fields for item statuses * * @param kCatDBItem $object * @param kEvent $event * @return void * @access protected */ protected function prepareObject(&$object, &$event) { if ($event->Special != '-virtual') { $object =& $event->getObject( Array('skip_autoload' => true) ); $object->addCalculatedField( 'IsNew', ' IF(%1$s.NewItem = 2, IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - '. $this->Application->ConfigValue('Category_DaysNew'). '*3600*24), 1, 0), %1$s.NewItem )'); } } /** * Set correct parent path for newly created categories * * @param kEvent $event */ function OnAfterCopyToLive(&$event) { $object =& $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true, 'live_table' => true)); /* @var $object CategoriesItem */ $parent_path = false; $object->Load( $event->getEventParam('id') ); if ( $event->getEventParam('temp_id') == 0 ) { if ( $object->isLoaded() ) { // update path only for real categories (not including "Home" root category) $fields_hash = Array ('ParentPath' => $object->buildParentPath()); $this->Conn->doUpdate($fields_hash, $object->TableName, 'CategoryId = ' . $object->GetID()); $parent_path = $fields_hash['ParentPath']; } } else { $parent_path = $object->GetDBField('ParentPath'); } if ( $parent_path ) { $cache_updater =& $this->Application->makeClass('kPermCacheUpdater', Array (null, $parent_path)); /* @var $cache_updater kPermCacheUpdater */ $cache_updater->OneStepRun(); } } /** * Set cache modification mark if needed * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeDeleteFromLive(&$event) { parent::OnBeforeDeleteFromLive($event); $id = $event->getEventParam('id'); // loading anyway, because this object is needed by "c-perm:OnBeforeDeleteFromLive" event $temp_object =& $event->getObject(Array ('skip_autoload' => true)); /* @var $temp_object CategoriesItem */ $temp_object->Load($id); if ( $id == 0 ) { if ( $temp_object->isLoaded() ) { // new category -> update cache (not loaded when "Home" category) $this->Application->StoreVar('PermCache_UpdateRequired', 1); } return ; } // existing category was edited, check if in-cache fields are modified $live_object =& $this->Application->recallObject($event->Prefix . '.-item', null, Array ('live_table' => true, 'skip_autoload' => true)); /* @var $live_object CategoriesItem */ $live_object->Load($id); $cached_fields = Array ('l' . $this->Application->GetDefaultLanguageId() . '_Name', 'Filename', 'Template', 'ParentId', 'Priority'); foreach ($cached_fields as $cached_field) { if ( $live_object->GetDBField($cached_field) != $temp_object->GetDBField($cached_field) ) { // use session instead of REQUEST because of permission editing in category can contain // multiple submits, that changes data before OnSave event occurs $this->Application->StoreVar('PermCache_UpdateRequired', 1); break; } } // remember category filename change between temp and live records if ( $temp_object->GetDBField('Filename') != $live_object->GetDBField('Filename') ) { $filename_changes = $this->Application->GetVar($event->Prefix . '_filename_changes', Array ()); $filename_changes[ $live_object->GetID() ] = Array ( 'from' => $live_object->GetDBField('Filename'), 'to' => $temp_object->GetDBField('Filename') ); $this->Application->SetVar($event->Prefix . '_filename_changes', $filename_changes); } } /** * Calls kDBEventHandler::OnSave original event * Used in proj-cms:StructureEventHandler->OnSave * * @param kEvent $event */ function parentOnSave(&$event) { parent::OnSave($event); } /** * Reset root-category flag when new category is created * * @param kEvent $event * @return void * @access protected */ protected function OnPreCreate(&$event) { // 1. for permission editing of Home category $this->Application->RemoveVar('IsRootCategory_' . $this->Application->GetVar('m_wid')); parent::OnPreCreate($event); $object =& $event->getObject(); /* @var $object kDBItem */ // 2. preset template $category_id = $this->Application->GetVar('m_cat_id'); $root_category = $this->Application->getBaseCategory(); if ( $category_id == $root_category ) { $object->SetDBField('Template', $this->_getDefaultDesign()); } // 3. set default owner $object->SetDBField('CreatedById', $this->Application->RecallVar('user_id')); } /** * Checks cache update mark and redirect to cache if needed * * @param kEvent $event * @return void * @access protected */ protected function OnSave(&$event) { // get data from live table before it is overwritten by parent OnSave method call $ids = $this->getSelectedIDs($event, true); $is_editing = implode('', $ids); $old_statuses = $is_editing ? $this->_getCategoryStatus($ids) : Array (); $object =& $event->getObject(); /* @var $object CategoriesItem */ parent::OnSave($event); if ( $event->status != kEvent::erSUCCESS ) { return; } if ( $this->Application->RecallVar('PermCache_UpdateRequired') ) { $this->Application->RemoveVar('IsRootCategory_' . $this->Application->GetVar('m_wid')); } $this->Application->StoreVar('RefreshStructureTree', 1); $this->_resetMenuCache(); if ( $is_editing ) { // send email event to category owner, when it's status is changed (from admin) $object->SwitchToLive(); $new_statuses = $this->_getCategoryStatus($ids); $process_statuses = Array (STATUS_ACTIVE, STATUS_DISABLED); foreach ($new_statuses as $category_id => $new_status) { if ( $new_status != $old_statuses[$category_id] && in_array($new_status, $process_statuses) ) { $object->Load($category_id); $email_event = $new_status == STATUS_ACTIVE ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY'; $this->Application->EmailEventUser($email_event, $object->GetDBField('CreatedById')); } } } // change opener stack in case if edited category filename was changed $filename_changes = $this->Application->GetVar($event->Prefix . '_filename_changes', Array ()); if ( $filename_changes ) { $opener_stack =& $this->Application->makeClass('kOpenerStack'); /* @var $opener_stack kOpenerStack */ list ($template, $params, $index_file) = $opener_stack->pop(); foreach ($filename_changes as $change_info) { $template = str_ireplace($change_info['from'], $change_info['to'], $template); } $opener_stack->push($template, $params, $index_file); $opener_stack->save(); } } /** * Returns statuses of given categories * * @param Array $category_ids * @return Array */ function _getCategoryStatus($category_ids) { $id_field = $this->Application->getUnitOption($this->Prefix, 'IDField'); $table_name = $this->Application->getUnitOption($this->Prefix, 'TableName'); $sql = 'SELECT Status, ' . $id_field . ' FROM ' . $table_name . ' WHERE ' . $id_field . ' IN (' . implode(',', $category_ids) . ')'; return $this->Conn->GetCol($sql, $id_field); } /** * Creates a new item in temp table and * stores item id in App vars and Session on success * * @param kEvent $event * @return void * @access protected */ protected function OnPreSaveCreated(&$event) { $object =& $event->getObject( Array ('skip_autoload' => true) ); /* @var $object CategoriesItem */ if ( $object->IsRoot() ) { // don't create root category while saving permissions return; } parent::OnPreSaveCreated($event); } /** * Deletes sym link to other category * * @param kEvent $event */ function OnAfterItemDelete(&$event) { parent::OnAfterItemDelete($event); $object =& $event->getObject(); /* @var $object kDBItem */ $sql = 'UPDATE '.$object->TableName.' SET SymLinkCategoryId = NULL WHERE SymLinkCategoryId = '.$object->GetID(); $this->Conn->Query($sql); } /** * Exclude root categories from deleting * * @param kEvent $event * @param string $type * @return void * @access protected */ protected function customProcessing(&$event, $type) { if ( $event->Name == 'OnMassDelete' && $type == 'before' ) { $ids = $event->getEventParam('ids'); if ( !$ids || $this->Application->ConfigValue('AllowDeleteRootCats') ) { return; } $root_categories = Array (); // get module root categories and exclude them foreach ($this->Application->ModuleInfo as $module_info) { $root_categories[] = $module_info['RootCat']; } $root_categories = array_unique($root_categories); if ( $root_categories && array_intersect($ids, $root_categories) ) { $event->setEventParam('ids', array_diff($ids, $root_categories)); $this->Application->StoreVar('root_delete_error', 1); } } } /** * Checks, that given template exists (physically) in given theme * * @param string $template * @param int $theme_id * @return bool */ function _templateFound($template, $theme_id = null) { static $init_made = false; if (!$init_made) { $this->Application->InitParser(true); $init_made = true; } if (!isset($theme_id)) { $theme_id = $this->_getCurrentThemeId(); } $theme_name = $this->_getThemeName($theme_id); return $this->Application->TemplatesCache->TemplateExists('theme:' . $theme_name . '/' . $template); } /** * Removes ".tpl" in template path * * @param string $template * @return string */ function _stripTemplateExtension($template) { // return preg_replace('/\.[^.\\\\\\/]*$/', '', $template); return preg_replace('/^[\\/]{0,1}(.*)\.tpl$/', "$1", $template); } /** * Deletes all selected items. * Automatically recourse 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(&$event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return ; } $to_delete = Array (); $ids = $this->StoreSelectedIDs($event); $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); $cat =& $event->getObject(Array ('skip_autoload' => true)); /* @var $cat CategoriesItem */ foreach ($ids as $id) { $cat->Load($id); if ( preg_match('/^' . preg_quote($rb->GetDBField('ParentPath'), '/') . '/', $cat->GetDBField('ParentPath')) ) { $to_delete[] = $id; continue; } $cat->SetDBField('ParentId', $recycle_bin); $cat->Update(); } $ids = $to_delete; $event->redirect = 'categories/cache_updater'; } $event->setEventParam('ids', $ids); $this->customProcessing($event, 'before'); $ids = $event->getEventParam('ids'); if ( $ids ) { $recursive_helper =& $this->Application->recallObject('RecursiveHelper'); /* @var $recursive_helper kRecursiveHelper */ foreach ($ids as $id) { $recursive_helper->DeleteCategory($id, $event->Prefix); } } $this->clearSelectedIDs($event); $this->Application->StoreVar('RefreshStructureTree', 1); $this->_resetMenuCache(); } /** * Add selected items to clipboard with mode = COPY (CLONE) * * @param kEvent $event */ 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 */ 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); } /** * Controls all item paste operations. Can occur only with filled clipboard. * * @param kEvent $event */ function OnPasteClipboard(&$event) { $clipboard = unserialize( $this->Application->RecallVar('clipboard') ); foreach ($clipboard as $prefix => $clipboard_data) { $paste_event = new kEvent($prefix.':OnPaste', Array('clipboard_data' => $clipboard_data)); $this->Application->HandleEvent($paste_event); $event->copyFrom($paste_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; } /** * Paste categories with sub-items from clipboard * * @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; } // 1. get ParentId of moved category(-es) before it gets updated!!!) $source_category_id = 0; $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); $table_name = $this->Application->getUnitOption($event->Prefix, 'TableName'); if ( $clipboard_data['cut'] ) { $sql = 'SELECT ParentId FROM ' . $table_name . ' WHERE ' . $id_field . ' = ' . $clipboard_data['cut'][0]; $source_category_id = $this->Conn->GetOne($sql); } $recursive_helper =& $this->Application->recallObject('RecursiveHelper'); /* @var $recursive_helper kRecursiveHelper */ if ( $clipboard_data['cut'] ) { $recursive_helper->MoveCategories($clipboard_data['cut'], $this->Application->GetVar('m_cat_id')); } if ( $clipboard_data['copy'] ) { // don't allow to copy/paste system OR theme-linked virtual pages $sql = 'SELECT ' . $id_field . ' FROM ' . $table_name . ' WHERE ' . $id_field . ' IN (' . implode(',', $clipboard_data['copy']) . ') AND (`Type` = ' . PAGE_TYPE_VIRTUAL . ') AND (ThemeId = 0)'; $allowed_ids = $this->Conn->GetCol($sql); if ( !$allowed_ids ) { return; } foreach ($allowed_ids as $id) { $recursive_helper->PasteCategory($id, $event->Prefix); } } $priority_helper =& $this->Application->recallObject('PriorityHelper'); /* @var $priority_helper kPriorityHelper */ if ( $clipboard_data['cut'] ) { $ids = $priority_helper->recalculatePriorities($event, 'ParentId = ' . $source_category_id); if ( $ids ) { $priority_helper->massUpdateChanged($event->Prefix, $ids); } } // recalculate priorities of newly pasted categories in destination category $parent_id = $this->Application->GetVar('m_cat_id'); $ids = $priority_helper->recalculatePriorities($event, 'ParentId = ' . $parent_id); if ( $ids ) { $priority_helper->massUpdateChanged($event->Prefix, $ids); } if ( $clipboard_data['cut'] || $clipboard_data['copy'] ) { // rebuild with progress bar if ( $this->Application->ConfigValue('QuickCategoryPermissionRebuild') ) { $updater =& $this->Application->makeClass('kPermCacheUpdater'); /* @var $updater kPermCacheUpdater */ $updater->OneStepRun(); } else { $event->redirect = 'categories/cache_updater'; } $this->_resetMenuCache(); $this->Application->StoreVar('RefreshStructureTree', 1); } } /** * Occurs when pasting category * * @param kEvent $event */ /*function OnCatPaste(&$event) { $inp_clipboard = $this->Application->RecallVar('ClipBoard'); $inp_clipboard = explode('-', $inp_clipboard, 2); if($inp_clipboard[0] == 'COPY') { $saved_cat_id = $this->Application->GetVar('m_cat_id'); $cat_ids = $event->getEventParam('cat_ids'); $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); $table = $this->Application->getUnitOption($event->Prefix, 'TableName'); $ids_sql = 'SELECT '.$id_field.' FROM '.$table.' WHERE ResourceId IN (%s)'; $resource_ids_sql = 'SELECT ItemResourceId FROM '.TABLE_PREFIX.'CategoryItems WHERE CategoryId = %s AND PrimaryCat = 1'; $object =& $this->Application->recallObject($event->Prefix.'.item', $event->Prefix, Array('skip_autoload' => true)); foreach($cat_ids as $source_cat => $dest_cat) { $item_resource_ids = $this->Conn->GetCol( sprintf($resource_ids_sql, $source_cat) ); if(!$item_resource_ids) continue; $this->Application->SetVar('m_cat_id', $dest_cat); $item_ids = $this->Conn->GetCol( sprintf($ids_sql, implode(',', $item_resource_ids) ) ); $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); if($item_ids) $temp->CloneItems($event->Prefix, $event->Special, $item_ids); } $this->Application->SetVar('m_cat_id', $saved_cat_id); } }*/ /** * Clears clipboard content * * @param kEvent $event */ function OnClearClipboard(&$event) { $this->Application->RemoveVar('clipboard'); } /** * Sets correct status for new categories created on front-end * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(&$event) { parent::OnBeforeItemCreate($event); + $object =& $event->getObject(); + /* @var $object CategoriesItem */ + + if ( $object->GetDBField('ParentId') <= 0 ) { + // no parent category - use current (happens during import) + $object->SetDBField('ParentId', $this->Application->GetVar('m_cat_id')); + } + $this->_beforeItemChange($event); if ( $this->Application->isAdminUser || $event->Prefix == 'st' ) { // don't check category permissions when auto-creating structure pages return ; } $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $new_status = false; $category_id = $this->Application->GetVar('m_cat_id'); if ( $perm_helper->CheckPermission('CATEGORY.ADD', 0, $category_id) ) { $new_status = STATUS_ACTIVE; } else { if ( $perm_helper->CheckPermission('CATEGORY.ADD.PENDING', 0, $category_id) ) { $new_status = STATUS_PENDING; } } if ( $new_status ) { - $object =& $event->getObject(); - /* @var $object kDBItem */ - $object->SetDBField('Status', $new_status); // don't forget to set Priority for suggested from Front-End categories $min_priority = $this->_getNextPriority($object->GetDBField('ParentId'), $object->TableName); $object->SetDBField('Priority', $min_priority); } else { $event->status = kEvent::erPERM_FAIL; return ; } } /** * Returns next available priority for given category from given table * * @param int $category_id * @param string $table_name * @return int */ function _getNextPriority($category_id, $table_name) { $sql = 'SELECT MIN(Priority) FROM ' . $table_name . ' WHERE ParentId = ' . $category_id; return (int)$this->Conn->GetOne($sql) - 1; } /** * Sets correct status for new categories created on front-end * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(&$event) { parent::OnBeforeItemUpdate($event); + $this->_beforeItemChange($event); + $object =& $event->getObject(); /* @var $object kDBItem */ if ( $object->GetChangedFields() ) { $object->SetDBField('ModifiedById', $this->Application->RecallVar('user_id')); } - - $this->_beforeItemChange($event); } /** * Performs redirect to correct suggest confirmation template * * @param kEvent $event */ function OnCreate(&$event) { parent::OnCreate($event); if ($this->Application->isAdminUser || $event->status != kEvent::erSUCCESS) { // don't sent email or rebuild cache directly after category is created by admin return ; } $object =& $event->getObject(); /* @var $object kDBItem */ $cache_updater =& $this->Application->makeClass('kPermCacheUpdater', Array (null, $object->GetDBField('ParentPath'))); /* @var $cache_updater kPermCacheUpdater */ $cache_updater->OneStepRun(); $is_active = ($object->GetDBField('Status') == STATUS_ACTIVE); $next_template = $is_active ? 'suggest_confirm_template' : 'suggest_pending_confirm_template'; $event->redirect = $this->Application->GetVar($next_template); $event->SetRedirectParam('opener', 's'); // send email events $perm_prefix = $this->Application->getUnitOption($event->Prefix, 'PermItemPrefix'); $event_suffix = $is_active ? 'ADD' : 'ADD.PENDING'; $this->Application->EmailEventAdmin($perm_prefix.'.'.$event_suffix); $this->Application->EmailEventUser($perm_prefix.'.'.$event_suffix, $object->GetDBField('CreatedById')); } /** * Returns current per-page setting for list * * @param kEvent $event * @return int */ function getPerPage(&$event) { if (!$this->Application->isAdmin) { $event->setEventParam('same_special', true); } return parent::getPerPage($event); } /** * Set's correct page for list * based on data provided with event * * @param kEvent $event * @access private * @see OnListBuild */ function SetPagination(&$event) { parent::SetPagination($event); if ( !$this->Application->isAdmin ) { $page_var = $event->getEventParam('page_var'); if ( $page_var !== false ) { $page = $this->Application->GetVar($page_var); if ( is_numeric($page) ) { $object =& $event->getObject(); /* @var $object kDBList */ $object->SetPage($page); } } } } /** * Apply same processing to each item being selected in grid * * @param kEvent $event * @return void * @access protected */ protected function iterateItems(&$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 CategoriesItem */ $ids = $this->StoreSelectedIDs($event); if ( $ids ) { $propagate_category_status = $this->Application->GetVar('propagate_category_status'); $status_field = array_shift( $this->Application->getUnitOption($event->Prefix, 'StatusField') ); foreach ($ids as $id) { $object->Load($id); $object->SetDBField($status_field, $event->Name == 'OnMassApprove' ? 1 : 0); if ( $object->Update() ) { if ( $propagate_category_status ) { $sql = 'UPDATE ' . $object->TableName . ' SET ' . $status_field . ' = ' . $object->GetDBField($status_field) . ' WHERE TreeLeft BETWEEN ' . $object->GetDBField('TreeLeft') . ' AND ' . $object->GetDBField('TreeRight'); $this->Conn->Query($sql); } $event->status = kEvent::erSUCCESS; $email_event = $event->Name == 'OnMassApprove' ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY'; $this->Application->EmailEventUser($email_event, $object->GetDBField('CreatedById')); } else { $event->status = kEvent::erFAIL; $event->redirect = false; break; } } } $this->clearSelectedIDs($event); $this->Application->StoreVar('RefreshStructureTree', 1); } /** * Checks, that currently loaded item is allowed for viewing (non permission-based) * * @param kEvent $event * @return bool */ function checkItemStatus(&$event) { $object =& $event->getObject(); /* @var $object kDBItem */ if ( !$object->isLoaded() ) { return true; } if ( $object->GetDBField('Status') != STATUS_ACTIVE && $object->GetDBField('Status') != 4 ) { if ( !$object->GetDBField('DirectLinkEnabled') || !$object->GetDBField('DirectLinkAuthKey') ) { return false; } return $this->Application->GetVar('authkey') == $object->GetDBField('DirectLinkAuthKey'); } return true; } // ============= for cms page processing ======================= /** * Returns default design template * * @return string */ function _getDefaultDesign() { $default_design = trim($this->Application->ConfigValue('cms_DefaultDesign'), '/'); if (!$default_design) { // theme-based alias for default design return '#default_design#'; } if (strpos($default_design, '#') === false) { // real template, not alias, so prefix with "/" return '/' . $default_design; } // alias return $default_design; } /** * Returns default design based on given virtual template (used from kApplication::Run) * * @param string $t * @return string * @access public */ public function GetDesignTemplate($t = null) { if ( !isset($t) ) { $t = $this->Application->GetVar('t'); } $page =& $this->Application->recallObject($this->Prefix . '.-virtual', null, Array ('page' => $t)); /* @var $page CategoriesItem */ if ( $page->isLoaded() ) { $real_t = $page->GetDBField('CachedTemplate'); $this->Application->SetVar('m_cat_id', $page->GetDBField('CategoryId')); if ( $page->GetDBField('FormId') ) { $this->Application->SetVar('form_id', $page->GetDBField('FormId')); } } else { $not_found = $this->Application->ConfigValue('ErrorTemplate'); $real_t = $not_found ? $not_found : 'error_notfound'; $themes_helper =& $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ $theme_id = $this->Application->GetVar('m_theme'); $category_id = $themes_helper->getPageByTemplate($real_t, $theme_id); $this->Application->SetVar('m_cat_id', $category_id); header('HTTP/1.0 404 Not Found'); } // replace alias in form #alias_name# to actual template used in this theme $theme =& $this->Application->recallObject('theme.current'); /* @var $theme kDBItem */ $template = $theme->GetField('TemplateAliases', $real_t); if ( $template ) { return $template; } return $real_t; } /** * Sets category id based on found template (used from kApplication::Run) * * @deprecated */ /*function SetCatByTemplate() { $t = $this->Application->GetVar('t'); $page =& $this->Application->recallObject($this->Prefix . '.-virtual'); if ($page->isLoaded()) { $this->Application->SetVar('m_cat_id', $page->GetDBField('CategoryId') ); } }*/ /** * Prepares template paths * * @param kEvent $event */ function _beforeItemChange(&$event) { $object =& $event->getObject(); - /* @var $object kDBItem */ + /* @var $object CategoriesItem */ + + $object->checkFilename(); + $object->generateFilename(); $now = adodb_mktime(); if ( !$this->Application->isDebugMode() && strpos($event->Special, 'rebuild') === false ) { $object->SetDBField('Type', $object->GetOriginalField('Type')); $object->SetDBField('Protected', $object->GetOriginalField('Protected')); if ( $object->GetDBField('Protected') ) { // some fields are read-only for protected pages, when debug mode is off $object->SetDBField('AutomaticFilename', $object->GetOriginalField('AutomaticFilename')); $object->SetDBField('Filename', $object->GetOriginalField('Filename')); $object->SetDBField('Status', $object->GetOriginalField('Status')); } } + $is_admin = $this->Application->isAdminUser; + + if ( (!$object->IsTempTable() && !$is_admin) || ($is_admin && !$object->GetDBField('CreatedById')) ) { + $object->SetDBField('CreatedById', $this->Application->RecallVar('user_id')); + } + if ($object->GetChangedFields()) { $object->SetDBField('Modified_date', $now); $object->SetDBField('Modified_time', $now); } $object->setRequired('PageCacheKey', $object->GetDBField('OverridePageCacheKey')); $object->SetDBField('Template', $this->_stripTemplateExtension( $object->GetDBField('Template') )); if ($object->GetDBField('Type') == PAGE_TYPE_TEMPLATE) { if (!$this->_templateFound($object->GetDBField('Template'), $object->GetDBField('ThemeId'))) { $object->SetError('Template', 'template_file_missing', 'la_error_TemplateFileMissing'); } } $this->_saveTitleField($object, 'Title'); $this->_saveTitleField($object, 'MenuTitle'); $root_category = $this->Application->getBaseCategory(); if ( file_exists(FULL_PATH . '/themes') && ($object->GetDBField('ParentId') == $root_category) && ($object->GetDBField('Template') == CATEGORY_TEMPLATE_INHERIT) ) { // there are themes + creating top level category $object->SetError('Template', 'no_inherit'); } if ( !$this->Application->isAdminUser && $object->isVirtualField('cust_RssSource') ) { // only administrator can set/change "cust_RssSource" field if ($object->GetDBField('cust_RssSource') != $object->GetOriginalField('cust_RssSource')) { $object->SetError('cust_RssSource', 'not_allowed', 'la_error_OperationNotAllowed'); } } if ( !$object->GetDBField('DirectLinkAuthKey') ) { $key_parts = Array ( $object->GetID(), $object->GetDBField('ParentId'), $object->GetField('Name'), 'b38' ); $object->SetDBField('DirectLinkAuthKey', substr( md5( implode(':', $key_parts) ), 0, 20 )); } } /** * Sets page name to requested field in case when: * 1. page was auto created (through theme file rebuild) * 2. requested field is empty * * @param kDBItem $object * @param string $field * @author Alex */ function _saveTitleField(&$object, $field) { $value = $object->GetField($field, 'no_default'); // current value of target field $ml_formatter =& $this->Application->recallObject('kMultiLanguage'); /* @var $ml_formatter kMultiLanguage */ $src_field = $ml_formatter->LangFieldName('Name'); $dst_field = $ml_formatter->LangFieldName($field); $dst_field_not_changed = $object->GetOriginalField($dst_field) == $value; if ($value == '' || preg_match('/^_Auto: (.*)/', $value) || (($object->GetOriginalField($src_field) == $value) && $dst_field_not_changed)) { // target field is empty OR target field value starts with "_Auto: " OR (source field value // before change was equals to current target field value AND target field value wasn't changed) $object->SetField($dst_field, $object->GetField($src_field)); } } /** * Don't allow to delete system pages, when not in debug mode * * @param kEvent $event */ function OnBeforeItemDelete(&$event) { $object =& $event->getObject(); /* @var $object kDBItem */ if ( $object->GetDBField('Protected') && !$this->Application->isDebugMode() ) { $event->status = kEvent::erFAIL; } } /** * Creates category based on given TPL file * * @param CategoriesItem $object * @param string $template * @param int $theme_id * @param int $system_mode * @param array $template_info * @return bool */ function _prepareAutoPage(&$object, $template, $theme_id = null, $system_mode = SMS_MODE_AUTO, $template_info = Array ()) { $template = $this->_stripTemplateExtension($template); if ($system_mode == SMS_MODE_AUTO) { $page_type = $this->_templateFound($template, $theme_id) ? PAGE_TYPE_TEMPLATE : PAGE_TYPE_VIRTUAL; } else { $page_type = $system_mode == SMS_MODE_FORCE ? PAGE_TYPE_TEMPLATE : PAGE_TYPE_VIRTUAL; } if (($page_type == PAGE_TYPE_TEMPLATE) && ($template_info === false)) { // do not auto-create system pages, when browsing through site return false; } if (!isset($theme_id)) { $theme_id = $this->_getCurrentThemeId(); } $root_category = $this->Application->getBaseCategory(); $page_category = $this->Application->GetVar('m_cat_id'); if (!$page_category) { $page_category = $root_category; $this->Application->SetVar('m_cat_id', $page_category); } if (($page_type == PAGE_TYPE_VIRTUAL) && (strpos($template, '/') !== false)) { // virtual page, but have "/" in template path -> create it's path $category_path = explode('/', $template); $template = array_pop($category_path); $page_category = $this->_getParentCategoryFromPath($category_path, $root_category, $theme_id); } $page_name = ($page_type == PAGE_TYPE_TEMPLATE) ? '_Auto: ' . $template : $template; $page_description = ''; if ($page_type == PAGE_TYPE_TEMPLATE) { $design_template = strtolower($template); // leading "/" not added ! if ($template_info) { if (array_key_exists('name', $template_info) && $template_info['name']) { $page_name = $template_info['name']; } if (array_key_exists('desc', $template_info) && $template_info['desc']) { $page_description = $template_info['desc']; } if (array_key_exists('section', $template_info) && $template_info['section']) { // this will override any global "m_cat_id" $page_category = $this->_getParentCategoryFromPath(explode('||', $template_info['section']), $root_category, $theme_id); } } } else { $design_template = $this->_getDefaultDesign(); // leading "/" added ! } $object->Clear(); $object->SetDBField('ParentId', $page_category); $object->SetDBField('Type', $page_type); $object->SetDBField('Protected', 1); // $page_type == PAGE_TYPE_TEMPLATE $object->SetDBField('IsMenu', 0); $object->SetDBField('ThemeId', $theme_id); // put all templates to then end of list (in their category) $min_priority = $this->_getNextPriority($page_category, $object->TableName); $object->SetDBField('Priority', $min_priority); $object->SetDBField('Template', $design_template); $object->SetDBField('CachedTemplate', $design_template); $primary_language = $this->Application->GetDefaultLanguageId(); $current_language = $this->Application->GetVar('m_lang'); $object->SetDBField('l' . $primary_language . '_Name', $page_name); $object->SetDBField('l' . $current_language . '_Name', $page_name); $object->SetDBField('l' . $primary_language . '_Description', $page_description); $object->SetDBField('l' . $current_language . '_Description', $page_description); return $object->Create(); } function _getParentCategoryFromPath($category_path, $base_category, $theme_id = null) { static $category_ids = Array (); if (!$category_path) { return $base_category; } if (array_key_exists(implode('||', $category_path), $category_ids)) { return $category_ids[ implode('||', $category_path) ]; } $backup_category_id = $this->Application->GetVar('m_cat_id'); $object =& $this->Application->recallObject($this->Prefix . '.rebuild-path', null, Array ('skip_autoload' => true)); /* @var $object CategoriesItem */ $parent_id = $base_category; $filenames_helper =& $this->Application->recallObject('FilenamesHelper'); /* @var $filenames_helper kFilenamesHelper */ $safe_category_path = array_map(Array (&$filenames_helper, 'replaceSequences'), $category_path); foreach ($category_path as $category_order => $category_name) { $this->Application->SetVar('m_cat_id', $parent_id); // get virtual category first, when possible $sql = 'SELECT ' . $object->IDField . ' FROM ' . $object->TableName . ' WHERE ( Filename = ' . $this->Conn->qstr($safe_category_path[$category_order]) . ' OR Filename = ' . $this->Conn->qstr( $filenames_helper->replaceSequences('_Auto: ' . $category_name) ) . ' ) AND (ParentId = ' . $parent_id . ') AND (ThemeId = 0 OR ThemeId = ' . $theme_id . ') ORDER BY ThemeId ASC'; $parent_id = $this->Conn->GetOne($sql); if ($parent_id === false) { // page not found $template = implode('/', array_slice($safe_category_path, 0, $category_order + 1)); // don't process system templates in sub-categories $system = $this->_templateFound($template, $theme_id) && (strpos($template, '/') === false); if (!$this->_prepareAutoPage($object, $category_name, $theme_id, $system ? SMS_MODE_FORCE : false)) { // page was not created break; } $parent_id = $object->GetID(); } } $this->Application->SetVar('m_cat_id', $backup_category_id); $category_ids[ implode('||', $category_path) ] = $parent_id; return $parent_id; } /** * Returns theme name by it's id. Used in structure page creation. * * @param int $theme_id * @return string */ function _getThemeName($theme_id) { static $themes = null; if (!isset($themes)) { $id_field = $this->Application->getUnitOption('theme', 'IDField'); $table_name = $this->Application->getUnitOption('theme', 'TableName'); $sql = 'SELECT Name, ' . $id_field . ' FROM ' . $table_name . ' WHERE Enabled = 1'; $themes = $this->Conn->GetCol($sql, $id_field); } return array_key_exists($theme_id, $themes) ? $themes[$theme_id] : false; } /** * Resets SMS-menu cache * * @param kEvent $event */ function OnResetCMSMenuCache(&$event) { if ($this->Application->GetVar('ajax') == 'yes') { $event->status = kEvent::erSTOP; } $this->_resetMenuCache(); $event->SetRedirectParam('action_completed', 1); } /** * Performs reset of category-related caches (menu, structure dropdown, template mapping) * * @return void * @access protected */ protected function _resetMenuCache() { // reset cms menu cache (all variables are automatically rebuild, when missing) if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $this->Application->rebuildCache('master:cms_menu', kCache::REBUILD_LATER, CacheSettings::$cmsMenuRebuildTime); $this->Application->rebuildCache('master:StructureTree', kCache::REBUILD_LATER, CacheSettings::$structureTreeRebuildTime); $this->Application->rebuildCache('master:template_mapping', kCache::REBUILD_LATER, CacheSettings::$templateMappingRebuildTime); } else { $this->Application->rebuildDBCache('cms_menu', kCache::REBUILD_LATER, CacheSettings::$cmsMenuRebuildTime); $this->Application->rebuildDBCache('StructureTree', kCache::REBUILD_LATER, CacheSettings::$structureTreeRebuildTime); $this->Application->rebuildDBCache('template_mapping', kCache::REBUILD_LATER, CacheSettings::$templateMappingRebuildTime); } } /** * Updates structure config * * @param kEvent $event */ function OnAfterConfigRead(&$event) { parent::OnAfterConfigRead($event); if (defined('IS_INSTALL') && IS_INSTALL) { // skip any processing, because Category table doesn't exists until install is finished return ; } $site_config_helper =& $this->Application->recallObject('SiteConfigHelper'); /* @var $site_config_helper SiteConfigHelper */ $settings = $site_config_helper->getSettings(); $root_category = $this->Application->getBaseCategory(); // set root category $section_adjustments = $this->Application->getUnitOption($event->Prefix, 'SectionAdjustments'); $section_adjustments['in-portal:browse'] = Array ( 'url' => Array ('m_cat_id' => $root_category), 'late_load' => Array ('m_cat_id' => $root_category), 'onclick' => 'checkCatalog(' . $root_category . ')', ); $section_adjustments['in-portal:browse_site'] = Array ( 'url' => Array ('editing_mode' => $settings['default_editing_mode']), ); $this->Application->setUnitOption($event->Prefix, 'SectionAdjustments', $section_adjustments); // prepare structure dropdown $category_helper =& $this->Application->recallObject('CategoryHelper'); /* @var $category_helper CategoryHelper */ $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); $fields['ParentId']['default'] = (int)$this->Application->GetVar('m_cat_id'); $fields['ParentId']['options'] = $category_helper->getStructureTreeAsOptions(); // limit design list by theme $theme_id = $this->_getCurrentThemeId(); $design_sql = $fields['Template']['options_sql']; $design_sql = str_replace('(tf.FilePath = "/designs")', '(' . implode(' OR ', $this->getDesignFolders()) . ')' . ' AND (t.ThemeId = ' . $theme_id . ')', $design_sql); $fields['Template']['options_sql'] = $design_sql; // adds "Inherit From Parent" option to "Template" field $fields['Template']['options'] = Array (CATEGORY_TEMPLATE_INHERIT => $this->Application->Phrase('la_opt_InheritFromParent')); $this->Application->setUnitOption($event->Prefix, 'Fields', $fields); if ($this->Application->isAdmin) { // don't sort by Front-End sorting fields $config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping'); $remove_keys = Array ('DefaultSorting1Field', 'DefaultSorting2Field', 'DefaultSorting1Dir', 'DefaultSorting2Dir'); foreach ($remove_keys as $remove_key) { unset($config_mapping[$remove_key]); } $this->Application->setUnitOption($event->Prefix, 'ConfigMapping', $config_mapping); } else { // sort by parent path on Front-End only $list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings', Array ()); $list_sortings['']['ForcedSorting'] = Array ("CurrentSort" => 'asc'); $this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings); } // 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_parent_category_td', 'filter_block' => 'grid_like_filter'); $grids[$process_grid . 'ShowAll'] = $grid_data; } $this->Application->setUnitOption($this->Prefix, 'Grids', $grids); } /** * Returns folders, that can contain design templates * * @return array * @access protected */ protected function getDesignFolders() { $ret = Array ('tf.FilePath = "/designs"', 'tf.FilePath = "/platform/designs"'); foreach ($this->Application->ModuleInfo as $module_info) { $ret[] = 'tf.FilePath = "/' . $module_info['TemplatePath'] . 'designs"'; } return array_unique($ret); } /** * Removes this item and it's children (recursive) from structure dropdown * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemLoad(&$event) { parent::OnAfterItemLoad($event); if ( !$this->Application->isAdmin ) { // calculate priorities dropdown only for admin return; } $object =& $event->getObject(); /* @var $object kDBItem */ // remove this category & it's children from dropdown $sql = 'SELECT ' . $object->IDField . ' FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' WHERE ParentPath LIKE "' . $object->GetDBField('ParentPath') . '%"'; $remove_categories = $this->Conn->GetCol($sql); $options = $object->GetFieldOption('ParentId', 'options'); foreach ($remove_categories as $remove_category) { unset($options[$remove_category]); } $object->SetFieldOption('ParentId', 'options', $options); } /** + * Occurs after creating item + * + * @param kEvent $event + * @return void + * @access protected + */ + protected function OnAfterItemCreate(&$event) + { + parent::OnAfterItemCreate($event); + + $object =& $event->getObject(); + /* @var $object CategoriesItem */ + + // need to update path after category is created, so category is included in that path + $parent_path = $object->buildParentPath(); + + $sql = 'UPDATE ' . $object->TableName . ' + SET ParentPath = ' . $this->Conn->qstr($parent_path) . ' + WHERE CategoryId = ' . $object->GetID(); + $this->Conn->Query($sql); + + $object->SetDBField('ParentPath', $parent_path); + } + + /** * Enter description here... * * @param kEvent $event */ function OnAfterRebuildThemes(&$event) { $sql = 'SELECT t.ThemeId, CONCAT( tf.FilePath, \'/\', tf.FileName ) AS Path, tf.FileMetaInfo FROM '.TABLE_PREFIX.'ThemeFiles AS tf LEFT JOIN '.TABLE_PREFIX.'Theme AS t ON t.ThemeId = tf.ThemeId WHERE t.Enabled = 1 AND tf.FileType = 1 AND ( SELECT COUNT(CategoryId) FROM ' . TABLE_PREFIX . 'Category c WHERE CONCAT(\'/\', c.Template, \'.tpl\') = CONCAT( tf.FilePath, \'/\', tf.FileName ) AND (c.ThemeId = t.ThemeId) ) = 0 '; $files = $this->Conn->Query($sql, 'Path'); if (!$files) { // all possible pages are already created return ; } set_time_limit(0); ini_set('memory_limit', -1); $dummy =& $this->Application->recallObject($event->Prefix . '.rebuild', null, Array ('skip_autoload' => true)); /* @var $dummy CategoriesItem */ $error_count = 0; foreach ($files as $a_file => $file_info) { $status = $this->_prepareAutoPage($dummy, $a_file, $file_info['ThemeId'], SMS_MODE_FORCE, unserialize($file_info['FileMetaInfo'])); // create system page if (!$status) { $error_count++; } } if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild')) { $updater =& $this->Application->makeClass('kPermCacheUpdater'); /* @var $updater kPermCacheUpdater */ $updater->OneStepRun(); } $this->_resetMenuCache(); if ($error_count) { // allow user to review error after structure page creation $event->MasterEvent->redirect = false; } } /** * Processes OnMassMoveUp, OnMassMoveDown events * * @param kEvent $event */ function OnChangePriority(&$event) { $this->Application->SetVar('priority_prefix', $event->getPrefixSpecial()); $event->CallSubEvent('priority:' . $event->Name); $this->Application->StoreVar('RefreshStructureTree', 1); $this->_resetMenuCache(); } /** * Completely recalculates priorities in current category * * @param kEvent $event */ function OnRecalculatePriorities(&$event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = kEvent::erFAIL; return; } $this->Application->SetVar('priority_prefix', $event->getPrefixSpecial()); $event->CallSubEvent('priority:' . $event->Name); $this->_resetMenuCache(); } /** * Update Preview Block for FCKEditor * * @param kEvent $event */ function OnUpdatePreviewBlock(&$event) { $event->status = kEvent::erSTOP; $string = kUtil::unhtmlentities($this->Application->GetVar('preview_content')); $category_helper =& $this->Application->recallObject('CategoryHelper'); /* @var $category_helper CategoryHelper */ $string = $category_helper->replacePageIds($string); $this->Application->StoreVar('_editor_preview_content_', $string); } /** * Makes simple search for categories * 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 = kUtil::unhtmlentities( 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) ) { // used when navigating by pages or changing sorting in search results return; } 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 $keywords = strtr($keywords, Array('%' => '\\%', '_' => '\\_')); $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 = 'In-Portal'; $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! $where_clause = $where_clause . ' AND (' . $items_table . '.Status = ' . STATUS_ACTIVE . ')'; if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') { if ($event->MasterEvent->getEventParam('ResultIds')) { $where_clause .= ' AND '.$items_table.'.ResourceId IN ('.implode(',', $event->MasterEvent->getEventParam('ResultIds')).')'; } } // exclude template based sections from search results (ie. registration) if ( $this->Application->ConfigValue('ExcludeTemplateSectionsFromSearch') ) { $where_clause .= ' AND ' . $items_table . '.ThemeId = 0'; } // 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); } } /** * 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.'SearchLog 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.'SearchLog'); } $this->Application->SetVar('search_logged', 1); } } /** * Load item if id is available * * @param kEvent $event * @return void * @access protected */ protected function LoadItem(&$event) { if ( $event->Special != '-virtual' ) { parent::LoadItem($event); return; } $object =& $event->getObject(); /* @var $object kDBItem */ $id = $this->getPassedID($event); if ( $object->isLoaded() && !is_array($id) && ($object->GetID() == $id) ) { // object is already loaded by same id return; } if ( $object->Load($id, null, true) ) { $actions =& $this->Application->recallObject('kActions'); /* @var $actions Params */ $actions->Set($event->getPrefixSpecial() . '_id', $object->GetID()); } else { $object->setID($id); } } /** * Returns constrain for priority calculations * * @param kEvent $event * @return void * @see PriorityEventHandler * @access protected */ protected function OnGetConstrainInfo(&$event) { $constrain = ''; // for OnSave $event_name = $event->getEventParam('original_event'); $actual_event_name = $event->getEventParam('actual_event'); if ( $actual_event_name == 'OnSavePriorityChanges' || $event_name == 'OnAfterItemLoad' || $event_name == 'OnAfterItemDelete' ) { $object =& $event->getObject(); /* @var $object kDBItem */ $constrain = 'ParentId = ' . $object->GetDBField('ParentId'); } elseif ( $actual_event_name == 'OnPreparePriorities' ) { $constrain = 'ParentId = ' . $this->Application->GetVar('m_cat_id'); } elseif ( $event_name == 'OnSave' ) { $constrain = ''; } else { $constrain = 'ParentId = ' . $this->Application->GetVar('m_cat_id'); } $event->setEventParam('constrain_info', Array ($constrain, '')); } /** * Parses category part of url, build main part of url * * @param int $rewrite_mode Mode in what rewrite listener was called. Possbile two modes: REWRITE_MODE_BUILD, REWRITE_MODE_PARSE. * @param string $prefix Prefix, that listener uses for system integration * @param Array $params Params, that are used for url building or created during url parsing. * @param Array $url_parts Url parts to parse (only for parsing). * @param bool $keep_events Keep event names in resulting url (only for building). * @return bool|string|Array Return true to continue to next listener; return false (when building) not to rewrite given prefix; return false (when parsing) to stop processing at this listener. */ public function CategoryRewriteListener($rewrite_mode = REWRITE_MODE_BUILD, $prefix, &$params, &$url_parts, $keep_events = false) { if ($rewrite_mode == REWRITE_MODE_BUILD) { return $this->_buildMainUrl($prefix, $params, $keep_events); } if ( $this->_parseFriendlyUrl($url_parts, $params) ) { // friendly urls work like exact match only! return false; } $this->_parseCategory($url_parts, $params); return true; } /** * Build main part of every url * * @param string $prefix_special * @param Array $params * @param bool $keep_events * @return string */ protected function _buildMainUrl($prefix_special, &$params, $keep_events) { $ret = ''; list ($prefix) = explode('.', $prefix_special); $rewrite_processor =& $this->Application->recallObject('kRewriteUrlProcessor'); /* @var $rewrite_processor kRewriteUrlProcessor */ $processed_params = $rewrite_processor->getProcessedParams($prefix_special, $params, $keep_events); if ($processed_params === false) { return ''; } // add language if ($processed_params['m_lang'] && ($processed_params['m_lang'] != $rewrite_processor->primaryLanguageId)) { $language_name = $this->Application->getCache('language_names[%LangIDSerial:' . $processed_params['m_lang'] . '%]'); if ($language_name === false) { $sql = 'SELECT PackName FROM ' . TABLE_PREFIX . 'Language WHERE LanguageId = ' . $processed_params['m_lang']; $language_name = $this->Conn->GetOne($sql); $this->Application->setCache('language_names[%LangIDSerial:' . $processed_params['m_lang'] . '%]', $language_name); } $ret .= $language_name . '/'; } // add theme if ($processed_params['m_theme'] && ($processed_params['m_theme'] != $rewrite_processor->primaryThemeId)) { $theme_name = $this->Application->getCache('theme_names[%ThemeIDSerial:' . $processed_params['m_theme'] . '%]'); if ($theme_name === false) { $sql = 'SELECT Name FROM ' . TABLE_PREFIX . 'Theme WHERE ThemeId = ' . $processed_params['m_theme']; $theme_name = $this->Conn->GetOne($sql); $this->Application->setCache('theme_names[%ThemeIDSerial:' . $processed_params['m_theme'] . '%]', $theme_name); } $ret .= $theme_name . '/'; } // inject custom url parts made by other rewrite listeners just after language/theme url parts if ($params['inject_parts']) { $ret .= implode('/', $params['inject_parts']) . '/'; } // add category if ($processed_params['m_cat_id'] > 0 && $params['pass_category']) { $category_filename = $this->Application->getCategoryCache($processed_params['m_cat_id'], 'filenames'); preg_match('/^Content\/(.*)/i', $category_filename, $regs); if ($regs) { $template = array_key_exists('t', $params) ? $params['t'] : false; if (strtolower($regs[1]) == strtolower($template)) { // we could have category path like "Content/<template_path>" in this case remove template $params['pass_template'] = false; } $ret .= $regs[1] . '/'; } $params['category_processed'] = true; } // reset category page $force_page_adding = false; if (array_key_exists('reset', $params) && $params['reset']) { unset($params['reset']); if ($processed_params['m_cat_id']) { $processed_params['m_cat_page'] = 1; $force_page_adding = true; } } if ((array_key_exists('category_processed', $params) && $params['category_processed'] && ($processed_params['m_cat_page'] > 1)) || $force_page_adding) { // category name was added before AND category page number found $ret = rtrim($ret, '/') . '_' . $processed_params['m_cat_page'] . '/'; } $template = array_key_exists('t', $params) ? $params['t'] : false; $category_template = ($processed_params['m_cat_id'] > 0) && $params['pass_category'] ? $this->Application->getCategoryCache($processed_params['m_cat_id'], 'category_designs') : ''; if ((strtolower($template) == '__default__') && ($processed_params['m_cat_id'] == 0)) { // for "Home" category set template to index when not set $template = 'index'; } // remove template from url if it is category index cached template OR site homepage if (($template == $category_template) || (mb_strtolower($template) == '__default__') || ($template == 'index')) { // given template is also default template for this category OR '__default__' given OR site homepage $params['pass_template'] = false; } if ($template && $params['pass_template']) { $ret .= $template . '/'; } return mb_strtolower( rtrim($ret, '/') ); } /** * Checks if whole url_parts matches a whole In-CMS page * * @param Array $url_parts * @param Array $vars * @return bool */ protected function _parseFriendlyUrl($url_parts, &$vars) { if (!$url_parts) { return false; } $sql = 'SELECT CategoryId, NamedParentPath FROM ' . TABLE_PREFIX . 'Category WHERE FriendlyURL = ' . $this->Conn->qstr(implode('/', $url_parts)); $friendly = $this->Conn->GetRow($sql); $rewrite_processor =& $this->Application->recallObject('kRewriteUrlProcessor'); /* @var $rewrite_processor kRewriteUrlProcessor */ if ($friendly) { $vars['m_cat_id'] = $friendly['CategoryId']; $vars['t'] = preg_replace('/^Content\//i', '', $friendly['NamedParentPath']); while ($url_parts) { $rewrite_processor->partParsed( array_shift($url_parts) ); } return true; } return false; } /** * Extracts category part from url * * @param Array $url_parts * @param Array $vars * @return bool */ protected function _parseCategory($url_parts, &$vars) { if (!$url_parts) { return false; } $res = false; $url_part = array_shift($url_parts); $category_id = 0; $last_category_info = false; $category_path = $url_part == 'content' ? '' : 'content'; $rewrite_processor =& $this->Application->recallObject('kRewriteUrlProcessor'); /* @var $rewrite_processor kRewriteUrlProcessor */ do { $category_path = trim($category_path . '/' . $url_part, '/'); // bb_<topic_id> -> forums/bb_2 if ( !preg_match('/^bb_[\d]+$/', $url_part) && preg_match('/(.*)_([\d]+)$/', $category_path, $rets) ) { $category_path = $rets[1]; $vars['m_cat_page'] = $rets[2]; } $sql = 'SELECT CategoryId, SymLinkCategoryId, NamedParentPath FROM ' . TABLE_PREFIX . 'Category WHERE (LOWER(NamedParentPath) = ' . $this->Conn->qstr($category_path) . ') AND (ThemeId = ' . $vars['m_theme'] . ' OR ThemeId = 0)'; $category_info = $this->Conn->GetRow($sql); if ($category_info !== false) { $last_category_info = $category_info; $rewrite_processor->partParsed($url_part); $url_part = array_shift($url_parts); $res = true; } } while ($category_info !== false && $url_part); if ($last_category_info) { // this category is symlink to other category, so use it's url instead // (used in case if url prior to symlink adding was indexed by spider or was bookmarked) if ($last_category_info['SymLinkCategoryId']) { $sql = 'SELECT CategoryId, NamedParentPath FROM ' . TABLE_PREFIX . 'Category WHERE (CategoryId = ' . $last_category_info['SymLinkCategoryId'] . ')'; $category_info = $this->Conn->GetRow($sql); if ($category_info) { // web symlinked category was found use it // TODO: maybe 302 redirect should be made to symlinked category url (all other url parts should stay) $last_category_info = $category_info; } } // 1. Set virtual page as template, this will be replaced to physical template later in kApplication::Run. // 2. Don't set CachedTemplate field as template here, because we will loose original page associated with it's cms blocks! $vars['t'] = mb_strtolower( preg_replace('/^Content\//i', '', $last_category_info['NamedParentPath']), 'UTF-8' ); $vars['m_cat_id'] = $last_category_info['CategoryId']; $vars['is_virtual'] = true; // for template from POST, strange code there! } else { $vars['m_cat_id'] = 0; } return $res; } + + /** + * 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/categories/categories_item.php =================================================================== --- branches/5.2.x/core/units/categories/categories_item.php (revision 14718) +++ branches/5.2.x/core/units/categories/categories_item.php (revision 14719) @@ -1,304 +1,255 @@ <?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 CategoriesItem extends kDBItem { - - /** - * Creates a record in the database table with current item' values - * - * @param mixed $force_id Set to TRUE to force creating of item's own ID or to value to force creating of passed id. Do not pass 1 for true, pass exactly TRUE! - * @param bool $system_create - * @return bool - * @access public - */ - public function Create($force_id = false, $system_create = false) - { - // set parent category first, so filename generation could use it - $parent_category = $this->GetDBField('ParentId') > 0 ? $this->GetDBField('ParentId') : $this->Application->GetVar('m_cat_id'); - $this->SetDBField('ParentId', $parent_category); - - $this->checkFilename(); - $this->generateFilename(); - - if ($this->Validate()) { - // TODO: such approach will not respect changes from CategoryEventHandler::OnBeforeItemCreate event - $this->SetDBField('ResourceId', $this->Application->NextResourceId()); - } - - // TODO: move to CategoryEventHandler::OnBeforeItemCreate - $is_admin = $this->Application->isAdminUser; - - if ((!$this->IsTempTable() && !$is_admin) || ($is_admin && !$this->GetDBField('CreatedById'))) { - $this->SetDBField('CreatedById', $this->Application->RecallVar('user_id')); - } - - $ret = parent::Create($force_id, $system_create); - - if ($ret) { - // TODO: move to CategoryEventHandler::OnAfterItemCreate method - $sql = 'UPDATE %s SET ParentPath = %s WHERE CategoryId = %s'; - $parent_path = $this->buildParentPath(); - $this->Conn->Query( sprintf($sql, $this->TableName, $this->Conn->qstr($parent_path), $this->GetID() ) ); - - $this->SetDBField('ParentPath', $parent_path); - } - - return $ret; - - } - /** - * Updates previously loaded record with current item' values + * Builds parent path for this category * - * @access public - * @param int $id Primary Key Id to update - * @param bool $system_update - * @return bool + * @return string * @access public */ - public function Update($id = null, $system_update = false) - { - $this->checkFilename(); - $this->generateFilename(); - - return parent::Update($id, $system_update); - } - - function buildParentPath() + public function buildParentPath() { $parent_id = $this->GetDBField('ParentId'); - if ($parent_id == 0) { + if ( $parent_id == 0 ) { $parent_path = '|'; } else { - $cat_table = $this->Application->getUnitOption($this->Prefix, 'TableName'); - $sql = 'SELECT ParentPath FROM '.$cat_table.' WHERE CategoryId = %s'; - $parent_path = $this->Conn->GetOne( sprintf($sql, $parent_id) ); + $sql = 'SELECT ParentPath + FROM ' . $this->Application->getUnitOption($this->Prefix, 'TableName') . ' WHERE + CategoryId = ' . $parent_id; + $parent_path = $this->Conn->GetOne($sql); } - return $parent_path.$this->GetID().'|'; + return $parent_path . $this->GetID() . '|'; } /** * replace not allowed symbols with "_" chars + remove duplicate "_" chars in result * * @param string $string * @return string + * @access protected */ - function stripDisallowed($string) + protected function stripDisallowed($string) { $filenames_helper =& $this->Application->recallObject('FilenamesHelper'); /* @var $filenames_helper kFilenamesHelper */ $string = $filenames_helper->replaceSequences($string); return $this->checkAutoFilename($string); } - function checkFilename() + public function checkFilename() { - if ($this->GetDBField('AutomaticFilename')) { + if ( $this->GetDBField('AutomaticFilename') ) { // filename will be generated from scratch, don't check anything here - return ; + return; } - elseif ($this->GetDBField('Type') == PAGE_TYPE_TEMPLATE) { + elseif ( $this->GetDBField('Type') == PAGE_TYPE_TEMPLATE ) { // system page with AutomaticFilename checkbox unchecked -> compatibility with Proj-CMS <= 4.3.9 (when "/" were allowed in Filename) - return ; + return; } $filename = $this->GetDBField('Filename'); $this->SetDBField('Filename', $this->stripDisallowed($filename)); } - function checkAutoFilename($filename) + protected function checkAutoFilename($filename) { static $current_theme = null; if (!$filename) { return $filename; } if (!isset($current_theme)) { $themes_helper =& $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ $current_theme = (int)$themes_helper->getCurrentThemeId(); } $escape_char = $this->Application->ConfigValue('FilenameSpecialCharReplacement'); $item_id = !$this->GetID() ? 0 : $this->GetID(); $item_theme = $this->GetDBField('ThemeId'); if (!$item_theme) { // user creates category manually, that's why ThemeId = 0 -> use current admin theme instead $item_theme = $current_theme; } $unique_clause = '(Filename = %s) AND (ThemeId = ' . $item_theme . ' OR ThemeId = 0) AND (ParentId = ' . $this->GetDBField('ParentId') . ')'; $sql_mask = ' SELECT ' . $this->IDField . ' FROM %s WHERE ' . sprintf($unique_clause, $this->Conn->qstr($filename)); // check temp table $sql_temp = sprintf($sql_mask, $this->TableName); $found_temp_ids = $this->Conn->GetCol($sql_temp); // check live table $sql_live = sprintf($sql_mask, $this->Application->GetLiveName($this->TableName)); $found_live_ids = $this->Conn->GetCol($sql_live); $found_item_ids = array_unique( array_merge($found_temp_ids, $found_live_ids) ); $has_page = preg_match('/(.*)_([\d]+)([a-z]*)$/', $filename, $rets); $duplicates_found = (count($found_item_ids) > 1) || ($found_item_ids && $found_item_ids[0] != $item_id); if ($duplicates_found || $has_page) {// other category has same filename as ours OR we have filename, that ends with _number $append = $duplicates_found ? $escape_char . 'a' : ''; if ($has_page) { $filename = $rets[1].'_'.$rets[2]; $append = $rets[3] ? $rets[3] : $escape_char . 'a'; } // check live & temp table $sql_temp = ' SELECT ' . $this->IDField . ' FROM ' . $this->TableName . ' WHERE ' . $unique_clause . ' AND (' . $this->IDField . ' != ' . $item_id . ')'; $sql_live = ' SELECT ' . $this->IDField . ' FROM ' . $this->Application->GetLiveName($this->TableName) . ' WHERE ' . $unique_clause . ' AND (' . $this->IDField . ' != ' . $item_id . ')'; while ( $this->Conn->GetOne( sprintf($sql_temp, $this->Conn->qstr($filename.$append)) ) > 0 || $this->Conn->GetOne( sprintf($sql_live, $this->Conn->qstr($filename.$append)) ) > 0 ) { if (mb_substr($append, -1) == 'z') $append .= 'a'; $append = mb_substr($append, 0, mb_strlen($append) - 1) . chr( ord( mb_substr($append, -1) ) + 1 ); } return $filename . $append; } return $filename; } /** * Generate item's filename based on it's title field value * * @return void - * @access protected + * @access public */ - protected function generateFilename() + public function generateFilename() { if ( !$this->GetDBField('AutomaticFilename') && $this->GetDBField('Filename') ) { return ; } $ml_formatter =& $this->Application->recallObject('kMultiLanguage'); /* @var $ml_formatter kMultiLanguage */ $name = $this->stripDisallowed( $this->GetDBField($ml_formatter->LangFieldName('Name', true)) ); if ( $name != $this->GetDBField('Filename') ) { $this->SetDBField('Filename', $name); } } /** * Allows to detect if root category being edited * * @return int + * @access public */ - function IsRoot() + public function IsRoot() { return $this->Application->RecallVar('IsRootCategory_'.$this->Application->GetVar('m_wid')); } + /** * Sets correct name to Home category while editing it * * @return bool + * @access public */ - function IsNewItem() + public function IsNewItem() { - if ($this->IsRoot() && $this->Prefix == 'c') { + if ( $this->IsRoot() && $this->Prefix == 'c' ) { $title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField'); $category_name = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name'); $this->SetDBField($title_field, $category_name); + return false; } + return parent::IsNewItem(); } /** * Sets new name for item in case if it is being copied in same table * * @param array $master Table data from TempHandler * @param int $foreign_key ForeignKey value to filter name check query by * @param string $title_field FieldName to alter, by default - TitleField of the prefix * @param string $format sprintf-style format of renaming pattern, by default Copy %1$s of %2$s which makes it Copy [Number] of Original Name * @access public */ public function NameCopy($master=null, $foreign_key=null, $title_field=null, $format='Copy %1$s of %2$s') { if (!isset($title_field)) { $title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField'); if (!$title_field || isset($this->CalculatedFields[$title_field]) ) return; } $new_name = $this->GetDBField($title_field); $cat_id = $this->Application->GetVar('m_cat_id'); $this->SetDBField('ParentId', $cat_id); $original_checked = false; do { if ( preg_match('/Copy ([0-9]*) *of (.*)/', $new_name, $regs) ) { $new_name = 'Copy '.($regs[1]+1).' of '.$regs[2]; } elseif ($original_checked) { $new_name = 'Copy of '.$new_name; } // if we are cloning in temp table this will look for names in temp table, // since object' TableName contains correct TableName (for temp also!) // if we are cloning live - look in live $query = ' SELECT ' . $title_field . ' FROM ' . $this->TableName . ' WHERE ParentId = ' . (int)$cat_id . ' AND ' . $title_field . ' = ' . $this->Conn->qstr($new_name); $foreign_key_field = getArrayValue($master, 'ForeignKey'); $foreign_key_field = is_array($foreign_key_field) ? $foreign_key_field[ $master['ParentPrefix'] ] : $foreign_key_field; if ($foreign_key_field && isset($foreign_key)) { $query .= ' AND '.$foreign_key_field.' = '.$foreign_key; } $res = $this->Conn->GetOne($query); /*// if not found in live table, check in temp table if applicable if ($res === false && $object->Special == 'temp') { $query = 'SELECT '.$name_field.' FROM '.$this->GetTempName($master['TableName']).' WHERE '.$name_field.' = '.$this->Conn->qstr($new_name); $res = $this->Conn->GetOne($query); }*/ $original_checked = true; } while ($res !== false); $this->SetDBField($title_field, $new_name); } } \ 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 14718) +++ branches/5.2.x/core/units/users/users_event_handler.php (revision 14719) @@ -1,1795 +1,1796 @@ <?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 * */ function mapPermissions() { parent::mapPermissions(); $permissions = Array ( // admin 'OnSetPersistantVariable' => Array('self' => 'view'), // because setting to logged in user only 'OnUpdateRootPassword' => Array('self' => true), '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); } /** * Builds item (loads if needed) * * Pattern: Prototype Manager * * @param kEvent $event * @access protected */ function OnItemBuild(&$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(&$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.'UserGroup', '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(&$event) { if ( $event->Name == 'OnLogin' || $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' && $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 = array_shift($this->Application->getUnitOption($event->Prefix, 'StatusField')); 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('UserSession'); // 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); } /** * [AGENT] 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 */ $object->SetFieldsFromHash( $this->getSubmittedFields($event) ); $username = $object->GetDBField('UserLogin'); $password = $object->GetDBField('UserPassword'); $remember_login = $object->GetDBField('UserRememberLogin') == 1; $user_helper =& $this->Application->recallObject('UserHelper'); /* @var $user_helper 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'); } } /** * [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 ; } $user_helper =& $this->Application->recallObject('UserHelper'); /* @var $user_helper 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) { $user_helper =& $this->Application->recallObject('UserHelper'); /* @var $user_helper 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(&$event) { parent::OnAfterItemCreate($event); $this->afterItemChanged($event); $this->assignToPrimaryGroup($event); } /** * Performs user registration * * @param kEvent $event */ function OnCreate(&$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); $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->SendEmailEvents(); $this->autoLoginUser($event); } } /** * 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 */ $this->Application->SetVar('u.current_id', $object->GetID()); if ( $object->GetDBField('Status') == STATUS_ACTIVE ) { $user_helper =& $this->Application->recallObject('UserHelper'); /* @var $user_helper UserHelper */ if ( $user_helper->checkLoginPermission() ) { $user_helper->loginUserById( $object->GetID() ); } } } /** * Set's new unique resource id to user * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(&$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 checck state-to-country relations for subscribers $cs_helper->CheckStateField($event, 'State', 'Country'); } $this->_makePasswordRequired($event); $cs_helper->PopulateStates($event, 'State', 'Country'); if ( $this->Application->ConfigValue('Email_As_Login') ) { $error_msgs = $object->GetFieldOption('Email', 'error_msgs'); $error_msgs['unique'] = '!lu_user_and_email_already_exist!'; $object->SetFieldOption('Email', 'error_msgs', $error_msgs); } $this->setUserGroup($object); $user_helper =& $this->Application->recallObject('UserHelper'); /* @var $user_helper UserHelper */ if ( !$user_helper->checkBanRules($object) ) { $object->SetError('Login', 'banned'); } $object->SetDBField('IPAddress', $_SERVER['REMOTE_ADDR']); } /** * 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 . 'PortalGroup 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 . 'UserGroup'; 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 */ - function OnAfterItemValidate(&$event) + protected function OnAfterItemValidate(kEvent &$event) { $object =& $event->getObject(); /* @var $object kDBItem */ $resource_id = $object->GetDBField('ResourceId'); - if (!$resource_id) { + 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 */ $object->SetFieldsFromHash( $this->getSubmittedFields($event) ); 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_event =& $this->Application->EmailEventUser('USER.SUGGEST', $user_id, $send_params); $email_event =& $this->Application->EmailEventAdmin('USER.SUGGEST'); if ( $email_event->status == kEvent::erSUCCESS ) { $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); $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 */ $object->SetFieldsFromHash( $this->getSubmittedFields($event) ); 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 ; } $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 ( $object->isRequired('Login') ) { $object->SetDBField('Login', $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 . 'UserGroup'); } $this->Application->EmailEventAdmin('USER.SUBSCRIBE'); $this->Application->EmailEventUser('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 . 'UserGroup WHERE PortalUserId = ' . $user_id . ' AND GroupId = ' . $group_id; $this->Conn->Query($sql); $this->Application->EmailEventAdmin('USER.UNSUBSCRIBE'); $this->Application->EmailEventUser('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 */ $object->SetFieldsFromHash( $this->getSubmittedFields($event) ); $user_object =& $this->Application->recallObject('u.tmp', null, Array('skip_autoload' => true)); /* @var $user_object UsersItem */ $found = $allow_reset = false; $username = $object->GetDBField('ForgotLogin'); $email = $object->GetDBField('ForgotEmail'); if ( strlen($username) ) { $user_object->Load($username, 'Login'); } elseif ( strlen($email) ) { $user_object->Load($email, 'Email'); } if ( $user_object->isLoaded() ) { $min_pwd_reset_delay = $this->Application->ConfigValue('Users_AllowReset'); $found = ($user_object->GetDBField('Status') == STATUS_ACTIVE) && strlen( $user_object->GetDBField('Password') ); if ( !$user_object->GetDBField('PwResetConfirm') ) { // no reset made -> allow $allow_reset = true; } else { // reset made -> wait N minutes, then allow $allow_reset = adodb_mktime() > $user_object->GetDBField('PwRequestTime') + $min_pwd_reset_delay; } } if ($found && $allow_reset) { $this->Application->EmailEventUser('USER.PSWDC', $user_object->GetID()); $event->redirect = $this->Application->GetVar('template_success'); return ; } if ( !strlen($username) && !strlen($email) ) { $object->SetError('ForgotLogin', 'required'); $object->SetError('ForgotEmail', 'required'); } else { if ( strlen($username) ) { $object->SetError('ForgotLogin', $found ? 'reset_denied' : 'unknown_username'); } if ( strlen($email) ) { $object->SetError('ForgotEmail', $found ? 'reset_denied' : 'unknown_email'); } } if ( !$object->ValidateField('ForgotLogin') || !$object->ValidateField('ForgotEmail') ) { $event->status = kEvent::erFAIL; } } /** * Updates kDBItem * * @param kEvent $event * @access protected */ protected function OnUpdate(&$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(&$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()); } } /** * 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.'UserGroup 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->EmailEventUser('USER.MEMBERSHIP.EXPIRATION.NOTICE', $record['PortalUserId']); $this->Application->EmailEventAdmin('USER.MEMBERSHIP.EXPIRATION.NOTICE'); $conditions[] = '(PortalUserId = '.$record['PortalUserId'].' AND GroupId = '.$record['GroupId'].')'; } $sql = 'UPDATE '.TABLE_PREFIX.'UserGroup 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.'UserGroup 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->EmailEventUser('USER.MEMBERSHIP.EXPIRED', $id); $this->Application->EmailEventAdmin('USER.MEMBERSHIP.EXPIRED'); } } $sql = 'DELETE FROM '.TABLE_PREFIX.'UserGroup 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, $fields) = each($item_info); $object =& $event->getObject( Array ('skip_autoload' => true) ); /* @var $object kDBItem */ $object->setID($id); $object->IgnoreValidation = true; $object->SetFieldsFromHash($fields); } /** * 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 */ function getPassedID(&$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': $user_helper =& $this->Application->recallObject('UserHelper'); /* @var $user_helper 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 */ function OnUpdateRootPassword(&$event) { return $this->OnUpdatePassword($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 = array_shift( $this->Application->getUnitOption($event->Prefix, 'StatusField') ); 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 */ // put salt to user's config $field_options = $object->GetFieldOptions('RootPassword'); $field_options['salt'] = 'b38'; // this is internal hack to allow root/root passwords for dev if ( $this->Application->isDebugMode() && $field_values['RootPassword'] == 'root' ) { $field_options['min_length'] = 4; } $object->SetFieldOptions('RootPassword', $field_options); $verify_options = $object->GetFieldOptions('VerifyRootPassword'); $verify_options['salt'] = 'b38'; $object->SetFieldOptions('VerifyRootPassword', $verify_options); $this->RemoveRequiredFields($object); $object->SetDBField('RootPassword', $this->Application->ConfigValue('RootPass')); $object->SetFieldsFromHash($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); 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 . 'PersistantSessionData 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 */ function checkItemStatus(&$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 */ function OnAfterItemUpdate(&$event) { parent::OnAfterItemUpdate($event); $this->afterItemChanged($event); $object =& $event->getObject(); /* @var $object UsersItem */ 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(&$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 */ function OnAfterCopyToLive(&$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->EmailEventUser($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->EmailEventUser($email_event, $user_id); $this->Application->EmailEventAdmin($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 . 'UserSession WHERE PortalUserId = ' . $user_id; $session_ids = $this->Conn->GetCol($sql); $this->Application->Session->DeleteSessions($session_ids); } } /** * OnAfterConfigRead for users * * @param kEvent $event */ function OnAfterConfigRead(&$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']); } $max_username = $this->Application->ConfigValue('MaxUserName'); $fields['Login']['min_len'] = $this->Application->ConfigValue('Min_UserName'); $fields['Login']['max_len'] = $max_username ? $max_username : 255; // 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('Email_As_Login') ) { // Login becomes required only, when it's used in registration process $form_fields['Login']['required'] = 1; } $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(&$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 = $this->Application->GetVar('g'); $primary_group_id = $group_ids ? array_shift( array_keys($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 . 'PortalUser 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(&$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); } /** * 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 */ function OnPreCreate(&$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(&$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) { } /** * Occurs just before logout (for hooking) * * @param kEvent $event */ function OnBeforeLogout(&$event) { } /** * 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 ) { $user_helper =& $this->Application->recallObject('UserHelper'); /* @var $user_helper 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(); $new_root_password_encrypted = $password_formatter->EncryptPassword($new_root_password, 'b38'); $this->Application->SetConfigValue('RootPass', $new_root_password_encrypted); $this->Application->EmailEventAdmin('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) { $user_helper =& $this->Application->recallObject('UserHelper'); /* @var $user_helper 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() ); } } }