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(kEvent $event) { if ( $event->Name == 'OnResetCMSMenuCache' ) { // events from "Tools -> System Tools" section are controlled via that section "edit" permission /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); $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); /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); 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' ) { /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); $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( 'OnStoreSelected', '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; } /** * Creates "EDITING_MODE" constant. * * @param kEvent $event Event. * * @return void */ protected function OnAfterStartupHook(kEvent $event) { if ( !$this->Application->GetVar('admin') ) { // User can't edit anything. kUtil::safeDefine('EDITING_MODE', ''); return; } /** @var Session $admin_session */ $admin_session = $this->Application->recallObject('Session.admin'); // Store Admin Console User's ID to Front-End's session for cross-session permission checks. $this->Application->StoreVar('admin_user_id', (int)$admin_session->RecallVar('user_id')); $base_category = $this->Application->getBaseCategory(); if ( $this->Application->CheckAdminPermission('CATEGORY.MODIFY', 0, $base_category) ) { // User can edit cms blocks (when viewing front-end through admin's frame). $editing_mode = $this->Application->GetVar('editing_mode'); define('EDITING_MODE', $editing_mode ? $editing_mode : EDITING_MODE_BROWSE); } // User can't edit anything. kUtil::safeDefine('EDITING_MODE', ''); } /** * Set's mark, that root category is edited * * @param kEvent $event * @return void * @access protected */ protected function OnEdit(kEvent $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) { /** @var kDBItem $object */ $object = $event->getObject(); $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(kEvent $event) { parent::SetCustomQuery($event); /** @var kDBList $object */ $object = $event->getObject(); // 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.'Categories 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 ( (string)$event->getEventParam('parent_cat_id') !== '' ) { $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', '%1$s.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight']); } } else { $object->addFilter('parent_filter', '%1$s.ParentId = '.$parent_cat_id); } } $this->applyViewPermissionFilter($object); 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('ItemType not defined for prefix ' . $event->Prefix . '', 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) /** @var kDBList $list */ $list = $this->Application->recallObject($prefix_special); $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'); } } /** @var kCatDBItem $p_item */ $p_item = $this->Application->recallObject($related_prefix . '.current', null, Array('skip_autoload' => true)); $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.'CatalogRelationships 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.'CatalogRelationships 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.'CatalogRelationships 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.'CatalogRelationships 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; /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); 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); /** @var kDBList $object */ $object = $event->getObject(); $search_sql = ' FROM ' . $search_helper->getSearchTable() . ' search_result JOIN %1$s ON %1$s.ResourceId = search_result.ResourceId'; $sql = str_replace('FROM %1$s', $search_sql, $object->GetPlainSelectSQL()); $object->SetSelectSQL($sql); $object->addCalculatedField('Relevance', 'search_result.Relevance'); $type_clauses['search']['include'] = '1'; $type_clauses['search']['except'] = '0'; $type_clauses['search']['having_filter'] = false; } $search_helper->SetComplexFilter($event, $type_clauses, implode(',', $types), implode(',', $except_types)); } /** * Adds filter, that uses *.VIEW permissions to determine if an item should be shown to a user. * * @param kDBList $object Object. * * @return void * @access protected */ protected function applyViewPermissionFilter(kDBList $object) { if ( !$this->Application->ConfigValue('CheckViewPermissionsInCatalog') ) { return; } if ( $this->Application->RecallVar('user_id') == USER_ROOT ) { // for "root" CATEGORY.VIEW permission is checked for items lists too $view_perm = 1; } else { /** @var kCountHelper $count_helper */ $count_helper = $this->Application->recallObject('CountHelper'); list ($view_perm, $view_filter) = $count_helper->GetPermissionClause($object->Prefix, 'perm'); $object->addFilter('perm_filter2', $view_filter); } $object->addFilter('perm_filter', 'perm.PermId = ' . $view_perm); // check for CATEGORY.VIEW permission } /** * Returns current theme id * * @return int */ function _getCurrentThemeId() { /** @var kThemesHelper $themes_helper */ $themes_helper = $this->Application->recallObject('ThemesHelper'); return (int)$themes_helper->getCurrentThemeId(); } /** * Returns ID of current item to be edited * by checking ID passed in get/post as prefix_id * or by looking at first from selected ids, stored. * Returned id is also stored in Session in case * it was explicitly passed as get/post * * @param kEvent $event * @return int * @access public */ public function getPassedID(kEvent $event) { if ( ($event->Special == 'page') || $this->_isVirtual($event) || ($event->Prefix == 'st') ) { return $this->_getPassedStructureID($event); } if ( $this->Application->isAdmin ) { return parent::getPassedID($event); } $event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true); 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' ) { $event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true); 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) ) { $template_crc = kUtil::crc32(mb_strtolower($template)); $sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . ' FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' WHERE ( (NamedParentPathHash = ' . $template_crc . ') OR (`Type` = ' . PAGE_TYPE_TEMPLATE . ' AND CachedTemplateHash = ' . $template_crc . ') ) AND (ThemeId = ' . $this->_getCurrentThemeId() . ' OR ThemeId = 0)'; $page_id = $this->Conn->GetOne($sql); } else { $page_id = $page_by_template[$template]; } if ( $page_id ) { $page_by_template[$template] = $page_id; } } if ( !$page_id && !$this->Application->isAdmin ) { $page_id = $this->Application->GetVar('m_cat_id'); $event->setEventParam(kEvent::FLAG_ID_FROM_REQUEST, true); } 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, kEvent $event) { if ( $this->_isVirtual($event) ) { return; } /** @var kDBItem $object */ $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 )'); } /** * Checks, that this is virtual page * * @param kEvent $event * @return int * @access protected */ protected function _isVirtual(kEvent $event) { return strpos($event->Special, '-virtual') !== false; } /** * Gets right special for configuring virtual page * * @param kEvent $event * @return string * @access protected */ protected function _getCategorySpecial(kEvent $event) { return $this->_isVirtual($event) ? '-virtual' : $event->Special; } /** * Set correct parent path for newly created categories * * @param kEvent $event * @return void * @access protected */ protected function OnAfterCopyToLive(kEvent $event) { parent::OnAfterCopyToLive($event); /** @var CategoriesItem $object */ $object = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true, 'live_table' => true)); $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 = $object->buildParentBasedFields(); $this->Conn->doUpdate($fields_hash, $object->TableName, 'CategoryId = ' . $object->GetID()); $parent_path = $fields_hash['ParentPath']; } } else { $parent_path = $object->GetDBField('ParentPath'); } if ( $parent_path ) { /** @var kPermCacheUpdater $cache_updater */ $cache_updater = $this->Application->makeClass('kPermCacheUpdater', Array (null, $parent_path)); $cache_updater->OneStepRun(); } } /** * Set cache modification mark if needed * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeDeleteFromLive(kEvent $event) { parent::OnBeforeDeleteFromLive($event); $id = $event->getEventParam('id'); // loading anyway, because this object is needed by "c-perm:OnBeforeDeleteFromLive" event /** @var CategoriesItem $temp_object */ $temp_object = $event->getObject(Array ('skip_autoload' => true)); $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 /** @var CategoriesItem $live_object */ $live_object = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('live_table' => true, 'skip_autoload' => true)); $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(kEvent $event) { // 1. for permission editing of Home category $this->Application->RemoveVar('IsRootCategory_' . $this->Application->GetVar('m_wid')); parent::OnPreCreate($event); /** @var kDBItem $object */ $object = $event->getObject(); // 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(kEvent $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 (); /** @var CategoriesItem $object */ $object = $event->getObject(); 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->emailUser($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 ) { /** @var kOpenerStack $opener_stack */ $opener_stack = $this->Application->makeClass('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(kEvent $event) { /** @var CategoriesItem $object */ $object = $event->getObject( Array ('skip_autoload' => true) ); if ( $object->IsRoot() ) { // don't create root category while saving permissions return; } parent::OnPreSaveCreated($event); } /** * Deletes sym link to other category * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemDelete(kEvent $event) { parent::OnAfterItemDelete($event); /** @var kDBItem $object */ $object = $event->getObject(); $sql = 'UPDATE ' . $object->TableName . ' SET SymLinkCategoryId = NULL WHERE SymLinkCategoryId = ' . $object->GetID(); $this->Conn->Query($sql); // delete direct subscriptions to category, that was deleted $sql = 'SELECT SubscriptionId FROM ' . TABLE_PREFIX . 'SystemEventSubscriptions WHERE CategoryId = ' . $object->GetID(); $ids = $this->Conn->GetCol($sql); if ( $ids ) { /** @var kTempTablesHandler $temp_handler */ $temp_handler = $this->Application->recallObject('system-event-subscription_TempHandler', 'kTempTablesHandler'); $temp_handler->DeleteItems('system-event-subscription', '', $ids); } } /** * Exclude root categories from deleting * * @param kEvent $event * @param string $type * @return void * @access protected */ protected function customProcessing(kEvent $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(kEvent $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 ) { /** @var CategoriesItem $rb */ $rb = $this->Application->recallObject('c.recycle', null, Array ('skip_autoload' => true)); $rb->Load($recycle_bin); /** @var CategoriesItem $cat */ $cat = $event->getObject(Array ('skip_autoload' => true)); foreach ($ids as $id) { $cat->Load($id); if ( preg_match('/^' . preg_quote($rb->GetDBField('ParentPath'), '/') . '/', $cat->GetDBField('ParentPath')) ) { // already in "Recycle Bin" -> delete for real $to_delete[] = $id; continue; } // just move into "Recycle Bin" category $cat->SetDBField('ParentId', $recycle_bin); $cat->Update(); } $ids = $to_delete; } $event->setEventParam('ids', $ids); $this->customProcessing($event, 'before'); $ids = $event->getEventParam('ids'); if ( $ids ) { /** @var kRecursiveHelper $recursive_helper */ $recursive_helper = $this->Application->recallObject('RecursiveHelper'); foreach ($ids as $id) { $recursive_helper->DeleteCategory($id, $event->Prefix); } } $this->clearSelectedIDs($event); $this->_ensurePermCacheRebuild($event); } /** * Add selected items to clipboard with mode = COPY (CLONE) * * @param kEvent $event */ function OnCopy($event) { $this->Application->RemoveVar('clipboard'); /** @var kClipboardHelper $clipboard_helper */ $clipboard_helper = $this->Application->recallObject('ClipboardHelper'); $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'); /** @var kClipboardHelper $clipboard_helper */ $clipboard_helper = $this->Application->recallObject('ClipboardHelper'); $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) { /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); $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); } /** @var kRecursiveHelper $recursive_helper */ $recursive_helper = $this->Application->recallObject('RecursiveHelper'); 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); } } /** @var kPriorityHelper $priority_helper */ $priority_helper = $this->Application->recallObject('PriorityHelper'); 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'] ) { $this->_ensurePermCacheRebuild($event); } } /** * Ensures, that category permission cache is rebuild when category is added/edited/deleted * * @param kEvent $event * @return void * @access protected */ protected function _ensurePermCacheRebuild(kEvent $event) { $this->Application->StoreVar('PermCache_UpdateRequired', 1); $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'); } /** * Validates category data. * * @param kEvent $event Event. * * @return void */ protected function OnBeforeItemValidate(kEvent $event) { parent::OnBeforeItemValidate($event); /** @var kDBItem $object */ $object = $event->getObject(); $friendly_url = $object->GetDBField('FriendlyURL'); if ( strlen($friendly_url) && $friendly_url != $object->GetOriginalField('FriendlyURL') ) { $sql = 'SELECT CategoryId FROM %s WHERE NamedParentPath = ' . $this->Conn->qstr('Content/' . $friendly_url); $duplicate_id = $this->Conn->GetOne(sprintf($sql, $object->TableName)); if ( $duplicate_id === false && $object->IsTempTable() ) { $duplicate_id = $this->Conn->GetOne(sprintf( $sql, $this->Application->GetLiveName($object->TableName) )); } if ( $duplicate_id !== false ) { $object->SetError('FriendlyURL', 'unique'); } } } /** * Sets correct status for new categories created on front-end * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); /** @var CategoriesItem $object */ $object = $event->getObject(); 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->isAdmin || $event->Prefix == 'st' ) { // don't check category permissions when auto-creating structure pages return ; } /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); $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->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(kEvent $event) { parent::OnBeforeItemUpdate($event); $this->_beforeItemChange($event); /** @var kDBItem $object */ $object = $event->getObject(); if ( $object->GetChangedFields() ) { $object->SetDBField('ModifiedById', $this->Application->RecallVar('user_id')); } } /** * Creates needed sql query to load item, * if no query is defined in config for * special requested, then use list query * * @param kEvent $event * @return string * @access protected */ protected function ItemPrepareQuery(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $sqls = $object->getFormOption('ItemSQLs', Array ()); $category_special = $this->_getCategorySpecial($event); $special = isset($sqls[$category_special]) ? $category_special : ''; // preferred special not found in ItemSQLs -> use analog from ListSQLs return isset($sqls[$special]) ? $sqls[$special] : $this->ListPrepareQuery($event); } /** * Creates needed sql query to load list, * if no query is defined in config for * special requested, then use default * query * * @param kEvent $event * @return string * @access protected */ protected function ListPrepareQuery(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); $special = $this->_getCategorySpecial($event); $sqls = $object->getFormOption('ListSQLs', Array ()); return $sqls[array_key_exists($special, $sqls) ? $special : '']; } /** * Performs redirect to correct suggest confirmation template * * @param kEvent $event * @return void * @access protected */ protected function OnCreate(kEvent $event) { parent::OnCreate($event); if ( $this->Application->isAdmin || $event->status != kEvent::erSUCCESS ) { // don't sent email or rebuild cache directly after category is created by admin return; } /** @var kDBItem $object */ $object = $event->getObject(); /** @var kPermCacheUpdater $cache_updater */ $cache_updater = $this->Application->makeClass('kPermCacheUpdater', Array (null, $object->GetDBField('ParentPath'))); $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->emailAdmin($perm_prefix . '.' . $event_suffix); $this->Application->emailUser($perm_prefix . '.' . $event_suffix, $object->GetDBField('CreatedById')); } /** * Returns current per-page setting for list * * @param kEvent $event * @return int * @access protected */ protected function getPerPage(kEvent $event) { if ( !$this->Application->isAdmin ) { $same_special = $event->getEventParam('same_special'); $event->setEventParam('same_special', true); $per_page = parent::getPerPage($event); $event->setEventParam('same_special', $same_special); } return parent::getPerPage($event); } /** * Set's correct page for list based on data provided with event * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetPagination(kEvent $event) { 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) ) { /** @var kDBList $object */ $object = $event->getObject(); $object->SetPage($page); } } } } /** * Apply same processing to each item being selected in grid * * @param kEvent $event * @return void * @access protected */ protected function iterateItems(kEvent $event) { if ( $event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline' ) { parent::iterateItems($event); } if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } /** @var CategoriesItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); $ids = $this->StoreSelectedIDs($event); if ( $ids ) { $status_field = $object->getStatusField(); $propagate_category_status = $this->Application->GetVar('propagate_category_status'); 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); } $email_event = $event->Name == 'OnMassApprove' ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY'; $this->Application->emailUser($email_event, $object->GetDBField('CreatedById')); } } } $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 * @access protected */ protected function checkItemStatus(kEvent $event) { /** @var kDBItem $object */ $object = $event->getObject(); 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; } /** * Set's correct sorting for list based on data provided with event * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetSorting(kEvent $event) { $types = $event->getEventParam('types'); $types = $types ? explode(',', $types) : Array (); if ( in_array('search', $types) ) { $event->setPseudoClass('_List'); /** @var kDBList $object */ $object = $event->getObject(); // 1. no user sorting - sort by relevance $default_sortings = parent::_getDefaultSorting($event); $default_sorting = key($default_sortings['Sorting']) . ',' . current($default_sortings['Sorting']); if ( $object->isMainList() ) { $sort_by = $this->Application->GetVar('sort_by', ''); if ( !$sort_by ) { $this->Application->SetVar('sort_by', 'Relevance,desc|' . $default_sorting); } elseif ( strpos($sort_by, 'Relevance,') !== false ) { $this->Application->SetVar('sort_by', $sort_by . '|' . $default_sorting); } } else { $sorting_settings = $this->getListSetting($event, 'Sortings'); $sort_by = trim(getArrayValue($sorting_settings, 'Sort1') . ',' . getArrayValue($sorting_settings, 'Sort1_Dir'), ','); if ( !$sort_by ) { $event->setEventParam('sort_by', 'Relevance,desc|' . $default_sorting); } elseif ( strpos($sort_by, 'Relevance,') !== false ) { $event->setEventParam('sort_by', $sort_by . '|' . $default_sorting); } } $this->_removeForcedSortings($event); } parent::SetSorting($event); } /** * Removes forced sortings * * @param kEvent $event */ protected function _removeForcedSortings(kEvent $event) { /** @var Array $list_sortings */ $list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings', Array ()); foreach ($list_sortings as $special => $sortings) { unset($list_sortings[$special]['ForcedSorting']); } $this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings); } /** * Default sorting in search results only comes from relevance field * * @param kEvent $event * @return Array * @access protected */ protected function _getDefaultSorting(kEvent $event) { $types = $event->getEventParam('types'); $types = $types ? explode(',', $types) : Array (); return in_array('search', $types) ? Array () : parent::_getDefaultSorting($event); } // ============= 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'); } /** @var CategoriesItem $page */ $page = $this->Application->recallObject($this->Prefix . '.-virtual', null, Array ('page' => $t)); 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 { $this->Application->UrlManager->show404(); } // replace alias in form #alias_name# to actual template used in this theme if ( $this->Application->isAdmin ) { /** @var kThemesHelper $themes_helper */ $themes_helper = $this->Application->recallObject('ThemesHelper'); // only, used when in "Design Mode" $this->Application->SetVar('theme.current_id', $themes_helper->getCurrentThemeId()); } /** @var kDBItem $theme */ $theme = $this->Application->recallObject('theme.current'); $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) { /** @var CategoriesItem $object */ $object = $event->getObject(); $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')); } } $object->checkFilename(); $object->generateFilename(); // Don't allow creating records on behalf of another user. if ( !$this->Application->isAdminUser && !defined('CRON') ) { $object->SetDBField('CreatedById', $object->GetOriginalField('CreatedById')); } // Auto-assign records to currently logged-in user. if ( !$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') )); $category_type = $object->GetDBField('Type'); // Changing category type would associate/disassociate it to theme. if ( $category_type != $object->GetOriginalField('Type') ) { if ( $category_type == PAGE_TYPE_TEMPLATE ) { $object->SetDBField('ThemeId', $this->_getCurrentThemeId()); } else { $object->SetDBField('ThemeId', 0); } } if ( $category_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 /** @var kMultiLanguage $ml_formatter */ $ml_formatter = $this->Application->recallObject('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 * @return void * @access protected */ protected function OnBeforeItemDelete(kEvent $event) { parent::OnBeforeItemDelete($event); /** @var kDBItem $object */ $object = $event->getObject(); if ( $object->GetDBField('Protected') && !$this->Application->isDebugMode(false) ) { $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'); /** @var CategoriesItem $object */ $object = $this->Application->recallObject($this->Prefix . '.rebuild-path', null, Array ('skip_autoload' => true)); $parent_id = $base_category; /** @var kFilenamesHelper $filenames_helper */ $filenames_helper = $this->Application->recallObject('FilenamesHelper'); $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 * @return void * @access protected */ protected function OnAfterConfigRead(kEvent $event) { parent::OnAfterConfigRead($event); if (defined('IS_INSTALL') && IS_INSTALL) { // skip any processing, because Categories table doesn't exists until install is finished $this->addViewPermissionJoin($event); return ; } /** @var SiteConfigHelper $site_config_helper */ $site_config_helper = $this->Application->recallObject('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 . ', "c")', ); if ( $this->Application->ConfigValue('Catalog_PreselectModuleTab') ) { $section_adjustments['in-portal:browse']['url']['anchor'] = 'tab-c'; } $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 /** @var CategoryHelper $category_helper */ $category_helper = $this->Application->recallObject('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); } $this->addViewPermissionJoin($event); // add grids for advanced view (with primary category column) $grids = $this->Application->getUnitOption($this->Prefix, 'Grids'); $process_grids = Array ('Default', 'Radio'); foreach ($process_grids as $process_grid) { $grid_data = $grids[$process_grid]; $grid_data['Fields']['CachedNavbar'] = Array ('title' => 'la_col_Path', 'data_block' => 'grid_parent_category_td', 'filter_block' => 'grid_like_filter'); $grids[$process_grid . 'ShowAll'] = $grid_data; } $this->Application->setUnitOption($this->Prefix, 'Grids', $grids); } /** * Adds permission table table JOIN clause only, when advanced catalog view permissions enabled. * * @param kEvent $event Event. * * @return self * @access protected */ protected function addViewPermissionJoin(kEvent $event) { if ( $this->Application->ConfigValue('CheckViewPermissionsInCatalog') ) { $join_clause = 'LEFT JOIN ' . TABLE_PREFIX . 'CategoryPermissionsCache perm ON perm.CategoryId = %1$s.CategoryId'; } else { $join_clause = ''; } /** @var array $list_sqls */ $list_sqls = $this->Application->getUnitOption($event->Prefix, 'ListSQLs'); foreach ($list_sqls as $special => $list_sql) { $list_sqls[$special] = str_replace('{PERM_JOIN}', $join_clause, $list_sql); } $this->Application->setUnitOption($event->Prefix, 'ListSQLs', $list_sqls); return $this; } /** * Returns 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(kEvent $event) { parent::OnAfterItemLoad($event); if ( !$this->Application->isAdmin ) { // calculate priorities dropdown only for admin return; } /** @var kDBItem $object */ $object = $event->getObject(); // 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(kEvent $event) { parent::OnAfterItemCreate($event); /** @var CategoriesItem $object */ $object = $event->getObject(); // need to update path after category is created, so category is included in that path $fields_hash = $object->buildParentBasedFields(); $this->Conn->doUpdate($fields_hash, $object->TableName, $object->IDField . ' = ' . $object->GetID()); $object->SetDBFieldsFromHash($fields_hash); } /** * 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 . 'Themes AS t ON t.ThemeId = tf.ThemeId WHERE t.Enabled = 1 AND tf.FileType = 1 AND ( SELECT COUNT(CategoryId) FROM ' . TABLE_PREFIX . 'Categories 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; } kUtil::setResourceLimit(); /** @var CategoriesItem $dummy */ $dummy = $this->Application->recallObject($event->Prefix . '.rebuild', NULL, Array ('skip_autoload' => true)); $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('CategoryPermissionRebuildMode') == CategoryPermissionRebuild::SILENT ) { /** @var kPermCacheUpdater $updater */ $updater = $this->Application->makeClass('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 = $this->Application->unescapeRequestVariable($this->Application->GetVar('preview_content')); /** @var CategoryHelper $category_helper */ $category_helper = $this->Application->recallObject('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; $keywords = $this->Application->unescapeRequestVariable(trim($this->Application->GetVar('keywords'))); /** @var kHTTPQuery $query_object */ $query_object = $this->Application->recallObject('HTTPQuery'); /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); $search_table = $search_helper->getSearchTable(); $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')) { $search_helper->ensureEmptySearchTable(); $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'); /** @var kDBList $object */ $object = $event->getObject(); $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. $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') { $sub_search_ids = $event->MasterEvent->getEventParam('ResultIds'); if ( $sub_search_ids !== false ) { if ( $sub_search_ids ) { $where_clause .= 'AND (' . $items_table . '.ResourceId IN (' . implode(',', $sub_search_ids) . '))'; } else { $where_clause .= 'AND FALSE'; } } } // 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)'; }*/ if ( count($positive_words) > 1 ) { $condition = $field . ' LIKE "%' . implode(' ', $positive_words) . '%"'; $revelance_parts[] = 'IF(' . $condition . ', ' . $weight_sum . ', 0)'; } // search by partial word matches too 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); $this->Application->StoreVar('search_performed', 1); } } /** * Enter description here... * * @param kEvent $event */ function OnSubSearch($event) { // keep search results from other items after doing a sub-search on current item type $this->Application->SetVar('do_not_drop_search_table', true); /** @var kSearchHelper $search_helper */ $search_helper = $this->Application->recallObject('SearchHelper'); $search_table = $search_helper->getSearchTable(); $sql = 'SHOW TABLES LIKE "' . $search_table . '"'; $ids = array(); if ( $this->Conn->Query($sql) ) { $item_type = $this->Application->getUnitOption($event->Prefix, 'ItemType'); // 1. get ids to be used as search bounds $sql = 'SELECT DISTINCT ResourceId FROM ' . $search_table . ' WHERE ItemType = ' . $item_type; $ids = $this->Conn->GetCol($sql); // 2. delete previously found ids $sql = 'DELETE FROM ' . $search_table . ' WHERE ItemType = ' . $item_type; $this->Conn->Query($sql); } $event->setEventParam('ResultIds', $ids); $event->CallSubEvent('OnSimpleSearch'); } /** * Make record to search log * * @param string $keywords * @param int $search_type 0 - simple search, 1 - advanced search */ function saveToSearchLog($keywords, $search_type = 0) { // don't save keywords for each module separately, just one time // static variable can't help here, because each module uses it's own class instance ! if (!$this->Application->GetVar('search_logged')) { $sql = 'UPDATE '.TABLE_PREFIX.'SearchLogs SET Indices = Indices + 1 WHERE Keyword = '.$this->Conn->qstr($keywords).' AND SearchType = '.$search_type; // 0 - simple search, 1 - advanced search $this->Conn->Query($sql); if ($this->Conn->getAffectedRows() == 0) { $fields_hash = Array('Keyword' => $keywords, 'Indices' => 1, 'SearchType' => $search_type); $this->Conn->doInsert($fields_hash, TABLE_PREFIX.'SearchLogs'); } $this->Application->SetVar('search_logged', 1); } } /** * Load item if id is available * * @param kEvent $event * @return void * @access protected */ protected function LoadItem(kEvent $event) { if ( !$this->_isVirtual($event) ) { parent::LoadItem($event); return; } /** @var kDBItem $object */ $object = $event->getObject(); $id = $this->getPassedID($event); if ( $object->isLoaded() && !is_array($id) && ($object->GetID() == $id) ) { // object is already loaded by same id return; } if ( $object->Load($id, null, true) ) { /** @var Params $actions */ $actions = $this->Application->recallObject('kActions'); $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(kEvent $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' ) { /** @var kDBItem $object */ $object = $event->getObject(); $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); /** @var kRewriteUrlProcessor $rewrite_processor */ $rewrite_processor = $this->Application->recallObject('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 . 'Languages 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 . 'Themes 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/" 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 if ( ($template == $category_template) || (mb_strtolower($template) == '__default__') ) { // given template is also default template for this category OR '__default__' given $params['pass_template'] = false; } // remove template from url if it is site homepage on primary language & theme if ( ($template == 'index') && $processed_params['m_lang'] == $rewrite_processor->primaryLanguageId && $processed_params['m_theme'] == $rewrite_processor->primaryThemeId ) { // given template is site homepage on primary language & theme $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 . 'Categories WHERE FriendlyURL = ' . $this->Conn->qstr(implode('/', $url_parts)); $friendly = $this->Conn->GetRow($sql); /** @var kRewriteUrlProcessor $rewrite_processor */ $rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor'); if ( $friendly ) { $vars['is_friendly_url'] = true; $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'; /** @var kRewriteUrlProcessor $rewrite_processor */ $rewrite_processor = $this->Application->recallObject('kRewriteUrlProcessor'); do { $category_path = trim($category_path . '/' . $url_part, '/'); // bb_ -> 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 . 'Categories 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 . 'Categories 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'])); $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) { /** @var kDBItem $object */ $object = $event->getObject(); $resource_id = $object->GetDBField('ResourceId'); if ( !$resource_id ) { $object->SetDBField('ResourceId', $this->Application->NextResourceId()); } } /** * Occurs before an item has been cloned * Id of newly created item is passed as event' 'id' param * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeClone(kEvent $event) { parent::OnBeforeClone($event); /** @var kDBItem $object */ $object = $event->getObject(); $object->SetDBField('ResourceId', 0); // this will reset it } }