Index: branches/5.1.x/core/kernel/db/cat_event_handler.php =================================================================== --- branches/5.1.x/core/kernel/db/cat_event_handler.php (revision 13986) +++ branches/5.1.x/core/kernel/db/cat_event_handler.php (revision 13987) @@ -1,2678 +1,2678 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class kCatDBEventHandler extends kDBEventHandler { /** * Allows to override standart permission mapping * */ function mapPermissions() { parent::mapPermissions(); $permissions = Array( 'OnSaveSettings' => Array ('self' => 'add|edit|advanced:import'), 'OnResetSettings' => Array ('self' => 'add|edit|advanced:import'), 'OnBeforeDeleteOriginal' => Array ('self' => 'edit|advanced:approve'), 'OnCopy' => Array ('self' => true), 'OnDownloadFile' => Array ('self' => 'view'), 'OnCancelAction' => Array ('self' => true), 'OnItemBuild' => Array ('self' => true), 'OnMakeVote' => Array ('self' => true), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Load item if id is available * * @param kEvent $event */ function LoadItem(&$event) { $object =& $event->getObject(); $id = $this->getPassedID($event); if ($object->Load($id)) { $actions =& $this->Application->recallObject('kActions'); $actions->Set($event->Prefix_Special.'_id', $object->GetID() ); $use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing'); if ($use_pending_editing && $event->Special != 'original') { $this->Application->SetVar($event->Prefix.'.original_id', $object->GetDBField('OrgId')); } } else { $object->setID($id); } } /** * Checks permissions of user * * @param kEvent $event */ function CheckPermission(&$event) { if (!$this->Application->isAdmin) { if ($event->Name == 'OnSetSortingDirect') { // allow sorting on front event without view permission return true; } } if ($event->Name == 'OnExport') { // save category_id before doing export $this->Application->LinkVar('m_cat_id'); } if (in_array($event->Name, $this->_getMassPermissionEvents())) { $items = $this->_getPermissionCheckInfo($event); $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ if (($event->Name == 'OnSave') && array_key_exists(0, $items)) { // adding new item (ID = 0) $perm_value = $perm_helper->AddCheckPermission($items[0]['CategoryId'], $event->Prefix) > 0; } else { // leave only items, that can be edited $ids = Array (); $check_method = in_array($event->Name, Array ('OnMassDelete', 'OnCut')) ? 'DeleteCheckPermission' : 'ModifyCheckPermission'; foreach ($items as $item_id => $item_data) { if ($perm_helper->$check_method($item_data['CreatedById'], $item_data['CategoryId'], $event->Prefix) > 0) { $ids[] = $item_id; } } if (!$ids) { // no items left for editing -> no permission return $perm_helper->finalizePermissionCheck($event, false); } $perm_value = true; $event->setEventParam('ids', $ids); // will be used later by "kDBEventHandler::StoreSelectedIDs" method } return $perm_helper->finalizePermissionCheck($event, $perm_value); } $export_events = Array ('OnSaveSettings', 'OnResetSettings', 'OnExportBegin'); if (in_array($event->Name, $export_events)) { // when import settings before selecting target import category return $this->Application->CheckPermission('in-portal:main_import.view'); } if ($event->Name == 'OnProcessSelected') { if ($this->Application->RecallVar('dst_field') == 'ImportCategory') { // when selecting target import category return $this->Application->CheckPermission('in-portal:main_import.view'); } } return parent::CheckPermission($event); } /** * Returns events, that require item-based (not just event-name based) permission check * * @return Array */ function _getMassPermissionEvents() { return Array ( 'OnEdit', 'OnSave', 'OnMassDelete', 'OnMassApprove', 'OnMassDecline', 'OnMassMoveUp', 'OnMassMoveDown', 'OnCut', ); } /** * Returns category item IDs, that require permission checking * * @param kEvent $event * @return string */ function _getPermissionCheckIDs(&$event) { if ($event->Name == 'OnSave') { $selected_ids = implode(',', $this->getSelectedIDs($event, true)); if (!$selected_ids) { $selected_ids = 0; // when saving newly created item (OnPreCreate -> OnPreSave -> OnSave) } } else { // OnEdit, OnMassDelete events, when items are checked in grid $selected_ids = implode(',', $this->StoreSelectedIDs($event)); } return $selected_ids; } /** * Returns information used in permission checking * * @param kEvent $event * @return Array */ function _getPermissionCheckInfo(&$event) { $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ // when saving data from temp table to live table check by data from temp table $item_ids = $this->_getPermissionCheckIDs($event); $items = $perm_helper->GetCategoryItemData($event->Prefix, $item_ids, $event->Name == 'OnSave'); if (!$items) { // when item not present in temp table, then permission is not checked, because there are no data in db to check $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); list ($id, $fields_hash) = each($items_info); if (array_key_exists('CategoryId', $fields_hash)) { $item_category = $fields_hash['CategoryId']; } else { $item_category = $this->Application->GetVar('m_cat_id'); } $items[$id] = Array ( 'CreatedById' => $this->Application->RecallVar('use_id'), 'CategoryId' => $item_category, ); } return $items; } /** * Add selected items to clipboard with mode = COPY (CLONE) * * @param kEvent $event */ function OnCopy(&$event) { $this->Application->RemoveVar('clipboard'); $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'); $clipboard_helper =& $this->Application->recallObject('ClipboardHelper'); $clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event)); $this->clearSelectedIDs($event); } /** * Checks permission for OnPaste event * * @param kEvent $event * @return bool */ function _checkPastePermission(&$event) { $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $category_id = $this->Application->GetVar('m_cat_id'); if ($perm_helper->AddCheckPermission($category_id, $event->Prefix) == 0) { // no items left for editing -> no permission return $perm_helper->finalizePermissionCheck($event, false); } return true; } /** * Performs category item paste * * @param kEvent $event */ function OnPaste(&$event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event)) { $event->status = erFAIL; return; } $clipboard_data = $event->getEventParam('clipboard_data'); if (!$clipboard_data['cut'] && !$clipboard_data['copy']) { return false; } if ($clipboard_data['copy']) { $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); /* @var $temp kTempTablesHandler */ $this->Application->SetVar('ResetCatBeforeClone', 1); // used in "kCatDBEventHandler::OnBeforeClone" $temp->CloneItems($event->Prefix, $event->Special, $clipboard_data['copy']); } if ($clipboard_data['cut']) { $object =& $this->Application->recallObject($event->getPrefixSpecial().'.item', $event->Prefix, Array('skip_autoload' => true)); foreach ($clipboard_data['cut'] as $id) { $object->Load($id); $object->MoveToCat(); } } } /** * Deletes all selected items. * Automatically recurse into sub-items using temp handler, and deletes sub-items * by calling its Delete method if sub-item has AutoDelete set to true in its config file * * @param kEvent $event */ function OnMassDelete(&$event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = erFAIL; return; } $event->status=erSUCCESS; $ids = $this->StoreSelectedIDs($event); $to_delete = array(); if ($recycle_bin = $this->Application->ConfigValue('RecycleBinFolder')) { $rb =& $this->Application->recallObject('c.recycle', null, array('skip_autoload' => true)); $rb->Load($recycle_bin); $object =& $this->Application->recallObject($event->Prefix.'.recycleitem', null, Array ('skip_autoload' => true)); foreach ($ids as $id) { $object->Load($id); if (preg_match('/^'.preg_quote($rb->GetDBField('ParentPath'),'/').'/', $object->GetDBField('ParentPath'))) { $to_delete[] = $id; continue; } $object->MoveToCat($recycle_bin); } $ids = $to_delete; } $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); $event->setEventParam('ids', $ids); $this->customProcessing($event, 'before'); $ids = $event->getEventParam('ids'); if($ids) { $temp->DeleteItems($event->Prefix, $event->Special, $ids); } $this->clearSelectedIDs($event); } /** * Return type clauses for list bulding on front * * @param kEvent $event * @return Array */ function getTypeClauses(&$event) { $types = $event->getEventParam('types'); $types = $types ? explode(',', $types) : Array (); $except_types = $event->getEventParam('except'); $except_types = $except_types ? explode(',', $except_types) : Array (); $type_clauses = Array(); $user_id = $this->Application->RecallVar('user_id'); $owner_field = $this->getOwnerField($event->Prefix); $type_clauses['my_items']['include'] = '%1$s.'.$owner_field.' = '.$user_id; $type_clauses['my_items']['except'] = '%1$s.'.$owner_field.' <> '.$user_id; $type_clauses['my_items']['having_filter'] = false; $type_clauses['pick']['include'] = '%1$s.EditorsPick = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1'; $type_clauses['pick']['except'] = '%1$s.EditorsPick! = 1 AND '.TABLE_PREFIX.'CategoryItems.PrimaryCat = 1'; $type_clauses['pick']['having_filter'] = false; $type_clauses['hot']['include'] = '`IsHot` = 1 AND PrimaryCat = 1'; $type_clauses['hot']['except'] = '`IsHot`! = 1 AND PrimaryCat = 1'; $type_clauses['hot']['having_filter'] = true; $type_clauses['pop']['include'] = '`IsPop` = 1 AND PrimaryCat = 1'; $type_clauses['pop']['except'] = '`IsPop`! = 1 AND PrimaryCat = 1'; $type_clauses['pop']['having_filter'] = true; $type_clauses['new']['include'] = '`IsNew` = 1 AND PrimaryCat = 1'; $type_clauses['new']['except'] = '`IsNew`! = 1 AND PrimaryCat = 1'; $type_clauses['new']['having_filter'] = true; $type_clauses['displayed']['include'] = ''; $displayed = $this->Application->GetVar($event->Prefix.'_displayed_ids'); if ($displayed) { $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); $type_clauses['displayed']['except'] = '%1$s.'.$id_field.' NOT IN ('.$displayed.')'; } else { $type_clauses['displayed']['except'] = ''; } $type_clauses['displayed']['having_filter'] = false; if (in_array('search', $types) || in_array('search', $except_types)) { $event_mapping = Array ( 'simple' => 'OnSimpleSearch', 'subsearch' => 'OnSubSearch', 'advanced' => 'OnAdvancedSearch' ); $type = $this->Application->GetVar('search_type', 'simple'); if ($keywords = $event->getEventParam('keyword_string')) { // processing keyword_string param of ListProducts tag $this->Application->SetVar('keywords', $keywords); $type = 'simple'; } $search_event = $event_mapping[$type]; $this->$search_event($event); $search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search'; $sql = 'SHOW TABLES LIKE "'.$search_table.'"'; if ($this->Conn->Query($sql)) { $search_res_ids = $this->Conn->GetCol('SELECT ResourceId FROM '.$search_table); } if (isset($search_res_ids) && $search_res_ids) { $type_clauses['search']['include'] = '%1$s.ResourceId IN ('.implode(',', $search_res_ids).') AND PrimaryCat = 1 AND ('.TABLE_PREFIX.'Category.Status = '.STATUS_ACTIVE.')'; $type_clauses['search']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $search_res_ids).') AND PrimaryCat = 1 AND ('.TABLE_PREFIX.'Category.Status = '.STATUS_ACTIVE.')'; } else { $type_clauses['search']['include'] = '0'; $type_clauses['search']['except'] = '1'; } $type_clauses['search']['having_filter'] = false; } if (in_array('related', $types) || in_array('related', $except_types)) { $related_to = $event->getEventParam('related_to'); if (!$related_to) { $related_prefix = $event->Prefix; } else { $sql = 'SELECT Prefix FROM '.TABLE_PREFIX.'ItemTypes WHERE ItemName = '.$this->Conn->qstr($related_to); $related_prefix = $this->Conn->GetOne($sql); } $rel_table = $this->Application->getUnitOption('rel', 'TableName'); $item_type = (int)$this->Application->getUnitOption($event->Prefix, 'ItemType'); if ($item_type == 0) { trigger_error('<strong>ItemType</strong> not defined for prefix <strong>' . $event->Prefix . '</strong>', E_USER_WARNING); } // process case, then this list is called inside another list $prefix_special = $event->getEventParam('PrefixSpecial'); if (!$prefix_special) { $prefix_special = $this->Application->Parser->GetParam('PrefixSpecial'); } $id = false; if ($prefix_special !== false) { $processed_prefix = $this->Application->processPrefix($prefix_special); if ($processed_prefix['prefix'] == $related_prefix) { // printing related categories within list of items (not on details page) $list =& $this->Application->recallObject($prefix_special); /* @var $list kDBList */ $id = $list->GetID(); } } if ($id === false) { // printing related categories for single item (possibly on details page) if ($related_prefix == 'c') { $id = $this->Application->GetVar('m_cat_id'); } else { $id = $this->Application->GetVar($related_prefix . '_id'); } } $p_item =& $this->Application->recallObject($related_prefix.'.current', null, Array('skip_autoload' => true)); $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).') AND PrimaryCat = 1'; $type_clauses['related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_ids).') AND PrimaryCat = 1'; } else { $type_clauses['related']['include'] = '0'; $type_clauses['related']['except'] = '1'; } $type_clauses['related']['having_filter'] = false; } if (in_array('favorites', $types) || in_array('favorites', $except_types)) { $sql = 'SELECT ResourceId FROM '.$this->Application->getUnitOption('fav', 'TableName').' WHERE PortalUserId = '.$this->Application->RecallVar('user_id'); $favorite_ids = $this->Conn->GetCol($sql); if ($favorite_ids) { $type_clauses['favorites']['include'] = '%1$s.ResourceId IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1'; $type_clauses['favorites']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $favorite_ids).') AND PrimaryCat = 1'; } else { $type_clauses['favorites']['include'] = 0; $type_clauses['favorites']['except'] = 1; } $type_clauses['favorites']['having_filter'] = false; } return $type_clauses; } /** * Returns SQL clause, that will help to select only data from specified category & it's children * * @param int $category_id * @return string */ function getCategoryLimitClause($category_id) { if (!$category_id) { return false; } $tree_indexes = $this->Application->getTreeIndex($category_id); if (!$tree_indexes) { // id of non-existing category was given return 'FALSE'; } return TABLE_PREFIX.'Category.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight']; } /** * Apply filters to list * * @param kEvent $event */ function SetCustomQuery(&$event) { parent::SetCustomQuery($event); $object =& $event->getObject(); /* @var $object kDBList */ // add category filter if needed if ($event->Special != 'showall' && $event->Special != 'user') { if ($event->getEventParam('parent_cat_id') !== false) { $parent_cat_id = $event->getEventParam('parent_cat_id'); } else { $parent_cat_id = $this->Application->GetVar('c_id'); if (!$parent_cat_id) { $parent_cat_id = $this->Application->GetVar('m_cat_id'); } if (!$parent_cat_id) { $parent_cat_id = 0; } } if ("$parent_cat_id" == '0') { // replace "0" category with "Content" category id (this way template - $parent_cat_id = $this->Application->findModule('Name', 'Core', 'RootCat'); + $parent_cat_id = $this->Application->getBaseCategory(); } if ((string)$parent_cat_id != 'any') { if ($event->getEventParam('recursive')) { $filter_clause = $this->getCategoryLimitClause($parent_cat_id); if ($filter_clause !== false) { $object->addFilter('category_filter', $filter_clause); } $object->addFilter('primary_filter', 'PrimaryCat = 1'); } else { $object->addFilter('category_filter', TABLE_PREFIX.'CategoryItems.CategoryId = '.$parent_cat_id ); } } else { $object->addFilter('primary_filter', 'PrimaryCat = 1'); } } else { $object->addFilter('primary_filter', 'PrimaryCat = 1'); // if using recycle bin don't show items from there $recycle_bin = $this->Application->ConfigValue('RecycleBinFolder'); if ($recycle_bin) { $object->addFilter('recyclebin_filter', TABLE_PREFIX.'CategoryItems.CategoryId <> '.$recycle_bin); } } if ($event->Special == 'user') { $editable_user = $this->Application->GetVar('u_id'); $object->addFilter('owner_filter', '%1$s.'.$this->getOwnerField($event->Prefix).' = '.$editable_user); } // add permission filter if ($this->Application->RecallVar('user_id') == USER_ROOT) { // for "root" CATEGORY.VIEW permission is checked for items lists too $view_perm = 1; } else { // for any real user itemlist view permission is checked instead of CATEGORY.VIEW $count_helper =& $this->Application->recallObject('CountHelper'); /* @var $count_helper kCountHelper */ list ($view_perm, $view_filter) = $count_helper->GetPermissionClause($event->Prefix, 'perm'); $object->addFilter('perm_filter2', $view_filter); } $object->addFilter('perm_filter', 'perm.PermId = '.$view_perm); $types = $event->getEventParam('types'); $this->applyItemStatusFilter($object, $types); $except_types = $event->getEventParam('except'); $type_clauses = $this->getTypeClauses($event); // convert prepared type clauses into list filters $includes_or_filter =& $this->Application->makeClass('kMultipleFilter'); $includes_or_filter->setType(FLT_TYPE_OR); $excepts_and_filter =& $this->Application->makeClass('kMultipleFilter'); $excepts_and_filter->setType(FLT_TYPE_AND); $includes_or_filter_h =& $this->Application->makeClass('kMultipleFilter'); $includes_or_filter_h->setType(FLT_TYPE_OR); $excepts_and_filter_h =& $this->Application->makeClass('kMultipleFilter'); $excepts_and_filter_h->setType(FLT_TYPE_AND); if ($types) { $types_array = explode(',', $types); for ($i = 0; $i < sizeof($types_array); $i++) { $type = trim($types_array[$i]); if (isset($type_clauses[$type])) { if ($type_clauses[$type]['having_filter']) { $includes_or_filter_h->removeFilter('filter_'.$type); $includes_or_filter_h->addFilter('filter_'.$type, $type_clauses[$type]['include']); }else { $includes_or_filter->removeFilter('filter_'.$type); $includes_or_filter->addFilter('filter_'.$type, $type_clauses[$type]['include']); } } } } if ($except_types) { $except_types_array = explode(',', $except_types); for ($i = 0; $i < sizeof($except_types_array); $i++) { $type = trim($except_types_array[$i]); if (isset($type_clauses[$type])) { if ($type_clauses[$type]['having_filter']) { $excepts_and_filter_h->removeFilter('filter_'.$type); $excepts_and_filter_h->addFilter('filter_'.$type, $type_clauses[$type]['except']); }else { $excepts_and_filter->removeFilter('filter_'.$type); $excepts_and_filter->addFilter('filter_'.$type, $type_clauses[$type]['except']); } } } } /*if (!$this->Application->isAdminUser) { $object->addFilter('expire_filter', '%1$s.Expire IS NULL OR %1$s.Expire > UNIX_TIMESTAMP()'); }*/ /*$list_type = $event->getEventParam('ListType'); switch($list_type) { case 'favorites': $fav_table = $this->Application->getUnitOption('fav','TableName'); $user_id =& $this->Application->RecallVar('user_id'); $sql = 'SELECT DISTINCT f.ResourceId FROM '.$fav_table.' f LEFT JOIN '.$object->TableName.' p ON p.ResourceId = f.ResourceId WHERE f.PortalUserId = '.$user_id; $ids = $this->Conn->GetCol($sql); if(!$ids) $ids = Array(-1); $object->addFilter('category_filter', TABLE_PREFIX.'CategoryItems.PrimaryCat = 1'); $object->addFilter('favorites_filter', '%1$s.`ResourceId` IN ('.implode(',',$ids).')'); break; case 'search': $search_results_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search'; $sql = ' SELECT DISTINCT ResourceId FROM '.$search_results_table.' WHERE ItemType=11'; $ids = $this->Conn->GetCol($sql); if(!$ids) $ids = Array(-1); $object->addFilter('search_filter', '%1$s.`ResourceId` IN ('.implode(',',$ids).')'); break; } */ $object->addFilter('includes_filter', $includes_or_filter); $object->addFilter('excepts_filter', $excepts_and_filter); $object->addFilter('includes_filter_h', $includes_or_filter_h, HAVING_FILTER); $object->addFilter('excepts_filter_h', $excepts_and_filter_h, HAVING_FILTER); } /** * Adds filter that filters out items with non-required statuses * * @param kDBList $object * @param string $types */ function applyItemStatusFilter(&$object, $types) { // Link1 (before modifications) [Status = 1, OrgId = NULL], Link2 (after modifications) [Status = -2, OrgId = Link1_ID] $pending_editing = $this->Application->getUnitOption($object->Prefix, 'UsePendingEditing'); if (!$this->Application->isAdminUser) { $types = explode(',', $types); if (in_array('my_items', $types)) { $allow_statuses = Array (STATUS_ACTIVE, STATUS_PENDING, STATUS_PENDING_EDITING); $object->addFilter('status_filter', '%1$s.Status IN ('.implode(',', $allow_statuses).')'); if ($pending_editing) { $user_id = $this->Application->RecallVar('user_id'); $this->applyPendingEditingFilter($object, $user_id); } } else { $object->addFilter('status_filter', '(%1$s.Status = ' . STATUS_ACTIVE . ') AND (' . TABLE_PREFIX . 'Category.Status = ' . STATUS_ACTIVE . ')'); if ($pending_editing) { // if category item uses pending editing abilities, then in no cases show pending copies on front $object->addFilter('original_filter', '%1$s.OrgId = 0 OR %1$s.OrgId IS NULL'); } } } else { if ($pending_editing) { $this->applyPendingEditingFilter($object); } } } /** * Adds filter, that removes live items if they have pending editing copies * * @param kDBList $object * @param int $user_id */ function applyPendingEditingFilter(&$object, $user_id = null) { $sql = 'SELECT OrgId FROM '.$object->TableName.' WHERE Status = '.STATUS_PENDING_EDITING.' AND OrgId IS NOT NULL'; if (isset($user_id)) { $owner_field = $this->getOwnerField($object->Prefix); $sql .= ' AND '.$owner_field.' = '.$user_id; } $pending_ids = $this->Conn->GetCol($sql); if ($pending_ids) { $object->addFilter('no_original_filter', '%1$s.'.$object->IDField.' NOT IN ('.implode(',', $pending_ids).')'); } } /** * Adds calculates fields for item statuses * * @param kCatDBItem $object * @param kEvent $event */ function prepareObject(&$object, &$event) { $this->prepareItemStatuses($event); $object->addCalculatedField('CachedNavbar', 'l'.$this->Application->GetVar('m_lang').'_CachedNavbar'); if ($event->Special == 'export' || $event->Special == 'import') { $export_helper =& $this->Application->recallObject('CatItemExportHelper'); $export_helper->prepareExportColumns($event); } } /** * Creates calculated fields for all item statuses based on config settings * * @param kEvent $event */ function prepareItemStatuses(&$event) { $object =& $event->getObject( Array('skip_autoload' => true) ); $property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings'); if (!$property_map) { return ; } // new items $object->addCalculatedField('IsNew', ' IF(%1$s.NewItem = 2, IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - '. $this->Application->ConfigValue($property_map['NewDays']). '*3600*24), 1, 0), %1$s.NewItem )'); // hot items (cache updated every hour) if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $serial_name = $this->Application->incrementCacheSerial($event->Prefix, null, false); $hot_limit = $this->Application->getCache($property_map['HotLimit'] . '[%' . $serial_name . '%]'); } else { $hot_limit = $this->Application->getDBCache($property_map['HotLimit']); } if ($hot_limit === false) { $hot_limit = $this->CalculateHotLimit($event); } $object->addCalculatedField('IsHot', ' IF(%1$s.HotItem = 2, IF(%1$s.'.$property_map['ClickField'].' >= '.$hot_limit.', 1, 0), %1$s.HotItem )'); // popular items $object->addCalculatedField('IsPop', ' IF(%1$s.PopItem = 2, IF(%1$s.CachedVotesQty >= '. $this->Application->ConfigValue($property_map['MinPopVotes']). ' AND %1$s.CachedRating >= '. $this->Application->ConfigValue($property_map['MinPopRating']). ', 1, 0), %1$s.PopItem)'); } function CalculateHotLimit(&$event) { $property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings'); if (!$property_map) { return; } $click_field = $property_map['ClickField']; $last_hot = $this->Application->ConfigValue($property_map['MaxHotNumber']) - 1; $sql = 'SELECT '.$click_field.' FROM '.$this->Application->getUnitOption($event->Prefix, 'TableName').' ORDER BY '.$click_field.' DESC LIMIT '.$last_hot.', 1'; $res = $this->Conn->GetCol($sql); $hot_limit = (double)array_shift($res); if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $serial_name = $this->Application->incrementCacheSerial($event->Prefix, null, false); $this->Application->setCache($property_map['HotLimit'] . '[%' . $serial_name . '%]', $hot_limit); } else { $this->Application->setDBCache($property_map['HotLimit'], $hot_limit, 3600); } return $hot_limit; } /** * Moves item to preferred category, updates item hits * * @param kEvent $event */ function OnBeforeItemUpdate(&$event) { parent::OnBeforeItemUpdate($event); $object =& $event->getObject(); /* @var $object kCatDBItem */ // update hits field $property_map = $this->Application->getUnitOption($event->Prefix, 'ItemPropertyMappings'); if ($property_map) { $click_field = $property_map['ClickField']; if( $this->Application->isAdminUser && ($this->Application->GetVar($click_field.'_original') !== false) && floor($this->Application->GetVar($click_field.'_original')) != $object->GetDBField($click_field) ) { $sql = 'SELECT MAX('.$click_field.') FROM '.$this->Application->getUnitOption($event->Prefix, 'TableName').' WHERE FLOOR('.$click_field.') = '.$object->GetDBField($click_field); $hits = ( $res = $this->Conn->GetOne($sql) ) ? $res + 0.000001 : $object->GetDBField($click_field); $object->SetDBField($click_field, $hits); } } // change category $target_category = $object->GetDBField('CategoryId'); if ($object->GetOriginalField('CategoryId') != $target_category) { $object->MoveToCat($target_category); } } /** * Load price from temp table if product mode is temp table * * @param kEvent $event */ function OnAfterItemLoad(&$event) { $special = substr($event->Special, -6); $object =& $event->getObject(); /* @var $object kCatDBItem */ if ($special == 'import' || $special == 'export') { $image_data = $object->getPrimaryImageData(); if ($image_data) { $thumbnail_image = $image_data[$image_data['LocalThumb'] ? 'ThumbPath' : 'ThumbUrl']; if ($image_data['SameImages']) { $full_image = ''; } else { $full_image = $image_data[$image_data['LocalImage'] ? 'LocalPath' : 'Url']; } $object->SetDBField('ThumbnailImage', $thumbnail_image); $object->SetDBField('FullImage', $full_image); $object->SetDBField('ImageAlt', $image_data['AltName']); } } // substituiting pending status value for pending editing if ($object->HasField('OrgId') && $object->GetDBField('OrgId') > 0 && $object->GetDBField('Status') == -2) { $options = $object->Fields['Status']['options']; foreach ($options as $key => $val) { if ($key == 2) $key = -2; $new_options[$key] = $val; } $object->Fields['Status']['options'] = $new_options; } // linking existing images for item with virtual fields $image_helper =& $this->Application->recallObject('ImageHelper'); /* @var $image_helper ImageHelper */ $image_helper->LoadItemImages($object); // linking existing files for item with virtual fields $file_helper =& $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ $file_helper->LoadItemFiles($object); // set item's additional categories to virtual field (used in editing) $item_categories = $this->getItemCategories($object->GetDBField('ResourceId')); $object->SetDBField('MoreCategories', $item_categories ? '|'.implode('|', $item_categories).'|' : ''); } function OnAfterItemUpdate(&$event) { $this->CalculateHotLimit($event); if ( substr($event->Special, -6) == 'import') { $this->setCustomExportColumns($event); } if (!$this->Application->isAdminUser) { $object =& $event->getObject(); /* @var $object kDBItem */ $image_helper =& $this->Application->recallObject('ImageHelper'); /* @var $image_helper ImageHelper */ // process image upload in virtual fields $image_helper->SaveItemImages($object); $file_helper =& $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ // process file upload in virtual fields $file_helper->SaveItemFiles($object); if ($event->Special != '-item') { // don't touch categories during cloning $this->processAdditionalCategories($object, 'update'); } } } /** * sets values for import process * * @param kEvent $event */ function OnAfterItemCreate(&$event) { if ( substr($event->Special, -6) == 'import') { $this->setCustomExportColumns($event); } if (!$this->Application->isAdminUser) { $object =& $event->getObject(); /* @var $object kDBItem */ $image_helper =& $this->Application->recallObject('ImageHelper'); /* @var $image_helper ImageHelper */ // process image upload in virtual fields $image_helper->SaveItemImages($object); $file_helper =& $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ // process file upload in virtual fields $file_helper->SaveItemFiles($object); if ($event->Special != '-item') { // don't touch categories during cloning $this->processAdditionalCategories($object, 'create'); } } } /** * Make record to search log * * @param string $keywords * @param int $search_type 0 - simple search, 1 - advanced search */ function saveToSearchLog($keywords, $search_type = 0) { // don't save keywords for each module separately, just one time // static variable can't help here, because each module uses it's own class instance ! if (!$this->Application->GetVar('search_logged')) { $sql = 'UPDATE '.TABLE_PREFIX.'SearchLog SET Indices = Indices + 1 WHERE Keyword = '.$this->Conn->qstr($keywords).' AND SearchType = '.$search_type; // 0 - simple search, 1 - advanced search $this->Conn->Query($sql); if ($this->Conn->getAffectedRows() == 0) { $fields_hash = Array('Keyword' => $keywords, 'Indices' => 1, 'SearchType' => $search_type); $this->Conn->doInsert($fields_hash, TABLE_PREFIX.'SearchLog'); } $this->Application->SetVar('search_logged', 1); } } /** * Makes simple search for category items * based on keywords string * * @param kEvent $event */ function OnSimpleSearch(&$event) { $event->redirect = false; $search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search'; $keywords = unhtmlentities( trim($this->Application->GetVar('keywords')) ); $query_object =& $this->Application->recallObject('HTTPQuery'); $sql = 'SHOW TABLES LIKE "'.$search_table.'"'; if(!isset($query_object->Get['keywords']) && !isset($query_object->Post['keywords']) && $this->Conn->Query($sql)) { return; // used when navigating by pages or changing sorting in search results } if(!$keywords || strlen($keywords) < $this->Application->ConfigValue('Search_MinKeyword_Length')) { $this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); $this->Application->SetVar('keywords_too_short', 1); return; // if no or too short keyword entered, doing nothing } $this->Application->StoreVar('keywords', $keywords); $this->saveToSearchLog($keywords, 0); // 0 - simple search, 1 - advanced search $event->setPseudoClass('_List'); $object =& $event->getObject(); /* @var $object kDBList */ $this->Application->SetVar($event->getPrefixSpecial().'_Page', 1); $lang = $this->Application->GetVar('m_lang'); $items_table = $this->Application->getUnitOption($event->Prefix, 'TableName'); $module_name = $this->Application->findModule('Var', $event->Prefix, 'Name'); $sql = 'SELECT * FROM ' . $this->Application->getUnitOption('confs', 'TableName') . ' WHERE ModuleName = ' . $this->Conn->qstr($module_name) . ' AND SimpleSearch = 1'; $search_config = $this->Conn->Query($sql, 'FieldName'); $field_list = array_keys($search_config); $join_clauses = Array(); // field processing $weight_sum = 0; $alias_counter = 0; $custom_fields = $this->Application->getUnitOption($event->Prefix, 'CustomFields'); if ($custom_fields) { $custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName'); $join_clauses[] = ' LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId'; } // what field in search config becomes what field in sql (key - new field, value - old field (from searchconfig table)) $search_config_map = Array(); foreach ($field_list as $key => $field) { $options = $object->getFieldOptions($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 (getArrayValue($options, '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 if ($foreign_field = $search_config[$field]['ForeignField']) { $exploded = explode(':', $foreign_field, 2); if ($exploded[0] == 'CALC') { // ignoring having type clauses in simple search unset($field_list[$key]); continue; } else { $multi_lingual = false; if ($exploded[0] == 'MULTI') { $multi_lingual = true; $foreign_field = $exploded[1]; } $exploded = explode('.', $foreign_field); // format: table.field_name $foreign_table = TABLE_PREFIX.$exploded[0]; $alias_counter++; $alias = 't'.$alias_counter; if ($multi_lingual) { $field_list[$key] = $alias.'.'.'l'.$lang.'_'.$exploded[1]; $field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field; $search_config_map[ $field_list[$key] ] = $field; $search_config_map[ $field_list[$key.'_primary'] ] = $field; } else { $field_list[$key] = $alias.'.'.$exploded[1]; $search_config_map[ $field_list[$key] ] = $field; } $join_clause = str_replace('{ForeignTable}', $alias, $search_config[$field]['JoinClause']); $join_clause = str_replace('{LocalTable}', $items_table, $join_clause); $join_clauses[] = ' LEFT JOIN '.$foreign_table.' '.$alias.' ON '.$join_clause; } } else { // processing fields from local table if ($search_config[$field]['CustomFieldId']) { $local_table = 'custom_data'; // search by custom field value on current language $custom_field_id = array_search($field_list[$key], $custom_fields); $field_list[$key] = 'l'.$lang.'_cust_'.$custom_field_id; // search by custom field value on primary language $field_list[$key.'_primary'] = $local_table.'.l'.$this->Application->GetDefaultLanguageId().'_cust_'.$custom_field_id; $search_config_map[ $field_list[$key.'_primary'] ] = $field; } $field_list[$key] = $local_table.'.'.$field_list[$key]; $search_config_map[ $field_list[$key] ] = $field; } } // keyword string processing $search_helper =& $this->Application->recallObject('SearchHelper'); /* @var $search_helper kSearchHelper */ $where_clause = Array (); foreach ($field_list as $field) { if (preg_match('/^' . preg_quote($items_table, '/') . '\.(.*)/', $field, $regs)) { // local real field $filter_data = $search_helper->getSearchClause($object, $regs[1], $keywords, false); if ($filter_data) { $where_clause[] = $filter_data['value']; } } elseif (preg_match('/^custom_data\.(.*)/', $field, $regs)) { $custom_field_name = 'cust_' . $search_config_map[$field]; $filter_data = $search_helper->getSearchClause($object, $custom_field_name, $keywords, false); if ($filter_data) { $where_clause[] = str_replace('`' . $custom_field_name . '`', $field, $filter_data['value']); } } else { $where_clause[] = $search_helper->buildWhereClause($keywords, Array ($field)); } } $where_clause = '(' . implode(') OR (', $where_clause) . ')'; $search_scope = $this->Application->GetVar('search_scope'); if ($search_scope == 'category') { $category_id = $this->Application->GetVar('m_cat_id'); $category_filter = $this->getCategoryLimitClause($category_id); if ($category_filter !== false) { $join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'CategoryItems ON '.TABLE_PREFIX.'CategoryItems.ItemResourceId = '.$items_table.'.ResourceId'; $join_clauses[] = ' LEFT JOIN '.TABLE_PREFIX.'Category ON '.TABLE_PREFIX.'Category.CategoryId = '.TABLE_PREFIX.'CategoryItems.CategoryId'; $where_clause = '('.$this->getCategoryLimitClause($category_id).') AND '.$where_clause; } } $where_clause = $where_clause.' AND '.$items_table.'.Status=1'; if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') { if ($event->MasterEvent->getEventParam('ResultIds')) { $where_clause .= ' AND '.$items_table.'.ResourceId IN ('.implode(',', $event->MasterEvent->getEventParam('ResultIds')).')'; } } // making relevance clause $positive_words = $search_helper->getPositiveKeywords($keywords); $this->Application->StoreVar('highlight_keywords', serialize($positive_words)); $revelance_parts = Array(); reset($search_config); foreach ($positive_words as $keyword_index => $positive_word) { $positive_word = $search_helper->transformWildcards($positive_word); $positive_words[$keyword_index] = $this->Conn->escape($positive_word); } foreach ($field_list as $field) { if (!array_key_exists($field, $search_config_map)) { $map_key = $search_config_map[$items_table . '.' . $field]; } else { $map_key = $search_config_map[$field]; } $config_elem = $search_config[ $map_key ]; $weight = $config_elem['Priority']; $revelance_parts[] = 'IF('.$field.' LIKE "%'.implode(' ', $positive_words).'%", '.$weight_sum.', 0)'; foreach ($positive_words as $keyword) { $revelance_parts[] = 'IF('.$field.' LIKE "%'.$keyword.'%", '.$weight.', 0)'; } } $revelance_parts = array_unique($revelance_parts); $conf_postfix = $this->Application->getUnitOption($event->Prefix, 'SearchConfigPostfix'); $rel_keywords = $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix) / 100; $rel_pop = $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix) / 100; $rel_rating = $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix) / 100; $relevance_clause = '('.implode(' + ', $revelance_parts).') / '.$weight_sum.' * '.$rel_keywords; if ($rel_pop && isset($object->Fields['Hits'])) { $relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop; } if ($rel_rating && isset($object->Fields['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'); $res = $this->Conn->Query($sql); } /** * Enter description here... * * @param kEvent $event */ function OnSubSearch(&$event) { $search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search'; $sql = 'SHOW TABLES LIKE "'.$search_table.'"'; if($this->Conn->Query($sql)) { $sql = 'SELECT DISTINCT ResourceId FROM '.$search_table; $ids = $this->Conn->GetCol($sql); } $event->setEventParam('ResultIds', $ids); $event->CallSubEvent('OnSimpleSearch'); } /** * Enter description here... * * @param kEvent $event * @todo Change all hardcoded Products table & In-Commerce module usage to dynamic usage from item config !!! */ function OnAdvancedSearch(&$event) { $query_object =& $this->Application->recallObject('HTTPQuery'); if(!isset($query_object->Post['andor'])) { return; // used when navigating by pages or changing sorting in search results } $this->Application->RemoveVar('keywords'); $this->Application->RemoveVar('Search_Keywords'); $module_name = $this->Application->findModule('Var', $event->Prefix, 'Name'); $sql = 'SELECT * FROM '.$this->Application->getUnitOption('confs', 'TableName').' WHERE (ModuleName = '.$this->Conn->qstr($module_name).') AND (AdvancedSearch = 1)'; $search_config = $this->Conn->Query($sql); $lang = $this->Application->GetVar('m_lang'); $object =& $event->getObject(); $object->SetPage(1); $items_table = $this->Application->getUnitOption($event->Prefix, 'TableName'); $search_keywords = $this->Application->GetVar('value'); // will not be changed $keywords = $this->Application->GetVar('value'); // will be changed down there $verbs = $this->Application->GetVar('verb'); $glues = $this->Application->GetVar('andor'); $and_conditions = Array(); $or_conditions = Array(); $and_having_conditions = Array(); $or_having_conditions = Array(); $join_clauses = Array(); $highlight_keywords = Array(); $relevance_parts = Array(); $alias_counter = 0; $custom_fields = $this->Application->getUnitOption($event->Prefix, 'CustomFields'); if ($custom_fields) { $custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName'); $join_clauses[] = ' LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId'; } $search_log = ''; $weight_sum = 0; // processing fields and preparing conditions foreach ($search_config as $record) { $field = $record['FieldName']; $join_clause = ''; $condition_mode = 'WHERE'; // field processing $options = $object->getFieldOptions($field); $local_table = TABLE_PREFIX.$record['TableName']; $weight_sum += $record['Priority']; // counting weight sum; used when making relevance clause // processing multilingual fields if (getArrayValue($options, 'formatter') == 'kMultiLanguage') { $field_name = 'l'.$lang.'_'.$field; } else { $field_name = $field; } // processing fields from other tables if ($foreign_field = $record['ForeignField']) { $exploded = explode(':', $foreign_field, 2); if($exploded[0] == 'CALC') { $user_groups = $this->Application->RecallVar('UserGroups'); $field_name = str_replace('{PREFIX}', TABLE_PREFIX, $exploded[1]); $join_clause = str_replace('{PREFIX}', TABLE_PREFIX, $record['JoinClause']); $join_clause = str_replace('{USER_GROUPS}', $user_groups, $join_clause); $join_clause = ' LEFT JOIN '.$join_clause; $condition_mode = 'HAVING'; } else { $exploded = explode('.', $foreign_field); $foreign_table = TABLE_PREFIX.$exploded[0]; if($record['CustomFieldId']) { $exploded[1] = 'l'.$lang.'_'.$exploded[1]; } $alias_counter++; $alias = 't'.$alias_counter; $field_name = $alias.'.'.$exploded[1]; $join_clause = str_replace('{ForeignTable}', $alias, $record['JoinClause']); $join_clause = str_replace('{LocalTable}', $items_table, $join_clause); if($record['CustomFieldId']) { $join_clause .= ' AND '.$alias.'.CustomFieldId='.$record['CustomFieldId']; } $join_clause = ' LEFT JOIN '.$foreign_table.' '.$alias.' ON '.$join_clause; } } else { // processing fields from local table if ($record['CustomFieldId']) { $local_table = 'custom_data'; $field_name = 'l'.$lang.'_cust_'.array_search($field_name, $custom_fields); } $field_name = $local_table.'.'.$field_name; } $condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords); if ($record['CustomFieldId'] && strlen($condition)) { // search in primary value of custom field + value in current language $field_name = $local_table.'.'.'l'.$this->Application->GetDefaultLanguageId().'_cust_'.array_search($field, $custom_fields); $primary_condition = $this->getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, $highlight_keywords); $condition = '('.$condition.' OR '.$primary_condition.')'; } if ($condition) { if ($join_clause) { $join_clauses[] = $join_clause; } $relevance_parts[] = 'IF('.$condition.', '.$record['Priority'].', 0)'; if ($glues[$field] == 1) { // and if ($condition_mode == 'WHERE') { $and_conditions[] = $condition; } else { $and_having_conditions[] = $condition; } } else { // or if ($condition_mode == 'WHERE') { $or_conditions[] = $condition; } else { $or_having_conditions[] = $condition; } } // create search log record $search_log_data = Array('search_config' => $record, 'verb' => getArrayValue($verbs, $field), 'value' => ($record['FieldType'] == 'range') ? $search_keywords[$field.'_from'].'|'.$search_keywords[$field.'_to'] : $search_keywords[$field]); $search_log[] = $this->Application->Phrase('la_Field').' "'.$this->getHuman('Field', $search_log_data).'" '.$this->getHuman('Verb', $search_log_data).' '.$this->Application->Phrase('la_Value').' '.$this->getHuman('Value', $search_log_data).' '.$this->Application->Phrase($glues[$field] == 1 ? 'lu_And' : 'lu_Or'); } } if ($search_log) { $search_log = implode('<br />', $search_log); $search_log = preg_replace('/(.*) '.preg_quote($this->Application->Phrase('lu_and'), '/').'|'.preg_quote($this->Application->Phrase('lu_or'), '/').'$/is', '\\1', $search_log); $this->saveToSearchLog($search_log, 1); // advanced search } $this->Application->StoreVar('highlight_keywords', serialize($highlight_keywords)); // making relevance clause if($relevance_parts) { $conf_postfix = $this->Application->getUnitOption($event->Prefix, 'SearchConfigPostfix'); $rel_keywords = $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix) / 100; $rel_pop = $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix) / 100; $rel_rating = $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix) / 100; $relevance_clause = '('.implode(' + ', $relevance_parts).') / '.$weight_sum.' * '.$rel_keywords; $relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop; $relevance_clause .= ' + (CachedRating + 1) / (MAX(CachedRating) + 1) * '.$rel_rating; } else { $relevance_clause = '0'; } // building having clause if($or_having_conditions) { $and_having_conditions[] = '('.implode(' OR ', $or_having_conditions).')'; } $having_clause = implode(' AND ', $and_having_conditions); $having_clause = $having_clause ? ' HAVING '.$having_clause : ''; // building where clause if($or_conditions) { $and_conditions[] = '('.implode(' OR ', $or_conditions).')'; } // $and_conditions[] = $items_table.'.Status = 1'; $where_clause = implode(' AND ', $and_conditions); if(!$where_clause) { if($having_clause) { $where_clause = '1'; } else { $where_clause = '0'; $this->Application->SetVar('adv_search_error', 1); } } $where_clause .= ' AND '.$items_table.'.Status = 1'; // building final search query $search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search'; $this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); $pick_field = isset($fields['EditorsPick']) ? $items_table.'.EditorsPick' : '0'; $sql = ' CREATE TABLE '.$search_table.' SELECT '.$relevance_clause.' AS Relevance, '.$items_table.'.'.$id_field.' AS ItemId, '.$items_table.'.ResourceId AS ResourceId, 11 AS ItemType, '.$pick_field.' AS EdPick FROM '.$items_table.' '.implode(' ', $join_clauses).' WHERE '.$where_clause.' GROUP BY '.$items_table.'.'.$id_field. $having_clause; $res = $this->Conn->Query($sql); } function getAdvancedSearchCondition($field_name, $record, $keywords, $verbs, &$highlight_keywords) { $field = $record['FieldName']; $condition_patterns = Array ( 'any' => '%s LIKE %s', 'contains' => '%s LIKE %s', 'notcontains' => '(NOT (%1$s LIKE %2$s) OR %1$s IS NULL)', 'is' => '%s = %s', 'isnot' => '(%1$s != %2$s OR %1$s IS NULL)' ); $condition = ''; switch ($record['FieldType']) { case 'select': $keywords[$field] = unhtmlentities( $keywords[$field] ); if ($keywords[$field]) { $condition = sprintf($condition_patterns['is'], $field_name, $this->Conn->qstr( $keywords[$field] )); } break; case 'multiselect': $keywords[$field] = unhtmlentities( $keywords[$field] ); if ($keywords[$field]) { $condition = Array (); $values = explode('|', substr($keywords[$field], 1, -1)); foreach ($values as $selected_value) { $condition[] = sprintf($condition_patterns['contains'], $field_name, $this->Conn->qstr('%|'.$selected_value.'|%')); } $condition = '('.implode(' OR ', $condition).')'; } break; case 'text': $keywords[$field] = unhtmlentities( $keywords[$field] ); if (mb_strlen($keywords[$field]) >= $this->Application->ConfigValue('Search_MinKeyword_Length')) { $highlight_keywords[] = $keywords[$field]; if (in_array($verbs[$field], Array('any', 'contains', 'notcontains'))) { $keywords[$field] = '%'.strtr($keywords[$field], Array('%' => '\\%', '_' => '\\_')).'%'; } $condition = sprintf($condition_patterns[$verbs[$field]], $field_name, $this->Conn->qstr( $keywords[$field] )); } break; case 'boolean': if ($keywords[$field] != -1) { $property_mappings = $this->Application->getUnitOption($this->Prefix, 'ItemPropertyMappings'); $items_table = $this->Application->getUnitOption($event->Prefix, 'TableName'); switch ($field) { case 'HotItem': $hot_limit_var = getArrayValue($property_mappings, 'HotLimit'); if ($hot_limit_var) { $hot_limit = (int)$this->Application->getDBCache($hot_limit_var); $condition = 'IF('.$items_table.'.HotItem = 2, IF('.$items_table.'.Hits >= '. $hot_limit. ', 1, 0), '.$items_table.'.HotItem) = '.$keywords[$field]; } break; case 'PopItem': $votes2pop_var = getArrayValue($property_mappings, 'VotesToPop'); $rating2pop_var = getArrayValue($property_mappings, 'RatingToPop'); if ($votes2pop_var && $rating2pop_var) { $condition = 'IF('.$items_table.'.PopItem = 2, IF('.$items_table.'.CachedVotesQty >= '. $this->Application->ConfigValue($votes2pop_var). ' AND '.$items_table.'.CachedRating >= '. $this->Application->ConfigValue($rating2pop_var). ', 1, 0), '.$items_table.'.PopItem) = '.$keywords[$field]; } break; case 'NewItem': $new_days_var = getArrayValue($property_mappings, 'NewDays'); if ($new_days_var) { $condition = 'IF('.$items_table.'.NewItem = 2, IF('.$items_table.'.CreatedOn >= (UNIX_TIMESTAMP() - '. $this->Application->ConfigValue($new_days_var). '*3600*24), 1, 0), '.$items_table.'.NewItem) = '.$keywords[$field]; } break; case 'EditorsPick': $condition = $items_table.'.EditorsPick = '.$keywords[$field]; break; } } break; case 'range': $range_conditions = Array(); if ($keywords[$field.'_from'] && !preg_match("/[^0-9]/i", $keywords[$field.'_from'])) { $range_conditions[] = $field_name.' >= '.$keywords[$field.'_from']; } if ($keywords[$field.'_to'] && !preg_match("/[^0-9]/i", $keywords[$field.'_to'])) { $range_conditions[] = $field_name.' <= '.$keywords[$field.'_to']; } if ($range_conditions) { $condition = implode(' AND ', $range_conditions); } break; case 'date': if ($keywords[$field]) { if (in_array($keywords[$field], Array('today', 'yesterday'))) { $current_time = getdate(); $day_begin = adodb_mktime(0, 0, 0, $current_time['mon'], $current_time['mday'], $current_time['year']); $time_mapping = Array('today' => $day_begin, 'yesterday' => ($day_begin - 86400)); $min_time = $time_mapping[$keywords[$field]]; } else { $time_mapping = Array ( 'last_week' => 604800, 'last_month' => 2628000, 'last_3_months' => 7884000, 'last_6_months' => 15768000, 'last_year' => 31536000, ); $min_time = adodb_mktime() - $time_mapping[$keywords[$field]]; } $condition = $field_name.' > '.$min_time; } break; } return $condition; } function getHuman($type, $search_data) { $type = ucfirst(strtolower($type)); extract($search_data); switch ($type) { case 'Field': return $this->Application->Phrase($search_config['DisplayName']); break; case 'Verb': return $verb ? $this->Application->Phrase('lu_advsearch_'.$verb) : ''; break; case 'Value': switch ($search_config['FieldType']) { case 'date': $values = Array(0 => 'lu_comm_Any', 'today' => 'lu_comm_Today', 'yesterday' => 'lu_comm_Yesterday', 'last_week' => 'lu_comm_LastWeek', 'last_month' => 'lu_comm_LastMonth', 'last_3_months' => 'lu_comm_Last3Months', 'last_6_months' => 'lu_comm_Last6Months', 'last_year' => 'lu_comm_LastYear'); $ret = $this->Application->Phrase($values[$value]); break; case 'range': $value = explode('|', $value); return $this->Application->Phrase('lu_comm_From').' "'.$value[0].'" '.$this->Application->Phrase('lu_comm_To').' "'.$value[1].'"'; break; case 'boolean': $values = Array(1 => 'lu_comm_Yes', 0 => 'lu_comm_No', -1 => 'lu_comm_Both'); $ret = $this->Application->Phrase($values[$value]); break; default: $ret = $value; break; } return '"'.$ret.'"'; break; } } /** * Set's correct page for list * based on data provided with event * * @param kEvent $event * @access private * @see OnListBuild */ function SetPagination(&$event) { $object =& $event->getObject(); /* @var $object kDBList */ // get PerPage (forced -> session -> config -> 10) $object->SetPerPage( $this->getPerPage($event) ); // main lists on Front-End have special get parameter for page $page = $object->mainList ? $this->Application->GetVar('page') : false; if (!$page) { // page is given in "env" variable for given prefix $page = $this->Application->GetVar($event->getPrefixSpecial() . '_Page'); } if (!$page && $event->Special) { // when not part of env, then variables like "prefix.special_Page" are // replaced (by PHP) with "prefix_special_Page", so check for that too $page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page'); } if (!$object->mainList) { // main lists doesn't use session for page storing $this->Application->StoreVarDefault($event->getPrefixSpecial() . '_Page', 1, true); // true for optional if (!$page) { if ($this->Application->RewriteURLs()) { // when page not found by prefix+special, then try to search it without special at all $page = $this->Application->GetVar($event->Prefix . '_Page'); if (!$page) { // page not found in request -> get from session $page = $this->Application->RecallVar($event->Prefix . '_Page'); } if ($page) { // page found in request -> store in session $this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional } } else { // page not found in request -> get from session $page = $this->Application->RecallVar($event->getPrefixSpecial() . '_Page'); } } else { // page found in request -> store in session $this->Application->StoreVar($event->getPrefixSpecial() . '_Page', $page, true); //true for optional } if ( !$event->getEventParam('skip_counting') ) { // when stored page is larger, then maximal list page number // (such case is also processed in kDBList::Query method) $pages = $object->GetTotalPages(); if ($page > $pages) { $page = 1; $this->Application->StoreVar($event->getPrefixSpecial().'_Page', 1, true); } } } $object->SetPage($page); } /* === RELATED TO IMPORT/EXPORT: BEGIN === */ /** * Shows export dialog * * @param kEvent $event */ function OnExport(&$event) { $selected_ids = $this->StoreSelectedIDs($event); if (implode(',', $selected_ids) == '') { // K4 fix when no ids found bad selected ids array is formed $selected_ids = false; } $selected_cats_ids = $this->Application->GetVar('export_categories'); $this->Application->StoreVar($event->Prefix.'_export_ids', $selected_ids ? implode(',', $selected_ids) : '' ); $this->Application->StoreVar($event->Prefix.'_export_cats_ids', $selected_cats_ids); $export_helper =& $this->Application->recallObject('CatItemExportHelper'); /* @var $export_helper kCatDBItemExportHelper */ $redirect_params = Array ( $this->Prefix.'.export_event' => 'OnNew', 'pass' => 'all,'.$this->Prefix.'.export' ); $event->setRedirectParams($redirect_params); } /** * Performs each export step & displays progress percent * * @param kEvent $event */ function OnExportProgress(&$event) { $export_object =& $this->Application->recallObject('CatItemExportHelper'); /* @var $export_object kCatDBItemExportHelper */ $event = new kEvent($event->getPrefixSpecial().':OnDummy'); $action_method = 'perform'.ucfirst($event->Special); $field_values = $export_object->$action_method($event); // finish code is done from JS now if ($field_values['start_from'] == $field_values['total_records']) { if ($event->Special == 'import') { $this->Application->StoreVar('PermCache_UpdateRequired', 1); $url_params = Array( 't' => 'catalog/catalog', 'm_cat_id' => $this->Application->RecallVar('ImportCategory'), 'anchor' => 'tab-' . $event->Prefix, ); $this->Application->EventManager->openerStackChange($url_params); $event->SetRedirectParam('opener', 'u'); } elseif ($event->Special == 'export') { $event->redirect = $export_object->getModuleName($event) . '/' . $event->Special . '_finish'; $event->SetRedirectParam('pass', 'all'); } return ; } $export_options = $export_object->loadOptions($event); echo $export_options['start_from'] * 100 / $export_options['total_records']; $event->status = erSTOP; } /** * Returns specific to each item type columns only * * @param kEvent $event * @return Array */ function getCustomExportColumns(&$event) { return Array( '__VIRTUAL__ThumbnailImage' => 'ThumbnailImage', '__VIRTUAL__FullImage' => 'FullImage', '__VIRTUAL__ImageAlt' => 'ImageAlt'); } /** * Sets non standart virtual fields (e.g. to other tables) * * @param kEvent $event */ function setCustomExportColumns(&$event) { $this->restorePrimaryImage($event); } /** * Create/Update primary image record in info found in imported data * * @param kEvent $event */ function restorePrimaryImage(&$event) { $object =& $event->getObject(); $has_image_info = $object->GetDBField('ImageAlt') && ($object->GetDBField('ThumbnailImage') || $object->GetDBField('FullImage')); if (!$has_image_info) { return false; } $image_data = $object->getPrimaryImageData(); $image =& $this->Application->recallObject('img', null, Array('skip_autoload' => true)); if ($image_data) { $image->Load($image_data['ImageId']); } else { $image->Clear(); $image->SetDBField('Name', 'main'); $image->SetDBField('DefaultImg', 1); $image->SetDBField('ResourceId', $object->GetDBField('ResourceId')); } $image->SetDBField('AltName', $object->GetDBField('ImageAlt')); if ($object->GetDBField('ThumbnailImage')) { $thumbnail_field = $this->isURL( $object->GetDBField('ThumbnailImage') ) ? 'ThumbUrl' : 'ThumbPath'; $image->SetDBField($thumbnail_field, $object->GetDBField('ThumbnailImage') ); $image->SetDBField('LocalThumb', $thumbnail_field == 'ThumbPath' ? 1 : 0); } if (!$object->GetDBField('FullImage')) { $image->SetDBField('SameImages', 1); } else { $image->SetDBField('SameImages', 0); $full_field = $this->isURL( $object->GetDBField('FullImage') ) ? 'Url' : 'LocalPath'; $image->SetDBField($full_field, $object->GetDBField('FullImage') ); $image->SetDBField('LocalImage', $full_field == 'LocalPath' ? 1 : 0); } if ($image->isLoaded()) { $image->Update(); } else { $image->Create(); } } function isURL($path) { return preg_match('#(http|https)://(.*)#', $path); } /** * Prepares item for import/export operations * * @param kEvent $event */ function OnNew(&$event) { parent::OnNew($event); if ($event->Special == 'import' || $event->Special == 'export') { $export_helper =& $this->Application->recallObject('CatItemExportHelper'); $export_helper->setRequiredFields($event); } } /** * Process items selected in item_selector * * @param kEvent $event */ function OnProcessSelected(&$event) { $selected_ids = $this->Application->GetVar('selected_ids'); $dst_field = $this->Application->RecallVar('dst_field'); if ($dst_field == 'ItemCategory') { // Item Edit -> Categories Tab -> New Categories $object =& $event->getObject(); $category_ids = explode(',', $selected_ids['c']); foreach ($category_ids as $category_id) { $object->assignToCategory($category_id); } } if ($dst_field == 'ImportCategory') { // Tools -> Import -> Item Import -> Select Import Category $this->Application->StoreVar('ImportCategory', $selected_ids['c']); // $this->Application->StoreVar($event->getPrefixSpecial().'_ForceNotValid', 1); // not to loose import/export values on form refresh $url_params = Array ( $event->getPrefixSpecial() . '_id' => 0, $event->getPrefixSpecial() . '_event' => 'OnExportBegin', // 'm_opener' => 's', ); $this->Application->EventManager->openerStackChange($url_params); } $event->SetRedirectParam('opener', 'u'); } /** * Saves Import/Export settings to session * * @param kEvent $event */ function OnSaveSettings(&$event) { $event->redirect = false; $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if ($items_info) { list($id, $field_values) = each($items_info); $object =& $event->getObject( Array('skip_autoload' => true) ); $object->SetFieldsFromHash($field_values); $field_values['ImportFilename'] = $object->GetDBField('ImportFilename'); //if upload formatter has renamed the file during moving !!! $field_values['ImportSource'] = 2; $field_values['ImportLocalFilename'] = $object->GetDBField('ImportFilename'); $items_info[$id] = $field_values; $this->Application->StoreVar($event->getPrefixSpecial().'_ItemsInfo', serialize($items_info)); } } /** * Saves Import/Export settings to session * * @param kEvent $event */ function OnResetSettings(&$event) { - $this->Application->StoreVar('ImportCategory', $this->Application->findModule('Name', 'Core', 'RootCat')); + $this->Application->StoreVar('ImportCategory', $this->Application->getBaseCategory()); } function OnCancelAction(&$event) { $event->redirect_params = Array('pass' => 'all,'.$event->GetPrefixSpecial()); $event->redirect = $this->Application->GetVar('cancel_template'); } /* === RELATED TO IMPORT/EXPORT: END === */ /** * Stores item's owner login into separate field together with id * * @param kEvent $event * @param string $id_field * @param string $cached_field */ function cacheItemOwner(&$event, $id_field, $cached_field) { $object =& $event->getObject(); $user_id = $object->GetDBField($id_field); $options = $object->GetFieldOptions($id_field); if (isset($options['options'][$user_id])) { $object->SetDBField($cached_field, $options['options'][$user_id]); } else { $id_field = $this->Application->getUnitOption('u', 'IDField'); $table_name = $this->Application->getUnitOption('u', 'TableName'); $sql = 'SELECT Login FROM '.$table_name.' WHERE '.$id_field.' = '.$user_id; $object->SetDBField($cached_field, $this->Conn->GetOne($sql)); } } /** * Saves item beeing edited into temp table * * @param kEvent $event */ function OnPreSave(&$event) { parent::OnPreSave($event); $use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing'); if ($event->status == erSUCCESS && $use_pending_editing) { // decision: clone or not clone $object =& $event->getObject(); if ($object->GetID() == 0 || $object->GetDBField('OrgId') > 0) { // new items or cloned items shouldn't be cloned again return true; } $perm_helper =& $this->Application->recallObject('PermissionsHelper'); $owner_field = $this->getOwnerField($event->Prefix); if ($perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix) == 2) { // 1. clone original item $temp_handler =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); $cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array($object->GetID()), null, null, null, true); $ci_table = $this->Application->GetTempName(TABLE_PREFIX.'CategoryItems'); // 2. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem $sql = 'SELECT ResourceId FROM '.$object->TableName.' WHERE '.$object->IDField.' = '.$cloned_ids[0]; $clone_resource_id = $this->Conn->GetOne($sql); $sql = 'DELETE FROM '.$ci_table.' WHERE ItemResourceId = '.$clone_resource_id.' AND PrimaryCat = 1'; $this->Conn->Query($sql); // 3. copy main item categoryitems to cloned item $sql = ' INSERT INTO '.$ci_table.' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename) SELECT CategoryId, '.$clone_resource_id.' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename FROM '.$ci_table.' WHERE ItemResourceId = '.$object->GetDBField('ResourceId'); $this->Conn->Query($sql); // 4. put cloned id to OrgId field of item being cloned $sql = 'UPDATE '.$object->TableName.' SET OrgId = '.$object->GetID().' WHERE '.$object->IDField.' = '.$cloned_ids[0]; $this->Conn->Query($sql); // 5. substitute id of item being cloned with clone id $this->Application->SetVar($event->getPrefixSpecial().'_id', $cloned_ids[0]); $selected_ids = $this->getSelectedIDs($event, true); $selected_ids[ array_search($object->GetID(), $selected_ids) ] = $cloned_ids[0]; $this->StoreSelectedIDs($event, $selected_ids); // 6. delete original item from temp table $temp_handler->DeleteItems($event->Prefix, $event->Special, Array($object->GetID())); } } } /** * Sets default expiration based on module setting * * @param kEvent $event */ function OnPreCreate(&$event) { parent::OnPreCreate($event); if ($event->status == erSUCCESS) { $object =& $event->getObject(); $owner_field = $this->getOwnerField($event->Prefix); $object->SetDBField($owner_field, $this->Application->RecallVar('user_id')); } } /** * Occures before original item of item in pending editing got deleted (for hooking only) * * @param kEvent $event */ function OnBeforeDeleteOriginal(&$event) { } /** * Occures before an item is cloneded * Id of ORIGINAL item is passed as event' 'id' param * Do not call object' Update method in this event, just set needed fields! * * @param kEvent $event */ function OnBeforeClone(&$event) { if ($this->Application->GetVar('ResetCatBeforeClone')) { $object =& $event->getObject(); $object->SetDBField('CategoryId', null); } } /** * Set status for new category item based on user permission in category * * @param kEvent $event */ function OnBeforeItemCreate(&$event) { if ($this->Application->isAdminUser) { // don't set permission-based status, when creating categories in admin return true; } $use_pending_editing = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing'); if ($use_pending_editing) { $object =& $event->getObject(); /* @var $object kDBItem */ $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $primary_category = $object->GetDBField('CategoryId') > 0 ? $object->GetDBField('CategoryId') : $this->Application->GetVar('m_cat_id'); $item_status = $perm_helper->AddCheckPermission($primary_category, $event->Prefix); if ($item_status == STATUS_DISABLED) { $event->status = erFAIL; return false; } else { $object->SetDBField('Status', $item_status); } } } /** * Creates category item & redirects to confirmation template (front-end only) * * @param kEvent $event */ function OnCreate(&$event) { parent::OnCreate($event); $this->SetFrontRedirectTemplate($event, 'suggest'); } /** * Returns item's categories (allows to exclude primary category) * * @param int $resource_id * @param bool $with_primary * @return Array */ function getItemCategories($resource_id, $with_primary = false) { $sql = 'SELECT CategoryId FROM '.TABLE_PREFIX.'CategoryItems WHERE (ItemResourceId = '.$resource_id.')'; if (!$with_primary) { $sql .= ' AND (PrimaryCat = 0)'; } return $this->Conn->GetCol($sql); } /** * Adds new and removes old additional categories from category item * * @param kCatDBItem $object */ function processAdditionalCategories(&$object, $mode) { if (!array_key_exists('MoreCategories', $object->VirtualFields)) { // given category item doesn't require such type of processing return ; } $process_categories = $object->GetDBField('MoreCategories'); if ($process_categories === '') { // field was not in submit & have default value (when no categories submitted, then value is null) return ; } if ($mode == 'create') { // prevents first additional category to become primary $object->assignPrimaryCategory(); } $process_categories = $process_categories ? explode('|', substr($process_categories, 1, -1)) : Array (); $existing_categories = $this->getItemCategories($object->GetDBField('ResourceId')); $add_categories = array_diff($process_categories, $existing_categories); foreach ($add_categories as $category_id) { $object->assignToCategory($category_id); } $remove_categories = array_diff($existing_categories, $process_categories); foreach ($remove_categories as $category_id) { $object->removeFromCategory($category_id); } } /** * Creates category item & redirects to confirmation template (front-end only) * * @param kEvent $event */ function OnUpdate(&$event) { $use_pending = $this->Application->getUnitOption($event->Prefix, 'UsePendingEditing'); if ($this->Application->isAdminUser || !$use_pending) { parent::OnUpdate($event); $this->SetFrontRedirectTemplate($event, 'modify'); return ; } $object =& $event->getObject(Array('skip_autoload' => true)); /* @var $object kCatDBItem */ $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ($items_info) { $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $temp_handler =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); /* @var $temp_handler kTempTablesHandler */ $owner_field = $this->getOwnerField($event->Prefix); $file_helper =& $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ foreach ($items_info as $id => $field_values) { $object->Load($id); $edit_perm = $perm_helper->ModifyCheckPermission($object->GetDBField($owner_field), $object->GetDBField('CategoryId'), $event->Prefix); if ($use_pending && !$object->GetDBField('OrgId') && ($edit_perm == STATUS_PENDING)) { // pending editing enabled + not pending copy -> get/create pending copy & save changes to it $original_id = $object->GetID(); $original_resource_id = $object->GetDBField('ResourceId'); $file_helper->PreserveItemFiles($field_values); $object->Load($original_id, 'OrgId'); if (!$object->isLoaded()) { // 1. user has no pending copy of live item -> clone live item $cloned_ids = $temp_handler->CloneItems($event->Prefix, $event->Special, Array($original_id), null, null, null, true); $object->Load($cloned_ids[0]); $object->SetFieldsFromHash($field_values); // 1a. delete record from CategoryItems (about cloned item) that was automatically created during call of Create method of kCatDBItem $ci_table = $this->Application->getUnitOption('ci', 'TableName'); $sql = 'DELETE FROM '.$ci_table.' WHERE ItemResourceId = '.$object->GetDBField('ResourceId').' AND PrimaryCat = 1'; $this->Conn->Query($sql); // 1b. copy main item categoryitems to cloned item $sql = 'INSERT INTO '.$ci_table.' (CategoryId, ItemResourceId, PrimaryCat, ItemPrefix, Filename) SELECT CategoryId, '.$object->GetDBField('ResourceId').' AS ItemResourceId, PrimaryCat, ItemPrefix, Filename FROM '.$ci_table.' WHERE ItemResourceId = '.$original_resource_id; $this->Conn->Query($sql); // 1c. put cloned id to OrgId field of item being cloned $object->SetDBField('Status', STATUS_PENDING_EDITING); $object->SetDBField('OrgId', $original_id); } else { // 2. user has pending copy of live item -> just update field values $object->SetFieldsFromHash($field_values); } // update id in request (used for redirect in mod-rewrite mode) $this->Application->SetVar($event->getPrefixSpecial().'_id', $object->GetID()); } else { // 3. already editing pending copy -> just update field values $object->SetFieldsFromHash($field_values); } if ($object->Update()) { $event->status = erSUCCESS; } else { $event->status = erFAIL; $event->redirect = false; break; } } } $this->SetFrontRedirectTemplate($event, 'modify'); } /** * Sets next template to one required for front-end after adding/modifying item * * @param kEvent $event * @param string $template_key - {suggest,modify} */ function SetFrontRedirectTemplate(&$event, $template_key) { if ($this->Application->isAdminUser || $event->status != erSUCCESS) { return ; } // prepare redirect template $object =& $event->getObject(); $is_active = ($object->GetDBField('Status') == STATUS_ACTIVE); $next_template = $is_active ? 'confirm_template' : 'pending_confirm_template'; $event->redirect = $this->Application->GetVar($template_key.'_'.$next_template); $event->SetRedirectParam('opener', 's'); // send email events $perm_prefix = $this->Application->getUnitOption($event->Prefix, 'PermItemPrefix'); switch ($event->Name) { case 'OnCreate': $event_suffix = $is_active ? 'ADD' : 'ADD.PENDING'; $owner_field = $this->getOwnerField($event->Prefix); $this->Application->EmailEventAdmin($perm_prefix.'.'.$event_suffix); // there are no ADD.PENDING event for admin :( $this->Application->EmailEventUser($perm_prefix.'.'.$event_suffix, $object->GetDBField($owner_field)); break; case 'OnUpdate': $event_suffix = $is_active ? 'MODIFY' : 'MODIFY.PENDING'; $this->Application->EmailEventAdmin($perm_prefix.'.'.$event_suffix); // there are no ADD.PENDING event for admin :( $this->Application->EmailEventUser($perm_prefix.'.'.$event_suffix, $object->GetDBField('ModifiedById')); break; } } /** * Apply same processing to each item beeing selected in grid * * @param kEvent $event * @access private */ function iterateItems(&$event) { if ($event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline') { return parent::iterateItems($event); } if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = erFAIL; return; } $object =& $event->getObject( Array('skip_autoload' => true) ); $ids = $this->StoreSelectedIDs($event); if ($ids) { foreach ($ids as $id) { $object->Load($id); switch ($event->Name) { case 'OnMassApprove': $ret = $object->ApproveChanges(); break; case 'OnMassDecline': $ret = $object->DeclineChanges(); break; } if (!$ret) { $event->status = erFAIL; $event->redirect = false; break; } } } $this->clearSelectedIDs($event); } /** * Deletes items & preserves clean env * * @param kEvent $event */ function OnDelete(&$event) { parent::OnDelete($event); if ($event->status == erSUCCESS && !$this->Application->isAdmin) { $event->SetRedirectParam('pass', 'm'); $event->SetRedirectParam('m_cat_id', 0); } } /** * Checks, that currently loaded item is allowed for viewing (non permission-based) * * @param kEvent $event * @return bool */ function checkItemStatus(&$event) { $object =& $event->getObject(); if (!$object->isLoaded()) { $this->_errorNotFound($event); return true; } $status = $object->GetDBField('Status'); $user_id = $this->Application->RecallVar('user_id'); $owner_field = $this->getOwnerField($event->Prefix); if (($status == STATUS_PENDING_EDITING || $status == STATUS_PENDING) && ($object->GetDBField($owner_field) == $user_id)) { return true; } return $status == STATUS_ACTIVE; } /** * Set's correct sorting for list * based on data provided with event * * @param kEvent $event * @access private * @see OnListBuild */ function SetSorting(&$event) { if (!$this->Application->isAdmin) { $event->setEventParam('same_special', true); } parent::SetSorting($event); } /** * Returns current per-page setting for list * * @param kEvent $event * @return int */ function getPerPage(&$event) { if (!$this->Application->isAdmin) { $event->setEventParam('same_special', true); } return parent::getPerPage($event); } function getOwnerField($prefix) { $owner_field = $this->Application->getUnitOption($prefix, 'OwnerField'); if (!$owner_field) { $owner_field = 'CreatedById'; } return $owner_field; } /** * Creates virtual image fields for item * * @param kEvent $event */ function OnAfterConfigRead(&$event) { parent::OnAfterConfigRead($event); if (defined('IS_INSTALL') && IS_INSTALL) { return ; } $file_helper =& $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ $file_helper->createItemFiles($event->Prefix, true); // create image fields $file_helper->createItemFiles($event->Prefix, false); // create file fields // add EditorsPick to ForcedSorting if needed $config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping'); if (array_key_exists('ForceEditorPick', $config_mapping) && $this->Application->ConfigValue($config_mapping['ForceEditorPick'])) { $list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings'); $new_forced_sorting = Array ('EditorsPick' => 'DESC'); if (array_key_exists('ForcedSorting', $list_sortings[''])) { foreach ($list_sortings['']['ForcedSorting'] as $sort_field => $sort_order) { $new_forced_sorting[$sort_field] = $sort_order; } } $list_sortings['']['ForcedSorting'] = $new_forced_sorting; $this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings); } // add grids for advanced view (with primary category column) $grids = $this->Application->getUnitOption($this->Prefix, 'Grids'); $process_grids = Array ('Default', 'Radio'); foreach ($process_grids as $process_grid) { $grid_data = $grids[$process_grid]; $grid_data['Fields']['CachedNavbar'] = Array ('title' => 'la_col_Path', 'data_block' => 'grid_primary_category_td', 'filter_block' => 'grid_like_filter'); $grids[$process_grid . 'ShowAll'] = $grid_data; } $this->Application->setUnitOption($this->Prefix, 'Grids', $grids); // add options for CategoryId field (quick way to select item's primary category) $category_helper =& $this->Application->recallObject('CategoryHelper'); /* @var $category_helper CategoryHelper */ $virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields'); $virtual_fields['CategoryId']['default'] = (int)$this->Application->GetVar('m_cat_id'); $virtual_fields['CategoryId']['options'] = $category_helper->getStructureTreeAsOptions(); $this->Application->setUnitOption($event->Prefix, 'VirtualFields', $virtual_fields); } /** * Returns file contents associated with item * * @param kEvent $event */ function OnDownloadFile(&$event) { $object =& $event->getObject(); /* @var $object kDBItem */ $event->status = erSTOP; $field = $this->Application->GetVar('field'); if (!preg_match('/^File([\d]+)/', $field)) { return ; } $file_helper =& $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ $filename = $object->GetField($field, 'full_path'); $file_helper->DownloadFile($filename); } /** * Saves user's vote * * @param kEvent $event */ function OnMakeVote(&$event) { $event->status = erSTOP; if ($this->Application->GetVar('ajax') != 'yes') { // this is supposed to call from AJAX only return ; } $rating_helper =& $this->Application->recallObject('RatingHelper'); /* @var $rating_helper RatingHelper */ $object =& $event->getObject( Array ('skip_autoload' => true) ); /* @var $object kDBItem */ $object->Load( $this->Application->GetVar('id') ); echo $rating_helper->makeVote($object); } /** * [HOOK] Allows to add cloned subitem to given prefix * * @param kEvent $event */ function OnCloneSubItem(&$event) { parent::OnCloneSubItem($event); if ($event->MasterEvent->Prefix == 'fav') { $clones = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'Clones'); $subitem_prefix = $event->Prefix . '-' . $event->MasterEvent->Prefix; $clones[$subitem_prefix]['ParentTableKey'] = 'ResourceId'; $clones[$subitem_prefix]['ForeignKey'] = 'ResourceId'; $this->Application->setUnitOption($event->MasterEvent->Prefix, 'Clones', $clones); } } } \ No newline at end of file Index: branches/5.1.x/core/kernel/application.php =================================================================== --- branches/5.1.x/core/kernel/application.php (revision 13986) +++ branches/5.1.x/core/kernel/application.php (revision 13987) @@ -1,3235 +1,3249 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ /** * Basic class for Kernel4-based Application * * This class is a Facade for any other class which needs to deal with Kernel4 framework.<br> * The class incapsulates the main run-cycle of the script, provide access to all other objects in the framework.<br> * <br> * The class is a singleton, which means that there could be only one instance of kApplication in the script.<br> * This could be guranteed by NOT calling the class constuctor directly, but rather calling kApplication::Instance() method, * which returns an instance of the application. The method gurantees that it will return exactly the same instance for any call.<br> * See singleton pattern by GOF. * @package kernel4 */ defined('FULL_PATH') or die('restricted access!'); class kApplication { /** * Is true, when Init method was called already, prevents double initialization * * @var bool */ var $InitDone = false; /** * Holds internal NParser object * @access private * @var NParser */ var $Parser; /** * Holds parser output buffer * @access private * @var string */ var $HTML; /** * Prevents request from beeing proceeded twice in case if application init is called mere then one time * * @var bool * @todo This is not good anyway (by Alex) */ var $RequestProcessed = false; /** * The main Factory used to create * almost any class of kernel and * modules * * @access private * @var kFactory */ var $Factory; /** * All ConfigurationValues table content (hash) here * * @var Array * @access private */ var $ConfigHash = Array(); /** * Ids of config variables used in current run (for caching) * * @var Array * @access private */ var $ConfigCacheIds = array(); /** * Template names, that will be used instead of regular templates * * @var Array */ var $ReplacementTemplates = Array (); /** * Mod-Rewrite listeners used during url building and parsing * * @var Array */ var $RewriteListeners = Array (); /** * Reference to debugger * * @var Debugger */ var $Debugger = null; /** * Holds all phrases used * in code and template * * @var PhrasesCache */ var $Phrases; /** * Modules table content, key - module name * * @var Array */ var $ModuleInfo = Array(); /** * Holds DBConnection * * @var kDBConnection */ var $Conn = null; /** * Maintains list of user-defined error handlers * * @var Array */ var $errorHandlers = Array(); // performance needs: /** * Holds a refererence to httpquery * * @var kHttpQuery */ var $HttpQuery = null; /** * Holds a reference to UnitConfigReader * * @var kUnitConfigReader */ var $UnitConfigReader = null; /** * Holds a reference to Session * * @var Session */ var $Session = null; /** * Holds a ref to kEventManager * * @var kEventManager */ var $EventManager = null; /** * Ref to itself, needed because everybody used to write $this->Application, even inside kApplication * * @var kApplication */ var $Application = null; /** * Ref for TemplatesChache * * @var TemplatesCache */ var $TemplatesCache = null; /** * Physical template name mapping to their template names based on structure * * @var Array */ var $structureTemplateMapping = Array (); var $CompilationCache = array(); //used when compiling templates var $CachedProcessors = array(); //used when running compiled templates var $LambdaElements = 1; // for autonumbering unnamed RenderElements [any better place for this prop? KT] /** * Holds current NParser tag while parsing, can be used in error messages to display template file and line * * @var _BlockTag */ var $CurrentNTag = null; /** * Object of memory caching class * * @var kCache */ var $memoryCache = null; /** * Tells, that administrator has authentificated in administrative console * Should be used to manipulate data change OR data restrictioning! * * @var bool */ var $isAdminUser = false; /** * Tells, that admin version of "index.php" was used, nothing more! * Should be used to manipulate data display! * * @var bool */ var $isAdmin = false; /** * Instance of site domain object * * @var kDBItem */ var $siteDomain = null; /** * Returns kApplication instance anywhere in the script. * * This method should be used to get single kApplication object instance anywhere in the * Kernel-based application. The method is guranteed to return the SAME instance of kApplication. * Anywhere in the script you could write: * <code> * $application =& kApplication::Instance(); * </code> * or in an object: * <code> * $this->Application =& kApplication::Instance(); * </code> * to get the instance of kApplication. Note that we call the Instance method as STATIC - directly from the class. * To use descendand of standard kApplication class in your project you would need to define APPLICATION_CLASS constant * BEFORE calling kApplication::Instance() for the first time. If APPLICATION_CLASS is not defined the method would * create and return default KernelApplication instance. * @static * @access public * @return kApplication */ function &Instance() { static $instance = false; if (!$instance) { $class = defined('APPLICATION_CLASS') ? APPLICATION_CLASS : 'kApplication'; $instance = new $class(); $instance->Application =& $instance; } return $instance; } /** * Initializes the Application * * @access public * @see kHTTPQuery * @see Session * @see TemplatesCache * @return bool Was Init actually made now or before */ function Init() { if($this->InitDone) { return false; } $this->isAdmin = constOn('ADMIN'); if (!constOn('SKIP_OUT_COMPRESSION')) { ob_start(); // collect any output from method (other then tags) into buffer } if (defined('DEBUG_MODE') && $this->isDebugMode() && constOn('DBG_PROFILE_MEMORY')) { $this->Debugger->appendMemoryUsage('Application before Init:'); } if (!$this->isDebugMode() && !constOn('DBG_ZEND_PRESENT')) { error_reporting(0); ini_set('display_errors', 0); } if (!constOn('DBG_ZEND_PRESENT')) { $error_handler = set_error_handler( Array (&$this, 'handleError') ); if ($error_handler) { // wrap around previous error handler, if any was set $this->errorHandlers[] = $error_handler; } } $this->Conn = new kDBConnection(SQL_TYPE, Array(&$this, 'handleSQLError') ); $this->Conn->debugMode = $this->isDebugMode(); $this->Conn->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB); $this->Factory = new kFactory(); $this->registerDefaultClasses(); $this->Phrases = new PhrasesCache(); $this->memoryCache =& $this->Factory->makeClass('Cache'); $this->EventManager =& $this->Factory->makeClass('EventManager'); $this->Factory->Storage['EventManager'] =& $this->EventManager; $this->RegisterDefaultBuildEvents(); $this->SetDefaultConstants(); if (defined('DEBUG_MODE') && $this->isDebugMode()) { $this->Debugger->appendTimestamp('Before UnitConfigReader'); } $this->UnitConfigReader =& $this->recallObject('kUnitConfigReader'); $this->UnitConfigReader->scanModules(MODULES_PATH); $this->registerModuleConstants(); if (defined('DEBUG_MODE') && $this->isDebugMode()) { $this->Debugger->appendTimestamp('After UnitConfigReader'); } define('MOD_REWRITE', $this->ConfigValue('UseModRewrite') && !$this->isAdmin ? 1 : 0); $this->HttpQuery =& $this->recallObject('HTTPQuery'); if (defined('DEBUG_MODE') && $this->isDebugMode()) { $this->Debugger->appendTimestamp('Processed HTTPQuery initial'); } $this->Session =& $this->recallObject('Session'); if (defined('DEBUG_MODE') && $this->isDebugMode()) { $this->Debugger->appendTimestamp('Processed Session'); } if (!$this->RecallVar('UserGroups')) { $user_groups = trim($this->Session->GetField('GroupList'), ','); if (!$user_groups) { $user_groups = $this->ConfigValue('User_GuestGroup'); } $this->Session->SetField('GroupList', $user_groups); $this->StoreVar('UserGroups', $user_groups, true); // true for optional } $this->LoadStructureTemplateMapping(); $this->HttpQuery->AfterInit(); $this->Session->ValidateExpired(); if (defined('DEBUG_MODE') && $this->isDebugMode()) { $this->Debugger->appendTimestamp('Processed HTTPQuery AfterInit'); } $this->LoadCache(); $this->InitConfig(); $site_timezone = $this->ConfigValue('Config_Site_Time'); if ($site_timezone) { putenv('TZ=' . $site_timezone); } if (defined('DEBUG_MODE') && $this->isDebugMode()) { $this->Debugger->appendTimestamp('Loaded cache and phrases'); } $this->ValidateLogin(); // must be called before AfterConfigRead, because current user should be available there $this->UnitConfigReader->AfterConfigRead(); if (defined('DEBUG_MODE') && $this->isDebugMode()) { $this->Debugger->appendTimestamp('Processed AfterConfigRead'); } if ($this->GetVar('m_cat_id') === false) { $this->SetVar('m_cat_id', 0); } if (!$this->RecallVar('curr_iso')) { $this->StoreVar('curr_iso', $this->GetPrimaryCurrency(), true); // true for optional } $visit_id = $this->RecallVar('visit_id'); if ($visit_id !== false) { $this->SetVar('visits_id', $visit_id); } $language =& $this->recallObject( 'lang.current', null, Array('live_table' => true) ); if (preg_match('/utf-8/', $language->GetDBField('Charset'))) { setlocale(LC_ALL, 'en_US.UTF-8'); mb_internal_encoding('UTF-8'); } if (defined('DEBUG_MODE') && $this->isDebugMode()) { $this->Debugger->profileFinish('kernel4_startup'); } $this->InitDone = true; $this->HandleEvent( new kEvent('adm:OnStartup') ); return true; } /** * Returns module information. Searches module by requested field * * @param string $field * @param mixed $value * @param string field value to returns, if not specified, then return all fields * @param string field to return * @return Array */ function findModule($field, $value, $return_field = null) { $found = false; foreach ($this->ModuleInfo as $module_name => $module_info) { if (strtolower($module_info[$field]) == strtolower($value)) { $found = true; break; } } if ($found) { return isset($return_field) ? $module_info[$return_field] : $module_info; } return false; } function refreshModuleInfo() { if (defined('IS_INSTALL') && IS_INSTALL && !$this->TableFound('Modules')) { $this->registerModuleConstants(); return false; } $modules_helper =& $this->recallObject('ModulesHelper'); /* @var $modules_helper kModulesHelper */ $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'Modules WHERE Loaded = 1 ORDER BY LoadOrder'; $this->ModuleInfo = $this->Conn->Query($sql, 'Name'); $sql = 'SELECT * FROM '.TABLE_PREFIX.'Modules WHERE '.$modules_helper->getWhereClause().' ORDER BY LoadOrder'; $this->ModuleInfo = $this->Conn->Query($sql, 'Name'); $this->registerModuleConstants(); } /** * Checks if passed language id if valid and sets it to primary otherwise * */ function VerifyLanguageId() { $language_id = $this->GetVar('m_lang'); if (!$language_id) { $language_id = 'default'; } $this->SetVar('lang.current_id', $language_id); $this->SetVar('m_lang', $language_id); $lang_mode = $this->GetVar('lang_mode'); $this->SetVar('lang_mode', ''); $lang =& $this->recallObject('lang.current'); /* @var $lang kDBItem */ if (!$lang->isLoaded() || (!$this->isAdmin && !$lang->GetDBField('Enabled'))) { if (!defined('IS_INSTALL')) { $this->ApplicationDie('Unknown or disabled language'); } } $this->SetVar('lang_mode',$lang_mode); } /** * Checks if passed theme id if valid and sets it to primary otherwise * */ function VerifyThemeId() { if ($this->isAdmin) { safeDefine('THEMES_PATH', '/core/admin_templates'); return; } $path = $this->GetFrontThemePath(); if ($path === false) { $this->ApplicationDie('No Primary Theme Selected or Current Theme is Unknown or Disabled'); } safeDefine('THEMES_PATH', $path); /*$theme_id = $this->GetVar('m_theme'); if (!$theme_id) { $theme_id = $this->GetDefaultThemeId(); if (!$theme_id) { if (!defined('IS_INSTALL')) $this->ApplicationDie('No Primary Theme Selected'); } } $this->SetVar('m_theme', $theme_id); $this->SetVar('theme.current_id', $theme_id ); // KOSTJA: this is to fool theme' getPassedId $theme =& $this->recallObject('theme.current'); if (!$theme->IsLoaded() || !$theme->GetDBField('Enabled')) { if (!defined('IS_INSTALL')) $this->ApplicationDie('Unknown or disabled theme'); } safeDefine('THEMES_PATH', '/themes/'.$theme->GetDBField('Name'));*/ } function GetFrontThemePath($force=0) { static $path=null; if (!$force && isset($path)) return $path; $theme_id = $this->GetVar('m_theme'); if (!$theme_id) { // $theme_id = $this->GetDefaultThemeId(1); //1 to force front-end mode! $theme_id = 'default'; } $this->SetVar('m_theme', $theme_id); $this->SetVar('theme.current_id', $theme_id ); // KOSTJA: this is to fool theme' getPassedId $theme =& $this->recallObject('theme.current'); if (!$theme->IsLoaded() || !$theme->GetDBField('Enabled')) { return false; } $path = '/themes/'.$theme->GetDBField('Name'); return $path; } function GetDefaultLanguageId($init = false) { $cache_key = 'primary_language_info[%LangSerial%]'; $language_info = $this->getCache($cache_key); if ($language_info === false) { // cache primary language info first $table = $this->getUnitOption('lang', 'TableName'); $id_field = $this->getUnitOption('lang', 'IDField'); $this->Conn->nextQueryCachable = true; $sql = 'SELECT ' . $id_field . ', IF(AdminInterfaceLang, "Admin", "Front") AS LanguageKey FROM ' . $table . ' WHERE (AdminInterfaceLang = 1 OR PrimaryLang = 1) AND (Enabled = 1)'; $language_info = $this->Conn->GetCol($sql, 'LanguageKey'); if ($language_info !== false) { $this->setCache($cache_key, $language_info); } } $language_key = ($this->isAdmin && $init) || count($language_info) == 1 ? 'Admin' : 'Front'; if (array_key_exists($language_key, $language_info) && $language_info[$language_key] > 0) { // get from cache return $language_info[$language_key]; } $language_id = $language_info && array_key_exists($language_key, $language_info) ? $language_info[$language_key] : false; if (!$language_id && defined('IS_INSTALL') && IS_INSTALL) { $language_id = 1; } return $language_id; } function GetDefaultThemeId($force_front=0) { static $theme_id = 0; if ($theme_id > 0) { return $theme_id; } if (constOn('DBG_FORCE_THEME')) { $theme_id = DBG_FORCE_THEME; } elseif (!$force_front && $this->isAdmin) { $theme_id = 999; } else { $cache_key = 'primary_theme[%ThemeSerial%]'; $theme_id = $this->getCache($cache_key); if ($theme_id === false) { $this->Conn->nextQueryCachable = true; $sql = 'SELECT ' . $this->getUnitOption('theme', 'IDField') . ' FROM ' . $this->getUnitOption('theme', 'TableName') . ' WHERE (PrimaryTheme = 1) AND (Enabled = 1)'; $theme_id = $this->Conn->GetOne($sql); if ($theme_id !== false) { $this->setCache($cache_key, $theme_id); } } } return $theme_id; } /** * Returns site primary currency ISO code * * @return string */ function GetPrimaryCurrency() { $cache_key = 'primary_currency[%CurrSerial%][%SiteDomainSerial%]:' . $this->siteDomainField('DomainId'); $currency_iso = $this->getCache($cache_key); if ($currency_iso === false) { if ($this->isModuleEnabled('In-Commerce')) { $this->Conn->nextQueryCachable = true; $currency_id = $this->siteDomainField('PrimaryCurrencyId'); $sql = 'SELECT ISO FROM ' . $this->getUnitOption('curr', 'TableName') . ' WHERE ' . ($currency_id > 0 ? 'CurrencyId = ' . $currency_id : 'IsPrimary = 1'); $currency_iso = $this->Conn->GetOne($sql); } else { $currency_iso = 'USD'; } $this->setCache($cache_key, $currency_iso); } return $currency_iso; } /** * Returns site domain field. When none of site domains are found false is returned. * * @param string $field * @param bool $formatted * @param string $format */ function siteDomainField($field, $formatted = false, $format = null) { if ($this->isAdmin) { // don't apply any filtering in administrative console return false; } if (!$this->siteDomain) { $this->siteDomain =& $this->recallObject('site-domain.current'); /* @var $site_domain kDBItem */ } if ($this->siteDomain->isLoaded()) { return $formatted ? $this->siteDomain->GetField($field, $format) : $this->siteDomain->GetDBField($field); } return false; } /** * Registers default classes such as ItemController, GridController and LoginController * * Called automatically while initializing Application * @access private * @return void */ function RegisterDefaultClasses() { $this->registerClass('kTempTablesHandler', KERNEL_PATH . '/utility/temp_handler.php'); $this->registerClass('kEventManager', KERNEL_PATH . '/event_manager.php', 'EventManager'); $this->registerClass('kUnitConfigReader', KERNEL_PATH . '/utility/unit_config_reader.php'); $this->registerClass('kArray', KERNEL_PATH . '/utility/params.php'); $this->registerClass('Params', KERNEL_PATH . '/utility/params.php'); $this->registerClass('Params', KERNEL_PATH . '/utility/params.php', 'kActions'); $this->registerClass('kCache', KERNEL_PATH . '/utility/cache.php', 'Cache', 'Params'); $this->registerClass('kHTTPQuery', KERNEL_PATH . '/utility/http_query.php', 'HTTPQuery', 'Params'); $this->registerClass('kHelper', KERNEL_PATH . '/kbase.php'); $this->registerClass('kMultipleFilter', KERNEL_PATH . '/utility/filters.php'); $this->registerClass('Session', KERNEL_PATH . '/session/session.php'); $this->registerClass('SessionStorage', KERNEL_PATH . '/session/session.php'); $this->registerClass('InpSession', KERNEL_PATH . '/session/inp_session.php', 'Session'); $this->registerClass('InpSessionStorage', KERNEL_PATH . '/session/inp_session.php', 'SessionStorage'); $this->registerClass('kTagProcessor', KERNEL_PATH . '/processors/tag_processor.php'); $this->registerClass('kMainTagProcessor', KERNEL_PATH . '/processors/main_processor.php','m_TagProcessor', 'kTagProcessor'); $this->registerClass('kDBList', KERNEL_PATH . '/db/dblist.php'); $this->registerClass('kDBItem', KERNEL_PATH . '/db/dbitem.php'); $this->registerClass('kDBEventHandler', KERNEL_PATH . '/db/db_event_handler.php'); $this->registerClass('kDBTagProcessor', KERNEL_PATH . '/db/db_tag_processor.php', null, 'kTagProcessor'); $this->registerClass('kCatDBItem', KERNEL_PATH . '/db/cat_dbitem.php'); $this->registerClass('kCatDBList', KERNEL_PATH . '/db/cat_dblist.php'); $this->registerClass('kCatDBEventHandler', KERNEL_PATH . '/db/cat_event_handler.php'); $this->registerClass('kCatDBTagProcessor', KERNEL_PATH . '/db/cat_tag_processor.php'); $this->registerClass('NParser', KERNEL_PATH . '/nparser/nparser.php'); $this->registerClass('TemplatesCache', KERNEL_PATH . '/nparser/template_cache.php', null, Array ('kHelper', 'kDBTagProcessor')); $this->registerClass('kEmailSendingHelper', KERNEL_PATH . '/utility/email_send.php', 'EmailSender', 'kHelper'); $this->registerClass('kSocket', KERNEL_PATH . '/utility/socket.php', 'Socket'); if (file_exists(MODULES_PATH . '/in-commerce/units/currencies/currency_rates.php')) { $this->registerClass('kCurrencyRates', MODULES_PATH . '/in-commerce/units/currencies/currency_rates.php'); } // do not move to config - this helper is used before configs are read $this->registerClass('kModulesHelper', KERNEL_PATH . '/../units/helpers/modules_helper.php', 'ModulesHelper'); } function RegisterDefaultBuildEvents() { $event_manager =& $this->recallObject('EventManager'); $event_manager->registerBuildEvent('kTempTablesHandler', 'OnTempHandlerBuild'); } /** * Returns cached category informaton by given cache name. All given category * information is recached, when at least one of 4 caches is missing. * * @param int $category_id * @param string $name cache name = {filenames, category_designs, category_tree} * @return string */ function getCategoryCache($category_id, $name) { $serial_name = '[%CIDSerial:' . $category_id . '%]'; $cache_key = $name . $serial_name; $ret = $this->getCache($cache_key); if ($ret === false) { if (!$category_id) { // don't query database for "Home" category (ID = 0), because it doesn't exist in database return false; } // this allows to save 2 sql queries for each category $this->Conn->nextQueryCachable = true; $sql = 'SELECT NamedParentPath, CachedTemplate, TreeLeft, TreeRight FROM ' . TABLE_PREFIX . 'Category WHERE CategoryId = ' . (int)$category_id; $category_data = $this->Conn->GetRow($sql); if ($category_data !== false) { // only direct links to category pages work (symlinks, container pages and so on won't work) $this->setCache('filenames' . $serial_name, $category_data['NamedParentPath']); $this->setCache('category_designs' . $serial_name, ltrim($category_data['CachedTemplate'], '/')); $this->setCache('category_tree' . $serial_name, $category_data['TreeLeft'] . ';' . $category_data['TreeRight']); } } return $this->getCache($cache_key); } /** * Returns item's filename that corresponds id passed. If possible, then get it from cache * * @param string $prefix * @param int $id * @param int $category_id * @return string */ function getFilename($prefix, $id, $category_id = null) { if ($prefix == 'c') { trigger_error('Method "<strong>' . __FUNCTION__ . '</strong>" no longer work with "<strong>c</strong>" prefix. Please use "<strong>getCategoryCache</strong>" method instead.', E_USER_ERROR); return false; } $category_id = isset($category_id) ? $category_id : $this->GetVar('m_cat_id'); $cache_key = 'filenames[%' . $this->incrementCacheSerial($prefix, $id, false) . '%]:' . (int)$category_id; $filename = $this->getCache($cache_key); if ($filename === false) { $this->Conn->nextQueryCachable = true; $sql = 'SELECT ResourceId FROM ' . $this->getUnitOption($prefix, 'TableName') . ' WHERE ' . $this->getUnitOption($prefix, 'IDField') . ' = ' . $this->Conn->qstr($id); $resource_id = $this->Conn->GetOne($sql); $this->Conn->nextQueryCachable = true; $sql = 'SELECT Filename FROM ' . TABLE_PREFIX . 'CategoryItems WHERE (ItemResourceId = ' . $resource_id . ') AND (CategoryId = ' . (int)$category_id . ')'; $filename = $this->Conn->GetOne($sql); if ($filename !== false) { $this->setCache($cache_key, $filename); } } return $filename; } /** * Returns caching type (none, memory, temporary) * * @return int */ function isCachingType($caching_type) { return $this->memoryCache->getCachingType() == $caching_type; } /** * Increments serial based on prefix and it's ID (optional) * * @param string $prefix * @param int $id ID (value of IDField) or ForeignKeyField:ID * @param bool $increment */ function incrementCacheSerial($prefix, $id = null, $increment = true) { $pascal_case_prefix = implode('', array_map('ucfirst', explode('-', $prefix))); $serial_name = $pascal_case_prefix . (isset($id) ? 'IDSerial:' . $id : 'Serial'); if ($increment) { if (defined('DEBUG_MODE') && DEBUG_MODE && $this->isDebugMode()) { $this->Application->Debugger->appendHTML('Incrementing serial: <strong>' . $serial_name . '</strong>.'); } $this->setCache($serial_name, (int)$this->getCache($serial_name) + 1); if (!defined('IS_INSTALL') || !IS_INSTALL) { // delete cached mod-rewrite urls related to given prefix and id $delete_clause = isset($id) ? $prefix . ':' . $id : $prefix; $sql = 'DELETE FROM ' . TABLE_PREFIX . 'CachedUrls WHERE Prefixes LIKE ' . $this->Conn->qstr('%|' . $delete_clause . '|%'); $this->Conn->Query($sql); } } return $serial_name; } /** * Adds new value to cache $cache_name and identified by key $key * * @param int $key key name to add to cache * @param mixed $value value of chached record * @param int $expiration when value expires (0 - doesn't expire) */ function setCache($key, $value, $expiration = 0) { return $this->memoryCache->setCache($key, $value, $expiration); } /** * Sets value to database cache * * @param string $name * @param mixed $value * @param int $expiration */ function setDBCache($name, &$value, $expiration = false) { if ((int)$expiration <= 0) { $expiration = -1; } $fields_hash = Array ( 'VarName' => $name, 'Data' => &$value, 'Cached' => adodb_mktime(), 'LifeTime' => (int)$expiration, ); $this->Conn->nextQueryCachable = true; $this->Conn->doInsert($fields_hash, TABLE_PREFIX . 'Cache', 'REPLACE'); } /** * Returns cached $key value from cache named $cache_name * * @param int $key key name from cache * @param bool $store_locally store data locally after retrieved * @return mixed */ function getCache($key, $store_locally = true) { return $this->memoryCache->getCache($key, $store_locally); } /** * Returns value from database cache * * @param string $name key name * @return mixed */ function getDBCache($name) { $this->Conn->nextQueryCachable = true; $sql = 'SELECT Data, Cached, LifeTime FROM ' . TABLE_PREFIX . 'Cache WHERE VarName = ' . $this->Conn->qstr($name); $data = $this->Conn->GetRow($sql); if ($data) { $lifetime = (int)$data['LifeTime']; // in seconds if (($lifetime > 0) && ($data['Cached'] + $lifetime < adodb_mktime())) { // delete expired $this->Conn->nextQueryCachable = true; $sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache WHERE VarName = ' . $this->Conn->qstr($name); $this->Conn->Query($sql); return false; } return $data['Data']; } return false; } /** * Deletes key from cache * * @param string $key */ function deleteCache($key) { $this->memoryCache->delete($key); } /** * Deletes key from database cache * * @param string $name */ function deleteDBCache($name) { $sql = 'DELETE FROM ' . TABLE_PREFIX . 'Cache WHERE VarName = ' . $this->Conn->qstr($name); $this->Conn->Query($sql); } /** * Defines default constants if it's not defined before - in config.php * * @access private */ function SetDefaultConstants() // it's defined in startup.php - can be removed?? { safeDefine('SERVER_NAME', $_SERVER['HTTP_HOST']); } /** * Registers each module specific constants if any found * */ function registerModuleConstants() { if (file_exists(KERNEL_PATH.'/constants.php')) { k4_include_once(KERNEL_PATH.'/constants.php'); } if (!$this->ModuleInfo) { return false; } foreach ($this->ModuleInfo as $module_name => $module_info) { $contants_file = FULL_PATH . '/' . $module_info['Path'] . 'constants.php'; if (file_exists($contants_file)) { k4_include_once($contants_file); } } return true; } function ProcessRequest() { $event_manager =& $this->recallObject('EventManager'); /* @var $event_manager kEventManager */ if (defined('DEBUG_MODE') && $this->isDebugMode() && constOn('DBG_SHOW_HTTPQUERY')) { $this->Debugger->appendHTML('HTTPQuery:'); $this->Debugger->dumpVars($this->HttpQuery->_Params); } $event_manager->ProcessRequest(); $event_manager->RunRegularEvents(reBEFORE); $this->RequestProcessed = true; } /** * Actually runs the parser against current template and stores parsing result * * This method gets t variable passed to the script, loads the template given in t variable and * parses it. The result is store in {@link $this->HTML} property. * @access public * @return void */ function Run() { if (defined('DEBUG_MODE') && $this->isDebugMode() && constOn('DBG_PROFILE_MEMORY')) { $this->Debugger->appendMemoryUsage('Application before Run:'); } if ($this->isAdminUser) { // for permission checking in events & templates $this->LinkVar('module'); // for common configuration templates $this->LinkVar('module_key'); // for common search templates $this->LinkVar('section'); // for common configuration templates if ($this->GetVar('m_opener') == 'p') { $this->LinkVar('main_prefix'); // window prefix, that opened selector $this->LinkVar('dst_field'); // field to set value choosed in selector } if ($this->GetVar('ajax') == 'yes' && !$this->GetVar('debug_ajax')) { // hide debug output from ajax requests automatically define('DBG_SKIP_REPORTING', 1); } } elseif ($this->GetVar('admin')) { // viewing front-end through admin's frame $admin_session =& $this->Application->recallObject('Session.admin'); $user = (int)$admin_session->RecallVar('user_id'); // in case, when no valid admin session found $perm_helper =& $this->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ - if ($perm_helper->CheckUserPermission($user, 'CATEGORY.MODIFY', 0, $this->ModuleInfo['Core']['RootCat'])) { + if ($perm_helper->CheckUserPermission($user, 'CATEGORY.MODIFY', 0, $this->getBaseCategory())) { // user can edit cms blocks $editing_mode = $this->GetVar('editing_mode'); define('EDITING_MODE', $editing_mode ? $editing_mode : EDITING_MODE_BROWSE); } } safeDefine('EDITING_MODE', ''); // user can't edit anything $this->Phrases->setPhraseEditing(); if (!$this->RequestProcessed) $this->ProcessRequest(); $this->InitParser(); $t = $this->GetVar('t'); if (!$this->TemplatesCache->TemplateExists($t) && !$this->isAdmin) { $cms_handler =& $this->recallObject('st_EventHandler'); /* @var $cms_handler CategoriesEventHandler */ $t = ltrim($cms_handler->GetDesignTemplate(), '/'); if (defined('DEBUG_MODE') && $this->isDebugMode()) { $this->Debugger->appendHTML('<strong>Design Template</strong>: ' . $t . '; <strong>CategoryID</strong>: ' . $this->GetVar('m_cat_id')); } } /*else { $cms_handler->SetCatByTemplate(); }*/ if (defined('DEBUG_MODE') && $this->isDebugMode() && constOn('DBG_PROFILE_MEMORY')) { $this->Debugger->appendMemoryUsage('Application before Parsing:'); } $this->HTML = $this->Parser->Run($t); if (defined('DEBUG_MODE') && $this->isDebugMode() && constOn('DBG_PROFILE_MEMORY')) { $this->Debugger->appendMemoryUsage('Application after Parsing:'); } } function InitParser($theme_name = false) { if( !is_object($this->Parser) ) { $this->Parser =& $this->recallObject('NParser'); $this->TemplatesCache =& $this->recallObject('TemplatesCache'); } $this->TemplatesCache->forceThemeName = $theme_name; } /** * Send the parser results to browser * * Actually send everything stored in {@link $this->HTML}, to the browser by echoing it. * @access public * @return void */ function Done() { $this->HandleEvent( new kEvent('adm:OnBeforeShutdown') ); $debug_mode = defined('DEBUG_MODE') && $this->isDebugMode(); if ($debug_mode && constOn('DBG_PROFILE_MEMORY')) { $this->Debugger->appendMemoryUsage('Application before Done:'); } if ($debug_mode) { $this->EventManager->RunRegularEvents(reAFTER); $this->Session->SaveData(); if (constOn('DBG_CACHE')) { $this->memoryCache->printStatistics(); } $this->HTML = ob_get_clean() . $this->HTML . $this->Debugger->printReport(true); } else { // send "Set-Cookie" header before any output is made $this->Session->SetSession(); $this->HTML = ob_get_clean() . $this->HTML; } if ($this->UseOutputCompression()) { $compression_level = $this->ConfigValue('OutputCompressionLevel'); if ($compression_level < 0 || $compression_level > 9) { $compression_level = 7; } header('Content-Encoding: gzip'); echo gzencode($this->HTML, $compression_level); } else { echo $this->HTML; } $this->UpdateCache(); flush(); if (!$debug_mode) { $this->EventManager->RunRegularEvents(reAFTER); $this->Session->SaveData(); } if (defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !$this->isAdmin) { $this->_storeStatistics(); } } /** * Stores script execution statistics to database * */ function _storeStatistics() { global $start; $script_time = getmicrotime() - $start; $query_statistics = $this->Conn->getQueryStatistics(); // time & count $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'StatisticsCapture WHERE TemplateName = ' . $this->Conn->qstr( $this->GetVar('t') ); $data = $this->Conn->GetRow($sql); if ($data) { $this->_updateAverageStatistics($data, 'ScriptTime', $script_time); $this->_updateAverageStatistics($data, 'SqlTime', $query_statistics['time']); $this->_updateAverageStatistics($data, 'SqlCount', $query_statistics['count']); $data['Hits']++; $data['LastHit'] = adodb_mktime(); $this->Conn->doUpdate($data, TABLE_PREFIX . 'StatisticsCapture', 'StatisticsId = ' . $data['StatisticsId']); } else { $data['ScriptTimeMin'] = $data['ScriptTimeAvg'] = $data['ScriptTimeMax'] = $script_time; $data['SqlTimeMin'] = $data['SqlTimeAvg'] = $data['SqlTimeMax'] = $query_statistics['time']; $data['SqlCountMin'] = $data['SqlCountAvg'] = $data['SqlCountMax'] = $query_statistics['count']; $data['TemplateName'] = $this->GetVar('t'); $data['Hits'] = 1; $data['LastHit'] = adodb_mktime(); $this->Conn->doInsert($data, TABLE_PREFIX . 'StatisticsCapture'); } } /** * Calculates average time for statistics * * @param Array $data * @param string $field_prefix * @param float $current_value */ function _updateAverageStatistics(&$data, $field_prefix, $current_value) { $data[$field_prefix . 'Avg'] = (($data['Hits'] * $data[$field_prefix . 'Avg']) + $current_value) / ($data['Hits'] + 1); if ($current_value < $data[$field_prefix . 'Min']) { $data[$field_prefix . 'Min'] = $current_value; } if ($current_value > $data[$field_prefix . 'Max']) { $data[$field_prefix . 'Max'] = $current_value; } } function logSlowQuery($slow_sql, $time) { $query_crc = crc32($slow_sql); $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'SlowSqlCapture WHERE QueryCrc = ' . $query_crc; $data = $this->Conn->Query($sql, null, true); if ($data) { $this->_updateAverageStatistics($data, 'Time', $time); $template_names = explode(',', $data['TemplateNames']); array_push($template_names, $this->GetVar('t')); $data['TemplateNames'] = implode(',', array_unique($template_names)); $data['Hits']++; $data['LastHit'] = adodb_mktime(); $this->Conn->doUpdate($data, TABLE_PREFIX . 'SlowSqlCapture', 'CaptureId = ' . $data['CaptureId']); } else { $data['TimeMin'] = $data['TimeAvg'] = $data['TimeMax'] = $time; $data['SqlQuery'] = $slow_sql; $data['QueryCrc'] = $query_crc; $data['TemplateNames'] = $this->GetVar('t'); $data['Hits'] = 1; $data['LastHit'] = adodb_mktime(); $this->Conn->doInsert($data, TABLE_PREFIX . 'SlowSqlCapture'); } } /** * Checks if output compression options is available * * @return string */ function UseOutputCompression() { if (constOn('IS_INSTALL') || constOn('DBG_ZEND_PRESENT') || constOn('SKIP_OUT_COMPRESSION')) return false; return $this->ConfigValue('UseOutputCompression') && function_exists('gzencode') && strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip'); } // Facade /** * Returns current session id (SID) * @access public * @return longint */ function GetSID() { $session =& $this->recallObject('Session'); return $session->GetID(); } function DestroySession() { $session =& $this->recallObject('Session'); $session->Destroy(); } /** * Returns variable passed to the script as GET/POST/COOKIE * * @access public * @param string $name Name of variable to retrieve * @param int $default default value returned in case if varible not present * @return mixed */ function GetVar($name, $default = false) { return isset($this->HttpQuery->_Params[$name]) ? $this->HttpQuery->_Params[$name] : $default; } /** * Returns ALL variables passed to the script as GET/POST/COOKIE * * @access public * @return array */ function GetVars() { return $this->HttpQuery->GetParams(); } /** * Set the variable 'as it was passed to the script through GET/POST/COOKIE' * * This could be useful to set the variable when you know that * other objects would relay on variable passed from GET/POST/COOKIE * or you could use SetVar() / GetVar() pairs to pass the values between different objects.<br> * * This method is formerly known as $this->Session->SetProperty. * @param string $var Variable name to set * @param mixed $val Variable value * @access public * @return void */ function SetVar($var,$val) { return $this->HttpQuery->Set($var, $val); } /** * Deletes kHTTPQuery variable * * @param string $var * @todo think about method name */ function DeleteVar($var) { return $this->HttpQuery->Remove($var); } /** * Deletes Session variable * * @param string $var */ function RemoveVar($var) { return $this->Session->RemoveVar($var); } function RemovePersistentVar($var) { return $this->Session->RemovePersistentVar($var); } /** * Restores Session variable to it's db version * * @param string $var */ function RestoreVar($var) { return $this->Session->RestoreVar($var); } /** * Returns session variable value * * Return value of $var variable stored in Session. An optional default value could be passed as second parameter. * * @see SimpleSession * @access public * @param string $var Variable name * @param mixed $default Default value to return if no $var variable found in session * @return mixed */ function RecallVar($var,$default=false) { return $this->Session->RecallVar($var,$default); } function RecallPersistentVar($var, $default = false) { return $this->Session->RecallPersistentVar($var, $default); } /** * Stores variable $val in session under name $var * * Use this method to store variable in session. Later this variable could be recalled. * @see RecallVar * @access public * @param string $var Variable name * @param mixed $val Variable value */ function StoreVar($var, $val, $optional = false) { $session =& $this->recallObject('Session'); $this->Session->StoreVar($var, $val, $optional); } function StorePersistentVar($var, $val, $optional = false) { $this->Session->StorePersistentVar($var, $val, $optional); } function StoreVarDefault($var, $val, $optional=false) { $session =& $this->recallObject('Session'); $this->Session->StoreVarDefault($var, $val, $optional); } /** * Links HTTP Query variable with session variable * * If variable $var is passed in HTTP Query it is stored in session for later use. If it's not passed it's recalled from session. * This method could be used for making sure that GetVar will return query or session value for given * variable, when query variable should overwrite session (and be stored there for later use).<br> * This could be used for passing item's ID into popup with multiple tab - * in popup script you just need to call LinkVar('id', 'current_id') before first use of GetVar('id'). * After that you can be sure that GetVar('id') will return passed id or id passed earlier and stored in session * @access public * @param string $var HTTP Query (GPC) variable name * @param mixed $ses_var Session variable name * @param mixed $default Default variable value */ function LinkVar($var, $ses_var = null, $default = '', $optional = false) { if (!isset($ses_var)) $ses_var = $var; if ($this->GetVar($var) !== false) { $this->StoreVar($ses_var, $this->GetVar($var), $optional); } else { $this->SetVar($var, $this->RecallVar($ses_var, $default)); } } /** * Returns variable from HTTP Query, or from session if not passed in HTTP Query * * The same as LinkVar, but also returns the variable value taken from HTTP Query if passed, or from session if not passed. * Returns the default value if variable does not exist in session and was not passed in HTTP Query * * @see LinkVar * @access public * @param string $var HTTP Query (GPC) variable name * @param mixed $ses_var Session variable name * @param mixed $default Default variable value * @return mixed */ function GetLinkedVar($var, $ses_var = null, $default = '') { $this->LinkVar($var, $ses_var, $default); return $this->GetVar($var); } function AddBlock($name, $tpl) { $this->cache[$name] = $tpl; } function ProcessParsedTag($prefix, $tag, $params) { $processor = $this->Parser->GetProcessor($prefix); return $processor->ProcessParsedTag($tag, $params, $prefix); } /** * Return ADODB Connection object * * Returns ADODB Connection object already connected to the project database, configurable in config.php * @access public * @return kDBConnection */ function &GetADODBConnection() { return $this->Conn; } /** * Allows to parse given block name or include template * * @param Array $params Parameters to pass to block. Reserved parameter "name" used to specify block name. * @param Array $pass_params Forces to pass current parser params to this block/template. Use with cauntion, because you can accidently pass "block_no_data" parameter. * @param bool $as_template * @return string */ function ParseBlock($params, $pass_params = 0, $as_template = false) { if (substr($params['name'], 0, 5) == 'html:') { return substr($params['name'], 6); } return $this->Parser->ParseBlock($params, $pass_params, $as_template); } /** * Checks, that we have given block defined * * @param string $name * @return bool */ function ParserBlockFound($name) { return $this->Parser->blockFound($name); } /** * Allows to include template with a given name and given parameters * * @param Array $params Parameters to pass to template. Reserved parameter "name" used to specify template name. * @return string */ function IncludeTemplate($params) { return $this->Parser->IncludeTemplate($params, isset($params['is_silent']) ? 1 : 0); } /** * Returns index file, that could be passed as parameter to method, as parameter to tag and as constant or not passed at all * * @param string $prefix * @param string $index_file * @param Array $params * @return string */ function getIndexFile($prefix, $index_file, &$params) { if (isset($params['index_file'])) { $index_file = $params['index_file']; unset($params['index_file']); return $index_file; } if (isset($index_file)) { return $index_file; } if (defined('INDEX_FILE')) { return INDEX_FILE; } $cut_prefix = trim(BASE_PATH, '/').'/'.trim($prefix, '/'); return trim(preg_replace('/'.preg_quote($cut_prefix, '/').'(.*)/', '\\1', $_SERVER['PHP_SELF']), '/'); } /** * Return href for template * * @access public * @param string $t Template path * @var string $prefix index.php prefix - could be blank, 'admin' */ function HREF($t, $prefix = '', $params = null, $index_file = null) { static $theme_id = null; if (!isset($theme_id)) { $theme_id = $this->GetVar('m_theme'); } if (!$t) { // when template not specified, use current $t = $this->GetVar('t'); } $t = preg_replace('/^Content\//i', '', $t); if (substr($t, -4) == '.tpl') { // cut template extension (deprecated link format) $t = substr($t, 0, strlen($t) - 4); } if (substr($t, 0, 3) == 'id:') { // link to structure page using it's id $params['m_cat_id'] = substr($t, 3); $t = $this->structureTemplateMapping[$t]; } if (array_key_exists('use_section', $params)) { $use_section = $params['use_section']; unset($params['use_section']); } if (isset($use_section) && $use_section && array_key_exists($t . ':' . $theme_id, $this->structureTemplateMapping)) { // structure template corresponding to given physical template $t = $this->structureTemplateMapping[$t . ':' . $theme_id]; unset($params['use_section']); } if (preg_match('/external:(.*)/', $t, $rets)) { // external url return $rets[1]; } if ($this->isAdmin && $prefix == '') $prefix = ADMIN_DIRECTORY; if ($this->isAdmin && $prefix == '_FRONT_END_') $prefix = ''; $index_file = $this->getIndexFile($prefix, $index_file, $params); if (isset($params['_auto_prefix_'])) { unset($params['_auto_prefix_']); // this is parser-related param, do not need to pass it here } $ssl = isset($params['__SSL__']) ? $params['__SSL__'] : null; if ($ssl !== null) { $session =& $this->recallObject('Session'); /* @var $session Session */ $target_url = rtrim($this->BaseURL('', $ssl, false), '/'); $cookie_url = trim($session->CookieDomain . $session->CookiePath, '/.'); // set session to GET_ONLY, to pass sid only if sid is REAL AND session is set if (!preg_match('#' . preg_quote($cookie_url) . '#', $target_url) && $session->SessionSet) { // when SSL<->NON-SSL redirect to different domain pass SID in url $session->SetMode(smGET_ONLY); } } if (isset($params['opener']) && $params['opener'] == 'u') { $wid = $this->Application->GetVar('m_wid'); $stack_name = rtrim('opener_stack_'.$wid, '_'); $opener_stack = $this->RecallVar($stack_name); if ($opener_stack && $opener_stack != serialize(Array())) { $opener_stack = unserialize($opener_stack); list($index_file, $env) = explode('|', $opener_stack[count($opener_stack) - 1]); $ret = $this->BaseURL($prefix, $ssl).$index_file.'?'.ENV_VAR_NAME.'='.$env; if ( getArrayValue($params,'escape') ) $ret = addslashes($ret); if (isset($params['m_opener']) && $params['m_opener'] == 'u') { array_pop($opener_stack); if (!$opener_stack) { $this->RemoveVar($stack_name); // remove popups last templates, because popup is closing now $this->RemoveVar('last_template_'.$wid); $this->RemoveVar('last_template_popup_'.$wid); // don't save popups last templates again :) $this->SetVar('skip_last_template', 1); } else { $this->StoreVar($stack_name, serialize($opener_stack)); } /*// store window relations $window_relations = $this->Application->RecallVar('window_relations'); $window_relations = $window_relations ? unserialize($window_relations) : Array (); if (array_key_exists($wid, $window_relations)) { unset($window_relations[$wid]); $this->Application->StoreVar('window_relations', serialize($window_relations)); }*/ } return $ret; } else { //define('DBG_REDIRECT', 1); $t = $this->GetVar('t'); } } $pass = isset($params['pass']) ? $params['pass'] : ''; // pass events with url $pass_events = false; if( isset($params['pass_events']) ) { $pass_events = $params['pass_events']; unset($params['pass_events']); } $map_link = ''; if( isset($params['anchor']) ) { $map_link = '#'.$params['anchor']; unset($params['anchor']); } if ( isset($params['no_amp']) ) { $params['__URLENCODE__'] = $params['no_amp']; unset($params['no_amp']); } $no_rewrite = false; if( isset($params['__NO_REWRITE__']) ) { $no_rewrite = true; unset($params['__NO_REWRITE__']); } $force_rewrite = false; if( isset($params['__MOD_REWRITE__']) ) { $force_rewrite = true; unset($params['__MOD_REWRITE__']); } $force_no_sid = false; if( isset($params['__NO_SID__']) ) { $force_no_sid = true; unset($params['__NO_SID__']); } // append pass through variables to each link to be build // $params = array_merge_recursive2($this->getPassThroughVariables($params), $params); $params = array_merge($this->getPassThroughVariables($params), $params); if ($force_rewrite || ($this->RewriteURLs($ssl) && !$no_rewrite)) { static $rewrite_listeners_done = false; if (!$rewrite_listeners_done) { $mod_rewrite_helper =& $this->recallObject('ModRewriteHelper'); /* @var $mod_rewrite_helper kModRewriteHelper */ $mod_rewrite_helper->initRewriteListeners(); $rewrite_listeners_done = true; } $session =& $this->recallObject('Session'); if ($session->NeedQueryString() && !$force_no_sid) { $params['sid'] = $this->GetSID(); } $url = $this->BuildEnv_NEW($t, $params, $pass, $pass_events); $ret = $this->BaseURL($prefix, $ssl).$url.$map_link; } else { unset($params['pass_category']); // we don't need to pass it when mod_rewrite is off $env = $this->BuildEnv($t, $params, $pass, $pass_events); $ret = $this->BaseURL($prefix, $ssl).$index_file.'?'.$env.$map_link; } return $ret; } /** * Returns variables with values that should be passed throught with this link + variable list * * @param Array $params * @return Array */ function getPassThroughVariables(&$params) { static $cached_pass_through = null; if (isset($params['no_pass_through']) && $params['no_pass_through']) { unset($params['no_pass_through']); return Array(); } // because pass through is not changed during script run, then we can cache it if (is_null($cached_pass_through)) { $cached_pass_through = Array(); $pass_through = $this->Application->GetVar('pass_through'); if ($pass_through) { // names of variables to pass to each link $cached_pass_through['pass_through'] = $pass_through; $pass_through = explode(',', $pass_through); foreach ($pass_through as $pass_through_var) { $cached_pass_through[$pass_through_var] = $this->Application->GetVar($pass_through_var); } } } return $cached_pass_through; } /** * Returns sorted array of passed prefixes (to build url from) * * @param string $pass * @return Array */ function getPassInfo($pass = 'all') { if (!$pass) $pass = 'all'; $pass = trim( preg_replace( '/(?<=,|\\A)all(?=,|\\z)/', trim($this->GetVar('passed'), ','), trim($pass, ',') ), ','); if (!$pass) { return Array(); } $pass_info = array_unique( explode(',', $pass) ); // array( prefix[.special], prefix[.special] ... // we need to keep that sorting despite the sorting below, because this sorts prefixes with same priority by name sort($pass_info, SORT_STRING); // to be prefix1,prefix1.special1,prefix1.special2,prefix3.specialX foreach ($pass_info as $prefix) { list ($prefix_only, ) = explode('.', $prefix, 2); $sorted[$prefix] = $this->getUnitOption($prefix_only, 'RewritePriority', 0); } asort($sorted, SORT_NUMERIC); $pass_info = array_keys($sorted); // ensure that "m" prefix is at the beginning $main_index = array_search('m', $pass_info); if ($main_index !== false) { unset($pass_info[$main_index]); array_unshift($pass_info, 'm'); } return $pass_info; } function BuildEnv_NEW($t, $params, $pass='all', $pass_events = false) { if ($this->GetVar('admin') || (array_key_exists('admin', $params) && $params['admin'])) { $params['admin'] = 1; if (!array_key_exists('editing_mode', $params)) { $params['editing_mode'] = EDITING_MODE; } } $ret = ''; $env = ''; $encode = false; if (isset($params['__URLENCODE__'])) { $encode = $params['__URLENCODE__']; unset($params['__URLENCODE__']); } if (isset($params['__SSL__'])) { unset($params['__SSL__']); } $catalog_item_found = false; $pass_info = $this->getPassInfo($pass); if ($pass_info) { if ($pass_info[0] == 'm') { array_shift($pass_info); } $inject_parts = Array (); // url parts for beginning of url $params['t'] = $t; // make template available for rewrite listeners $params['pass_template'] = true; // by default we keep given template in resulting url if (!array_key_exists('pass_category', $params)) { $params['pass_category'] = false; // by default we don't keep categories in url } foreach ($pass_info as $pass_index => $pass_element) { list ($prefix) = explode('.', $pass_element); $catalog_item = $this->findModule('Var', $prefix) && $this->getUnitOption($prefix, 'CatalogItem'); if (array_key_exists($prefix, $this->RewriteListeners)) { // if next prefix is same as current, but with special => exclude current prefix from url $next_prefix = array_key_exists($pass_index + 1, $pass_info) ? $pass_info[$pass_index + 1] : false; if ($next_prefix) { $next_prefix = substr($next_prefix, 0, strlen($prefix) + 1); if ($prefix . '.' == $next_prefix) { continue; } } // rewrited url part $url_part = $this->BuildModuleEnv_NEW($pass_element, $params, $pass_events); if (is_string($url_part) && $url_part) { $ret .= $url_part . '/'; if ($catalog_item) { // pass category later only for catalog items $catalog_item_found = true; } } elseif (is_array($url_part)) { // rewrite listener want to insert something at the beginning of url too if ($url_part[0]) { $inject_parts[] = $url_part[0]; } if ($url_part[1]) { $ret .= $url_part[1] . '/'; } if ($catalog_item) { // pass category later only for catalog items $catalog_item_found = true; } } elseif ($url_part === false) { // rewrite listener decided not to rewrite given $pass_element $env .= ':' . $this->BuildModuleEnv($pass_element, $params, $pass_events); } } else { $env .= ':' . $this->BuildModuleEnv($pass_element, $params, $pass_events); } } if ($catalog_item_found || preg_match('/c\.[-\d]*/', implode(',', $pass_info))) { // "c" prefix is present -> keep category $params['pass_category'] = true; } $params['inject_parts'] = $inject_parts; $ret = $this->BuildModuleEnv_NEW('m', $params, $pass_events) . '/' . $ret; $cat_processed = array_key_exists('category_processed', $params) && $params['category_processed']; // remove tempporary parameters used by listeners unset($params['t'], $params['inject_parts'], $params['pass_template'], $params['pass_category'], $params['category_processed']); if (array_key_exists('url_ending', $params)) { $ret = trim($ret, '/') . $params['url_ending']; unset($params['url_ending']); } else { $ret = trim($ret, '/') . MOD_REWRITE_URL_ENDING; } if ($env) { $params[ENV_VAR_NAME] = ltrim($env, ':'); } } unset($params['pass'], $params['opener'], $params['m_event']); if (array_key_exists('escape', $params) && $params['escape']) { $ret = addslashes($ret); unset($params['escape']); } $ret = str_replace('%2F', '/', urlencode($ret)); $params_str = ''; $join_string = $encode ? '&' : '&'; foreach ($params as $param => $value) { $params_str .= $join_string . $param . '=' . $value; } if ($params_str) { $ret .= '?' . substr($params_str, strlen($join_string)); } if ($encode) { $ret = str_replace('\\', '%5C', $ret); } return $ret; } function BuildModuleEnv_NEW($prefix_special, &$params, $keep_events = false) { list ($prefix) = explode('.', $prefix_special); $url_parts = Array (); $listener = $this->RewriteListeners[$prefix][0]; $ret = $listener[0]->$listener[1](REWRITE_MODE_BUILD, $prefix_special, $params, $url_parts, $keep_events); return $ret; } /** * Builds env part that corresponds prefix passed * * @param string $prefix_special item's prefix & [special] * @param Array $params url params * @param bool $pass_events */ function BuildModuleEnv($prefix_special, &$params, $pass_events = false) { list($prefix) = explode('.', $prefix_special); $query_vars = $this->getUnitOption($prefix, 'QueryString'); //if pass events is off and event is not implicity passed if( !$pass_events && !isset($params[$prefix_special.'_event']) ) { $params[$prefix_special.'_event'] = ''; // remove event from url if requested //otherwise it will use value from get_var } if(!$query_vars) return ''; $tmp_string = Array(0 => $prefix_special); foreach($query_vars as $index => $var_name) { //if value passed in params use it, otherwise use current from application $var_name = $prefix_special.'_'.$var_name; $tmp_string[$index] = isset( $params[$var_name] ) ? $params[$var_name] : $this->GetVar($var_name); if ( isset($params[$var_name]) ) unset( $params[$var_name] ); } $escaped = array(); foreach ($tmp_string as $tmp_val) { $escaped[] = str_replace(Array('-',':'), Array('\-','\:'), $tmp_val); } $ret = implode('-', $escaped); if ($this->getUnitOption($prefix, 'PortalStyleEnv') == true) { $ret = preg_replace('/^([a-zA-Z]+)-([0-9]+)-(.*)/','\\1\\2-\\3', $ret); } return $ret; } function BuildEnv($t, $params, $pass='all', $pass_events = false, $env_var = true) { if ($this->GetVar('admin') || (array_key_exists('admin', $params) && $params['admin'])) { $params['admin'] = 1; if (!array_key_exists('editing_mode', $params)) { $params['editing_mode'] = EDITING_MODE; } } $session =& $this->recallObject('Session'); $ssl = isset($params['__SSL__']) ? $params['__SSL__'] : 0; $sid = $session->NeedQueryString() && !$this->RewriteURLs($ssl) ? $this->GetSID() : ''; // if (getArrayValue($params,'admin') == 1) $sid = $this->GetSID(); $ret = ''; if ($env_var) { $ret = ENV_VAR_NAME.'='; } $ret .= $sid . '-'; // SID-TEMPLATE $encode = false; if (isset($params['__URLENCODE__'])) { $encode = $params['__URLENCODE__']; unset($params['__URLENCODE__']); } if (isset($params['__SSL__'])) { unset($params['__SSL__']); } $env_string = ''; $category_id = isset($params['m_cat_id']) ? $params['m_cat_id'] : $this->GetVar('m_cat_id'); $item_id = false; $pass_info = $this->getPassInfo($pass); if ($pass_info) { if ($pass_info[0] == 'm') array_shift($pass_info); foreach ($pass_info as $pass_element) { list($prefix) = explode('.', $pass_element); $require_rewrite = $this->findModule('Var', $prefix); if ($require_rewrite) { $item_id = isset($params[$pass_element.'_id']) ? $params[$pass_element.'_id'] : $this->GetVar($pass_element.'_id'); } $env_string .= ':'.$this->BuildModuleEnv($pass_element, $params, $pass_events); } } if (strtolower($t) == '__default__') { if (is_numeric($item_id)) { $mod_rw_helper =& $this->Application->recallObject('ModRewriteHelper'); /* @var $mod_rw_helper kModRewriteHelper */ $t = $mod_rw_helper->GetItemTemplate($category_id, $pass_element); // $pass_element should be the last processed element // $t = $this->getCategoryCache($category_id, 'item_templates'); } elseif ($category_id) { $t = strtolower(preg_replace('/^Content\//i', '', $this->getCategoryCache($category_id, 'filenames') )); } else { $t = 'index'; } } $ret .= $t.':'.$this->BuildModuleEnv('m', $params, $pass_events).$env_string; unset($params['pass'], $params['opener'], $params['m_event']); if (array_key_exists('escape', $params) && $params['escape']) { $ret = addslashes($ret); unset($params['escape']); } $join_string = $encode ? '&' : '&'; $params_str = ''; foreach ($params as $param => $value) { $params_str .= $join_string.$param.'='.$value; } $ret .= $params_str; if ($encode) { $ret = str_replace('\\', '%5C', $ret); } return $ret; } function BaseURL($prefix = '', $ssl = null, $add_port = true) { if ($ssl === null) { // stay on same encryption level return PROTOCOL . SERVER_NAME . ($add_port && defined('PORT') ? ':' . PORT : '') . rtrim(BASE_PATH, '/') . $prefix . '/'; } if ($ssl) { // going from http:// to https:// $base_url = $this->isAdmin ? $this->ConfigValue('AdminSSL_URL') : false; if (!$base_url) { $ssl_url = $this->siteDomainField('SSLUrl'); $base_url = $ssl_url !== false ? $ssl_url : $this->ConfigValue('SSL_URL'); } return rtrim($base_url, '/') . $prefix . '/'; } // going from https:// to http:// $domain = $this->siteDomainField('DomainName'); if ($domain === false) { $domain = DOMAIN; } return 'http://' . $domain . ($add_port && defined('PORT') ? ':' . PORT : '') . rtrim($this->ConfigValue('Site_Path'), '/') . $prefix . '/'; } function Redirect($t = '', $params = Array(), $prefix = '', $index_file = null) { $js_redirect = getArrayValue($params, 'js_redirect'); if ($t == '' || $t === true) { $t = $this->GetVar('t'); } // pass prefixes and special from previous url if (array_key_exists('js_redirect', $params)) { unset($params['js_redirect']); } // allows to send custom responce code along with redirect header if (array_key_exists('response_code', $params)) { $response_code = (int)$params['response_code']; unset($params['response_code']); } else { $response_code = 302; // Found } if (!array_key_exists('pass', $params)) { $params['pass'] = 'all'; } if ($this->GetVar('ajax') == 'yes' && $t == $this->GetVar('t')) { // redirects to the same template as current $params['ajax'] = 'yes'; } $params['__URLENCODE__'] = 1; $location = $this->HREF($t, $prefix, $params, $index_file); if ($this->isDebugMode() && (constOn('DBG_REDIRECT') || (constOn('DBG_RAISE_ON_WARNINGS') && $this->Application->Debugger->WarningCount))) { $this->Debugger->appendTrace(); echo '<strong>Debug output above !!!</strong><br/>' . "\n"; if ( array_key_exists('HTTP_REFERER', $_SERVER) ) { echo 'Referer: <strong>' . $_SERVER['HTTP_REFERER'] . '</strong><br/>' . "\n"; } echo "Proceed to redirect: <a href=\"{$location}\">{$location}</a><br/>\n"; } else { if ($js_redirect) { // show "redirect" template instead of redirecting, // because "Set-Cookie" header won't work, when "Location" // header is used later $this->SetVar('t', 'redirect'); $this->SetVar('redirect_to', $location); // make all additional parameters available on "redirect" template too foreach ($params as $name => $value) { $this->SetVar($name, $value); } return true; } else { if ($this->GetVar('ajax') == 'yes' && $t != $this->GetVar('t')) { // redirection to other then current template during ajax request echo '#redirect#' . $location; } elseif (headers_sent() != '') { // some output occured -> redirect using javascript echo '<script type="text/javascript">window.location.href = \'' . $location . '\';</script>'; } else { // no output before -> redirect using HTTP header // header('HTTP/1.1 302 Found'); header('Location: ' . $location, true, $response_code); } } } ob_end_flush(); // session expiration is called from session initialization, // that's why $this->Session may be not defined here $session =& $this->Application->recallObject('Session'); /* @var $session Session */ $this->HandleEvent( new kEvent('adm:OnBeforeShutdown') ); $session->SaveData(); exit; } function Phrase($label, $allow_editing = true, $use_admin = false) { return $this->Phrases->GetPhrase($label, $allow_editing, $use_admin); } /** * Replace language tags in exclamation marks found in text * * @param string $text * @param bool $force_escape force escaping, not escaping of resulting string * @return string * @access public */ function ReplaceLanguageTags($text, $force_escape=null) { // !!!!!!!! // if( !is_object($this->Phrases) ) $this->Debugger->appendTrace(); return $this->Phrases->ReplaceLanguageTags($text,$force_escape); } /** * Checks if user is logged in, and creates * user object if so. User object can be recalled * later using "u.current" prefix_special. Also you may * get user id by getting "u.current_id" variable. * * @access private */ function ValidateLogin() { $session =& $this->recallObject('Session'); $user_id = $session->GetField('PortalUserId'); if (!$user_id && $user_id != USER_ROOT) { $user_id = USER_GUEST; } $this->SetVar('u.current_id', $user_id); if (!$this->isAdmin) { // needed for "profile edit", "registration" forms ON FRONT ONLY $this->SetVar('u_id', $user_id); } $this->StoreVar('user_id', $user_id, $user_id == USER_GUEST); // storing Guest user_id (-2) is optional $this->isAdminUser = $this->isAdmin && $this->LoggedIn(); if ($this->GetVar('expired') == 1) { // this parameter is set only from admin $user =& $this->recallObject('u.current'); $user->SetError('ValidateLogin', 'session_expired', 'la_text_sess_expired'); } if (($user_id != USER_GUEST) && constOn('DBG_REQUREST_LOG') ) { $http_query =& $this->recallObject('HTTPQuery'); $http_query->writeRequestLog(DBG_REQUREST_LOG); } if ($user_id != USER_GUEST) { // normal users + root $this->LoadPersistentVars(); } } /** * Loads current user persistent session data * */ function LoadPersistentVars() { $this->Session->LoadPersistentVars(); } function LoadCache() { // TODO: maybe language part isn't required, since same phrase from different languages have one ID now $cache_key = $this->GetVar('t') . $this->GetVar('m_theme') . $this->GetVar('m_lang') . $this->isAdmin; $sql = 'SELECT PhraseList, ConfigVariables FROM ' . TABLE_PREFIX . 'PhraseCache WHERE Template = ' . $this->Conn->qstr( md5($cache_key) ); $res = $this->Conn->GetRow($sql); if ($res) { $this->Caches['PhraseList'] = $res['PhraseList'] ? explode(',', $res['PhraseList']) : Array (); $config_ids = $res['ConfigVariables'] ? explode(',', $res['ConfigVariables']) : Array (); if (isset($this->Caches['ConfigVariables'])) { $config_ids = array_diff($config_ids, $this->Caches['ConfigVariables']); } } else { $config_ids = Array (); } $this->Phrases->Init('phrases'); $this->Caches['ConfigVariables'] = $config_ids; $this->ConfigCacheIds = $config_ids; } /** * Loads template mapping for Front-End * */ function LoadStructureTemplateMapping() { if (!$this->isAdmin) { $category_helper =& $this->Application->recallObject('CategoryHelper'); /* @var $category_helper CategoryHelper */ $this->structureTemplateMapping = $category_helper->getTemplateMapping(); } } function UpdateCache() { $update = false; //something changed $update = $update || $this->Phrases->NeedsCacheUpdate(); $update = $update || (count($this->ConfigCacheIds) && $this->ConfigCacheIds != $this->Caches['ConfigVariables']); if ($update) { $cache_key = $this->GetVar('t').$this->GetVar('m_theme').$this->GetVar('m_lang').$this->isAdmin; $query = sprintf("REPLACE %s (PhraseList, CacheDate, Template, ConfigVariables) VALUES (%s, %s, %s, %s)", TABLE_PREFIX.'PhraseCache', $this->Conn->qstr(join(',', $this->Phrases->Ids)), adodb_mktime(), $this->Conn->qstr(md5($cache_key)), $this->Conn->qstr(implode(',', array_unique($this->ConfigCacheIds)))); $this->Conn->Query($query); } } function InitConfig() { if (isset($this->Caches['ConfigVariables']) && count($this->Caches['ConfigVariables']) > 0) { $sql = 'SELECT VariableValue, VariableName FROM ' . TABLE_PREFIX . 'ConfigurationValues WHERE VariableId IN (' . implode(',', $this->Caches['ConfigVariables']) . ')'; $this->ConfigHash = array_merge($this->ConfigHash, $this->Conn->GetCol($sql, 'VariableName')); } } /** * Returns configuration option value by name * * @param string $name * @return string */ function ConfigValue($name) { if ($name == 'Smtp_AdminMailFrom') { $res = $this->siteDomainField('AdminEmail'); if ($res) { return $res; } } $res = array_key_exists($name, $this->ConfigHash) ? $this->ConfigHash[$name] : false; if ($res !== false) { return $res; } if (defined('IS_INSTALL') && IS_INSTALL && !$this->TableFound('ConfigurationValues')) { return false; } $sql = 'SELECT VariableId, VariableValue FROM '.TABLE_PREFIX.'ConfigurationValues WHERE VariableName = '.$this->Conn->qstr($name); $res = $this->Conn->GetRow($sql); if ($res !== false) { $this->ConfigHash[$name] = $res['VariableValue']; $this->ConfigCacheIds[] = $res['VariableId']; return $res['VariableValue']; } return false; } function UpdateConfigCache() { if ($this->ConfigCacheIds) { } } /** * Allows to process any type of event * * @param kEvent $event * @access public * @author Alex */ function HandleEvent(&$event, $params=null, $specificParams=null) { if ( isset($params) ) { $event = new kEvent( $params, $specificParams ); } if (!isset($this->EventManager)) { $this->EventManager =& $this->recallObject('EventManager'); } $this->EventManager->HandleEvent($event); } /** * Registers new class in the factory * * @param string $real_class Real name of class as in class declaration * @param string $file Filename in what $real_class is declared * @param string $pseudo_class Name under this class object will be accessed using getObject method * @param Array $dependecies List of classes required for this class functioning * @access public * @author Alex */ function registerClass($real_class, $file, $pseudo_class = null, $dependecies = Array() ) { $this->Factory->registerClass($real_class, $file, $pseudo_class, $dependecies); } /** * Add $class_name to required classes list for $depended_class class. * All required class files are included before $depended_class file is included * * @param string $depended_class * @param string $class_name * @author Alex */ function registerDependency($depended_class, $class_name) { $this->Factory->registerDependency($depended_class, $class_name); } /** * Registers Hook from subprefix event to master prefix event * * @param string $hookto_prefix * @param string $hookto_special * @param string $hookto_event * @param string $mode * @param string $do_prefix * @param string $do_special * @param string $do_event * @param string $conditional * @access public * @todo take care of a lot parameters passed * @author Kostja */ function registerHook($hookto_prefix, $hookto_special, $hookto_event, $mode, $do_prefix, $do_special, $do_event, $conditional) { $event_manager =& $this->recallObject('EventManager'); $event_manager->registerHook($hookto_prefix, $hookto_special, $hookto_event, $mode, $do_prefix, $do_special, $do_event, $conditional); } /** * Allows one TagProcessor tag act as other TagProcessor tag * * @param Array $tag_info * @author Kostja */ function registerAggregateTag($tag_info) { $aggregator =& $this->recallObject('TagsAggregator', 'kArray'); $aggregator->SetArrayValue($tag_info['AggregateTo'], $tag_info['AggregatedTagName'], Array($tag_info['LocalPrefix'], $tag_info['LocalTagName'], getArrayValue($tag_info, 'LocalSpecial'))); } /** * Returns object using params specified, * creates it if is required * * @param string $name * @param string $pseudo_class * @param Array $event_params * @return Object * @author Alex */ function &recallObject($name,$pseudo_class=null,$event_params=Array()) { $result =& $this->Factory->getObject($name, $pseudo_class, $event_params); return $result; } /** * Returns object using Variable number of params, * all params starting with 4th are passed to object consturctor * * @param string $name * @param string $pseudo_class * @param Array $event_params * @return Object * @author Alex */ function &recallObjectP($name,$pseudo_class=null,$event_params=Array()) { $func_args = func_get_args(); $result =& ref_call_user_func_array( Array(&$this->Factory, 'getObjectP'), $func_args ); return $result; } /** * Returns tag processor for prefix specified * * @param string $prefix * @return kDBTagProcessor */ function &recallTagProcessor($prefix) { $this->InitParser(); // because kDBTagProcesor is in NParser dependencies $result =& $this->recallObject($prefix . '_TagProcessor'); return $result; } /** * Checks if object with prefix passes was already created in factory * * @param string $name object presudo_class, prefix * @return bool * @author Kostja */ function hasObject($name) { return isset($this->Factory->Storage[$name]); } /** * Removes object from storage by given name * * @param string $name Object's name in the Storage * @author Kostja */ function removeObject($name) { $this->Factory->DestroyObject($name); } /** * Get's real class name for pseudo class, * includes class file and creates class * instance * * @param string $pseudo_class * @return Object * @access public * @author Alex */ function &makeClass($pseudo_class) { $func_args = func_get_args(); $result =& ref_call_user_func_array( Array(&$this->Factory, 'makeClass'), $func_args); return $result; } /** * Checks if application is in debug mode * * @param bool $check_debugger check if kApplication debugger is initialized too, not only for defined DEBUG_MODE constant * @return bool * @author Alex * @access public */ function isDebugMode($check_debugger = true) { $debug_mode = defined('DEBUG_MODE') && DEBUG_MODE; if ($check_debugger) { $debug_mode = $debug_mode && is_object($this->Debugger); } return $debug_mode; } /** * Apply url rewriting used by mod_rewrite or not * * @param bool $ssl Force ssl link to be build * @return bool */ function RewriteURLs($ssl = false) { // case #1,#4: // we want to create https link from http mode // we want to create https link from https mode // conditions: ($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL') // case #2,#3: // we want to create http link from https mode // we want to create http link from http mode // conditions: !$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://') $allow_rewriting = (!$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://')) // always allow mod_rewrite for http || // or allow rewriting for redirect TO httpS or when already in httpS (($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL')); // but only if it's allowed in config! return constOn('MOD_REWRITE') && $allow_rewriting; } /** * Reads unit (specified by $prefix) * option specified by $option * * @param string $prefix * @param string $option * @param mixed $default * @return string * @access public * @author Alex */ function getUnitOption($prefix, $option, $default = false) { /*if (!isset($this->UnitConfigReader)) { $this->UnitConfigReader =& $this->recallObject('kUnitConfigReader'); }*/ return $this->UnitConfigReader->getUnitOption($prefix, $option, $default); } /** * Set's new unit option value * * @param string $prefix * @param string $name * @param string $value * @author Alex * @access public */ function setUnitOption($prefix, $option, $value) { // $unit_config_reader =& $this->recallObject('kUnitConfigReader'); return $this->UnitConfigReader->setUnitOption($prefix,$option,$value); } /** * Read all unit with $prefix options * * @param string $prefix * @return Array * @access public * @author Alex */ function getUnitOptions($prefix) { // $unit_config_reader =& $this->recallObject('kUnitConfigReader'); return $this->UnitConfigReader->getUnitOptions($prefix); } /** * Returns true if config exists and is allowed for reading * * @param string $prefix * @return bool */ function prefixRegistred($prefix) { /*if (!isset($this->UnitConfigReader)) { $this->UnitConfigReader =& $this->recallObject('kUnitConfigReader'); }*/ return $this->UnitConfigReader->prefixRegistred($prefix); } /** * Splits any mixing of prefix and * special into correct ones * * @param string $prefix_special * @return Array * @access public * @author Alex */ function processPrefix($prefix_special) { return $this->Factory->processPrefix($prefix_special); } /** * Set's new event for $prefix_special * passed * * @param string $prefix_special * @param string $event_name * @access public */ function setEvent($prefix_special,$event_name) { $event_manager =& $this->recallObject('EventManager'); $event_manager->setEvent($prefix_special,$event_name); } /** * SQL Error Handler * * @param int $code * @param string $msg * @param string $sql * @return bool * @access private * @author Alex */ function handleSQLError($code, $msg, $sql) { if ( isset($this->Debugger) ) { $errorLevel = constOn('DBG_SQL_FAILURE') && !defined('IS_INSTALL') ? E_USER_ERROR : E_USER_WARNING; $this->Debugger->appendTrace(); $error_msg = '<span class="debug_error">'.$msg.' ('.$code.')</span><br><a href="javascript:$Debugger.SetClipboard(\''.htmlspecialchars($sql).'\');"><b>SQL</b></a>: '.$this->Debugger->formatSQL($sql); $long_id = $this->Debugger->mapLongError($error_msg); trigger_error( mb_substr($msg.' ('.$code.') ['.$sql.']',0,1000).' #'.$long_id, $errorLevel); return true; } else { //$errorLevel = constOn('IS_INSTALL') ? E_USER_WARNING : E_USER_ERROR; $errorLevel = E_USER_WARNING; trigger_error('<b>SQL Error</b> in sql: '.$sql.', code <b>'.$code.'</b> ('.$msg.')', $errorLevel); /*echo '<b>xProcessing SQL</b>: '.$sql.'<br>'; echo '<b>Error ('.$code.'):</b> '.$msg.'<br>';*/ return $errorLevel == E_USER_ERROR ? false : true; } } /** * Default error handler * * @param int $errno * @param string $errstr * @param string $errfile * @param int $errline * @param Array $errcontext */ function handleError($errno, $errstr, $errfile = '', $errline = '', $errcontext = '') { if (defined('SILENT_LOG') && SILENT_LOG) { if ( !(defined('DBG_IGNORE_STRICT_ERRORS') && DBG_IGNORE_STRICT_ERRORS && defined('E_STRICT') && ($errno == E_STRICT)) ) { $fp = fopen(FULL_PATH.'/silent_log.txt','a'); $time = adodb_date('d/m/Y H:i:s'); fwrite($fp, '['.$time.'] #'.$errno.': '.strip_tags($errstr).' in ['.$errfile.'] on line '.$errline."\n"); fclose($fp); } } $debug_mode = defined('DEBUG_MODE') && DEBUG_MODE; $skip_reporting = defined('DBG_SKIP_REPORTING') && DBG_SKIP_REPORTING; if (!$this->errorHandlers || ($debug_mode && $skip_reporting)) { // when debugger absent OR it's present, but we actually can't see it's error report (e.g. during ajax request) $ignore_fatal_errors = defined('DBG_IGNORE_FATAL_ERRORS') && DBG_IGNORE_FATAL_ERRORS; if (($errno == E_USER_ERROR) && !$ignore_fatal_errors) { echo (' <div style="background-color: #FEFFBF; margin: auto; padding: 10px; border: 2px solid red; text-align: center"> <strong>Fatal Error: </strong>'."$errstr in $errfile on line $errline".' </div>'); exit; } if (!$this->errorHandlers) { return true; } } $res = false; $i = 0; // while (not foreach) because it is array of references in some cases $eh_count = count($this->errorHandlers); while ($i < $eh_count) { if ( is_array($this->errorHandlers[$i]) ) { $object =& $this->errorHandlers[$i][0]; $method = $this->errorHandlers[$i][1]; $res = $object->$method($errno, $errstr, $errfile, $errline, $errcontext); } else { $function = $this->errorHandlers[$i]; $res = $function($errno, $errstr, $errfile, $errline, $errcontext); } $i++; } return $res; } /** * Returns & blocks next ResourceId available in system * * @return int * @access public * @author Alex */ function NextResourceId() { $table_name = TABLE_PREFIX.'IdGenerator'; $this->Conn->Query('LOCK TABLES '.$table_name.' WRITE'); $this->Conn->Query('UPDATE '.$table_name.' SET lastid = lastid + 1'); $id = $this->Conn->GetOne('SELECT lastid FROM '.$table_name); if($id === false) { $this->Conn->Query('INSERT INTO '.$table_name.' (lastid) VALUES (2)'); $id = 2; } $this->Conn->Query('UNLOCK TABLES'); return $id - 1; } /** * Returns genealogical main prefix for subtable prefix passes * OR prefix, that has been found in REQUEST and some how is parent of passed subtable prefix * * @param string $current_prefix * @param string $real_top if set to true will return real topmost prefix, regardless of its id is passed or not * @return string * @access public * @author Kostja / Alex */ function GetTopmostPrefix($current_prefix, $real_top = false) { // 1. get genealogical tree of $current_prefix $prefixes = Array ($current_prefix); while ( $parent_prefix = $this->getUnitOption($current_prefix, 'ParentPrefix') ) { if (!$this->prefixRegistred($parent_prefix)) { // stop searching, when parent prefix is not registered break; } $current_prefix = $parent_prefix; array_unshift($prefixes, $current_prefix); } if ($real_top) { return $current_prefix; } // 2. find what if parent is passed $passed = explode(',', $this->GetVar('all_passed')); foreach ($prefixes as $a_prefix) { if (in_array($a_prefix, $passed)) { return $a_prefix; } } return $current_prefix; } /** * Triggers email event of type Admin * * @param string $email_event_name * @param int $to_user_id * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text * @return kEvent */ function &EmailEventAdmin($email_event_name, $to_user_id = null, $send_params = Array ()) { $event =& $this->EmailEvent($email_event_name, EVENT_TYPE_ADMIN, $to_user_id, $send_params); return $event; } /** * Triggers email event of type User * * @param string $email_event_name * @param int $to_user_id * @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text * @return kEvent */ function &EmailEventUser($email_event_name, $to_user_id = null, $send_params = Array ()) { $event =& $this->EmailEvent($email_event_name, EVENT_TYPE_FRONTEND, $to_user_id, $send_params); return $event; } /** * Triggers general email event * * @param string $email_event_name * @param int $email_event_type (0 for User, 1 for Admin) * @param int $to_user_id * @param array $send_params associative array of direct send params, * possible keys: to_email, to_name, from_email, from_name, message, message_text * @return kEvent */ function &EmailEvent($email_event_name, $email_event_type, $to_user_id = null, $send_params = Array ()) { $params = Array ( 'EmailEventName' => $email_event_name, 'EmailEventToUserId' => $to_user_id, 'EmailEventType' => $email_event_type, 'DirectSendParams' => $send_params, ); if (array_key_exists('use_special', $send_params)) { $event_str = 'emailevents.' . $send_params['use_special'] . ':OnEmailEvent'; } else { $event_str = 'emailevents:OnEmailEvent'; } $this->HandleEvent($event, $event_str, $params); return $event; } /** * Allows to check if user in this session is logged in or not * * @return bool */ function LoggedIn() { // no session during expiration process return is_null($this->Session) ? false : $this->Session->LoggedIn(); } /** * Check current user permissions based on it's group permissions in specified category * * @param string $name permission name * @param int $cat_id category id, current used if not specified * @param int $type permission type {1 - system, 0 - per category} * @return int */ function CheckPermission($name, $type = 1, $cat_id = null) { $perm_helper =& $this->recallObject('PermissionsHelper'); return $perm_helper->CheckPermission($name, $type, $cat_id); } /** * Set's any field of current visit * * @param string $field * @param mixed $value */ function setVisitField($field, $value) { if ($this->isAdmin || !$this->ConfigValue('UseVisitorTracking')) { // admin logins are not registred in visits list return ; } $visit =& $this->recallObject('visits', null, Array ('raise_warnings' => 0)); /* @var $visit kDBItem */ if ($visit->isLoaded()) { $visit->SetDBField($field, $value); $visit->Update(); } } /** * Allows to check if in-portal is installed * * @return bool */ function isInstalled() { return $this->InitDone && (count($this->ModuleInfo) > 0); } /** * Allows to determine if module is installed & enabled * * @param string $module_name * @return bool */ function isModuleEnabled($module_name) { return $this->findModule('Name', $module_name) !== false; } function reportError($class, $method) { $this->Debugger->appendTrace(); trigger_error('depricated method <b>'.$class.'->'.$method.'(...)</b>', E_USER_ERROR); } /** * Returns Window ID of passed prefix main prefix (in edit mode) * * @param string $prefix * @return mixed */ function GetTopmostWid($prefix) { $top_prefix = $this->GetTopmostPrefix($prefix); $mode = $this->GetVar($top_prefix.'_mode'); return $mode != '' ? substr($mode, 1) : ''; } /** * Get temp table name * * @param string $table * @param mixed $wid * @return string */ function GetTempName($table, $wid = '') { if (preg_match('/prefix:(.*)/', $wid, $regs)) { $wid = $this->GetTopmostWid($regs[1]); } return TABLE_PREFIX.'ses_'.$this->GetSID().($wid ? '_'.$wid : '').'_edit_'.$table; } function GetTempTablePrefix($wid = '') { if (preg_match('/prefix:(.*)/', $wid, $regs)) { $wid = $this->GetTopmostWid($regs[1]); } return TABLE_PREFIX.'ses_'.$this->GetSID().($wid ? '_'.$wid : '').'_edit_'; } function IsTempTable($table) { return preg_match('/'.TABLE_PREFIX.'ses_'.$this->GetSID().'(_[\d]+){0,1}_edit_(.*)/',$table); } /** * Checks, that given prefix is in temp mode * * @param string $prefix * @return bool */ function IsTempMode($prefix, $special = '') { $top_prefix = $this->Application->GetTopmostPrefix($prefix); $var_names = Array ( $top_prefix, rtrim($top_prefix . '_' . $special, '_'), // from post rtrim($top_prefix . '.' . $special, '.'), // assembled locally ); $var_names = array_unique($var_names); $temp_mode = false; foreach ($var_names as $var_name) { $value = $this->Application->GetVar($var_name . '_mode'); if ($value && (substr($value, 0, 1) == 't')) { $temp_mode = true; break; } } return $temp_mode; } /** * Return live table name based on temp table name * * @param string $temp_table * @return string */ function GetLiveName($temp_table) { if( preg_match('/'.TABLE_PREFIX.'ses_'.$this->GetSID().'(_[\d]+){0,1}_edit_(.*)/',$temp_table, $rets) ) { // cut wid from table end if any return $rets[2]; } else { return $temp_table; } } function CheckProcessors($processors) { foreach ($processors as $a_processor) { if (!isset($this->CachedProcessors[$a_processor])) { $this->CachedProcessors[$a_processor] =& $this->recallObject($a_processor.'_TagProcessor'); } } } function ApplicationDie($message = '') { $message = ob_get_clean().$message; if ($this->isDebugMode()) { $message .= $this->Debugger->printReport(true); } echo $this->UseOutputCompression() ? gzencode($message, DBG_COMPRESSION_LEVEL) : $message; exit; } /* moved from MyApplication */ function getUserGroups($user_id) { switch ($user_id) { case USER_ROOT: $user_groups = $this->ConfigValue('User_LoggedInGroup'); break; case USER_GUEST: $user_groups = $this->ConfigValue('User_LoggedInGroup') . ',' . $this->ConfigValue('User_GuestGroup'); break; default: $sql = 'SELECT GroupId FROM ' . TABLE_PREFIX . 'UserGroup WHERE PortalUserId = ' . (int)$user_id; $res = $this->Conn->GetCol($sql); $user_groups = Array( $this->ConfigValue('User_LoggedInGroup') ); if ($res) { $user_groups = array_merge($user_groups, $res); } $user_groups = implode(',', $user_groups); } return $user_groups; } /** * Allows to detect if page is browsed by spider (293 agents supported) * * @return bool */ function IsSpider() { static $is_spider = null; if (!isset($is_spider)) { $user_agent = trim($_SERVER['HTTP_USER_AGENT']); $robots = file(FULL_PATH.'/core/robots_list.txt'); foreach ($robots as $robot_info) { $robot_info = explode("\t", $robot_info, 3); if ($user_agent == trim($robot_info[2])) { $is_spider = true; break; } } } return $is_spider; } /** * Allows to detect table's presense in database * * @param string $table_name * @return bool */ function TableFound($table_name) { return $this->Conn->TableFound($table_name); } /** * Returns counter value * * @param string $name counter name * @param Array $params counter parameters * @param string $query_name specify query name directly (don't generate from parmeters) * @param bool $multiple_results * @return mixed */ function getCounter($name, $params = Array (), $query_name = null, $multiple_results = false) { $count_helper =& $this->Application->recallObject('CountHelper'); /* @var $count_helper kCountHelper */ return $count_helper->getCounter($name, $params, $query_name, $multiple_results); } /** * Resets counter, whitch are affected by one of specified tables * * @param string $tables comma separated tables list used in counting sqls */ function resetCounters($tables) { if (constOn('IS_INSTALL')) { return ; } $count_helper =& $this->Application->recallObject('CountHelper'); /* @var $count_helper kCountHelper */ return $count_helper->resetCounters($tables); } /** * Sends XML header + optionally displays xml heading * * @param string $xml_version * @return string * @author Alex */ function XMLHeader($xml_version = false) { $lang =& $this->recallObject('lang.current'); header('Content-type: text/xml; charset='.$lang->GetDBField('Charset')); return $xml_version ? '<?xml version="'.$xml_version.'" encoding="'.$lang->GetDBField('Charset').'"?>' : ''; } /** * Returns category tree * * @param int $category_id * @return Array */ function getTreeIndex($category_id) { $tree_index = $this->getCategoryCache($category_id, 'category_tree'); if ($tree_index) { $ret = Array (); list ($ret['TreeLeft'], $ret['TreeRight']) = explode(';', $tree_index); return $ret; } return false; } + /** + * Base category of all categories + * Usually replaced category, with ID = 0 in category-related operations. + * + * @return int + */ + function getBaseCategory() + { + // same, what $this->findModule('Name', 'Core', 'RootCat') does + // don't cache while IS_INSTALL, because of kInstallToolkit::createModuleCategory and upgrade + + return $this->ModuleInfo['Core']['RootCat']; + } + } \ No newline at end of file Index: branches/5.1.x/core/units/categories/categories_tag_processor.php =================================================================== --- branches/5.1.x/core/units/categories/categories_tag_processor.php (revision 13986) +++ branches/5.1.x/core/units/categories/categories_tag_processor.php (revision 13987) @@ -1,1977 +1,1971 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ class CategoriesTagProcessor extends kDBTagProcessor { function SubCatCount($params) { $object =& $this->getObject($params); if (isset($params['today']) && $params['today']) { $sql = 'SELECT COUNT(*) FROM '.$object->TableName.' WHERE (ParentPath LIKE "'.$object->GetDBField('ParentPath').'%") AND (CreatedOn > '.(adodb_mktime() - 86400).')'; return $this->Conn->GetOne($sql) - 1; } return $object->GetDBField('CachedDescendantCatsQty'); } /** * Returns category count in system * * @param Array $params * @return int */ function CategoryCount($params) { $count_helper =& $this->Application->recallObject('CountHelper'); /* @var $count_helper kCountHelper */ $today_only = isset($params['today']) && $params['today']; return $count_helper->CategoryCount($today_only); } function IsNew($params) { $object =& $this->getObject($params); return $object->GetDBField('IsNew') ? 1 : 0; } function IsPick($params) { return $this->IsEditorsPick($params); } /** * Returns item's editors pick status (using not formatted value) * * @param Array $params * @return bool */ function IsEditorsPick($params) { $object =& $this->getObject($params); return $object->GetDBField('EditorsPick') == 1; } function ItemIcon($params) { $grids = $this->Application->getUnitOption($this->Prefix, 'Grids'); $grid = $grids[ $params['grid'] ]; if (!array_key_exists('Icons', $grid)) { return ''; } $icons = $grid['Icons']; $icon_prefix = array_key_exists('icon_prefix', $params)? $params['icon_prefix'] : 'icon16_'; if (array_key_exists('name', $params)) { $icon_name = $params['name']; return array_key_exists($icon_name, $icons) ? $icons[$icon_name] : ''; } $object =& $this->getObject($params); /* @var $object kDBList */ if ($object->GetDBField('ThemeId') > 0) { if (!$object->GetDBField('IsMenu')) { return $icon_prefix . 'section_menuhidden_system.png'; } return $icon_prefix . 'section_system.png'; } $status = $object->GetDBField('Status'); if ($status == STATUS_DISABLED) { return $icon_prefix . 'section_disabled.png'; } if (!$object->GetDBField('IsMenu')) { return $icon_prefix . 'section_menuhidden.png'; } if ($status == STATUS_PENDING) { return $icon_prefix . 'section_pending.png'; } if ($object->GetDBField('IsNew') && ($icon_prefix == 'icon16_')) { return $icon_prefix . 'section_new.png'; // show gris icon only in grids } return $icon_prefix . 'section.png'; } function ItemCount($params) { $object =& $this->getObject($params); /* @var $object kDBItem */ $ci_table = $this->Application->getUnitOption('ci', 'TableName'); $sql = 'SELECT COUNT(*) FROM ' . $object->TableName . ' c LEFT JOIN ' . $ci_table . ' ci ON c.CategoryId = ci.CategoryId WHERE (c.TreeLeft BETWEEN ' . $object->GetDBField('TreeLeft') . ' AND ' . $object->GetDBField('TreeRight') . ') AND NOT (ci.CategoryId IS NULL)'; return $this->Conn->GetOne($sql); } function ListCategories($params) { return $this->PrintList2($params); } function RootCategoryName($params) { return $this->Application->ProcessParsedTag('m', 'RootCategoryName', $params); } function CheckModuleRoot($params) { $module_name = getArrayValue($params, 'module') ? $params['module'] : 'In-Commerce'; $module_root_cat = $this->Application->findModule('Name', $module_name, 'RootCat'); $additional_cats = $this->SelectParam($params, 'add_cats'); if ($additional_cats) { $additional_cats = explode(',', $additional_cats); } else { $additional_cats = array(); } if ($this->Application->GetVar('m_cat_id') == $module_root_cat || in_array($this->Application->GetVar('m_cat_id'), $additional_cats)) { $home_template = getArrayValue($params, 'home_template'); if (!$home_template) return; $this->Application->Redirect($home_template, Array('pass'=>'all')); }; } function CategoryPath($params) { $category_helper =& $this->Application->recallObject('CategoryHelper'); /* @var $category_helper CategoryHelper */ return $category_helper->NavigationBar($params); } /** * Shows category path to specified category * * @param Array $params * @return string */ function FieldCategoryPath($params) { $object =& $this->getObject(); /* @var $object kDBItem */ $field = $this->SelectParam($params, 'name,field'); $category_id = $object->GetDBField($field); if ($category_id) { $params['cat_id'] = $category_id; return $this->CategoryPath($params); } return ''; } function CurrentCategoryName($params) { $cat_object =& $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix.'_List'); $sql = 'SELECT '.$this->getTitleField().' FROM '.$cat_object->TableName.' WHERE CategoryId = '.(int)$this->Application->GetVar('m_cat_id'); return $this->Conn->GetOne($sql); } /** * Returns current category name * * @param Array $params * @return string * @todo Find where it's used */ function CurrentCategory($params) { return $this->CurrentCategoryName($params); } function getTitleField() { $ml_formatter =& $this->Application->recallObject('kMultiLanguage'); return $ml_formatter->LangFieldName('Name'); } /** * Returns symlinked category for given category * * @param $category_id */ function getCategorySymLink($category_id) { if (!$category_id) { // don't bother to get symlink for "Home" category return $category_id; } $cache_key = 'category_symlinks[%CSerial%]'; $cache = $this->Application->getCache($cache_key); if ($cache === false) { $id_field = $this->Application->getUnitOption($this->Prefix, 'IDField'); $table_name = $this->Application->getUnitOption($this->Prefix, 'TableName'); // get symlinked categories, that are not yet deleted $this->Conn->nextQueryCachable = true; $sql = 'SELECT c1.SymLinkCategoryId, c1.' . $id_field . ' FROM ' . $table_name . ' c1 JOIN ' . $table_name . ' c2 ON c1.SymLinkCategoryId = c2.' . $id_field; $cache = $this->Conn->GetCol($sql, $id_field); $this->Application->setCache($cache_key, $cache); } return array_key_exists($category_id, $cache) ? $cache[$category_id] : $category_id; } function CategoryLink($params) { $category_id = getArrayValue($params, 'cat_id'); if ($category_id === false) { $category_id = $this->Application->GetVar($this->getPrefixSpecial() . '_id'); } if ("$category_id" == 'Root') { $category_id = $this->Application->findModule('Name', $params['module'], 'RootCat'); } elseif ("$category_id" == 'current') { $category_id = $this->Application->GetVar('m_cat_id'); } if (!array_key_exists('direct_link', $params) || !$params['direct_link']) { $category_id = $this->getCategorySymLink( (int)$category_id ); } else { unset($params['direct_link']); } unset($params['cat_id'], $params['module']); $new_params = Array ('pass' => 'm', 'm_cat_id' => $category_id, 'pass_category' => 1); $params = array_merge_recursive2($params, $new_params); return $this->Application->ProcessParsedTag('m', 't', $params); } function CategoryList($params) { //$object =& $this->Application->recallObject( $this->getPrefixSpecial() , $this->Prefix.'_List', $params ); $object =& $this->GetList($params); if ($object->RecordsCount == 0) { if (isset($params['block_no_cats'])) { $params['name'] = $params['block_no_cats']; return $this->Application->ParseBlock($params); } else { return ''; } } if (isset($params['block'])) { return $this->PrintList($params); } else { $params['block'] = $params['block_main']; if (isset($params['block_row_start'])) { $params['row_start_block'] = $params['block_row_start']; } if (isset($params['block_row_end'])) { $params['row_end_block'] = $params['block_row_end']; } return $this->PrintList2($params); } } function Meta($params) { $object =& $this->Application->recallObject($this->Prefix); // .'.-item' /* @var $object CategoriesItem */ $meta_type = $params['name']; if ($object->isLoaded()) { // 1. get module prefix by current category $category_helper =& $this->Application->recallObject('CategoryHelper'); /* @var $category_helper CategoryHelper */ $category_path = explode('|', substr($object->GetDBField('ParentPath'), 1, -1)); $module_info = $category_helper->getCategoryModule($params, $category_path); // In-Edit & Proj-CMS module prefixes doesn't have custom field with item template if ($module_info && $module_info['Var'] != 'adm' && $module_info['Var'] != 'st') { // 2. get item template by current category & module prefix $mod_rewrite_helper = $this->Application->recallObject('ModRewriteHelper'); /* @var $mod_rewrite_helper kModRewriteHelper */ $category_params = Array ( 'CategoryId' => $object->GetID(), 'ParentPath' => $object->GetDBField('ParentPath'), ); $item_template = $mod_rewrite_helper->GetItemTemplate($category_params, $module_info['Var']); if ($this->Application->GetVar('t') == $item_template) { // we are located on item's details page $item =& $this->Application->recallObject($module_info['Var']); /* @var $item kCatDBItem */ // 3. get item's meta data $value = $item->GetField('Meta'.$meta_type); if ($value) { return $value; } } // 4. get category meta data $value = $object->GetField('Meta'.$meta_type); if ($value) { return $value; } } } // 5. get default meta data switch ($meta_type) { case 'Description': $config_name = 'Category_MetaDesc'; break; case 'Keywords': $config_name = 'Category_MetaKey'; break; } return $this->Application->ConfigValue($config_name); } function BuildListSpecial($params) { if (($this->Special != '') && !is_numeric($this->Special)) { // When recursive category list is printed (like in sitemap), then special // should be generated even if it's already present. Without it list on this // level will erase list on previous level, because it will be stored in same object. return $this->Special; } if ( isset($params['parent_cat_id']) ) { $parent_cat_id = $params['parent_cat_id']; } else { $parent_cat_id = $this->Application->GetVar($this->Prefix.'_id'); if (!$parent_cat_id) { $parent_cat_id = $this->Application->GetVar('m_cat_id'); } if (!$parent_cat_id) { $parent_cat_id = 0; } } $list_unique_key = $this->getUniqueListKey($params); // check for "admin" variable, because we are parsing front-end template from admin when using template editor feature if ($this->Application->GetVar('admin') || !$this->Application->isAdmin) { // add parent category to special, when on Front-End, // because there can be many category lists on same page $list_unique_key .= $parent_cat_id; } if ($list_unique_key == '') { return parent::BuildListSpecial($params); } return crc32($list_unique_key); } function IsCurrent($params) { $object =& $this->getObject($params); if ($object->GetID() == $this->Application->GetVar('m_cat_id')) { return true; } else { return false; } } /** * Substitutes category in last template base on current category * This is required becasue when you navigate catalog using AJAX, last_template is not updated * but when you open item edit from catalog last_template is used to build opener_stack * So, if we don't substitute m_cat_id in last_template, after saving item we'll get redirected * to the first category we've opened, not the one we navigated to using AJAX * * @param Array $params */ function UpdateLastTemplate($params) { $category_id = $this->Application->GetVar('m_cat_id'); $wid = $this->Application->GetVar('m_wid'); list($index_file, $env) = explode('|', $this->Application->RecallVar(rtrim('last_template_'.$wid, '_')), 2); $vars_backup = Array (); $vars = $this->Application->HttpQuery->processQueryString( str_replace('%5C', '\\', $env) ); foreach ($vars as $var_name => $var_value) { $vars_backup[$var_name] = $this->Application->GetVar($var_name); $this->Application->SetVar($var_name, $var_value); } // update required fields $this->Application->SetVar('m_cat_id', $category_id); $this->Application->Session->SaveLastTemplate($params['template']); foreach ($vars_backup as $var_name => $var_value) { $this->Application->SetVar($var_name, $var_value); } } function GetParentCategory($params) { $parent_id = 0; $id_field = $this->Application->getUnitOption($this->Prefix, 'IDField'); $table = $this->Application->getUnitOption($this->Prefix,'TableName'); $cat_id = $this->Application->GetVar('m_cat_id'); if ($cat_id > 0) { $sql = 'SELECT ParentId FROM '.$table.' WHERE '.$id_field.' = '.$cat_id; $parent_id = $this->Conn->GetOne($sql); } return $parent_id; } function InitCacheUpdater($params) { safeDefine('CACHE_PERM_CHUNK_SIZE', 30); $continue = $this->Application->GetVar('continue'); $total_cats = (int) $this->Conn->GetOne('SELECT COUNT(*) FROM '.TABLE_PREFIX.'Category'); if ($continue === false && $total_cats > CACHE_PERM_CHUNK_SIZE) { // first step, if category count > CACHE_PERM_CHUNK_SIZE, then ask for cache update return true; } if ($continue === false) { // if we don't have to ask, then assume user selected "Yes" in permcache update dialog $continue = 1; } $updater =& $this->Application->recallObject('kPermCacheUpdater', null, Array('continue' => $continue)); /* @var $updater kPermCacheUpdater */ if ($continue === '0') { // No in dialog $updater->clearData(); $this->Application->Redirect($params['destination_template']); } $ret = false; // don't ask for update if ($continue == 1) { // Initial run $updater->setData(); } if ($continue == 2) { // Continuing // called from AJAX request => returns percent $needs_more = true; while ($needs_more && $updater->iteration <= CACHE_PERM_CHUNK_SIZE) { // until proceeeded in this step category count exceeds category per step limit $needs_more = $updater->DoTheJob(); } if ($needs_more) { // still some categories are left for next step $updater->setData(); } else { // all done, update left tree and redirect $updater->SaveData(); $this->Application->RemoveVar('PermCache_UpdateRequired'); $this->Application->StoreVar('RefreshStructureTree', 1); $this->Application->Redirect($params['destination_template']); } $ret = $updater->getDonePercent(); } return $ret; } /** * Parses warning block, but with style="display: none;". Used during permissions saving from AJAX * * @param Array $params * @return string */ function SaveWarning($params) { if ($this->Prefix == 'st') { // don't use this method for other prefixes then Category, that use this tag processor return parent::SaveWarning($params); } $main_prefix = getArrayValue($params, 'main_prefix'); if ($main_prefix && $main_prefix != '$main_prefix') { $top_prefix = $main_prefix; } else { $top_prefix = $this->Application->GetTopmostPrefix($this->Prefix); } $temp_tables = substr($this->Application->GetVar($top_prefix.'_mode'), 0, 1) == 't'; $modified = $this->Application->RecallVar($top_prefix.'_modified'); if (!$temp_tables) { $this->Application->RemoveVar($top_prefix.'_modified'); return ''; } $block_name = $this->SelectParam($params, 'render_as,name'); if ($block_name) { $block_params = $this->prepareTagParams($params); $block_params['name'] = $block_name; $block_params['edit_mode'] = $temp_tables ? 1 : 0; $block_params['display'] = $temp_tables && $modified ? 1 : 0; return $this->Application->ParseBlock($block_params); } else { return $temp_tables && $modified ? 1 : 0; } return ; } /** * Allows to detect if this prefix has something in clipboard * * @param Array $params * @return bool */ function HasClipboard($params) { $clipboard = $this->Application->RecallVar('clipboard'); if ($clipboard) { $clipboard = unserialize($clipboard); foreach ($clipboard as $prefix => $clipboard_data) { foreach ($clipboard_data as $mode => $ids) { if (count($ids)) return 1; } } } return 0; } /** * Allows to detect if root category being edited * * @param Array $params */ function IsRootCategory($params) { $object =& $this->getObject($params); return $object->IsRoot(); } /** * Returns home category id * * @param Array $params * @return int */ function HomeCategory($params) { - static $root_category = null; - - if (!isset($root_category)) { - $root_category = $this->Application->findModule('Name', 'Core', 'RootCat'); - } - - return $root_category; + return $this->Application->getBaseCategory(); } /** * Used for disabling "Home" and "Up" buttons in category list * * @param Array $params * @return bool */ function ModuleRootCategory($params) { - return $this->Application->GetVar('m_cat_id') == $this->HomeCategory($params); + return $this->Application->GetVar('m_cat_id') == $this->Application->getBaseCategory(); } function CatalogItemCount($params) { $params['skip_quering'] = true; $object =& $this->GetList($params); if (!$object->Counted) { $object->CountRecs(); } return $object->NoFilterCount != $object->RecordsCount ? $object->RecordsCount.' / '.$object->NoFilterCount : $object->RecordsCount; } function InitCatalog($params) { $tab_prefixes = $this->Application->GetVar('tp'); // {all, <prefixes_list>, none} if ($tab_prefixes === false) $tab_prefixes = 'all'; $skip_prefixes = isset($params['skip_prefixes']) && $params['skip_prefixes'] ? explode(',', $params['skip_prefixes']) : Array(); $replace_main = isset($params['replace_m']) && $params['replace_m']; // get all prefixes available $prefixes = Array(); foreach ($this->Application->ModuleInfo as $module_name => $module_data) { $prefix = $module_data['Var']; if ($prefix == 'adm'/* || $prefix == 'm'*/) continue; if ($prefix == 'm' && $replace_main) { $prefix = 'c'; } $prefixes[] = $prefix; } if ($tab_prefixes == 'none') { $skip_prefixes = array_unique(array_merge($skip_prefixes, $prefixes)); unset($skip_prefixes[ array_search($replace_main ? 'c' : 'm', $skip_prefixes) ]); } elseif ($tab_prefixes != 'all') { // prefix list here $tab_prefixes = explode(',', $tab_prefixes); // list of prefixes that should stay $skip_prefixes = array_unique(array_merge($skip_prefixes, array_diff($prefixes, $tab_prefixes))); } $params['name'] = $params['render_as']; $params['skip_prefixes'] = implode(',', $skip_prefixes); return $this->Application->ParseBlock($params); } /** * Determines, that printed category/menu item is currently active (will also match parent category) * * @param Array $params * @return bool */ function IsActive($params) { static $current_path = null; if (!isset($current_path)) { $sql = 'SELECT ParentPath FROM ' . TABLE_PREFIX . 'Category WHERE CategoryId = ' . (int)$this->Application->GetVar('m_cat_id'); $current_path = $this->Conn->GetOne($sql); } if (array_key_exists('parent_path', $params)) { $test_path = $params['parent_path']; } else { $template = $params['template']; if ($template) { // when using from "c:CachedMenu" tag $sql = 'SELECT ParentPath FROM ' . TABLE_PREFIX . 'Category WHERE NamedParentPath = ' . $this->Conn->qstr('Content/' . $template); $test_path = $this->Conn->GetOne($sql); } else { // when using from "c:PrintList" tag $cat_id = array_key_exists('cat_id', $params) && $params['cat_id'] ? $params['cat_id'] : false; if ($cat_id === false) { // category not supplied -> get current from PrintList $category =& $this->getObject($params); } else { if ("$cat_id" == 'Root') { $cat_id = $this->Application->findModule('Name', $params['module'], 'RootCat'); } $category =& $this->Application->recallObject($this->Prefix . '.-c' . $cat_id, $this->Prefix, Array ('skip_autoload' => true)); $category->Load($cat_id); } $test_path = $category->GetDBField('ParentPath'); } } return strpos($current_path, $test_path) !== false; } /** * Checks if user have one of required permissions * * @param Array $params * @return bool */ function HasPermission($params) { $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $params['raise_warnings'] = 0; $object =& $this->getObject($params); /* @var $object kDBItem */ $params['cat_id'] = $object->isLoaded() ? $object->GetDBField('ParentPath') : $this->Application->GetVar('m_cat_id'); return $perm_helper->TagPermissionCheck($params); } /** * Prepares name for field with event in it (used only on front-end) * * @param Array $params * @return string */ function SubmitName($params) { return 'events[' . $this->Prefix . '][' . $params['event'] . ']'; } /** * Returns last modification date of items in category / system * * @param Array $params * @return string */ function LastUpdated($params) { $category_id = (int)$this->Application->GetVar('m_cat_id'); $local = array_key_exists('local', $params) && ($category_id > 0) ? $params['local'] : false; $serial_name = $this->Application->incrementCacheSerial('c', $local ? $category_id : null, false); $cache_key = 'category_last_updated[%' . $serial_name . '%]'; $row_data = $this->Application->getCache($cache_key); if ($row_data === false) { if ($local && ($category_id > 0)) { // scan only current category & it's children list ($tree_left, $tree_right) = $this->Application->getTreeIndex($category_id); $sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate FROM ' . TABLE_PREFIX . 'Category WHERE TreeLeft BETWEEN ' . $tree_left . ' AND ' . $tree_right; } else { // scan all categories in system $sql = 'SELECT MAX(Modified) AS ModDate, MAX(CreatedOn) AS NewDate FROM ' . TABLE_PREFIX . 'Category'; } $this->Conn->nextQueryCachable = true; $row_data = $this->Conn->GetRow($sql); $this->Application->setCache($cache_key, $row_data); } if (!$row_data) { return ''; } $date = $row_data[ $row_data['NewDate'] > $row_data['ModDate'] ? 'NewDate' : 'ModDate' ]; // format date $format = isset($params['format']) ? $params['format'] : '_regional_DateTimeFormat'; if (preg_match("/_regional_(.*)/", $format, $regs)) { $lang =& $this->Application->recallObject('lang.current'); if ($regs[1] == 'DateTimeFormat') { // combined format $format = $lang->GetDBField('DateFormat') . ' ' . $lang->GetDBField('TimeFormat'); } else { // simple format $format = $lang->GetDBField($regs[1]); } } return adodb_date($format, $date); } function CategoryItemCount($params) { $object =& $this->getObject($params); /* @var $object kDBList */ $params['cat_id'] = $object->GetID(); $count_helper =& $this->Application->recallObject('CountHelper'); /* @var $count_helper kCountHelper */ return $count_helper->CategoryItemCount($params['prefix'], $params); } /** * Returns prefix + any word (used for shared between categories per page settings) * * @param Array $params * @return string */ function VarName($params) { return $this->Prefix.'_'.$params['type']; } /** * Checks if current category is valid symbolic link to another category * * @param Array $params * @return string */ function IsCategorySymLink($params) { $object =& $this->getObject($params); /* @var $object kDBList */ $sym_category_id = $object->GetDBField('SymLinkCategoryId'); if (is_null($sym_category_id)) { return false; } $id_field = $this->Application->getUnitOption($this->Prefix, 'IDField'); $table_name = $this->Application->getUnitOption($this->Prefix, 'TableName'); $sql = 'SELECT '.$id_field.' FROM '.$table_name.' WHERE '.$id_field.' = '.$sym_category_id; return $this->Conn->GetOne($sql)? true : false; } /** * Returns module prefix based on root category for given * * @param Array $params * @return string */ function GetModulePrefix($params) { $object =& $this->getObject($params); /* @var $object kDBItem */ $parent_path = explode('|', substr($object->GetDBField('ParentPath'), 1, -1)); $category_helper =& $this->Application->recallObject('CategoryHelper'); /* @var $category_helper CategoryHelper */ $module_info = $category_helper->getCategoryModule($params, $parent_path); return $module_info['Var']; } function ImageSrc($params) { list ($ret, $tag_processed) = $this->processAggregatedTag('ImageSrc', $params, $this->getPrefixSpecial()); return $tag_processed ? $ret : false; } function PageLink($params) { $params['m_cat_page'] = $this->Application->GetVar($this->getPrefixSpecial() . '_Page'); return parent::PageLink($params); } /** * Returns spelling suggestions against search keyword * * @param Array $params * @return string */ function SpellingSuggestions($params) { $keywords = unhtmlentities( trim($this->Application->GetVar('keywords')) ); if (!$keywords) { return ; } // 1. try to get already cached suggestion $cache_key = 'search.suggestion[%SpellingDictionary%]:' . $keywords; $suggestion = $this->Application->getCache($cache_key); if ($suggestion !== false) { return $suggestion; } $table_name = $this->Application->getUnitOption('spelling-dictionary', 'TableName'); // 2. search suggestion in database $this->Conn->nextQueryCachable = true; $sql = 'SELECT SuggestedCorrection FROM ' . $table_name . ' WHERE MisspelledWord = ' . $this->Conn->qstr($keywords); $suggestion = $this->Conn->GetOne($sql); if ($suggestion !== false) { $this->Application->setCache($cache_key, $suggestion); return $suggestion; } // 3. suggestion not found in database, ask webservice $app_id = $this->Application->ConfigValue('YahooApplicationId'); $url = 'http://search.yahooapis.com/WebSearchService/V1/spellingSuggestion?appid=' . $app_id . '&query='; $curl_helper =& $this->Application->recallObject('CurlHelper'); /* @var $curl_helper kCurlHelper */ $xml_data = $curl_helper->Send($url . urlencode($keywords)); $xml_helper =& $this->Application->recallObject('kXMLHelper'); /* @var $xml_helper kXMLHelper */ $root_node =& $xml_helper->Parse($xml_data); $result = $root_node->FindChild('RESULT'); /* @var $result kXMLNode */ if (is_object($result)) { // webservice responded -> save in local database $fields_hash = Array ( 'MisspelledWord' => $keywords, 'SuggestedCorrection' => $result->Data, ); $this->Conn->doInsert($fields_hash, $table_name); $this->Application->setCache($cache_key, $result->Data); return $result->Data; } return ''; } /** * Shows link for searching by suggested word * * @param Array $params * @return string */ function SuggestionLink($params) { $params['keywords'] = $this->SpellingSuggestions($params); return $this->Application->ProcessParsedTag('m', 'Link', $params); } function InitCatalogTab($params) { $tab_params['mode'] = $this->Application->GetVar('tm'); // single/multi selection possible $tab_params['special'] = $this->Application->GetVar('ts'); // use special for this tab $tab_params['dependant'] = $this->Application->GetVar('td'); // is grid dependant on categories grid // set default params (same as in catalog) if ($tab_params['mode'] === false) $tab_params['mode'] = 'multi'; if ($tab_params['special'] === false) $tab_params['special'] = ''; if ($tab_params['dependant'] === false) $tab_params['dependant'] = 'yes'; // pass params to block with tab content $params['name'] = $params['render_as']; $special = $tab_params['special'] ? $tab_params['special'] : $this->Special; $params['prefix'] = trim($this->Prefix.'.'.$special, '.'); $prefix_append = $this->Application->GetVar('prefix_append'); if ($prefix_append) { $params['prefix'] .= $prefix_append; } $default_grid = array_key_exists('default_grid', $params) ? $params['default_grid'] : 'Default'; $radio_grid = array_key_exists('radio_grid', $params) ? $params['radio_grid'] : 'Radio'; $params['cat_prefix'] = trim('c.'.($tab_params['special'] ? $tab_params['special'] : $this->Special), '.'); $params['tab_mode'] = $tab_params['mode']; $params['grid_name'] = ($tab_params['mode'] == 'multi') ? $default_grid : $radio_grid; $params['tab_dependant'] = $tab_params['dependant']; $params['show_category'] = $tab_params['special'] == 'showall' ? 1 : 0; // this is advanced view -> show category name if ($special == 'showall' || $special == 'user') { $params['grid_name'] .= 'ShowAll'; } // use $pass_params to be able to pass 'tab_init' parameter from m_ModuleInclude tag return $this->Application->ParseBlock($params, 1); } /** * Show CachedNavbar of current item primary category * * @param Array $params * @return string */ function CategoryName($params) { // show category cachednavbar of $object =& $this->getObject($params); $category_id = isset($params['cat_id']) ? $params['cat_id'] : $object->GetDBField('CategoryId'); $cache_key = 'category_paths[%CIDSerial:' . $category_id . '%][%PhrasesSerial%][Adm:' . (int)$this->Application->isAdmin . ']'; $category_path = $this->Application->getCache($cache_key); if ($category_path === false) { // not chached if ($category_id > 0) { $cached_navbar = $object->GetField('CachedNavbar'); if ($category_id == $object->GetDBField('ParentId')) { // parent category cached navbar is one element smaller, then current ones $cached_navbar = explode('&|&', $cached_navbar); array_pop($cached_navbar); $cached_navbar = implode('&|&', $cached_navbar); } else { // no relation with current category object -> query from db $language_id = (int)$this->Application->GetVar('m_lang'); if (!$language_id) { $language_id = 1; } $sql = 'SELECT l' . $language_id . '_CachedNavbar FROM ' . $object->TableName . ' WHERE ' . $object->IDField . ' = ' . $category_id; $cached_navbar = $this->Conn->GetOne($sql); } $cached_navbar = preg_replace('/^(Content&\|&|Content)/i', '', $cached_navbar); $category_path = trim($this->CategoryName( Array('cat_id' => 0) ).' > '.str_replace('&|&', ' > ', $cached_navbar), ' > '); } else { $category_path = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name'); } $this->Application->setCache($cache_key, $category_path); } return $category_path; } // structure related /** * Returns page object based on requested params * * @param Array $params * @return PagesItem */ function &_getPage($params) { $page =& $this->Application->recallObject($this->Prefix . '.-virtual', null, $params); /* @var $page kDBItem */ // 1. load by given id $page_id = array_key_exists('page_id', $params) ? $params['page_id'] : false; if ($page_id) { if ($page_id != $page->GetID()) { // load if different $page->Load($page_id); } return $page; } // 2. load by template $template = array_key_exists('page', $params) ? $params['page'] : ''; if (!$template) { $template = $this->Application->GetVar('t'); } // different path in structure AND design template differes from requested template $structure_path_match = strtolower( $page->GetDBField('NamedParentPath') ) == strtolower('Content/' . $template); $design_match = $page->GetDBField('CachedTemplate') == $template; if (!$structure_path_match && !$design_match) { // Same sql like in "c:getPassedID". Load, when current page object doesn't match requested page object $themes_helper =& $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ $page_id = $themes_helper->getPageByTemplate($template); $page->Load($page_id); } return $page; } /** * Returns requested content block content of current or specified page * * @param Array $params * @return string */ function ContentBlock($params) { $num = getArrayValue($params, 'num'); if (!$num) { return 'NO CONTENT NUM SPECIFIED'; } $page =& $this->_getPage($params); /* @var $page kDBItem */ if (!$page->isLoaded()) { // page is not created yet => all blocks are empty return ''; } $page_id = $page->GetID(); $content =& $this->Application->recallObject('content.-block', null, Array ('skip_autoload' => true)); /* @var $content kDBItem */ $data = Array ('PageId' => $page_id, 'ContentNum' => $num); $content->Load($data); if (!$content->isLoaded()) { // bug: missing content blocks are created even if user have no SMS-management rights $content->SetFieldsFromHash($data); $content->Create(); } $edit_code_before = $edit_code_after = ''; if (EDITING_MODE == EDITING_MODE_CONTENT) { $bg_color = isset($params['bgcolor']) ? $params['bgcolor'] : '#ffffff'; $url_params = Array ( 'pass' => 'm,c,content', 'm_opener' => 'd', 'c_id' => $page->GetID(), 'content_id' => $content->GetID(), 'front' => 1, 'admin' => 1, '__URLENCODE__' => 1, '__NO_REWRITE__'=> 1, 'escape' => 1, 'index_file' => 'index.php', // 'bgcolor' => $bg_color, // '__FORCE_SID__' => 1 ); // link from Front-End to admin, don't remove "index.php" $edit_url = $this->Application->HREF('categories/edit_content', ADMIN_DIRECTORY, $url_params, 'index.php'); $edit_code_before = ' <div class="cms-edit-btn-container"> <div class="cms-edit-btn" onclick="$form_name=\'kf_cont_'.$content->GetID().'\'; std_edit_item(\'content\', \'categories/edit_content\');"> <div class="cms-btn-image"> <img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/content_mode.png" width="15" height="16" alt=""/> </div> <div class="cms-btn-text">' . $this->Application->Phrase('la_btn_EditContent', false, true) . ' '.(defined('DEBUG_MODE') && DEBUG_MODE ? " - #{$num}" : '').'</div> </div> <div class="cms-btn-content">'; $edit_form = '<form method="POST" style="display: inline; margin: 0px" name="kf_cont_'.$content->GetID().'" id="kf_cont_'.$content->GetID().'" action="'.$edit_url.'">'; $edit_form .= '<input type="hidden" name="c_id" value="'.$page->GetID().'"/>'; $edit_form .= '<input type="hidden" name="content_id" value="'.$content->GetID().'"/>'; $edit_form .= '<input type="hidden" name="front" value="1"/>'; $edit_form .= '<input type="hidden" name="bgcolor" value="'.$bg_color.'"/>'; $edit_form .= '<input type="hidden" name="m_lang" value="'.$this->Application->GetVar('m_lang').'"/>'; $edit_form .= '</form>'; $edit_code_after = '</div></div>'; if (array_key_exists('forms_later', $params) && $params['forms_later']) { $all_forms = $this->Application->GetVar('all_forms'); $this->Application->SetVar('all_forms', $all_forms . $edit_form); } else { $edit_code_after .= $edit_form; } } if ($this->Application->GetVar('_editor_preview_') == 1) { $data = $this->Application->RecallVar('_editor_preview_content_'); } else { $data = $content->GetField('Content'); } $data = $edit_code_before . $this->_transformContentBlockData($data, $params) . $edit_code_after; if ($data != '') { $this->Application->Parser->DataExists = true; } return $data; } /** * Apply all kinds of content block data transformations without rewriting ContentBlock tag * * @param string $data * @return string */ function _transformContentBlockData(&$data, $params) { return $data; } /** * Returns current page name or page based on page/page_id parameters * * @param Array $params * @return string * @todo Used? */ function PageName($params) { $page =& $this->_getPage($params); return $page->GetDBField('Name'); } /** * Returns current/given page information * * @param Array $params * @return string */ function PageInfo($params) { $page =& $this->_getPage($params); switch ($params['type']) { case 'title': $db_field = 'Title'; break; case 'htmlhead_title': $db_field = 'Name'; break; case 'meta_title': $db_field = 'MetaTitle'; break; case 'menu_title': $db_field = 'MenuTitle'; break; case 'meta_keywords': $db_field = 'MetaKeywords'; $cat_field = 'Keywords'; break; case 'meta_description': $db_field = 'MetaDescription'; $cat_field = 'Description'; break; case 'tracking': case 'index_tools': if (!EDITING_MODE) { $tracking = $page->GetDBField('IndexTools'); return $tracking ? $tracking : $this->Application->ConfigValue('cms_DefaultTrackingCode'); } // no break here on purpose default: return ''; } $default = isset($params['default']) ? $params['default'] : ''; $val = $page->GetField($db_field); if (!$default) { if ($this->Application->isModuleEnabled('In-Portal')) { if (!$val && ($params['type'] == 'meta_keywords' || $params['type'] == 'meta_description')) { // take category meta if it's not set for the page return $this->Application->ProcessParsedTag('c', 'Meta', Array('name' => $cat_field)); } } } if (isset($params['force_default']) && $params['force_default']) { return $default; } if (preg_match('/^_Auto:/', $val)) { $val = $default; /*if ($db_field == 'Title') { $page->SetDBField($db_field, $default); $page->Update(); }*/ } elseif ($page->GetID() == false) { return $default; } return $val; } /** * Includes admin css and js, that are required for cms usage on Front-Edn * * * @param Array $params * @return string */ function EditingScripts($params) { if ($this->Application->GetVar('admin_scripts_included') || !EDITING_MODE) { return ; } $this->Application->SetVar('admin_scripts_included', 1); $js_url = $this->Application->BaseURL() . 'core/admin_templates/js'; $minify_helper =& $this->Application->recallObject('MinifyHelper'); /* @var $minify_helper MinifyHelper */ $to_compress = Array ( $js_url . '/jquery/thickbox/thickbox.css', $js_url . '/../incs/cms.css', ); $css_compressed = $minify_helper->CompressScriptTag( Array ('files' => implode('|', $to_compress)) ); $ret = '<link rel="stylesheet" href="' . $css_compressed . '" type="text/css" media="screen"/>' . "\n"; if (EDITING_MODE == EDITING_MODE_DESIGN) { $ret .= ' <style type="text/css" media="all"> div.movable-element .movable-header { cursor: move; } </style>'; } $ret .= '<script type="text/javascript" src="' . $js_url . '/jquery/jquery.pack.js"></script>' . "\n"; $ret .= '<script type="text/javascript" src="' . $js_url . '/jquery/jquery-ui.custom.min.js"></script>' . "\n"; $to_compress = Array ( $js_url . '/is.js', $js_url . '/application.js', $js_url . '/script.js', $js_url . '/jquery/thickbox/thickbox.js', $js_url . '/template_manager.js', ); $js_compressed = $minify_helper->CompressScriptTag( Array ('files' => implode('|', $to_compress)) ); $ret .= '<script type="text/javascript" src="' . $js_compressed . '"></script>' . "\n"; $ret .= '<script language="javascript">' . "\n"; $ret .= "TB.pathToImage = '" . $js_url . "/jquery/thickbox/loadingAnimation.gif';" . "\n"; $template = $this->Application->GetVar('t'); $theme_id = $this->Application->GetVar('m_theme'); $url_params = Array ('block' => '#BLOCK#', 'theme-file_event' => '#EVENT#', 'theme_id' => $theme_id, 'source' => $template, 'pass' => 'all,theme-file', 'front' => 1, 'm_opener' => 'd', '__NO_REWRITE__' => 1, 'no_amp' => 1); $edit_template_url = $this->Application->HREF('themes/template_edit', ADMIN_DIRECTORY, $url_params, 'index.php'); $url_params = Array ('theme-file_event' => 'OnSaveLayout', 'source' => $template, 'pass' => 'all,theme-file', '__NO_REWRITE__' => 1, 'no_amp' => 1); $save_layout_url = $this->Application->HREF('index', '', $url_params); $this_url = $this->Application->HREF('', '', Array ('editing_mode' => '#EDITING_MODE#', '__NO_REWRITE__' => 1, 'no_amp' => 1)); $ret .= "var aTemplateManager = new TemplateManager('" . $edit_template_url . "', '" . $this_url . "', '" . $save_layout_url . "', " . (int)EDITING_MODE . ");\n"; $ret .= "var main_title = '" . addslashes( $this->Application->ConfigValue('Site_Name') ) . "';" . "\n"; $use_popups = (int)$this->Application->ConfigValue('UsePopups'); $ret .= "var \$use_popups = " . ($use_popups > 0 ? 'true' : 'false') . ";\n"; $ret .= "var \$modal_windows = " . ($use_popups == 2 ? 'true' : 'false') . ";\n"; if (EDITING_MODE != EDITING_MODE_BROWSE) { $ret .= "var base_url = '" . $this->Application->BaseURL() . "';" . "\n"; $ret .= 'TB.closeHtml = \'<img src="' . $js_url . '/../img/close_window15.gif" width="15" height="15" style="border-width: 0px;" alt="close"/><br/>\';' . "\n"; $url_params = Array('m_theme' => '', 'pass' => 'm', 'm_opener' => 'r', '__NO_REWRITE__' => 1, 'no_amp' => 1); $browse_url = $this->Application->HREF('catalog/catalog', ADMIN_DIRECTORY, $url_params, 'index.php'); $browse_url = preg_replace('/&(admin|editing_mode)=[\d]/', '', $browse_url); $ret .= ' var topmost = window.top; topmost.document.title = document.title + \' - ' . addslashes($this->Application->Phrase('la_AdministrativeConsole', false)) . '\'; t = \''.$this->Application->GetVar('t').'\'; if (window.parent.frames["menu"] != undefined) { if ( $.isFunction(window.parent.frames["menu"].SyncActive) ) { window.parent.frames["menu"].SyncActive("' . $browse_url . '"); } } '; } $ret .= '</script>' . "\n"; if (EDITING_MODE != EDITING_MODE_BROWSE) { // add form, so admin scripts could work $ret .= '<form id="kernel_form" name="kernel_form" enctype="multipart/form-data" method="post" action="' . $browse_url . '"> <input type="hidden" name="MAX_FILE_SIZE" id="MAX_FILE_SIZE" value="' . MAX_UPLOAD_SIZE . '" /> <input type="hidden" name="sid" id="sid" value="' . $this->Application->GetSID() . '" /> </form>'; } return $ret; } /** * Prints "Edit Page" button on cms page * * @param Array $params * @return string */ function EditPage($params) { if (!EDITING_MODE) { return ''; } $display_mode = array_key_exists('mode', $params) ? $params['mode'] : false; $edit_code = ''; $page =& $this->_getPage($params); if (!$page->isLoaded() || (($display_mode != 'end') && (EDITING_MODE == EDITING_MODE_BROWSE))) { // when "EditingScripts" tag is not used, make sure, that scripts are also included return $this->EditingScripts($params); } // show "EditPage" button only for pages, that exists in structure if ($display_mode != 'end') { $edit_btn = ''; if (EDITING_MODE == EDITING_MODE_CONTENT) { $url_params = Array( 'pass' => 'm,c', 'm_opener' => 'd', 'c_id' => $page->GetID(), 'c_mode' => 't', 'c_event' => 'OnEdit', 'front' => 1, '__URLENCODE__' => 1, '__NO_REWRITE__'=> 1, 'index_file' => 'index.php', ); $edit_url = $this->Application->HREF('categories/categories_edit', ADMIN_DIRECTORY, $url_params); $edit_btn .= ' <div class="cms-section-properties-btn"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . ' onmouseover="window.status=\'' . addslashes($edit_url) . '\'; return true" onclick="$form_name=\'kf_'.$page->GetID().'\'; std_edit_item(\'c\', \'categories/categories_edit\');"> <div class="cms-btn-image"> <img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/section_properties.png" width="15" height="16" alt=""/> </div> <div class="cms-btn-text">' . $this->Application->Phrase('la_btn_SectionProperties', false, true) . '</div> </div>' . "\n"; } elseif (EDITING_MODE == EDITING_MODE_DESIGN) { $url_params = Array( 'pass' => 'm,theme,theme-file', 'm_opener' => 'd', 'theme_id' => $this->Application->GetVar('m_theme'), 'theme_mode' => 't', 'theme_event' => 'OnEdit', 'theme-file_id' => $this->_getThemeFileId(), 'front' => 1, '__URLENCODE__' => 1, '__NO_REWRITE__'=> 1, 'index_file' => 'index.php', ); $edit_url = $this->Application->HREF('themes/file_edit', ADMIN_DIRECTORY, $url_params); $edit_btn .= ' <div class="cms-layout-btn-container"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . '> <div class="cms-save-layout-btn" onclick="aTemplateManager.saveLayout(); return false;"> <div class="cms-btn-image"> <img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/save_button.gif" width="16" height="16" alt=""/> </div> <div class="cms-btn-text">' . $this->Application->Phrase('la_btn_SaveChanges', false, true) . '</div> </div> <div class="cms-cancel-layout-btn" onclick="aTemplateManager.cancelLayout(); return false;"> <div class="cms-btn-image"> <img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/cancel_button.gif" width="16" height="16" alt=""/> </div> <div class="cms-btn-text">' . $this->Application->Phrase('la_btn_Cancel', false, true) . '</div> </div> </div> <div class="cms-section-properties-btn"' . ($display_mode === false ? ' style="margin: 0px;"' : '') . ' onmouseover="window.status=\'' . addslashes($edit_url) . '\'; return true" onclick="$form_name=\'kf_'.$page->GetID().'\'; std_edit_item(\'theme\', \'themes/file_edit\');"> <div class="cms-btn-image"> <img src="' . $this->Application->BaseURL() . 'core/admin_templates/img/top_frame/icons/section_properties.png" width="15" height="16" alt=""/> </div> <div class="cms-btn-text">' . $this->Application->Phrase('la_btn_SectionTemplate', false, true) . '</div> </div>' . "\n"; } if ($display_mode == 'start') { // button with border around the page $edit_code .= '<div class="cms-section-properties-btn-container">' . $edit_btn . '<div class="cms-btn-content">'; } else { // button without border around the page $edit_code .= $edit_btn; } } if ($display_mode == 'end') { // draw border around the page $edit_code .= '</div></div>'; } if ($display_mode != 'end') { $edit_code .= '<form method="POST" style="display: inline; margin: 0px" name="kf_'.$page->GetID().'" id="kf_'.$page->GetID().'" action="'.$edit_url.'"></form>'; // when "EditingScripts" tag is not used, make sure, that scripts are also included $edit_code .= $this->EditingScripts($params); } return $edit_code; } function _getThemeFileId() { $template = $this->Application->GetVar('t'); if (!$this->Application->TemplatesCache->TemplateExists($template) && !$this->Application->isAdmin) { $cms_handler =& $this->Application->recallObject($this->Prefix . '_EventHandler'); /* @var $cms_handler CategoriesEventHandler */ $template = ltrim($cms_handler->GetDesignTemplate(), '/'); } $file_path = dirname($template) == '.' ? '' : '/' . dirname($template); $file_name = basename($template); $sql = 'SELECT FileId FROM ' . TABLE_PREFIX . 'ThemeFiles WHERE (ThemeId = ' . (int)$this->Application->GetVar('m_theme') . ') AND (FilePath = ' . $this->Conn->qstr($file_path) . ') AND (FileName = ' . $this->Conn->qstr($file_name . '.tpl') . ')'; return $this->Conn->GetOne($sql); } /** * Builds site menu * * @param Array $params * @return string */ function CachedMenu($params) { $menu_helper =& $this->Application->recallObject('MenuHelper'); /* @var $menu_helper MenuHelper */ return $menu_helper->menuTag($this->getPrefixSpecial(), $params); } /** * Trick to allow some kind of output formatting when using CachedMenu tag * * @param Array $params * @return bool */ function SplitColumn($params) { return $this->Application->GetVar($params['i']) > ceil($params['total'] / $params['columns']); } /** * Returns direct children count of given category * * @param Array $params * @return int */ function HasSubCats($params) { $sql = 'SELECT COUNT(*) FROM ' . TABLE_PREFIX . 'Category WHERE ParentId = ' . $params['cat_id']; return $this->Conn->GetOne($sql); } /** * Prints sub-pages of given/current page. * * @param Array $params * @return string * @todo This could be reached by using "parent_cat_id" parameter. Only difference here is new block parameter "path". Need to rewrite. */ function PrintSubPages($params) { $list =& $this->Application->recallObject($this->getPrefixSpecial(), $this->Prefix.'_List', $params); /* @var $list kDBList */ $category_id = array_key_exists('category_id', $params) ? $params['category_id'] : $this->Application->GetVar('m_cat_id'); $list->addFilter('current_pages', TABLE_PREFIX . 'CategoryItems.CategoryId = ' . $category_id); $list->Query(); $list->GoFirst(); $o = ''; $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['render_as']; while (!$list->EOL()) { $block_params['path'] = $list->GetDBField('Path'); $o .= $this->Application->ParseBlock($block_params); $list->GoNext(); } return $o; } /** * Builds link for browsing current page on Front-End * * @param Array $params * @return string */ function PageBrowseLink($params) { $object =& $this->getObject($params); $themes_helper =& $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ $site_config_helper =& $this->Application->recallObject('SiteConfigHelper'); /* @var $site_config_helper SiteConfigHelper */ $settings = $site_config_helper->getSettings(); $url_params = Array ( 'm_cat_id' => $object->GetID(), 'm_theme' => $themes_helper->getCurrentThemeId(), 'editing_mode' => $settings['default_editing_mode'], 'pass' => 'm', 'admin' => 1, 'index_file' => 'index.php' ); if ($this->Application->ConfigValue('UseModRewrite')) { $url_params['__MOD_REWRITE__'] = 1; } return $this->Application->HREF($object->GetDBField('NamedParentPath'), '_FRONT_END_', $url_params); } /** * Builds link to cms page (used?) * * @param Array $params * @return string */ function ContentPageLink($params) { $object =& $this->getObject($params); $params['t'] = $object->GetDBField('NamedParentPath'); $params['m_cat_id'] = 0; return $this->Application->ProcessParsedTag('m', 'Link', $params); } /** * Prepares cms page description for search result page * * @param Array $params * @return string */ function SearchDescription($params) { $object =& $this->getObject($params); $desc = $object->GetField('MetaDescription'); if (!$desc) { $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'PageContent WHERE PageId = ' . $object->GetID() . ' AND ContentNum = 1'; $content = $this->Conn->GetRow($sql); if ($content['l'.$this->Application->GetVar('m_lang').'_Content']) { $desc = $content['l'.$this->Application->GetVar('m_lang').'_Content']; } else { $desc = $content['l'.$this->Application->GetDefaultLanguageId().'_Content']; } } return mb_substr($desc, 0, 300).(mb_strlen($desc) > 300 ? '...' : ''); } /** * Simplified version of "c:CategoryLink" for "c:PrintList" * * @param Array $params * @return string * @todo Used? Needs refactoring. */ function EnterCatLink($params) { $object =& $this->getObject($params); $url_params = Array ('pass' => 'm', 'm_cat_id' => $object->GetID()); return $this->Application->HREF($params['template'], '', $url_params); } /** * Simplified version of "c:CategoryPath", that do not use blocks for rendering * * @param Array $params * @return string * @todo Used? Maybe needs to be removed. */ function PagePath($params) { $object =& $this->getObject($params); $path = $object->GetField('CachedNavbar'); if ($path) { $items = explode('&|&', $path); array_shift($items); return implode(' -> ', $items); } return ''; } /** * Returns configuration variable value * * @param Array $params * @return string * @todo Needs to be replaced with "m:GetConfig" tag; Not used now (were used on structure_edit.tpl). */ function AllowManualFilenames($params) { return $this->Application->ConfigValue('ProjCMSAllowManualFilenames'); } /** * Draws path to current page (each page can be link to it) * * @param Array $params * @return string */ function CurrentPath($params) { $block_params = $this->prepareTagParams($params); $block_params['name'] = $block_params['render_as']; $object =& $this->Application->recallObject($this->Prefix); /* @var $object kDBItem */ $category_ids = explode('|', substr($object->GetDBField('ParentPath'), 1, -1)); $id_field = $this->Application->getUnitOption($this->Prefix, 'IDField'); $table_name = $this->Application->getUnitOption($this->Prefix, 'TableName'); $language = (int)$this->Application->GetVar('m_lang'); if (!$language) { $language = 1; } $sql = 'SELECT l'.$language.'_Name AS Name, NamedParentPath FROM '.$table_name.' WHERE '.$id_field.' IN ('.implode(',', $category_ids).')'; $categories_data = $this->Conn->Query($sql); $ret = ''; foreach ($categories_data as $index => $category_data) { if ($category_data['Name'] == 'Content') { continue; } $block_params['title'] = $category_data['Name']; $block_params['template'] = preg_replace('/^Content\//i', '', $category_data['NamedParentPath']); $block_params['is_first'] = $index == 1; // because Content is 1st element $block_params['is_last'] = $index == count($categories_data) - 1; $ret .= $this->Application->ParseBlock($block_params); } return $ret; } /** * Synonim to PrintList2 for "onlinestore" theme * * @param Array $params * @return string */ function ListPages($params) { return $this->PrintList2($params); } /** * Returns information about parser element locations in template * * @param Array $params * @return mixed */ function BlockInfo($params) { if (!EDITING_MODE) { return ''; } $template_helper =& $this->Application->recallObject('TemplateHelper'); /* @var $template_helper TemplateHelper */ return $template_helper->blockInfo( $params['name'] ); } /** * Hide all editing tabs except permission tab, when editing "Home" (ID = 0) category * * @param Array $params */ function ModifyUnitConfig($params) { $root_category = $this->Application->RecallVar('IsRootCategory_' . $this->Application->GetVar('m_wid')); if (!$root_category) { return ; } $edit_tab_presets = $this->Application->getUnitOption($this->Prefix, 'EditTabPresets'); $edit_tab_presets['Default'] = Array ( 'permissions' => $edit_tab_presets['Default']['permissions'], ); $this->Application->setUnitOption($this->Prefix, 'EditTabPresets', $edit_tab_presets); } /** * Prints catalog export templates * * @param Array $params * @return string */ function PrintCatalogExportTemplates($params) { $prefixes = explode(',', $params['prefixes']); $ret = Array (); foreach ($prefixes as $prefix) { if ($this->Application->prefixRegistred($prefix)) { $module_path = $this->Application->getUnitOption($prefix, 'ModuleFolder') . '/'; $module_name = $this->Application->findModule('Path', $module_path, 'Name'); $ret[$prefix] = mb_strtolower($module_name) . '/export'; } } $json_helper =& $this->Application->recallObject('JSONHelper'); /* @var $json_helper JSONHelper */ return $json_helper->encode($ret); } /** * Checks, that "view in browse mode" functionality available * * @param Array $params * @return bool */ function BrowseModeAvailable($params) { $valid_special = $params['Special'] != 'user'; $not_selector = $this->Application->GetVar('type') != 'item_selector'; return $valid_special && $not_selector; } /** * Returns a link for editing product * * @param Array $params * @return string */ function ItemEditLink($params) { $object =& $this->getObject(); /* @var $object kDBList */ $edit_template = $this->Application->getUnitOption($this->Prefix, 'AdminTemplatePath') . '/' . $this->Application->getUnitOption($this->Prefix, 'AdminTemplatePrefix') . 'edit'; $url_params = Array ( 'm_opener' => 'd', $this->Prefix.'_mode' => 't', $this->Prefix.'_event' => 'OnEdit', $this->Prefix.'_id' => $object->GetID(), 'm_cat_id' => $object->GetDBField('ParentId'), 'pass' => 'all,'.$this->Prefix, 'no_pass_through' => 1, ); return $this->Application->HREF($edit_template,'', $url_params); } function RelevanceIndicator($params) { $object =& $this->getObject($params); $search_results_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search'; $sql = 'SELECT Relevance FROM '.$search_results_table.' WHERE ResourceId = '.$object->GetDBField('ResourceId'); $percents_off = (int)(100 - (100 * $this->Conn->GetOne($sql))); $percents_off = ($percents_off < 0) ? 0 : $percents_off; if ($percents_off) { $params['percent_off'] = $percents_off; $params['percent_on'] = 100 - $percents_off; $params['name'] = $this->SelectParam($params, 'relevance_normal_render_as,block_relevance_normal'); } else { $params['name'] = $this->SelectParam($params, 'relevance_full_render_as,block_relevance_full'); } return $this->Application->ParseBlock($params); } /** * Returns list of categories, that have category add/edit permission * * @param Array $params * @return string */ function AllowedCategoriesJSON($params) { if ($this->Application->RecallVar('user_id') == USER_ROOT) { $categories = true; } else { $object =& $this->getObject($params); /* @var $object kDBItem */ $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $perm_prefix = $this->Application->getUnitOption($this->Prefix, 'PermItemPrefix'); $categories = $perm_helper->getPermissionCategories($perm_prefix . '.' . ($object->IsNewItem() ? 'ADD' : 'MODIFY')); } $json_helper =& $this->Application->recallObject('JSONHelper'); /* @var $json_helper JSONHelper */ return $json_helper->encode($categories); } function PageEditable($params) { if ($this->Application->isDebugMode()) { return true; } $object =& $this->getObject($params); /* @var $object kDBItem */ return !$object->GetDBField('Protected'); } } \ No newline at end of file Index: branches/5.1.x/core/units/categories/categories_event_handler.php =================================================================== --- branches/5.1.x/core/units/categories/categories_event_handler.php (revision 13986) +++ branches/5.1.x/core/units/categories/categories_event_handler.php (revision 13987) @@ -1,2441 +1,2441 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class CategoriesEventHandler extends kDBEventHandler { /** * Allows to override standart permission mapping * */ function mapPermissions() { parent::mapPermissions(); $permissions = Array ( 'OnRebuildCache' => Array ('self' => 'add|edit'), 'OnCopy' => Array ('self' => true), 'OnCut' => Array ('self' => 'edit'), 'OnPasteClipboard' => Array ('self' => true), 'OnPaste' => Array ('self' => 'add|edit', 'subitem' => 'edit'), 'OnRecalculatePriorities' => Array ('self' => 'add|edit'), // category ordering 'OnItemBuild' => Array ('self' => true), // always allow to view individual categories (regardless of CATEGORY.VIEW right) 'OnUpdatePreviewBlock' => Array ('self' => true), // for FCKEditor integration ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Categories are sorted using special sorting event * */ function mapEvents() { parent::mapEvents(); $events_map = Array ( 'OnMassMoveUp' => 'OnChangePriority', 'OnMassMoveDown' => 'OnChangePriority', ); $this->eventMethods = array_merge($this->eventMethods, $events_map); } /** * Checks permissions of user * * @param kEvent $event */ function CheckPermission(&$event) { if ($event->Name == 'OnResetCMSMenuCache') { // events from "Tools -> System Tools" section are controlled via that section "edit" permission $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $perm_value = $this->Application->CheckPermission('in-portal:service.edit'); return $perm_helper->finalizePermissionCheck($event, $perm_value); } if (!$this->Application->isAdmin) { if ($event->Name == 'OnSetSortingDirect') { // allow sorting on front event without view permission return true; } if ($event->Name == 'OnItemBuild') { $category_id = $this->getPassedID($event); if ($category_id == 0) { return true; } } } if (in_array($event->Name, $this->_getMassPermissionEvents())) { $items = $this->_getPermissionCheckInfo($event); $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ if (($event->Name == 'OnSave') && array_key_exists(0, $items)) { // adding new item (ID = 0) $perm_value = $perm_helper->AddCheckPermission($items[0]['ParentId'], $event->Prefix) > 0; } else { // leave only items, that can be edited $ids = Array (); $check_method = in_array($event->Name, Array ('OnMassDelete', 'OnCut')) ? 'DeleteCheckPermission' : 'ModifyCheckPermission'; foreach ($items as $item_id => $item_data) { if ($perm_helper->$check_method($item_data['CreatedById'], $item_data['ParentId'], $event->Prefix) > 0) { $ids[] = $item_id; } } if (!$ids) { // no items left for editing -> no permission return $perm_helper->finalizePermissionCheck($event, false); } $perm_value = true; $event->setEventParam('ids', $ids); // will be used later by "kDBEventHandler::StoreSelectedIDs" method } return $perm_helper->finalizePermissionCheck($event, $perm_value); } if ($event->Name == 'OnPasteClipboard') { // forces permission check to work by current category for "Paste In Category" operation $category_id = $this->Application->GetVar('m_cat_id'); $this->Application->SetVar('c_id', $category_id); } return parent::CheckPermission($event); } /** * Returns events, that require item-based (not just event-name based) permission check * * @return Array */ function _getMassPermissionEvents() { return Array ( 'OnEdit', 'OnSave', 'OnMassDelete', 'OnMassApprove', 'OnMassDecline', 'OnMassMoveUp', 'OnMassMoveDown', 'OnCut', ); } /** * Returns category item IDs, that require permission checking * * @param kEvent $event * @return string */ function _getPermissionCheckIDs(&$event) { if ($event->Name == 'OnSave') { $selected_ids = implode(',', $this->getSelectedIDs($event, true)); if (!$selected_ids) { $selected_ids = 0; // when saving newly created item (OnPreCreate -> OnPreSave -> OnSave) } } else { // OnEdit, OnMassDelete events, when items are checked in grid $selected_ids = implode(',', $this->StoreSelectedIDs($event)); } return $selected_ids; } /** * Returns information used in permission checking * * @param kEvent $event * @return Array */ function _getPermissionCheckInfo(&$event) { // when saving data from temp table to live table check by data from temp table $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); $table_name = $this->Application->getUnitOption($event->Prefix, 'TableName'); if ($event->Name == 'OnSave') { $table_name = $this->Application->GetTempName($table_name, 'prefix:' . $event->Prefix); } $sql = 'SELECT ' . $id_field . ', CreatedById, ParentId FROM ' . $table_name . ' WHERE ' . $id_field . ' IN (' . $this->_getPermissionCheckIDs($event) . ')'; $items = $this->Conn->Query($sql, $id_field); if (!$items) { // when creating new category, then no IDs are stored in session $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); list ($id, $fields_hash) = each($items_info); if (array_key_exists('ParentId', $fields_hash)) { $item_category = $fields_hash['ParentId']; } else { $item_category = $this->Application->RecallVar('m_cat_id'); // saved in c:OnPreCreate event permission checking } $items[$id] = Array ( 'CreatedById' => $this->Application->RecallVar('user_id'), 'ParentId' => $item_category, ); } return $items; } /** * Set's mark, that root category is edited * * @param kEvent $event */ function OnEdit(&$event) { $category_id = $this->Application->GetVar($event->getPrefixSpecial() . '_id'); - $home_category = $this->Application->findModule('Name', 'Core', 'RootCat'); + $home_category = $this->Application->getBaseCategory(); $this->Application->StoreVar('IsRootCategory_'.$this->Application->GetVar('m_wid'), ($category_id === '0') || ($category_id == $home_category)); parent::OnEdit($event); } /** * Adds selected link to listing * * @param kEvent $event */ function OnProcessSelected(&$event) { $object =& $event->getObject(); /* @var $object kDBItem */ $selected_ids = $this->Application->GetVar('selected_ids'); $this->RemoveRequiredFields($object); $object->SetDBField($this->Application->RecallVar('dst_field'), $selected_ids['c']); $object->Update(); $this->finalizePopup($event); } /** * Apply system filter to categories list * * @param kEvent $event */ function SetCustomQuery(&$event) { parent::SetCustomQuery($event); $object =& $event->getObject(); /* @var $object kDBList */ // don't show "Content" category in advanced view $object->addFilter('system_categories', '%1$s.Status <> 4'); // show system templates from current theme only + all virtual templates $object->addFilter('theme_filter', '%1$s.ThemeId = ' . $this->_getCurrentThemeId() . ' OR %1$s.ThemeId = 0'); if ($event->Special == 'showall') { // if using recycle bin don't show categories from there $recycle_bin = $this->Application->ConfigValue('RecycleBinFolder'); if ($recycle_bin) { $sql = 'SELECT TreeLeft, TreeRight FROM '.TABLE_PREFIX.'Category WHERE CategoryId = '.$recycle_bin; $tree_indexes = $this->Conn->GetRow($sql); $object->addFilter('recyclebin_filter', '%1$s.TreeLeft < '.$tree_indexes['TreeLeft'].' OR %1$s.TreeLeft > '.$tree_indexes['TreeRight']); } } if ($event->getEventParam('parent_cat_id') !== false) { $parent_cat_id = $event->getEventParam('parent_cat_id'); if ("$parent_cat_id" == 'Root') { $module_name = $event->getEventParam('module') ? $event->getEventParam('module') : 'In-Commerce'; $parent_cat_id = $this->Application->findModule('Name', $module_name, 'RootCat'); } } else { $parent_cat_id = $this->Application->GetVar('c_id'); if (!$parent_cat_id) { $parent_cat_id = $this->Application->GetVar('m_cat_id'); } if (!$parent_cat_id) { $parent_cat_id = 0; } } if ("$parent_cat_id" == '0') { // replace "0" category with "Content" category id (this way template - $parent_cat_id = $this->Application->findModule('Name', 'Core', 'RootCat'); + $parent_cat_id = $this->Application->getBaseCategory(); } if ("$parent_cat_id" != 'any') { if ($event->getEventParam('recursive')) { if ($parent_cat_id > 0) { // not "Home" category $tree_indexes = $this->Application->getTreeIndex($parent_cat_id); $object->addFilter('parent_filter', TABLE_PREFIX.'Category.TreeLeft BETWEEN '.$tree_indexes['TreeLeft'].' AND '.$tree_indexes['TreeRight']); } } else { $object->addFilter('parent_filter', 'ParentId = '.$parent_cat_id); } } $object->addFilter('perm_filter', 'PermId = 1'); // check for CATEGORY.VIEW permission if ($this->Application->RecallVar('user_id') != USER_ROOT) { // apply permission filters to all users except "root" $groups = explode(',',$this->Application->RecallVar('UserGroups')); foreach ($groups as $group) { $view_filters[] = 'FIND_IN_SET('.$group.', acl)'; } $view_filter = implode(' OR ', $view_filters); $object->addFilter('perm_filter2', $view_filter); } if (!$this->Application->isAdminUser) { // apply status filter only on front $object->addFilter('status_filter', $object->TableName.'.Status = 1'); } // process "types" and "except" parameters $type_clauses = Array(); $types = $event->getEventParam('types'); $types = $types ? explode(',', $types) : Array (); $except_types = $event->getEventParam('except'); $except_types = $except_types ? explode(',', $except_types) : Array (); if (in_array('related', $types) || in_array('related', $except_types)) { $related_to = $event->getEventParam('related_to'); if (!$related_to) { $related_prefix = $event->Prefix; } else { $sql = 'SELECT Prefix FROM '.TABLE_PREFIX.'ItemTypes WHERE ItemName = '.$this->Conn->qstr($related_to); $related_prefix = $this->Conn->GetOne($sql); } $rel_table = $this->Application->getUnitOption('rel', 'TableName'); $item_type = (int)$this->Application->getUnitOption($event->Prefix, 'ItemType'); if ($item_type == 0) { trigger_error('<strong>ItemType</strong> not defined for prefix <strong>' . $event->Prefix . '</strong>', E_USER_WARNING); } // process case, then this list is called inside another list $prefix_special = $event->getEventParam('PrefixSpecial'); if (!$prefix_special) { $prefix_special = $this->Application->Parser->GetParam('PrefixSpecial'); } $id = false; if ($prefix_special !== false) { $processed_prefix = $this->Application->processPrefix($prefix_special); if ($processed_prefix['prefix'] == $related_prefix) { // printing related categories within list of items (not on details page) $list =& $this->Application->recallObject($prefix_special); /* @var $list kDBList */ $id = $list->GetID(); } } if ($id === false) { // printing related categories for single item (possibly on details page) if ($related_prefix == 'c') { $id = $this->Application->GetVar('m_cat_id'); } else { $id = $this->Application->GetVar($related_prefix . '_id'); } } $p_item =& $this->Application->recallObject($related_prefix . '.current', null, Array('skip_autoload' => true)); $p_item->Load( (int)$id ); $p_resource_id = $p_item->GetDBField('ResourceId'); $sql = 'SELECT SourceId, TargetId FROM '.$rel_table.' WHERE (Enabled = 1) AND ( (Type = 0 AND SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.') OR (Type = 1 AND ( (SourceId = '.$p_resource_id.' AND TargetType = '.$item_type.') OR (TargetId = '.$p_resource_id.' AND SourceType = '.$item_type.') ) ) )'; $related_ids_array = $this->Conn->Query($sql); $related_ids = Array(); foreach ($related_ids_array as $key => $record) { $related_ids[] = $record[ $record['SourceId'] == $p_resource_id ? 'TargetId' : 'SourceId' ]; } if (count($related_ids) > 0) { $type_clauses['related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_ids).')'; $type_clauses['related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_ids).')'; } else { $type_clauses['related']['include'] = '0'; $type_clauses['related']['except'] = '1'; } $type_clauses['related']['having_filter'] = false; } if (in_array('category_related', $type_clauses)) { $object->removeFilter('parent_filter'); $resource_id = $this->Conn->GetOne(' SELECT ResourceId FROM '.$this->Application->getUnitOption($event->Prefix, 'TableName').' WHERE CategoryId = '.$parent_cat_id ); $sql = 'SELECT DISTINCT(TargetId) FROM '.TABLE_PREFIX.'Relationship WHERE SourceId = '.$resource_id.' AND SourceType = 1'; $related_cats = $this->Conn->GetCol($sql); $related_cats = is_array($related_cats) ? $related_cats : Array(); $sql = 'SELECT DISTINCT(SourceId) FROM '.TABLE_PREFIX.'Relationship WHERE TargetId = '.$resource_id.' AND TargetType = 1 AND Type = 1'; $related_cats2 = $this->Conn->GetCol($sql); $related_cats2 = is_array($related_cats2) ? $related_cats2 : Array(); $related_cats = array_unique( array_merge( $related_cats2, $related_cats ) ); if ($related_cats) { $type_clauses['category_related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_cats).')'; $type_clauses['category_related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_cats).')'; } else { $type_clauses['category_related']['include'] = '0'; $type_clauses['category_related']['except'] = '1'; } $type_clauses['category_related']['having_filter'] = false; } if (in_array('product_related', $types)) { $object->removeFilter('parent_filter'); $product_id = $event->getEventParam('product_id') ? $event->getEventParam('product_id') : $this->Application->GetVar('p_id'); $resource_id = $this->Conn->GetOne(' SELECT ResourceId FROM '.$this->Application->getUnitOption('p', 'TableName').' WHERE ProductId = '.$product_id ); $sql = 'SELECT DISTINCT(TargetId) FROM '.TABLE_PREFIX.'Relationship WHERE SourceId = '.$resource_id.' AND TargetType = 1'; $related_cats = $this->Conn->GetCol($sql); $related_cats = is_array($related_cats) ? $related_cats : Array(); $sql = 'SELECT DISTINCT(SourceId) FROM '.TABLE_PREFIX.'Relationship WHERE TargetId = '.$resource_id.' AND SourceType = 1 AND Type = 1'; $related_cats2 = $this->Conn->GetCol($sql); $related_cats2 = is_array($related_cats2) ? $related_cats2 : Array(); $related_cats = array_unique( array_merge( $related_cats2, $related_cats ) ); if ($related_cats) { $type_clauses['product_related']['include'] = '%1$s.ResourceId IN ('.implode(',', $related_cats).')'; $type_clauses['product_related']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $related_cats).')'; } else { $type_clauses['product_related']['include'] = '0'; $type_clauses['product_related']['except'] = '1'; } $type_clauses['product_related']['having_filter'] = false; } $type_clauses['menu']['include'] = '%1$s.IsMenu = 1'; $type_clauses['menu']['except'] = '%1$s.IsMenu = 0'; $type_clauses['menu']['having_filter'] = false; if (in_array('search', $types) || in_array('search', $except_types)) { $event_mapping = Array ( 'simple' => 'OnSimpleSearch', 'subsearch' => 'OnSubSearch', 'advanced' => 'OnAdvancedSearch' ); $type = $this->Application->GetVar('search_type', 'simple'); if ($keywords = $event->getEventParam('keyword_string')) { // processing keyword_string param of ListProducts tag $this->Application->SetVar('keywords', $keywords); $type = 'simple'; } $search_event = $event_mapping[$type]; $this->$search_event($event); $search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search'; $sql = 'SHOW TABLES LIKE "'.$search_table.'"'; if ($this->Conn->Query($sql)) { $search_res_ids = $this->Conn->GetCol('SELECT ResourceId FROM '.$search_table); } if (isset($search_res_ids) && $search_res_ids) { $type_clauses['search']['include'] = '%1$s.ResourceId IN ('.implode(',', $search_res_ids).')'; $type_clauses['search']['except'] = '%1$s.ResourceId NOT IN ('.implode(',', $search_res_ids).')'; } else { $type_clauses['search']['include'] = '0'; $type_clauses['search']['except'] = '1'; } $type_clauses['search']['having_filter'] = false; } $search_helper =& $this->Application->recallObject('SearchHelper'); /* @var $search_helper kSearchHelper */ $search_helper->SetComplexFilter($event, $type_clauses, implode(',', $types), implode(',', $except_types)); } /** * Returns current theme id * * @return int */ function _getCurrentThemeId() { $themes_helper =& $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ return (int)$themes_helper->getCurrentThemeId(); } /** * Enter description here... * * @param kEvent $event * @return int */ function getPassedID(&$event) { if (($event->Special == 'page') || ($event->Special == '-virtual') || ($event->Prefix == 'st')) { return $this->_getPassedStructureID($event); } if ($this->Application->isAdmin) { return parent::getPassedID($event); } return $this->Application->GetVar('m_cat_id'); } /** * Enter description here... * * @param kEvent $event * @return int */ function _getPassedStructureID(&$event) { static $page_by_template = Array (); if ($event->Special == 'current') { return $this->Application->GetVar('m_cat_id'); } $event->setEventParam('raise_warnings', 0); $page_id = parent::getPassedID($event); if ($page_id === false) { $template = $event->getEventParam('page'); if (!$template) { $template = $this->Application->GetVar('t'); } // bug: when template contains "-" symbols (or others, that stripDisallowed will replace) it's not found if (!array_key_exists($template, $page_by_template)) { $sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . ' FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' WHERE ( (NamedParentPath = ' . $this->Conn->qstr($template) . ') OR (NamedParentPath = ' . $this->Conn->qstr('Content/' . $template) . ') OR (`Type` = ' . PAGE_TYPE_TEMPLATE . ' AND CachedTemplate = ' . $this->Conn->qstr($template) . ') ) AND (ThemeId = ' . $this->_getCurrentThemeId() . ' OR ThemeId = 0)'; $page_id = $this->Conn->GetOne($sql); } else { $page_id = $page_by_template[$template]; } if ($page_id === false && EDITING_MODE) { // create missing pages, when in editing mode $object =& $this->Application->recallObject($this->Prefix . '.rebuild', null, Array('skip_autoload' => true)); /* @var $object kDBItem */ $created = $this->_prepareAutoPage($object, $template, null, SMS_MODE_AUTO, false); // create virtual (not system!) page if ($created) { if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild') || !$this->Application->isAdmin) { $updater =& $this->Application->recallObject('kPermCacheUpdater'); /* @var $updater kPermCacheUpdater */ $updater->OneStepRun(); } $this->_resetMenuCache(); $this->Application->RemoveVar('PermCache_UpdateRequired'); $page_id = $object->GetID(); $this->Application->SetVar('m_cat_id', $page_id); } } if ($page_id) { $page_by_template[$template] = $page_id; } } if (!$page_id && !$this->Application->isAdmin) { $page_id = $this->Application->GetVar('m_cat_id'); } return $page_id; } function ParentGetPassedId(&$event) { return parent::GetPassedId($event); } /** * Adds calculates fields for item statuses * * @param kCatDBItem $object * @param kEvent $event */ function prepareObject(&$object, &$event) { if ($event->Special != '-virtual') { $object =& $event->getObject( Array('skip_autoload' => true) ); $object->addCalculatedField( 'IsNew', ' IF(%1$s.NewItem = 2, IF(%1$s.CreatedOn >= (UNIX_TIMESTAMP() - '. $this->Application->ConfigValue('Category_DaysNew'). '*3600*24), 1, 0), %1$s.NewItem )'); } } /** * Set correct parent path for newly created categories * * @param kEvent $event */ function OnAfterCopyToLive(&$event) { $parent_path = false; $object =& $this->Application->recallObject($event->Prefix.'.-item', null, Array('skip_autoload' => true, 'live_table' => true)); $object->Load($event->getEventParam('id')); if ($event->getEventParam('temp_id') == 0) { if ($object->isLoaded()) { // update path only for real categories (not including "Home" root category) $fields_hash = Array('ParentPath' => $object->buildParentPath()); $this->Conn->doUpdate($fields_hash, $object->TableName, 'CategoryId = '.$object->GetID()); $parent_path = $fields_hash['ParentPath']; } } else { $parent_path = $object->GetDBField('ParentPath'); } if ($parent_path) { $cache_updater =& $this->Application->recallObject('kPermCacheUpdater', null, array('strict_path' => $parent_path)); $cache_updater->OneStepRun(); $cache_updater->StrictPath = false; } } /** * Set cache modification mark if needed * * @param kEvent $event */ function OnBeforeDeleteFromLive(&$event) { $id = $event->getEventParam('id'); // loding anyway, because this object is needed by "c-perm:OnBeforeDeleteFromLive" event $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 $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; } } } /** * 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 */ function OnPreCreate(&$event) { // 1. for permission editing of Home category $this->Application->RemoveVar('IsRootCategory_' . $this->Application->GetVar('m_wid')); parent::OnPreCreate($event); $object =& $event->getObject(); // 2. preset template $category_id = $this->Application->GetVar('m_cat_id'); - $root_category = $this->Application->findModule('Name', 'Core', 'RootCat'); + $root_category = $this->Application->getBaseCategory(); if ($category_id == $root_category) { $object->SetDBField('Template', $this->_getDefaultDesign()); } $priority_helper =& $this->Application->recallObject('PriorityHelper'); /* @var $priority_helper kPriorityHelper */ // 3. prepare priorities dropdown $priority_helper->preparePriorities($event, true, 'ParentId = ' . $category_id); // 4. set default owner $object->SetDBField('CreatedById', $this->Application->RecallVar('user_id')); } /** * Checks cache update mark and redirect to cache if needed * * @param kEvent $event */ function OnSave(&$event) { $ids = $this->getSelectedIDs($event, true); $is_editing = implode('', $ids); if ($is_editing) { $old_statuses = $this->_getCategoryStatus($ids); } $object =& $event->getObject(); /* @var $object CategoriesItem */ /*if ($object->IsRoot()) { $event->setEventParam('master_ids', Array(0)); $this->RemoveRequiredFields($object); }*/ parent::OnSave($event); if ($event->status != erSUCCESS) { return ; } // 1. update priorities $tmp = $this->Application->RecallVar('priority_changes'.$this->Application->GetVar('m_wid')); $changes = $tmp ? unserialize($tmp) : Array (); $changed_ids = array_keys($changes); $priority_helper =& $this->Application->recallObject('PriorityHelper'); /* @var $priority_helper kPriorityHelper */ $priority_helper->updatePriorities($event, $changes, Array (0 => $event->getEventParam('ids'))); 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) { // 2. send email event to category owner, when it's status is changed (from admin) $object->SwitchToLive(); $new_statuses = $this->_getCategoryStatus($ids); $process_statuses = Array (STATUS_ACTIVE, STATUS_DISABLED); foreach ($new_statuses as $category_id => $new_status) { if ($new_status != $old_statuses[$category_id] && in_array($new_status, $process_statuses)) { $object->Load($category_id); $email_event = $new_status == STATUS_ACTIVE ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY'; $this->Application->EmailEventUser($email_event, $object->GetDBField('CreatedById')); } } } } /** * 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 succsess * * @param kEvent $event */ function OnPreSaveCreated(&$event) { $object =& $event->getObject( Array('skip_autoload' => true) ); /* @var $object CategoriesItem */ if ($object->IsRoot()) { // don't create root category while saving permissions return ; } $priority_helper =& $this->Application->recallObject('PriorityHelper'); /* @var $priority_helper kPriorityHelper */ $category_id = $this->Application->GetVar('m_cat_id'); $priority_helper->preparePriorities($event, true, 'ParentId = ' . $category_id); parent::OnPreSaveCreated($event); } /** * Deletes sym link to other category * * @param kEvent $event */ function OnAfterItemDelete(&$event) { parent::OnAfterItemDelete($event); $object =& $event->getObject(); /* @var $object kDBItem */ $sql = 'UPDATE '.$object->TableName.' SET SymLinkCategoryId = NULL WHERE SymLinkCategoryId = '.$object->GetID(); $this->Conn->Query($sql); } /** * Exclude root categories from deleting * * @param kEvent $event */ function customProcessing(&$event, $type) { if ($event->Name == 'OnMassDelete' && $type == 'before') { $ids = $event->getEventParam('ids'); if (!$ids || $this->Application->ConfigValue('AllowDeleteRootCats')) { return ; } // 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); } } $change_events = Array ('OnPreSave', 'OnPreSaveCreated', 'OnUpdate', 'OnSave'); if ($type == 'after' && in_array($event->Name, $change_events)) { $object =& $event->getObject(); $tmp = $this->Application->RecallVar('priority_changes'.$this->Application->GetVar('m_wid')); $changes = $tmp ? unserialize($tmp) : array(); if (!isset($changes[$object->GetID()])) { $changes[$object->GetId()]['old'] = $object->GetID() == 0 ? 'new' : $object->GetDBField('OldPriority'); } if ($changes[$object->GetId()]['old'] == $object->GetDBField('Priority')) return ; $changes[$object->GetId()]['new'] = $object->GetDBField('Priority'); $changes[$object->GetId()]['parent'] = $object->GetDBField('ParentId'); $this->Application->StoreVar('priority_changes'.$this->Application->GetVar('m_wid'), serialize($changes)); } } /** * 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 recurse into sub-items using temp handler, and deletes sub-items * by calling its Delete method if sub-item has AutoDelete set to true in its config file * * @param kEvent $event */ function OnMassDelete(&$event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = erFAIL; return; } $ids = $this->StoreSelectedIDs($event); $to_delete = array(); if ($recycle_bin = $this->Application->ConfigValue('RecycleBinFolder')) { $rb =& $this->Application->recallObject('c.recycle', null, Array ('skip_autoload' => true)); $rb->Load($recycle_bin); $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'))) { $to_delete[] = $id; continue; } $cat->SetDBField('ParentId', $recycle_bin); $cat->Update(); } $ids = $to_delete; $event->redirect = 'categories/cache_updater'; } $event->setEventParam('ids', $ids); $this->customProcessing($event, 'before'); $ids = $event->getEventParam('ids'); if ($ids) { $recursive_helper =& $this->Application->recallObject('RecursiveHelper'); /* @var $recursive_helper kRecursiveHelper */ foreach ($ids as $id) { $recursive_helper->DeleteCategory($id, $event->Prefix); } } $this->clearSelectedIDs($event); // update priorities $priority_helper =& $this->Application->recallObject('PriorityHelper'); /* @var $priority_helper kPriorityHelper */ // after deleting categories, all priorities should be recalculated $parent_id = $this->Application->GetVar('m_cat_id'); $priority_helper->recalculatePriorities($event, 'ParentId = ' . $parent_id); $this->Application->StoreVar('RefreshStructureTree', 1); $this->_resetMenuCache(); } /** * Add selected items to clipboard with mode = COPY (CLONE) * * @param kEvent $event */ function OnCopy(&$event) { $this->Application->RemoveVar('clipboard'); $clipboard_helper =& $this->Application->recallObject('ClipboardHelper'); $clipboard_helper->setClipboard($event, 'copy', $this->StoreSelectedIDs($event)); $this->clearSelectedIDs($event); } /** * Add selected items to clipboard with mode = CUT * * @param kEvent $event */ function OnCut(&$event) { $this->Application->RemoveVar('clipboard'); $clipboard_helper =& $this->Application->recallObject('ClipboardHelper'); $clipboard_helper->setClipboard($event, 'cut', $this->StoreSelectedIDs($event)); $this->clearSelectedIDs($event); } /** * Controls all item paste operations. Can occur only with filled clipbord. * * @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->redirect = $paste_event->redirect; $event->redirect_params = $paste_event->redirect_params; $event->status = $paste_event->status; } } /** * Checks permission for OnPaste event * * @param kEvent $event * @return bool */ function _checkPastePermission(&$event) { $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $category_id = $this->Application->GetVar('m_cat_id'); if ($perm_helper->AddCheckPermission($category_id, $event->Prefix) == 0) { // no items left for editing -> no permission return $perm_helper->finalizePermissionCheck($event, false); } return true; } /** * Paste categories with subitems from clipboard * * @param kEvent $event */ function OnPaste(&$event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) || !$this->_checkPastePermission($event)) { $event->status = erFAIL; return ; } $clipboard_data = $event->getEventParam('clipboard_data'); if (!$clipboard_data['cut'] && !$clipboard_data['copy']) { return false; } // 1. get ParentId of moved category(-es) before it gets updated!!!) $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); $table_name = $this->Application->getUnitOption($event->Prefix, 'TableName'); if ($clipboard_data['cut']) { $sql = 'SELECT ParentId FROM ' . $table_name . ' WHERE ' . $id_field . ' = ' . $clipboard_data['cut'][0]; $source_category_id = $this->Conn->GetOne($sql); } $recursive_helper =& $this->Application->recallObject('RecursiveHelper'); /* @var $recursive_helper kRecursiveHelper */ if ($clipboard_data['cut']) { $recursive_helper->MoveCategories($clipboard_data['cut'], $this->Application->GetVar('m_cat_id')); } if ($clipboard_data['copy']) { // don't allow to copy/paste system OR theme-linked virtual pages $sql = 'SELECT ' . $id_field . ' FROM ' . $table_name . ' WHERE ' . $id_field . ' IN (' . implode(',', $clipboard_data['copy']) . ') AND (`Type` = ' . PAGE_TYPE_VIRTUAL . ') AND (ThemeId = 0)'; $allowed_ids = $this->Conn->GetCol($sql); if (!$allowed_ids) { return ; } foreach ($allowed_ids as $id) { $recursive_helper->PasteCategory($id, $event->Prefix); } } $priority_helper =& $this->Application->recallObject('PriorityHelper'); /* @var $priority_helper kPriorityHelper */ if ($clipboard_data['cut']) { $priority_helper->recalculatePriorities($event, 'ParentId = '.$source_category_id); } // recalculate priorities of newly pasted categories in destination category $parent_id = $this->Application->GetVar('m_cat_id'); $priority_helper->recalculatePriorities($event, 'ParentId = ' . $parent_id); if ($clipboard_data['cut'] || $clipboard_data['copy']) { // rebuild with progress bar if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild')) { $updater =& $this->Application->recallObject('kPermCacheUpdater'); /* @var $updater kPermCacheUpdater */ $updater->OneStepRun(); } else { $event->redirect = 'categories/cache_updater'; } $this->_resetMenuCache(); $this->Application->StoreVar('RefreshStructureTree', 1); } } /** * Occurs when pasting category * * @param kEvent $event */ /*function OnCatPaste(&$event) { $inp_clipboard = $this->Application->RecallVar('ClipBoard'); $inp_clipboard = explode('-', $inp_clipboard, 2); if($inp_clipboard[0] == 'COPY') { $saved_cat_id = $this->Application->GetVar('m_cat_id'); $cat_ids = $event->getEventParam('cat_ids'); $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); $table = $this->Application->getUnitOption($event->Prefix, 'TableName'); $ids_sql = 'SELECT '.$id_field.' FROM '.$table.' WHERE ResourceId IN (%s)'; $resource_ids_sql = 'SELECT ItemResourceId FROM '.TABLE_PREFIX.'CategoryItems WHERE CategoryId = %s AND PrimaryCat = 1'; $object =& $this->Application->recallObject($event->Prefix.'.item', $event->Prefix, Array('skip_autoload' => true)); foreach($cat_ids as $source_cat => $dest_cat) { $item_resource_ids = $this->Conn->GetCol( sprintf($resource_ids_sql, $source_cat) ); if(!$item_resource_ids) continue; $this->Application->SetVar('m_cat_id', $dest_cat); $item_ids = $this->Conn->GetCol( sprintf($ids_sql, implode(',', $item_resource_ids) ) ); $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); if($item_ids) $temp->CloneItems($event->Prefix, $event->Special, $item_ids); } $this->Application->SetVar('m_cat_id', $saved_cat_id); } }*/ /** * Cleares clipboard content * * @param kEvent $event */ function OnClearClipboard(&$event) { $this->Application->RemoveVar('clipboard'); } /** * Sets correct status for new categories created on front-end * * @param kEvent $event */ function OnBeforeItemCreate(&$event) { $this->_beforeItemChange($event); if ($this->Application->isAdminUser || $event->Prefix == 'st') { // don't check category permissions when auto-creating structure pages return ; } $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $new_status = false; $category_id = $this->Application->GetVar('m_cat_id'); if ($perm_helper->CheckPermission('CATEGORY.ADD', 0, $category_id)) { $new_status = STATUS_ACTIVE; } else if ($perm_helper->CheckPermission('CATEGORY.ADD.PENDING', 0, $category_id)) { $new_status = STATUS_PENDING; } if ($new_status) { $object =& $event->getObject(); /* @var $object kDBItem */ $object->SetDBField('Status', $new_status); // don't forget to set Priority for suggested from Front-End categories $min_priority = $this->_getNextPriority($object->GetDBField('ParentId'), $object->TableName); $object->SetDBField('Priority', $min_priority); /*if (!$this->Application->isAdminUser) { $object->SetDBField('IsMenu', 0); // add all suggested categories as non-menu }*/ } else { $event->status = 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 */ function OnBeforeItemUpdate(&$event) { parent::OnBeforeItemUpdate($event); $object =& $event->getObject(); /* @var $object kDBItem */ if ($object->GetChangedFields()) { $object->SetDBField('ModifiedById', $this->Application->RecallVar('user_id')); } $this->_beforeItemChange($event); } /** * Performs redirect to correct suggest confirmation template * * @param kEvent $event */ function OnCreate(&$event) { parent::OnCreate($event); if ($this->Application->isAdminUser || $event->status != erSUCCESS) { // don't sent email or rebuild cache directly after category is created by admin return ; } $object =& $event->getObject(); $cache_updater =& $this->Application->recallObject('kPermCacheUpdater', null, array('strict_path' => $object->GetDBField('ParentPath'))); $cache_updater->OneStepRun(); $cache_updater->StrictPath = false; $is_active = ($object->GetDBField('Status') == STATUS_ACTIVE); $next_template = $is_active ? 'suggest_confirm_template' : 'suggest_pending_confirm_template'; $event->redirect = $this->Application->GetVar($next_template); $event->SetRedirectParam('opener', 's'); // send email events $perm_prefix = $this->Application->getUnitOption($event->Prefix, 'PermItemPrefix'); $event_suffix = $is_active ? 'ADD' : 'ADD.PENDING'; $this->Application->EmailEventAdmin($perm_prefix.'.'.$event_suffix); $this->Application->EmailEventUser($perm_prefix.'.'.$event_suffix, $object->GetDBField('CreatedById')); } /** * Returns current per-page setting for list * * @param kEvent $event * @return int */ function getPerPage(&$event) { if (!$this->Application->isAdmin) { $event->setEventParam('same_special', true); } return parent::getPerPage($event); } /** * Set's correct page for list * based on data provided with event * * @param kEvent $event * @access private * @see OnListBuild */ function SetPagination(&$event) { parent::SetPagination($event); if (!$this->Application->isAdmin) { $page_var = $event->getEventParam('page_var'); if ($page_var !== false) { $page = $this->Application->GetVar($page_var); if (is_numeric($page)) { $object =& $event->getObject(); $object->SetPage($page); } } } } /** * Apply same processing to each item beeing selected in grid * * @param kEvent $event * @access private */ function iterateItems(&$event) { if ($event->Name != 'OnMassApprove' && $event->Name != 'OnMassDecline') { return parent::iterateItems($event); } if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = erFAIL; return; } $object =& $event->getObject( Array('skip_autoload' => true) ); $ids = $this->StoreSelectedIDs($event); if ($ids) { $propagate_category_status = $this->Application->GetVar('propagate_category_status'); $status_field = array_shift( $this->Application->getUnitOption($event->Prefix,'StatusField') ); foreach ($ids as $id) { $object->Load($id); $object->SetDBField($status_field, $event->Name == 'OnMassApprove' ? 1 : 0); if ($object->Update()) { if ($propagate_category_status) { $sql = 'UPDATE '.$object->TableName.' SET '.$status_field.' = '.$object->GetDBField($status_field).' WHERE TreeLeft BETWEEN '.$object->GetDBField('TreeLeft').' AND '.$object->GetDBField('TreeRight'); $this->Conn->Query($sql); } $event->status = erSUCCESS; $email_event = $event->Name == 'OnMassApprove' ? 'CATEGORY.APPROVE' : 'CATEGORY.DENY'; $this->Application->EmailEventUser($email_event, $object->GetDBField('CreatedById')); } else { $event->status = erFAIL; $event->redirect = false; break; } } } $this->clearSelectedIDs($event); $this->Application->StoreVar('RefreshStructureTree', 1); } /** * Checks, that currently loaded item is allowed for viewing (non permission-based) * * @param kEvent $event * @return bool */ function checkItemStatus(&$event) { $status_fields = $this->Application->getUnitOption($event->Prefix,'StatusField'); if (!$status_fields) { return true; } $status_field = array_shift($status_fields); if ($status_field == 'Status' || $status_field == 'Enabled') { $object =& $event->getObject(); if (!$object->isLoaded()) { return true; } return $object->GetDBField($status_field) == STATUS_ACTIVE || $object->GetDBField($status_field) == 4; } return true; } // ============= for cms page processing ======================= /** * Returns default design template * * @return string */ function _getDefaultDesign() { $default_design = trim($this->Application->ConfigValue('cms_DefaultDesign'), '/'); if (!$default_design) { // theme-based alias for default design return '#default_design#'; } if (strpos($default_design, '#') === false) { // real template, not alias, so prefix with "/" return '/' . $default_design; } // alias return $default_design; } /** * Returns default design based on given virtual template (used from kApplication::Run) * * @return string */ function GetDesignTemplate($t = null) { if (!isset($t)) { $t = $this->Application->GetVar('t'); } $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 { $not_found = $this->Application->ConfigValue('ErrorTemplate'); $real_t = $not_found ? $not_found : 'error_notfound'; $themes_helper =& $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ $theme_id = $this->Application->GetVar('m_theme'); $category_id = $themes_helper->getPageByTemplate($real_t, $theme_id); $this->Application->SetVar('m_cat_id', $category_id); header('HTTP/1.0 404 Not Found'); } // replace alias in form #alias_name# to actual template used in this theme $theme =& $this->Application->recallObject('theme.current'); /* @var $theme kDBItem */ $template = $theme->GetField('TemplateAliases', $real_t); if ($template) { return $template; } return $real_t; } /** * Sets category id based on found template (used from kApplication::Run) * * @deprecated */ /*function SetCatByTemplate() { $t = $this->Application->GetVar('t'); $page =& $this->Application->recallObject($this->Prefix . '.-virtual'); if ($page->isLoaded()) { $this->Application->SetVar('m_cat_id', $page->GetDBField('CategoryId') ); } }*/ /** * Prepares template paths * * @param kEvent $event */ function _beforeItemChange(&$event) { $object =& $event->getObject(); /* @var $object kDBItem */ $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')); } } if ($object->GetChangedFields()) { $object->SetDBField('Modified_date', $now); $object->SetDBField('Modified_time', $now); } $object->setRequired('PageCacheKey', $object->GetDBField('OverridePageCacheKey')); $object->SetDBField('Template', $this->_stripTemplateExtension( $object->GetDBField('Template') )); if ($object->GetDBField('Type') == PAGE_TYPE_TEMPLATE) { if (!$this->_templateFound($object->GetDBField('Template'), $object->GetDBField('ThemeId'))) { $object->SetError('Template', 'template_file_missing', 'la_error_TemplateFileMissing'); } } $this->_saveTitleField($object, 'Title'); $this->_saveTitleField($object, 'MenuTitle'); - $root_category = $this->Application->findModule('Name', 'Core', 'RootCat'); + $root_category = $this->Application->getBaseCategory(); if (($object->GetDBField('ParentId') == $root_category) && ($object->GetDBField('Template') == CATEGORY_TEMPLATE_INHERIT)) { $object->SetError('Template', 'no_inherit'); } if (!$this->Application->isAdminUser) { // 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_NotAllowed'); } } } /** * Sets page name to requested field in case when: * 1. page was auto created (through theme file rebuld) * 2. requested field is emtpy * * @param kDBItem $object * @param string $field * @author Alex */ function _saveTitleField(&$object, $field) { $value = $object->GetField($field, 'no_default'); // current value of target field $ml_formatter =& $this->Application->recallObject('kMultiLanguage'); /* @var $ml_formatter kMultiLanguage */ $src_field = $ml_formatter->LangFieldName('Name'); $dst_field = $ml_formatter->LangFieldName($field); $dst_field_not_changed = $object->GetOriginalField($dst_field) == $value; if ($value == '' || preg_match('/^_Auto: (.*)/', $value) || (($object->GetOriginalField($src_field) == $value) && $dst_field_not_changed)) { // target field is empty OR target field value starts with "_Auto: " OR (source field value // before change was equals to current target field value AND target field value wasn't changed) $object->SetField($dst_field, $object->GetField($src_field)); } } /** * Don't allow to delete system pages, when not in debug mode * * @param kEvent $event */ function OnBeforeItemDelete(&$event) { $object =& $event->getObject(); if ( $object->GetDBField('Protected') && !$this->Application->isDebugMode() ) { $event->status = erFAIL; } } /** * Enter description here... * * @param StructureItem $object * @param string $template */ 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 autocreate system pages, when browsing through site return false; } if (!isset($theme_id)) { $theme_id = $this->_getCurrentThemeId(); } - $root_category = $this->Application->findModule('Name', 'Core', 'RootCat'); + $root_category = $this->Application->getBaseCategory(); $page_category = $this->Application->GetVar('m_cat_id'); if (!$page_category) { $page_category = $root_category; $this->Application->SetVar('m_cat_id', $page_category); } if (($page_type == PAGE_TYPE_VIRTUAL) && (strpos($template, '/') !== false)) { // virtual page, but have "/" in template path -> create it's path $category_path = explode('/', $template); $template = array_pop($category_path); $page_category = $this->_getParentCategoryFromPath($category_path, $root_category, $theme_id); } $page_name = ($page_type == PAGE_TYPE_TEMPLATE) ? '_Auto: ' . $template : $template; $page_description = ''; if ($page_type == PAGE_TYPE_TEMPLATE) { $design_template = strtolower($template); // leading "/" not added ! if ($template_info) { if (array_key_exists('name', $template_info) && $template_info['name']) { $page_name = $template_info['name']; } if (array_key_exists('desc', $template_info) && $template_info['desc']) { $page_description = $template_info['desc']; } if (array_key_exists('section', $template_info) && $template_info['section']) { // this will override any global "m_cat_id" $page_category = $this->_getParentCategoryFromPath(explode('||', $template_info['section']), $root_category, $theme_id); } } } else { $design_template = $this->_getDefaultDesign(); // leading "/" added ! } $object->Clear(); $object->SetDBField('ParentId', $page_category); $object->SetDBField('Type', $page_type); $object->SetDBField('Protected', 1); // $page_type == PAGE_TYPE_TEMPLATE $object->SetDBField('IsMenu', 0); $object->SetDBField('ThemeId', $theme_id); // put all templates to then end of list (in their category) $min_priority = $this->_getNextPriority($page_category, $object->TableName); $object->SetDBField('Priority', $min_priority); $object->SetDBField('Template', $design_template); $object->SetDBField('CachedTemplate', $design_template); $primary_language = $this->Application->GetDefaultLanguageId(); $current_language = $this->Application->GetVar('m_lang'); $object->SetDBField('l' . $primary_language . '_Name', $page_name); $object->SetDBField('l' . $current_language . '_Name', $page_name); $object->SetDBField('l' . $primary_language . '_Description', $page_description); $object->SetDBField('l' . $current_language . '_Description', $page_description); return $object->Create(); } function _getParentCategoryFromPath($category_path, $base_category, $theme_id = null) { static $category_ids = Array (); if (!$category_path) { return $base_category; } if (array_key_exists(implode('||', $category_path), $category_ids)) { return $category_ids[ implode('||', $category_path) ]; } $backup_category_id = $this->Application->GetVar('m_cat_id'); $object =& $this->Application->recallObject($this->Prefix . '.rebuild-path', null, Array ('skip_autoload' => true)); /* @var $object kDBItem */ $parent_id = $base_category; $filenames_helper =& $this->Application->recallObject('FilenamesHelper'); /* @var $filenames_helper kFilenamesHelper */ $safe_category_path = array_map(Array (&$filenames_helper, 'replaceSequences'), $category_path); foreach ($category_path as $category_order => $category_name) { $this->Application->SetVar('m_cat_id', $parent_id); // get virtual category first, when possible $sql = 'SELECT ' . $object->IDField . ' FROM ' . $object->TableName . ' WHERE ( Filename = ' . $this->Conn->qstr($safe_category_path[$category_order]) . ' OR Filename = ' . $this->Conn->qstr( $filenames_helper->replaceSequences('_Auto: ' . $category_name) ) . ' ) AND (ParentId = ' . $parent_id . ') AND (ThemeId = 0 OR ThemeId = ' . $theme_id . ') ORDER BY ThemeId ASC'; $parent_id = $this->Conn->GetOne($sql); if ($parent_id === false) { // page not found $template = implode('/', array_slice($safe_category_path, 0, $category_order + 1)); // don't process system templates in sub-categories $system = $this->_templateFound($template, $theme_id) && (strpos($template, '/') === false); if (!$this->_prepareAutoPage($object, $category_name, $theme_id, $system ? SMS_MODE_FORCE : false)) { // page was not created break; } $parent_id = $object->GetID(); } } $this->Application->SetVar('m_cat_id', $backup_category_id); $category_ids[ implode('||', $category_path) ] = $parent_id; return $parent_id; } /** * Returns theme name by it's id. Used in structure page creation. * * @param int $theme_id * @return string */ function _getThemeName($theme_id) { static $themes = null; if (!isset($themes)) { $id_field = $this->Application->getUnitOption('theme', 'IDField'); $table_name = $this->Application->getUnitOption('theme', 'TableName'); $sql = 'SELECT Name, ' . $id_field . ' FROM ' . $table_name . ' WHERE Enabled = 1'; $themes = $this->Conn->GetCol($sql, $id_field); } return array_key_exists($theme_id, $themes) ? $themes[$theme_id] : false; } /** * Resets SMS-menu cache * * @param kEvent $event */ function OnResetCMSMenuCache(&$event) { if ($this->Application->GetVar('ajax') == 'yes') { $event->status = erSTOP; } $this->_resetMenuCache(); } function _resetMenuCache() { // reset cms menu cache (all variables are automatically rebuild, when missing) if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $this->Application->deleteCache('master:cms_menu'); $this->Application->deleteCache('master:StructureTree'); $this->Application->deleteCache('master:template_mapping'); } else { $this->Application->deleteDBCache('cms_menu'); $this->Application->deleteDBCache('StructureTree'); $this->Application->deleteDBCache('template_mapping'); } } /** * Updates structure config * * @param kEvent $event */ function OnAfterConfigRead(&$event) { parent::OnAfterConfigRead($event); if (defined('IS_INSTALL') && IS_INSTALL) { // skip any processing, because Category table doesn't exists until install is finished return ; } $site_config_helper =& $this->Application->recallObject('SiteConfigHelper'); /* @var $site_config_helper SiteConfigHelper */ $settings = $site_config_helper->getSettings(); - $root_category = $this->Application->findModule('Name', 'Core', 'RootCat'); + $root_category = $this->Application->getBaseCategory(); // set root category $section_ajustments = $this->Application->getUnitOption($event->Prefix, 'SectionAdjustments'); $section_ajustments['in-portal:browse'] = Array ( 'url' => Array ('m_cat_id' => $root_category), 'late_load' => Array ('m_cat_id' => $root_category), 'onclick' => 'checkCatalog(' . $root_category . ')', ); $section_ajustments['in-portal:browse_site'] = Array ( 'url' => Array ('editing_mode' => $settings['default_editing_mode']), ); $this->Application->setUnitOption($event->Prefix, 'SectionAdjustments', $section_ajustments); // prepare structure dropdown $category_helper =& $this->Application->recallObject('CategoryHelper'); /* @var $category_helper CategoryHelper */ $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); $fields['ParentId']['default'] = (int)$this->Application->GetVar('m_cat_id'); $fields['ParentId']['options'] = $category_helper->getStructureTreeAsOptions(); // limit design list by theme $design_folders = Array ('tf.FilePath = "/designs"', 'tf.FilePath = "/platform/designs"'); foreach ($this->Application->ModuleInfo as $module_name => $module_info) { $design_folders[] = 'tf.FilePath = "/' . $module_info['TemplatePath'] . 'designs"'; } $design_folders = array_unique($design_folders); $theme_id = $this->_getCurrentThemeId(); $design_sql = $fields['Template']['options_sql']; $design_sql = str_replace('(tf.FilePath = "/designs")', '(' . implode(' OR ', $design_folders) . ')' . ' 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'); $list_sortings['']['ForcedSorting'] = Array ("CurrentSort" => 'asc'); $this->Application->setUnitOption($event->Prefix, 'ListSortings', $list_sortings); } // add grids for advanced view (with primary category column) $grids = $this->Application->getUnitOption($this->Prefix, 'Grids'); $process_grids = Array ('Default', 'Radio'); foreach ($process_grids as $process_grid) { $grid_data = $grids[$process_grid]; $grid_data['Fields']['CachedNavbar'] = Array ('title' => 'la_col_Path', 'data_block' => 'grid_parent_category_td', 'filter_block' => 'grid_like_filter'); $grids[$process_grid . 'ShowAll'] = $grid_data; } $this->Application->setUnitOption($this->Prefix, 'Grids', $grids); } /** * Removes this item and it's children (recursive) from structure dropdown * * @param kEvent $event */ function OnAfterItemLoad(&$event) { parent::OnAfterItemLoad($event); if (!$this->Application->isAdmin) { // calculate priorities dropdown only for admin return ; } $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); $field_options = $object->GetFieldOptions('ParentId'); foreach ($remove_categories as $remove_category) { unset($field_options['options'][$remove_category]); } $object->SetFieldOptions('ParentId', $field_options); $priority_helper =& $this->Application->recallObject('PriorityHelper'); /* @var $priority_helper kPriorityHelper */ $priority_helper->preparePriorities($event, false, 'ParentId = '.$object->GetDBField('ParentId')); // storing prioriry right after load for comparing when updating $object->SetDBField('OldPriority', $object->GetDBField('Priority')); } /** * Builds list * * @param kEvent $event * @access protected */ function OnListBuild(&$event) { parent::OnListBuild($event); if (!$this->Application->isAdminUser) { return ; } $priority_helper =& $this->Application->recallObject('PriorityHelper'); /* @var $priority_helper kPriorityHelper */ $priority_helper->preparePriorities($event, false, 'ParentId = '.$this->Application->GetVar('m_cat_id')); } /** * Enter description here... * * @param kEvent $event */ function OnAfterRebuildThemes(&$event) { $sql = 'SELECT t.ThemeId, CONCAT( tf.FilePath, \'/\', tf.FileName ) AS Path, tf.FileMetaInfo FROM '.TABLE_PREFIX.'ThemeFiles AS tf LEFT JOIN '.TABLE_PREFIX.'Theme AS t ON t.ThemeId = tf.ThemeId WHERE t.Enabled = 1 AND tf.FileType = 1 AND ( SELECT COUNT(CategoryId) FROM ' . TABLE_PREFIX . 'Category c WHERE CONCAT(\'/\', c.Template, \'.tpl\') = CONCAT( tf.FilePath, \'/\', tf.FileName ) AND (c.ThemeId = t.ThemeId) ) = 0 '; $files = $this->Conn->Query($sql, 'Path'); if (!$files) { // all possible pages are already created return ; } set_time_limit(0); ini_set('memory_limit', -1); $dummy =& $this->Application->recallObject($event->Prefix . '.rebuild', null, Array ('skip_autoload' => true)); /* @var $dummy kDBItem */ $error_count = 0; foreach ($files as $a_file => $file_info) { $status = $this->_prepareAutoPage($dummy, $a_file, $file_info['ThemeId'], SMS_MODE_FORCE, unserialize($file_info['FileMetaInfo'])); // create system page if (!$status) { $error_count++; } } if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild')) { $updater =& $this->Application->recallObject('kPermCacheUpdater'); /* @var $updater kPermCacheUpdater */ $updater->OneStepRun(); } $this->_resetMenuCache(); if ($error_count) { // allow user to review error after structure page creation $event->MasterEvent->redirect = false; } } /** * Processes OnMassMoveUp, OnMassMoveDown events * * @param kEvent $event */ function OnChangePriority(&$event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = erFAIL; return; } $object =& $event->getObject( Array('skip_autoload' => true) ); $ids = $this->StoreSelectedIDs($event); if ($ids) { $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); $table_name = $this->Application->getUnitOption($event->Prefix, 'TableName'); $parent_id = $this->Application->GetVar('m_cat_id'); $sql = 'SELECT Priority, '.$id_field.' FROM '.$table_name.' WHERE '.$id_field.' IN ('.implode(',', $ids).')'; $priorities = $this->Conn->GetCol($sql, $id_field); $priority_helper =& $this->Application->recallObject('PriorityHelper'); /* @var $priority_helper kPriorityHelper */ foreach ($ids as $id) { $new_priority = $priorities[$id] + ($event->Name == 'OnMassMoveUp' ? +1 : -1); $changes = Array ( $id => Array ('old' => $priorities[$id], 'new' => $new_priority, 'parent' => $parent_id), ); $sql = 'UPDATE '.$table_name.' SET Priority = '.$new_priority.' WHERE '.$id_field.' = '.$id; $this->Conn->Query($sql); $priority_helper->updatePriorities($event, $changes, Array ($id => $id)); } } $this->clearSelectedIDs($event); $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 = erFAIL; return; } $priority_helper =& $this->Application->recallObject('PriorityHelper'); /* @var $priority_helper kPriorityHelper */ $parent_id = $this->Application->GetVar('m_cat_id'); $priority_helper->recalculatePriorities($event, 'ParentId = ' . $parent_id); $this->_resetMenuCache(); } /** * Update Preview Block for FCKEditor * * @param kEvent $event */ function OnUpdatePreviewBlock(&$event) { $event->status = erSTOP; $string = unhtmlentities($this->Application->GetVar('preview_content')); $category_helper =& $this->Application->recallObject('CategoryHelper'); /* @var $category_helper CategoryHelper */ $string = $category_helper->replacePageIds($string); $this->Application->StoreVar('_editor_preview_content_', $string); } /** * Makes simple search for categories * based on keywords string * * @param kEvent $event */ function OnSimpleSearch(&$event) { $event->redirect = false; $search_table = TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_'.TABLE_PREFIX.'Search'; $keywords = unhtmlentities( trim($this->Application->GetVar('keywords')) ); $query_object =& $this->Application->recallObject('HTTPQuery'); $sql = 'SHOW TABLES LIKE "'.$search_table.'"'; if(!isset($query_object->Get['keywords']) && !isset($query_object->Post['keywords']) && $this->Conn->Query($sql)) { return; // used when navigating by pages or changing sorting in search results } if(!$keywords || strlen($keywords) < $this->Application->ConfigValue('Search_MinKeyword_Length')) { $this->Conn->Query('DROP TABLE IF EXISTS '.$search_table); $this->Application->SetVar('keywords_too_short', 1); return; // if no or too short keyword entered, doing nothing } $this->Application->StoreVar('keywords', $keywords); $this->saveToSearchLog($keywords, 0); // 0 - simple search, 1 - advanced search $keywords = strtr($keywords, Array('%' => '\\%', '_' => '\\_')); $event->setPseudoClass('_List'); $object =& $event->getObject(); /* @var $object kDBList */ $this->Application->SetVar($event->getPrefixSpecial().'_Page', 1); $lang = $this->Application->GetVar('m_lang'); $items_table = $this->Application->getUnitOption($event->Prefix, 'TableName'); $module_name = 'In-Portal'; $sql = 'SELECT * FROM ' . $this->Application->getUnitOption('confs', 'TableName') . ' WHERE ModuleName = ' . $this->Conn->qstr($module_name) . ' AND SimpleSearch = 1'; $search_config = $this->Conn->Query($sql, 'FieldName'); $field_list = array_keys($search_config); $join_clauses = Array(); // field processing $weight_sum = 0; $alias_counter = 0; $custom_fields = $this->Application->getUnitOption($event->Prefix, 'CustomFields'); if ($custom_fields) { $custom_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName'); $join_clauses[] = ' LEFT JOIN '.$custom_table.' custom_data ON '.$items_table.'.ResourceId = custom_data.ResourceId'; } // what field in search config becomes what field in sql (key - new field, value - old field (from searchconfig table)) $search_config_map = Array(); foreach ($field_list as $key => $field) { $options = $object->getFieldOptions($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 (getArrayValue($options, '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 if ($foreign_field = $search_config[$field]['ForeignField']) { $exploded = explode(':', $foreign_field, 2); if ($exploded[0] == 'CALC') { // ignoring having type clauses in simple search unset($field_list[$key]); continue; } else { $multi_lingual = false; if ($exploded[0] == 'MULTI') { $multi_lingual = true; $foreign_field = $exploded[1]; } $exploded = explode('.', $foreign_field); // format: table.field_name $foreign_table = TABLE_PREFIX.$exploded[0]; $alias_counter++; $alias = 't'.$alias_counter; if ($multi_lingual) { $field_list[$key] = $alias.'.'.'l'.$lang.'_'.$exploded[1]; $field_list[$key.'_primary'] = 'l'.$this->Application->GetDefaultLanguageId().'_'.$field; $search_config_map[ $field_list[$key] ] = $field; $search_config_map[ $field_list[$key.'_primary'] ] = $field; } else { $field_list[$key] = $alias.'.'.$exploded[1]; $search_config_map[ $field_list[$key] ] = $field; } $join_clause = str_replace('{ForeignTable}', $alias, $search_config[$field]['JoinClause']); $join_clause = str_replace('{LocalTable}', $items_table, $join_clause); $join_clauses[] = ' LEFT JOIN '.$foreign_table.' '.$alias.' ON '.$join_clause; } } else { // processing fields from local table if ($search_config[$field]['CustomFieldId']) { $local_table = 'custom_data'; // search by custom field value on current language $custom_field_id = array_search($field_list[$key], $custom_fields); $field_list[$key] = 'l'.$lang.'_cust_'.$custom_field_id; // search by custom field value on primary language $field_list[$key.'_primary'] = $local_table.'.l'.$this->Application->GetDefaultLanguageId().'_cust_'.$custom_field_id; $search_config_map[ $field_list[$key.'_primary'] ] = $field; } $field_list[$key] = $local_table.'.'.$field_list[$key]; $search_config_map[ $field_list[$key] ] = $field; } } // keyword string processing $search_helper =& $this->Application->recallObject('SearchHelper'); /* @var $search_helper kSearchHelper */ $where_clause = Array (); foreach ($field_list as $field) { if (preg_match('/^' . preg_quote($items_table, '/') . '\.(.*)/', $field, $regs)) { // local real field $filter_data = $search_helper->getSearchClause($object, $regs[1], $keywords, false); if ($filter_data) { $where_clause[] = $filter_data['value']; } } elseif (preg_match('/^custom_data\.(.*)/', $field, $regs)) { $custom_field_name = 'cust_' . $search_config_map[$field]; $filter_data = $search_helper->getSearchClause($object, $custom_field_name, $keywords, false); if ($filter_data) { $where_clause[] = str_replace('`' . $custom_field_name . '`', $field, $filter_data['value']); } } else { $where_clause[] = $search_helper->buildWhereClause($keywords, Array ($field)); } } $where_clause = '(' . implode(') OR (', $where_clause) . ')'; $where_clause = $where_clause.' AND '.$items_table.'.Status=1'; if ($event->MasterEvent && $event->MasterEvent->Name == 'OnListBuild') { if ($event->MasterEvent->getEventParam('ResultIds')) { $where_clause .= ' AND '.$items_table.'.ResourceId IN ('.implode(',', $event->MasterEvent->getEventParam('ResultIds')).')'; } } // making relevance clause $positive_words = $search_helper->getPositiveKeywords($keywords); $this->Application->StoreVar('highlight_keywords', serialize($positive_words)); $revelance_parts = Array(); reset($search_config); foreach ($positive_words as $keyword_index => $positive_word) { $positive_word = $search_helper->transformWildcards($positive_word); $positive_words[$keyword_index] = $this->Conn->escape($positive_word); } foreach ($field_list as $field) { if (!array_key_exists($field, $search_config_map)) { $map_key = $search_config_map[$items_table . '.' . $field]; } else { $map_key = $search_config_map[$field]; } $config_elem = $search_config[ $map_key ]; $weight = $config_elem['Priority']; $revelance_parts[] = 'IF('.$field.' LIKE "%'.implode(' ', $positive_words).'%", '.$weight_sum.', 0)'; foreach ($positive_words as $keyword) { $revelance_parts[] = 'IF('.$field.' LIKE "%'.$keyword.'%", '.$weight.', 0)'; } } $revelance_parts = array_unique($revelance_parts); $conf_postfix = $this->Application->getUnitOption($event->Prefix, 'SearchConfigPostfix'); $rel_keywords = $this->Application->ConfigValue('SearchRel_Keyword_'.$conf_postfix) / 100; $rel_pop = $this->Application->ConfigValue('SearchRel_Pop_'.$conf_postfix) / 100; $rel_rating = $this->Application->ConfigValue('SearchRel_Rating_'.$conf_postfix) / 100; $relevance_clause = '('.implode(' + ', $revelance_parts).') / '.$weight_sum.' * '.$rel_keywords; if ($rel_pop && isset($object->Fields['Hits'])) { $relevance_clause .= ' + (Hits + 1) / (MAX(Hits) + 1) * '.$rel_pop; } if ($rel_rating && isset($object->Fields['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'); $res = $this->Conn->Query($sql); } /** * Make record to search log * * @param string $keywords * @param int $search_type 0 - simple search, 1 - advanced search */ function saveToSearchLog($keywords, $search_type = 0) { // don't save keywords for each module separately, just one time // static variable can't help here, because each module uses it's own class instance ! if (!$this->Application->GetVar('search_logged')) { $sql = 'UPDATE '.TABLE_PREFIX.'SearchLog SET Indices = Indices + 1 WHERE Keyword = '.$this->Conn->qstr($keywords).' AND SearchType = '.$search_type; // 0 - simple search, 1 - advanced search $this->Conn->Query($sql); if ($this->Conn->getAffectedRows() == 0) { $fields_hash = Array('Keyword' => $keywords, 'Indices' => 1, 'SearchType' => $search_type); $this->Conn->doInsert($fields_hash, TABLE_PREFIX.'SearchLog'); } $this->Application->SetVar('search_logged', 1); } } /** * Load item if id is available * * @param kEvent $event */ function LoadItem(&$event) { if ($event->Special != '-virtual') { parent::LoadItem($event); return ; } $object =& $event->getObject(); /* @var $object kDBItem */ $id = $this->getPassedID($event); if ($object->isLoaded() && !is_array($id) && ($object->GetID() == $id)) { // object is already loaded by same id return ; } if ($object->Load($id, null, true)) { $actions =& $this->Application->recallObject('kActions'); $actions->Set($event->Prefix_Special.'_id', $object->GetID() ); } else { $object->setID($id); } } } \ No newline at end of file Index: branches/5.1.x/core/units/helpers/category_helper.php =================================================================== --- branches/5.1.x/core/units/helpers/category_helper.php (revision 13986) +++ branches/5.1.x/core/units/helpers/category_helper.php (revision 13987) @@ -1,599 +1,599 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class CategoryHelper extends kHelper { /** * Structure tree for ParentId field in category or category items * * @var Array */ var $_structureTree = null; /** * ID of primary language (only for caching) * * @var int */ var $_primaryLanguageId = false; /** * Prints category path using given blocks. Also supports used defined path elements at the end. * * @param Array $params * @return string */ function NavigationBar($params) { $params['is_first'] = 1; $main_category_id = isset($params['cat_id']) ? $params['cat_id'] : $this->Application->GetVar('m_cat_id'); if (array_key_exists('shift', $params) && $params['shift']) { $home_element = ''; $params['shift']--; } else { $home_element = $this->getHomeCategoryPath($params, $main_category_id); unset($params['is_first']); } if (!getArrayValue($params, 'titles') && !getArrayValue($params, 'templates')) { // no static templates given, show only category path return $home_element . $this->getCategoryPath($main_category_id, $params); } $navigation_parts = $this->getNavigationParts($params['titles'], $params['templates']); $ret = ''; $block_params = Array (); //$params; // sort of TagProcessor:prepareTagParams $block_params['no_editing'] = 1; $block_params['category'] = 0; $block_params['separator'] = $params['separator']; $show_category = getArrayValue($params, 'show_category'); $current_template = $this->Application->GetVar('t'); $physical_template = array_search($current_template, $this->Application->structureTemplateMapping); if ($physical_template !== false) { // replace menu template name with it's actual template name on disk list ($current_template) = explode(':', $physical_template, 2); } foreach ($navigation_parts as $template => $title) { $block_params['template'] = $template; if ($title == '__item__') { if ($show_category) { $ret .= $this->getCategoryPath($main_category_id, $params); $show_category = false; } $category_path = $this->getCategoryParentPath($main_category_id); $module_info = $this->getCategoryModule($params, array_keys($category_path)); if (!$module_info) { continue; } $module_prefix = $module_info['Var']; $object =& $this->Application->recallObject($module_prefix); /* @var $object kCatDBItem */ $title_field = $this->Application->getUnitOption($module_prefix, 'TitleField'); $block_params['title'] = $object->GetField($title_field); $block_params['prefix'] = $module_prefix; $block_params['current'] = 0; $block_params['name'] = $this->SelectParam($params, 'module_item_render_as,render_as'); } else { $block_params['current'] = ($template == $current_template); $block_params['title'] = $this->Application->Phrase($title); $block_params['name'] = $template == $current_template ? $params['current_render_as'] : $params['render_as']; } $ret .= $this->Application->ParseBlock($block_params); } if ($show_category) { $params['no_current'] = true; return $home_element . ($show_category ? $this->getCategoryPath($main_category_id, $params) : '') . $ret; } return $home_element . $ret; } /** * Get navigation parts * * @param Array $titles * @param Array $templates * @return Array */ function getNavigationParts($titles, $templates) { $titles = explode(',', $titles); $templates = explode(',', $templates); $ret = Array (); foreach ($templates as $template_pos => $template) { $ret[$template] = $titles[$template_pos]; } return $ret; } /** * Renders path to given category using given blocks. * * @param int $main_category_id * @param Array $params * @return string */ function getCategoryPath($main_category_id, $params) { $category_path = $this->getCategoryParentPath($main_category_id); if (!$category_path) { // in "Home" category return ''; } if (array_key_exists('shift', $params) && $params['shift']) { array_splice($category_path, 0, $params['shift']); } $module_info = $this->getCategoryModule($params, array_keys($category_path)); $module_category_id = $module_info['RootCat']; $module_item_id = $this->Application->GetVar($module_info['Var'].'_id'); $ret = ''; $block_params['category'] = 1; $block_params['no_editing'] = 1; if (array_key_exists('is_first', $params)) { $block_params['is_first'] = $params['is_first']; } $block_params['separator'] = $params['separator']; $no_current = isset($params['no_current']) && $params['no_current']; $backup_category_id = $this->Application->GetVar('c_id'); foreach ($category_path as $category_id => $category_name) { $block_params['cat_id'] = $category_id; $block_params['cat_name'] = $block_params['title'] = $category_name; if ($no_current) { $block_params['current'] = 0; } else { $block_params['current'] = ($main_category_id == $category_id) && !$module_item_id ? 1 : 0; } $block_params['is_module_root'] = $category_id == $module_category_id ? 1 : 0; $block_params['name'] = $this->SelectParam($params, 'render_as,block'); // which block to parse as current ? if ($block_params['is_module_root']) { $block_params['name'] = $this->SelectParam($params, 'module_root_render_as,render_as'); $block_params['module_index'] = $module_info['TemplatePath'].'index'; } if ($block_params['current']) { $block_params['name'] = $this->SelectParam($params, 'current_render_as,render_as'); } $this->Application->SetVar('c_id', $category_id); $ret .= $this->Application->ParseBlock($block_params); if (array_key_exists('is_first', $block_params)) { unset($block_params['is_first']); } } $this->Application->SetVar('c_id', $backup_category_id); return $ret; } /** * Returns module information based on given module name or current category (relative to module root categories) * * @param Array $params * @param Array $category_ids category parent path (already as array) * @return Array */ function getCategoryModule($params, $category_ids) { if (isset($params['module'])) { // get module by name specified $module_info = $this->Application->findModule('Name', $params['module']); } elseif ($category_ids) { // get module by category path $module_root_categories = $this->getModuleRootCategories(); $common_categories = array_intersect($category_ids, $module_root_categories); $module_category_id = array_shift($common_categories); // get 1st common category $module_info = $this->Application->findModule('RootCat', $module_category_id); } return $module_info; } /** * Renders path to top catalog category * * @param Array $params * @param int $current_category * @return string */ function getHomeCategoryPath($params, $current_category) { - $block_params['cat_id'] = $this->Application->findModule('Name', 'Core', 'RootCat'); // 0; + $block_params['cat_id'] = $this->Application->getBaseCategory(); $block_params['no_editing'] = 1; $block_params['current'] = $current_category == $block_params['cat_id'] ? 1 : 0; $block_params['separator'] = $params['separator']; $block_params['is_first'] = $params['is_first']; $block_params['cat_name'] = $this->Application->Phrase(($this->Application->isAdmin ? 'la_' : 'lu_') . 'rootcategory_name'); $block_params['name'] = $this->SelectParam($params, 'root_cat_render_as,render_as'); return $this->Application->ParseBlock($block_params); } /** * Returns root categories from all modules * * @return Array */ function getModuleRootCategories() { static $root_categories = null; if (!isset($root_categories)) { $root_categories = Array (); foreach ($this->Application->ModuleInfo as $module_name => $module_info) { array_push($root_categories, $module_info['RootCat']); } $root_categories = array_unique($root_categories); } return $root_categories; } /** * Returns given category's parent path as array of id=>name elements * * @param int $main_category_id * @return Array */ function getCategoryParentPath($main_category_id) { if ($main_category_id == 0) { // don't query path for "Home" category return Array (); } $cache_key = 'parent_paths_named[%CIDSerial:' . $main_category_id . '%]'; $cached_path = $this->Application->getCache($cache_key); if ($cached_path === false) { $ml_formatter =& $this->Application->recallObject('kMultiLanguage'); $navbar_field = $ml_formatter->LangFieldName('CachedNavBar'); $id_field = $this->Application->getUnitOption('c', 'IDField'); $table_name = $this->Application->getUnitOption('c', 'TableName'); $this->Conn->nextQueryCachable = true; $sql = 'SELECT '.$navbar_field.', ParentPath FROM '.$table_name.' WHERE '.$id_field.' = '.$main_category_id; $category_data = $this->Conn->GetRow($sql); $cached_path = Array (); - $skip_category = $this->Application->findModule('Name', 'Core', 'RootCat'); + $skip_category = $this->Application->getBaseCategory(); if ($category_data) { $category_names = explode('&|&', $category_data[$navbar_field]); $category_ids = explode('|', substr($category_data['ParentPath'], 1, -1)); foreach ($category_ids as $category_index => $category_id) { if ($category_id == $skip_category) { continue; } $cached_path[$category_id] = $category_names[$category_index]; } } $this->Application->setCache($cache_key, $cached_path); } return $cached_path; } /** * Not tag, method for parameter * selection from list in this TagProcessor * * @param Array $params * @param string $possible_names * @return string * @access public */ function SelectParam($params, $possible_names) { if (!is_array($params)) return; if (!is_array($possible_names)) $possible_names = explode(',', $possible_names); foreach ($possible_names as $name) { if( isset($params[$name]) ) return $params[$name]; } return false; } /** * Converts multi-dimensional category structure in one-dimensional option array (category_id=>category_name) * * @param Array $data * @param int $parent_category_id * @param int_type $language_id * @param int $theme_id * @param int $level * @return Array */ function _printChildren(&$data, $parent_category_id, $language_id, $theme_id, $level = 0) { if ($data['ThemeId'] != $theme_id && $data['ThemeId'] != 0) { // don't show system templates from different themes return Array (); } $category_language = $data['l' . $language_id . '_Name'] ? $language_id : $this->_primaryLanguageId; $ret = Array($parent_category_id => str_repeat('-', $level).' '.$data['l' . $category_language . '_Name']); if ($data['children']) { $level++; foreach ($data['children'] as $category_id => $category_data) { $ret = array_merge_recursive2($ret, $this->_printChildren($data['children'][$category_id], $category_id, $language_id, $theme_id, $level)); } } return $ret; } /** * Returns information about children under parent path (recursive) * * @param int $parent_category_id * @param int $language_count * @return Array */ function _getChildren($parent_category_id, $language_count) { static $items_by_parent = null, $parent_mapping = null; if ( !isset($items_by_parent) ) { $fields = $items_by_parent = Array (); for ($i = 1; $i <= $language_count; $i++) { $fields[] = 'l' . $i . '_Name'; } $sql = 'SELECT CategoryId AS id, ' . implode(', ', $fields) . ', ParentId, ThemeId FROM ' . $this->Application->getUnitOption('c', 'TableName') . ' ORDER BY Priority DESC'; $items = $this->Conn->Query($sql, 'id'); foreach ($items as $item_id => $item_data) { $item_parent_id = $item_data['ParentId']; unset($item_data['ParentId']); if ( !array_key_exists($item_parent_id, $items_by_parent) ) { $items_by_parent[$item_parent_id] = Array (); } $item_data['children'] = false; $parent_mapping[$item_id] = $item_parent_id; $items_by_parent[$item_parent_id][$item_id] = $item_data; } } $data = $items_by_parent[ $parent_mapping[$parent_category_id] ][$parent_category_id]; $categories = array_key_exists($parent_category_id, $items_by_parent) ? $items_by_parent[$parent_category_id] : Array (); foreach ($categories as $category_id => $category_data) { if ($category_id == $parent_category_id) { // don't process myself - prevents recursion continue; } $data['children'][$category_id] = $this->_getChildren($category_id, $language_count); } return $data; } /** * Generates OR retrieves from cache structure tree * * @return Array */ function &_getStructureTree() { // get cached version of structure tree if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $data = $this->Application->getCache('master:StructureTree', false); } else { $data = $this->Application->getDBCache('StructureTree'); } if ($data) { $data = unserialize($data); return $data; } // generate structure tree from scratch $ml_helper =& $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $language_count = $ml_helper->getLanguageCount(); - $root_category = $this->Application->findModule('Name', 'Core', 'RootCat'); + $root_category = $this->Application->getBaseCategory(); $data = $this->_getChildren($root_category, $language_count); if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $this->Application->setCache('master:StructureTree', serialize($data)); } else { $this->Application->setDBCache('StructureTree', serialize($data)); } return $data; } function getTemplateMapping() { if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $data = $this->Application->getCache('master:template_mapping', false); } else { $data = $this->Application->getDBCache('template_mapping'); } if ($data) { return unserialize($data); } $sql = 'SELECT IF(c.`Type` = ' . PAGE_TYPE_TEMPLATE . ', CONCAT(c.Template, ":", c.ThemeId), CONCAT("id:", c.CategoryId)) AS SrcTemplate, LOWER( IF( c.SymLinkCategoryId IS NOT NULL, (SELECT cc.NamedParentPath FROM ' . TABLE_PREFIX . 'Category AS cc WHERE cc.CategoryId = c.SymLinkCategoryId), c.NamedParentPath ) ) AS DstTemplate, c.UseExternalUrl, c.ExternalUrl FROM ' . TABLE_PREFIX . 'Category AS c WHERE c.Status = ' . STATUS_ACTIVE; $pages = $this->Conn->Query($sql, 'SrcTemplate'); $mapping = Array (); $base_url = $this->Application->BaseURL(); foreach ($pages as $src_template => $page) { // process external url, before placing in cache if ($page['UseExternalUrl']) { $external_url = $page['ExternalUrl']; if (!preg_match('/^(.*?):\/\/(.*)$/', $external_url)) { // url without protocol will be relative url to our site $external_url = $base_url . $external_url; } $dst_template = 'external:' . $external_url; } else { $dst_template = preg_replace('/^Content\//i', '', $page['DstTemplate']); } $mapping[$src_template] = $dst_template; } if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $data = $this->Application->setCache('master:template_mapping', serialize($mapping)); } else { $this->Application->setDBCache('template_mapping', serialize($mapping)); } return $mapping; } /** * Returns category structure as field option list * * @return Array */ function getStructureTreeAsOptions() { if ((defined('IS_INSTALL') && IS_INSTALL) || !$this->Application->isAdmin) { // no need to create category structure during install // OR on Front-End, because it's not used there return Array (); } if (isset($this->_structureTree)) { return $this->_structureTree; } $themes_helper =& $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ $data = $this->_getStructureTree(); $theme_id = (int)$themes_helper->getCurrentThemeId(); - $root_category = $this->Application->findModule('Name', 'Core', 'RootCat'); + $root_category = $this->Application->getBaseCategory(); $this->_primaryLanguageId = $this->Application->GetDefaultLanguageId(); $this->_structureTree = $this->_printChildren($data, $root_category, $this->Application->GetVar('m_lang'), $theme_id); return $this->_structureTree; } /** * Replace links like "@@ID@@" to actual template names in given text * * @param string $text * @return string */ function replacePageIds($text) { if (!preg_match_all('/@@(\\d+)@@/', $text, $regs)) { return $text; } $page_ids = $regs[1]; $sql = 'SELECT NamedParentPath, CategoryId FROM ' . TABLE_PREFIX . 'Category WHERE CategoryId IN (' . implode(',', $page_ids) . ')'; $templates = $this->Conn->GetCol($sql, 'CategoryId'); foreach ($page_ids as $page_id) { if (!array_key_exists($page_id, $templates)) { // internal page was deleted, but link to it was found in given content block data continue; } $url_params = Array ('m_cat_id' => $page_id, 'pass' => 'm'); $page_url = $this->Application->HREF(strtolower($templates[$page_id]), '', $url_params); /*if ($this->Application->isAdmin) { $page_url = preg_replace('/&(admin|editing_mode)=[\d]/', '', $page_url); }*/ $text = str_replace('@@' . $page_id . '@@', $page_url, $text); } return $text; } } \ No newline at end of file Index: branches/5.1.x/core/units/helpers/cat_dbitem_export_helper.php =================================================================== --- branches/5.1.x/core/units/helpers/cat_dbitem_export_helper.php (revision 13986) +++ branches/5.1.x/core/units/helpers/cat_dbitem_export_helper.php (revision 13987) @@ -1,1468 +1,1468 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); define('EXPORT_STEP', 100); // export by 200 items (e.g. links) define('IMPORT_STEP', 20); // export by 200 items (e.g. links) define('IMPORT_CHUNK', 10240); // 10240); //30720); //50120); // 5 KB define('IMPORT_TEMP', 1); define('IMPORT_LIVE', 2); class kCatDBItemExportHelper extends kHelper { var $false = false; var $cache = Array(); /** * Allows to find out what items are new in cache * * @var Array */ var $cacheStatus = Array(); var $cacheTable = ''; var $exportFields = Array(); /** * Export options * * @var Array */ var $exportOptions = Array(); /** * Item beeing currenly exported * * @var kCatDBItem */ var $curItem = null; /** * Dummy category object * * @var CategoriesItem */ var $dummyCategory = null; /** * Pointer to opened file * * @var resource */ var $filePointer = null; /** * Custom fields definition of current item * * @var Array */ var $customFields = Array(); function kCatDBItemExportHelper() { parent::kHelper(); $this->cacheTable = TABLE_PREFIX.'ImportCache'; } /** * Returns value from cache if found or false otherwise * * @param string $type * @param int $key * @return mixed */ function getFromCache($type, $key) { return getArrayValue($this->cache, $type, $key); } /** * Adds value to be cached * * @param string $type * @param int $key * @param mixed $value */ function addToCache($type, $key, $value, $is_new = true) { // if (!isset($this->cache[$type])) $this->cache[$type] = Array(); $this->cache[$type][$key] = $value; if ($is_new) { $this->cacheStatus[$type][$key] = true; } } function storeCache($cache_types) { $cache_types = explode(',', $cache_types); $values_sql = ''; foreach ($cache_types as $cache_type) { $sql_mask = '('.$this->Conn->qstr($cache_type).',%s,%s),'; $cache = getArrayValue($this->cacheStatus, $cache_type); if (!$cache) $cache = Array(); foreach ($cache as $var_name => $cache_status) { $var_value = $this->cache[$cache_type][$var_name]; $values_sql .= sprintf($sql_mask, $this->Conn->qstr($var_name), $this->Conn->qstr($var_value) ); } } $values_sql = substr($values_sql, 0, -1); if ($values_sql) { $sql = 'INSERT INTO '.$this->cacheTable.'(`CacheName`,`VarName`,`VarValue`) VALUES '.$values_sql; $this->Conn->Query($sql); } } function loadCache() { $sql = 'SELECT * FROM '.$this->cacheTable; $records = $this->Conn->Query($sql); $this->cache = Array(); foreach ($records as $record) { $this->addToCache($record['CacheName'], $record['VarName'], $record['VarValue'], false); } } /** * Fill required fields with dummy values * * @param kEvent $event */ function fillRequiredFields(&$event, &$object, $set_status = false) { if ($object == $this->false) { $object =& $event->getObject(); } $has_empty = false; $fields = array_keys($object->Fields); foreach ($fields as $field_name) { $field_options =& $object->Fields[$field_name]; if (isset($object->VirtualFields[$field_name]) || !getArrayValue($field_options, 'required') ) continue; if ( $object->GetDBField($field_name) ) continue; $formatter_class = getArrayValue($field_options, 'formatter'); if ($formatter_class) // not tested { $formatter =& $this->Application->recallObject($formatter_class); $sample_value = $formatter->GetSample($field_name, $field_options, $object); } $has_empty = true; $object->SetField($field_name, isset($sample_value) && $sample_value ? $sample_value : 'no value'); } $object->UpdateFormattersSubFields(); if ($set_status && $has_empty) { $object->SetDBField('Status', 0); } } /** * Verifies that all user entered export params are correct * * @param kEvent $event */ function verifyOptions(&$event) { if ($this->Application->RecallVar($event->getPrefixSpecial().'_ForceNotValid')) { $this->Application->StoreVar($event->getPrefixSpecial().'_ForceNotValid', 0); return false; } $this->fillRequiredFields($event, $this->false); $object =& $event->getObject(); $cross_unique_fields = Array('FieldsSeparatedBy', 'FieldsEnclosedBy'); if (($object->GetDBField('CategoryFormat') == 1) || ($event->Special == 'import')) // in one field { $object->setRequired('CategorySeparator', true); $cross_unique_fields[] = 'CategorySeparator'; } $ret = $object->Validate(); // check if cross unique fields has no same values foreach ($cross_unique_fields as $field_index => $field_name) { if (getArrayValue($object->FieldErrors, $field_name, 'pseudo') == 'required') continue; $check_fields = $cross_unique_fields; unset($check_fields[$field_index]); foreach ($check_fields as $check_field) { if ($object->GetDBField($field_name) == $object->GetDBField($check_field)) { $object->SetError($check_field, 'unique'); } } } if ($event->Special == 'import') { $this->exportOptions = $this->loadOptions($event); $automatic_fields = ($object->GetDBField('FieldTitles') == 1); $object->setRequired('ExportColumns', !$automatic_fields); $category_prefix = '__CATEGORY__'; if ( $automatic_fields && ($this->exportOptions['SkipFirstRow']) ) { $this->openFile($event); $this->exportOptions['ExportColumns'] = $this->readRecord(); if (!$this->exportOptions['ExportColumns']) { $this->exportOptions['ExportColumns'] = Array (); } $this->closeFile(); // remove additional (non-parseble columns) foreach ($this->exportOptions['ExportColumns'] as $field_index => $field_name) { if (!$this->validateField($field_name, $object)) { unset($this->exportOptions['ExportColumns'][$field_index]); } } $category_prefix = ''; } // 1. check, that we have column definitions if (!$this->exportOptions['ExportColumns']) { $object->setError('ExportColumns', 'required'); $ret = false; } else { // 1.1. check that all required fields are present in imported file $missing_columns = Array(); foreach ($object->Fields as $field_name => $field_options) { if ($object->skipField($field_name)) continue; if (getArrayValue($field_options, 'required') && !in_array($field_name, $this->exportOptions['ExportColumns']) ) { $missing_columns[] = $field_name; $object->setError('ExportColumns', 'required_fields_missing', 'la_error_RequiredColumnsMissing'); $ret = false; } } if (!$ret && $this->Application->isDebugMode()) { $this->Application->Debugger->appendHTML('Missing required for import/export:'); $this->Application->Debugger->dumpVars($missing_columns); } } // 2. check, that we have only mixed category field or only separated category fields $category_found['mixed'] = false; $category_found['separated'] = false; foreach ($this->exportOptions['ExportColumns'] as $import_field) { if (preg_match('/^'.$category_prefix.'Category(Path|[0-9]+)/', $import_field, $rets)) { $category_found[$rets[1] == 'Path' ? 'mixed' : 'separated'] = true; } } if ($category_found['mixed'] && $category_found['separated']) { $object->SetError('ExportColumns', 'unique_category', 'la_error_unique_category_field'); $ret = false; } // 3. check, that duplicates check fields are selected & present in imported fields if ($this->exportOptions['ReplaceDuplicates']) { if ($this->exportOptions['CheckDuplicatesMethod'] == 1) { $check_fields = Array($object->IDField); } else { $check_fields = $this->exportOptions['DuplicateCheckFields'] ? explode('|', substr($this->exportOptions['DuplicateCheckFields'], 1, -1)) : Array(); $object =& $event->getObject(); $language_id = $this->Application->GetDefaultLanguageId(); foreach ($check_fields as $index => $check_field) { foreach ($object->Fields as $field_name => $field_options) { if ($field_name == 'l'.$language_id.'_'.$check_field) { $check_fields[$index] = 'l'.$language_id.'_'.$check_field; break; } } } } $this->exportOptions['DuplicateCheckFields'] = $check_fields; if (!$check_fields) { $object->setError('CheckDuplicatesMethod', 'required'); $ret = false; } else { foreach ($check_fields as $check_field) { $check_field = preg_replace('/^cust_(.*)/', 'Custom_\\1', $check_field); if (!in_array($check_field, $this->exportOptions['ExportColumns'])) { $object->setError('ExportColumns', 'required'); $ret = false; break; } } } } $this->saveOptions($event); } return $ret; } /** * Returns filename to read import data from * * @return string */ function getImportFilename() { if ($this->exportOptions['ImportSource'] == 1) { $ret = $this->exportOptions['ImportFilename']; // ['name']; commented by Kostja } else { $ret = $this->exportOptions['ImportLocalFilename']; } return EXPORT_PATH.'/'.$ret; } /** * Returns filename to write export data to * * @return string */ function getExportFilename() { $extension = $this->getFileExtension(); $filename = preg_replace('/(.*)\.' . $extension . '$/', '\1', $this->exportOptions['ExportFilename']) . '.' . $extension; return EXPORT_PATH . DIRECTORY_SEPARATOR . $filename; } /** * Opens file required for export/import operations * * @param kEvent $event */ function openFile(&$event) { $file_helper =& $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ $file_helper->CheckFolder(EXPORT_PATH); if ($event->Special == 'export') { $write_mode = ($this->exportOptions['start_from'] == 0) ? 'w' : 'a'; $this->filePointer = fopen($this->getExportFilename(), $write_mode); } else { $this->filePointer = fopen($this->getImportFilename(), 'r'); } // skip UTF-8 BOM Modifier $first_chars = fread($this->filePointer, 3); if (bin2hex($first_chars) != 'efbbbf') { fseek($this->filePointer, 0); } } /** * Closes opened file * */ function closeFile() { fclose($this->filePointer); } function getCustomSQL() { $ml_formatter =& $this->Application->recallObject('kMultiLanguage'); $custom_sql = ''; foreach ($this->customFields as $custom_id => $custom_name) { $custom_sql .= 'custom_data.'.$ml_formatter->LangFieldName('cust_'.$custom_id).' AS cust_'.$custom_name.', '; } return substr($custom_sql, 0, -2); } function getPlainExportSQL($count_only = false) { if ($count_only && isset($this->exportOptions['ForceCountSQL'])) { $sql = $this->exportOptions['ForceCountSQL']; } elseif (!$count_only && isset($this->exportOptions['ForceSelectSQL'])) { $sql = $this->exportOptions['ForceSelectSQL']; } else { $items_list =& $this->Application->recallObject($this->curItem->Prefix.'.export-items-list', $this->curItem->Prefix.'_List'); $items_list->SetPerPage(-1); if ($options['export_ids'] != '') { $items_list->AddFilter('export_ids', $items_list->TableName.'.'.$items_list->IDField.' IN ('.implode(',',$options['export_ids']).')'); } if ($count_only) { $sql = $items_list->getCountSQL( $items_list->GetSelectSQL(true,false) ); } else { $sql = $items_list->GetSelectSQL(); } } if (!$count_only) { $sql .= ' LIMIT '.$this->exportOptions['start_from'].','.EXPORT_STEP; } // else { // $sql = preg_replace("/^.*SELECT(.*?)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql); // } return $sql; } function getExportSQL($count_only = false) { if (!$this->Application->getUnitOption($this->curItem->Prefix, 'CatalogItem')) { return $this->GetPlainExportSQL($count_only); // in case this is not a CategoryItem } if ($this->exportOptions['export_ids'] === false) { // get links from current category & all it's subcategories $join_clauses = Array(); $custom_sql = $this->getCustomSQL(); if ($custom_sql) { $custom_table = $this->Application->getUnitOption($this->curItem->Prefix.'-cdata', 'TableName'); $join_clauses[$custom_table.' custom_data'] = 'custom_data.ResourceId = item_table.ResourceId'; } $join_clauses[TABLE_PREFIX.'CategoryItems ci'] = 'ci.ItemResourceId = item_table.ResourceId'; $join_clauses[TABLE_PREFIX.'Category c'] = 'c.CategoryId = ci.CategoryId'; $sql = 'SELECT item_table.*, ci.CategoryId'.($custom_sql ? ', '.$custom_sql : '').' FROM '.$this->curItem->TableName.' item_table'; foreach ($join_clauses as $table_name => $join_expression) { $sql .= ' LEFT JOIN '.$table_name.' ON '.$join_expression; } $sql .= ' WHERE '; if ($this->exportOptions['export_cats_ids'][0] == 0) { $sql .= '1'; } else { foreach ($this->exportOptions['export_cats_ids'] as $category_id) { $sql .= '(c.ParentPath LIKE "%|'.$category_id.'|%") OR '; } $sql = substr($sql, 0, -4); } $sql .= ' ORDER BY ci.PrimaryCat DESC'; // NEW } else { // get only selected links $sql = 'SELECT item_table.*, '.$this->exportOptions['export_cats_ids'][0].' AS CategoryId FROM '.$this->curItem->TableName.' item_table WHERE '.$this->curItem->IDField.' IN ('.implode(',', $this->exportOptions['export_ids']).')'; } if (!$count_only) { $sql .= ' LIMIT '.$this->exportOptions['start_from'].','.EXPORT_STEP; } else { $sql = preg_replace("/^.*SELECT(.*?)FROM(?!_)/is", "SELECT COUNT(*) AS count FROM ", $sql); } return $sql; } /** * Enter description here... * * @param kEvent $event */ function performExport(&$event) { $this->exportOptions = $this->loadOptions($event); $this->exportFields = $this->exportOptions['ExportColumns']; $this->curItem =& $event->getObject( Array('skip_autoload' => true) ); $this->customFields = $this->Application->getUnitOption($event->Prefix, 'CustomFields'); $this->openFile($event); if ($this->exportOptions['start_from'] == 0) // first export step { if (!getArrayValue($this->exportOptions, 'IsBaseCategory')) { $this->exportOptions['IsBaseCategory'] = 0; } if ($this->exportOptions['IsBaseCategory'] ) { $sql = 'SELECT ParentPath FROM '.TABLE_PREFIX.'Category WHERE CategoryId = ' . (int)$this->Application->GetVar('m_cat_id'); $parent_path = $this->Conn->GetOne($sql); $parent_path = explode('|', substr($parent_path, 1, -1)); - if ($parent_path && $parent_path[0] == $this->Application->findModule('Name', 'Core', 'RootCat')) { + if ($parent_path && $parent_path[0] == $this->Application->getBaseCategory()) { array_shift($parent_path); } $this->exportOptions['BaseLevel'] = count($parent_path); // level to cut from other categories } // 1. export field titles if required if ($this->exportOptions['IncludeFieldTitles']) { $data_array = Array(); foreach ($this->exportFields as $export_field) { $data_array = array_merge($data_array, $this->getFieldCaption($export_field)); } $this->writeRecord($data_array); } $this->exportOptions['total_records'] = $this->Conn->GetOne( $this->getExportSQL(true) ); } // 2. export data $records = $this->Conn->Query( $this->getExportSQL() ); $records_exported = 0; foreach ($records as $record_info) { $this->curItem->Clear(); $this->curItem->SetDBFieldsFromHash($record_info); $this->setCurrentID(); $this->curItem->raiseEvent('OnAfterItemLoad', $this->curItem->GetID() ); $data_array = Array(); foreach ($this->exportFields as $export_field) { $data_array = array_merge($data_array, $this->getFieldValue($export_field) ); } $this->writeRecord($data_array); $records_exported++; } $this->closeFile(); $this->exportOptions['start_from'] += $records_exported; $this->saveOptions($event); return $this->exportOptions; } function getItemFields() { // just in case dummy user selected automtic mode & moved columns too :( return array_merge($this->curItem->Fields['AvailableColumns']['options'], $this->curItem->Fields['ExportColumns']['options']); } /** * Checks if field really belongs to importable field list * * @param string $field_name * @param kCatDBItem $object * @return bool */ function validateField($field_name, &$object) { // 1. convert custom field $field_name = preg_replace('/^Custom_(.*)/', '__CUSTOM__\\1', $field_name); // 2. convert category field (mixed version & serparated version) $field_name = preg_replace('/^Category(Path|[0-9]+)/', '__CATEGORY__Category\\1', $field_name); $valid_fields = $object->getPossibleExportColumns(); return isset($valid_fields[$field_name]) || isset($valid_fields['__VIRTUAL__'.$field_name]); } /** * Enter description here... * * @param kEvent $event */ function performImport(&$event) { if (!$this->exportOptions) { // load import options in case if not previously loaded in verification function $this->exportOptions = $this->loadOptions($event); } $backup_category_id = $this->Application->GetVar('m_cat_id'); $this->Application->SetVar('m_cat_id', (int)$this->Application->RecallVar('ImportCategory') ); $this->openFile($event); $bytes_imported = 0; if ($this->exportOptions['start_from'] == 0) // first export step { // 1st time run if ($this->exportOptions['SkipFirstRow']) { $this->readRecord(); $this->exportOptions['start_from'] = ftell($this->filePointer); $bytes_imported = ftell($this->filePointer); } $current_category_id = $this->Application->GetVar('m_cat_id'); if ($current_category_id > 0) { $sql = 'SELECT ParentPath FROM '.TABLE_PREFIX.'Category WHERE CategoryId = '.$current_category_id; $this->exportOptions['ImportCategoryPath'] = $this->Conn->GetOne($sql); } else { $this->exportOptions['ImportCategoryPath'] = ''; } $this->exportOptions['total_records'] = filesize($this->getImportFilename()); } else { $this->loadCache(); } $this->exportFields = $this->exportOptions['ExportColumns']; $this->addToCache('category_parent_path', $this->Application->GetVar('m_cat_id'), $this->exportOptions['ImportCategoryPath']); // 2. import data $this->dummyCategory =& $this->Application->recallObject('c.-tmpitem', 'c', Array('skip_autoload' => true)); fseek($this->filePointer, $this->exportOptions['start_from']); $items_processed = 0; while (($bytes_imported < IMPORT_CHUNK && $items_processed < IMPORT_STEP) && !feof($this->filePointer)) { $data = $this->readRecord(); if ($data) { if ($this->exportOptions['ReplaceDuplicates']) { // set fields used as keys for replace duplicates code $this->resetImportObject($event, IMPORT_TEMP, $data); } $this->processCurrentItem($event, $data); } $bytes_imported = ftell($this->filePointer) - $this->exportOptions['start_from']; $items_processed++; } $this->closeFile(); $this->Application->SetVar('m_cat_id', $backup_category_id); $this->exportOptions['start_from'] += $bytes_imported; $this->storeCache('new_ids'); $this->saveOptions($event); if ($this->exportOptions['start_from'] == $this->exportOptions['total_records']) { $this->Conn->Query('TRUNCATE TABLE '.$this->cacheTable); } return $this->exportOptions; } function setCurrentID() { $this->curItem->setID( $this->curItem->GetDBField($this->curItem->IDField) ); } function setFieldValue($field_index, $value) { if (empty($value)) { $value = null; } $field_name = getArrayValue($this->exportFields, $field_index); if ($field_name == 'ResourceId') { return false; } if (substr($field_name, 0, 7) == 'Custom_') { $field_name = 'cust_'.substr($field_name, 7); $this->curItem->SetField($field_name, $value); } elseif ($field_name == 'CategoryPath' || $field_name == '__CATEGORY__CategoryPath') { $this->curItem->CategoryPath = $value ? explode($this->exportOptions['CategorySeparator'], $value) : Array(); } elseif (substr($field_name, 0, 8) == 'Category') { $this->curItem->CategoryPath[ (int)substr($field_name, 8) - 1 ] = $value; } elseif (substr($field_name, 0, 20) == '__CATEGORY__Category') { $this->curItem->CategoryPath[ (int)substr($field_name, 20) ] = $value; } elseif (substr($field_name, 0, 11) == '__VIRTUAL__') { $field_name = substr($field_name, 11); $this->curItem->SetField($field_name, $value); } else { $this->curItem->SetField($field_name, $value); } $pseudo_error = getArrayValue($this->curItem->FieldErrors, $field_name, 'pseudo'); if ($pseudo_error) { $this->curItem->SetDBField($field_name, null); unset($this->curItem->FieldErrors[$field_name]); } } function resetImportObject(&$event, $object_type, $record_data = null) { switch ($object_type) { case IMPORT_TEMP: $this->curItem =& $event->getObject( Array('skip_autoload' => true) ); break; case IMPORT_LIVE: $this->curItem =& $this->Application->recallObject($event->Prefix.'.-tmpitem'.$event->Special, $event->Prefix, Array('skip_autoload' => true)); break; } $this->curItem->Clear(); $this->customFields = $this->Application->getUnitOption($event->Prefix, 'CustomFields'); if (isset($record_data)) { $this->setImportData($record_data); } } function setImportData($record_data) { foreach ($record_data as $field_index => $field_value) { $this->setFieldValue($field_index, $field_value); } $this->setCurrentID(); } function getItemCategory() { static $lang_prefix = null; $backup_category_id = $this->Application->GetVar('m_cat_id'); $category_id = $this->getFromCache('category_names', implode(':', $this->curItem->CategoryPath)); if ($category_id) { $this->Application->SetVar('m_cat_id', $category_id); return $category_id; } if (is_null($lang_prefix)) { $lang_prefix = 'l'.$this->Application->GetVar('m_lang').'_'; } foreach ($this->curItem->CategoryPath as $category_index => $category_name) { if (!$category_name) continue; $category_key = crc32( implode(':', array_slice($this->curItem->CategoryPath, 0, $category_index + 1) ) ); $category_id = $this->getFromCache('category_names', $category_key); if ($category_id === false) { // get parent category path to search only in it $current_category_id = $this->Application->GetVar('m_cat_id'); // $parent_path = $this->getParentPath($current_category_id); // get category id from database by name $sql = 'SELECT CategoryId FROM '.TABLE_PREFIX.'Category WHERE ('.$lang_prefix.'Name = '.$this->Conn->qstr($category_name).') AND (ParentId = '.(int)$current_category_id.')'; $category_id = $this->Conn->GetOne($sql); if ($category_id === false) { // category not in db -> create $category_fields = Array( $lang_prefix.'Name' => $category_name, $lang_prefix.'Description' => $category_name, 'Status' => STATUS_ACTIVE, 'ParentId' => $current_category_id, 'AutomaticFilename' => 1 ); $this->dummyCategory->SetDBFieldsFromHash($category_fields); if ($this->dummyCategory->Create()) { $category_id = $this->dummyCategory->GetID(); $this->addToCache('category_parent_path', $category_id, $this->dummyCategory->GetDBField('ParentPath')); $this->addToCache('category_names', $category_key, $category_id); } } else { $this->addToCache('category_names', $category_key, $category_id); } } if ($category_id) { $this->Application->SetVar('m_cat_id', $category_id); } } if (!$this->curItem->CategoryPath) { $category_id = $backup_category_id; } return $category_id; } /** * Enter description here... * * @param kEvent $event */ function processCurrentItem(&$event, $record_data) { $save_method = 'Create'; $load_keys = Array(); // create/update categories $backup_category_id = $this->Application->GetVar('m_cat_id'); // perform replace duplicates code if ($this->exportOptions['ReplaceDuplicates']) { // get replace keys first, then reset current item to empty one $category_id = $this->getItemCategory(); if ($this->exportOptions['CheckDuplicatesMethod'] == 1) { if ($this->curItem->GetID()) { $load_keys = Array($this->curItem->IDField => $this->curItem->GetID()); } } else { $key_fields = $this->exportOptions['DuplicateCheckFields']; foreach ($key_fields as $key_field) { $load_keys[$key_field] = $this->curItem->GetDBField($key_field); } } $this->resetImportObject($event, IMPORT_LIVE); if (count($load_keys)) { $where_clause = ''; $language_id = (int)$this->Application->GetVar('m_lang'); if (!$language_id) { $language_id = 1; } foreach ($load_keys as $field_name => $field_value) { if (preg_match('/^cust_(.*)/', $field_name, $regs)) { $custom_id = array_search($regs[1], $this->customFields); $field_name = 'l'.$language_id.'_cust_'.$custom_id; $where_clause .= '(custom_data.`'.$field_name.'` = '.$this->Conn->qstr($field_value).') AND '; } else { $where_clause .= '(item_table.`'.$field_name.'` = '.$this->Conn->qstr($field_value).') AND '; } } $where_clause = substr($where_clause, 0, -5); $item_id = $this->getFromCache('new_ids', crc32($where_clause)); if (!$item_id) { if ($this->exportOptions['CheckDuplicatesMethod'] == 2) { // by other fields $parent_path = $this->getParentPath($category_id); $where_clause = '(c.ParentPath LIKE "'.$parent_path.'%") AND '.$where_clause; } $cdata_table = $this->Application->getUnitOption($event->Prefix.'-cdata', 'TableName'); $sql = 'SELECT '.$this->curItem->IDField.' FROM '.$this->curItem->TableName.' item_table LEFT JOIN '.$cdata_table.' custom_data ON custom_data.ResourceId = item_table.ResourceId LEFT JOIN '.TABLE_PREFIX.'CategoryItems ci ON ci.ItemResourceId = item_table.ResourceId LEFT JOIN '.TABLE_PREFIX.'Category c ON c.CategoryId = ci.CategoryId WHERE '.$where_clause; $item_id = $this->Conn->GetOne($sql); } $save_method = $item_id && $this->curItem->Load($item_id) ? 'Update' : 'Create'; if ($save_method == 'Update') { // replace id from csv file with found id (only when ID is found in cvs file) if (in_array($this->curItem->IDField, $this->exportFields)) { $record_data[ array_search($this->curItem->IDField, $this->exportFields) ] = $item_id; } } } $this->setImportData($record_data); } else { $this->resetImportObject($event, IMPORT_LIVE, $record_data); $category_id = $this->getItemCategory(); } // create main record if ($save_method == 'Create') { $this->fillRequiredFields($this->false, $this->curItem, true); } // $sql_start = getmicrotime(); if (!$this->curItem->$save_method()) { $this->Application->SetVar('m_cat_id', $backup_category_id); return false; } // $sql_end = getmicrotime(); // $this->saveLog('SQL ['.$save_method.'] Time: '.($sql_end - $sql_start).'s'); if ($load_keys && ($save_method == 'Create') && $this->exportOptions['ReplaceDuplicates']) { // map new id to old id $this->addToCache('new_ids', crc32($where_clause), $this->curItem->GetID() ); } // assign item to categories $this->curItem->assignToCategory($category_id, false); $this->Application->SetVar('m_cat_id', $backup_category_id); return true; } /*function saveLog($msg) { static $first_time = true; $fp = fopen(FULL_PATH.'/sqls.log', $first_time ? 'w' : 'a'); fwrite($fp, $msg."\n"); fclose($fp); $first_time = false; }*/ /** * Returns category parent path, if possible, then from cache * * @param int $category_id * @return string */ function getParentPath($category_id) { $parent_path = $this->getFromCache('category_parent_path', $category_id); if ($parent_path === false) { $sql = 'SELECT ParentPath FROM '.TABLE_PREFIX.'Category WHERE CategoryId = '.$category_id; $parent_path = $this->Conn->GetOne($sql); $this->addToCache('category_parent_path', $category_id, $parent_path); } return $parent_path; } function getFileExtension() { return $this->exportOptions['ExportFormat'] == 1 ? 'csv' : 'xml'; } function getLineSeparator($option = 'LineEndings') { return $this->exportOptions[$option] == 1 ? "\r\n" : "\n"; } /** * Returns field caption for any exported field * * @param string $field * @return string */ function getFieldCaption($field) { if (substr($field, 0, 10) == '__CUSTOM__') { $ret = 'Custom_'.substr($field, 10, strlen($field) ); } elseif (substr($field, 0, 12) == '__CATEGORY__') { return $this->getCategoryTitle(); } elseif (substr($field, 0, 11) == '__VIRTUAL__') { $ret = substr($field, 11); } else { $ret = $field; } return Array($ret); } /** * Returns requested field value (including custom fields and category fields) * * @param string $field * @return string */ function getFieldValue($field) { if (substr($field, 0, 10) == '__CUSTOM__') { $field = 'cust_'.substr($field, 10, strlen($field)); $ret = $this->curItem->GetField($field); } elseif (substr($field, 0, 12) == '__CATEGORY__') { return $this->getCategoryPath(); } elseif (substr($field, 0, 11) == '__VIRTUAL__') { $field = substr($field, 11); $ret = $this->curItem->GetField($field); } else { $ret = $this->curItem->GetField($field); } $ret = str_replace("\r\n", $this->getLineSeparator('LineEndingsInside'), $ret); return Array($ret); } /** * Returns category field(-s) caption based on export mode * * @return string */ function getCategoryTitle() { // category path in separated fields $category_count = $this->getMaxCategoryLevel(); if ($this->exportOptions['CategoryFormat'] == 1) { // category path in one field return $category_count ? Array('CategoryPath') : Array(); } else { $i = 0; $ret = Array(); while ($i < $category_count) { $ret[] = 'Category'.($i + 1); $i++; } return $ret; } } /** * Returns category path in required format for current link * * @return string */ function getCategoryPath() { $category_id = $this->curItem->GetDBField('CategoryId'); $category_path = $this->getFromCache('category_path', $category_id); if (!$category_path) { $ml_formatter =& $this->Application->recallObject('kMultiLanguage'); $sql = 'SELECT '.$ml_formatter->LangFieldName('CachedNavbar').' FROM '.TABLE_PREFIX.'Category WHERE CategoryId = '.$category_id; $category_path = $this->Conn->GetOne($sql); $category_path = $category_path ? explode('&|&', $category_path) : Array(); if ($category_path && strtolower($category_path[0]) == 'content') { array_shift($category_path); } if ($this->exportOptions['IsBaseCategory']) { $i = $this->exportOptions['BaseLevel']; while ($i > 0) { array_shift($category_path); $i--; } } $category_count = $this->getMaxCategoryLevel(); if ($this->exportOptions['CategoryFormat'] == 1) { // category path in single field $category_path = $category_count ? Array( implode($this->exportOptions['CategorySeparator'], $category_path) ) : Array(); } else { // category path in separated fields $levels_used = count($category_path); if ($levels_used < $category_count) { $i = 0; while ($i < $category_count - $levels_used) { $category_path[] = ''; $i++; } } } $this->addToCache('category_path', $category_id, $category_path); } return $category_path; } /** * Get maximal category deep level from links beeing exported * * @return int */ function getMaxCategoryLevel() { static $max_level = -1; if ($max_level != -1) { return $max_level; } $sql = 'SELECT IF(c.CategoryId IS NULL, 0, MAX( LENGTH(c.ParentPath) - LENGTH( REPLACE(c.ParentPath, "|", "") ) - 1 )) FROM '.$this->curItem->TableName.' item_table LEFT JOIN '.TABLE_PREFIX.'CategoryItems ci ON item_table.ResourceId = ci.ItemResourceId LEFT JOIN '.TABLE_PREFIX.'Category c ON c.CategoryId = ci.CategoryId WHERE (ci.PrimaryCat = 1) AND '; $where_clause = ''; if ($this->exportOptions['export_ids'] === false) { // get links from current category & all it's subcategories if ($this->exportOptions['export_cats_ids'][0] == 0) { $where_clause = 1; } else { foreach ($this->exportOptions['export_cats_ids'] as $category_id) { $where_clause .= '(c.ParentPath LIKE "%|'.$category_id.'|%") OR '; } $where_clause = substr($where_clause, 0, -4); } } else { // get only selected links $where_clause = $this->curItem->IDField.' IN ('.implode(',', $this->exportOptions['export_ids']).')'; } $max_level = $this->Conn->GetOne($sql.'('.$where_clause.')'); if ($this->exportOptions['IsBaseCategory'] ) { $max_level -= $this->exportOptions['BaseLevel']; } return $max_level; } /** * Saves one record to export file * * @param Array $fields_hash */ function writeRecord($fields_hash) { fputcsv2($this->filePointer, $fields_hash, $this->exportOptions['FieldsSeparatedBy'], $this->exportOptions['FieldsEnclosedBy'], $this->getLineSeparator() ); } function readRecord() { return fgetcsv($this->filePointer, 10000, $this->exportOptions['FieldsSeparatedBy'], $this->exportOptions['FieldsEnclosedBy']); } function saveOptions(&$event, $options = null) { if (!isset($options)) { $options = $this->exportOptions; } $this->Application->StoreVar($event->getPrefixSpecial().'_options', serialize($options) ); } function loadOptions(&$event) { return unserialize($this->Application->RecallVar($event->getPrefixSpecial().'_options')); } /** * Sets correct available & export fields * * @param kEvent $event */ function prepareExportColumns(&$event) { $object =& $event->getObject( Array('skip_autoload' => true) ); if (!array_key_exists('ExportColumns', $object->Fields)) { // import/export prefix was used (see kDBEventHandler::prepareObject) but object don't plan to be imported/exported return ; } $available_columns = Array(); if ($this->Application->getUnitOption($event->Prefix, 'CatalogItem')) { // category field (mixed) $available_columns['__CATEGORY__CategoryPath'] = 'CategoryPath'; if ($event->Special == 'import') { // category field (separated fields) $max_level = $this->Application->ConfigValue('MaxImportCategoryLevels'); $i = 0; while ($i < $max_level) { $available_columns['__CATEGORY__Category'.($i + 1)] = 'Category'.($i + 1); $i++; } } } // db fields foreach ($object->Fields as $field_name => $field_options) { if (!$object->skipField($field_name)) { $available_columns[$field_name] = $field_name.(getArrayValue($field_options, 'required') ? '*' : ''); } } $handler =& $this->Application->recallObject($event->Prefix.'_EventHandler'); $available_columns = array_merge_recursive2($available_columns, $handler->getCustomExportColumns($event)); // custom fields foreach ($object->customFields as $custom_id => $custom_name) { $available_columns['__CUSTOM__'.$custom_name] = $custom_name; } // columns already in use $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if ($items_info) { list($item_id, $field_values) = each($items_info); $export_keys = $field_values['ExportColumns']; $export_keys = $export_keys ? explode('|', substr($export_keys, 1, -1) ) : Array(); } else { $export_keys = Array(); } $export_columns = Array(); foreach ($export_keys as $field_key) { $field_name = $this->getExportField($field_key); $export_columns[$field_key] = $field_name; unset($available_columns[$field_key]); } $options = $object->GetFieldOptions('ExportColumns'); $options['options'] = $export_columns; $object->SetFieldOptions('ExportColumns', $options); $options = $object->GetFieldOptions('AvailableColumns'); $options['options'] = $available_columns; $object->SetFieldOptions('AvailableColumns', $options); $this->updateImportFiles($event); $this->PrepareExportPresets($event); } function PrepareExportPresets(&$event) { $object =& $event->getObject( Array('skip_autoload' => true) ); $options = $object->GetFieldOptions('ExportPresets'); $export_settings = $this->Application->RecallPersistentVar('export_settings'); if (!$export_settings) return ; $export_settings = unserialize($export_settings); if (!isset($export_settings[$event->Prefix])) return ; $export_presets = array(''=>''); foreach ($export_settings[$event->Prefix] as $key => $val) { $export_presets[implode('|', $val['ExportColumns'])] = $key; } $options['options'] = $export_presets; $object->SetFieldOptions('ExportPresets', $options); } function getExportField($field_key) { $prepends = Array('__CUSTOM__', '__CATEGORY__'); foreach ($prepends as $prepend) { if (substr($field_key, 0, strlen($prepend) ) == $prepend) { $field_key = substr($field_key, strlen($prepend), strlen($field_key) ); break; } } return $field_key; } /** * Updates uploaded files list * * @param kEvent $event */ function updateImportFiles(&$event) { if ($event->Special != 'import') { return false; } $object =& $event->getObject(); $import_filenames = Array(); $file_helper =& $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ $file_helper->CheckFolder(EXPORT_PATH); if ($folder_handle = opendir(EXPORT_PATH)) { while (false !== ($file = readdir($folder_handle))) { if (is_dir(EXPORT_PATH.'/'.$file) || substr($file, 0, 1) == '.' || strtolower($file) == 'cvs' || strtolower($file) == 'dummy' || filesize(EXPORT_PATH.'/'.$file) == 0) continue; $file_size = formatSize( filesize(EXPORT_PATH.'/'.$file) ); $import_filenames[$file] = $file.' ('.$file_size.')'; } closedir($folder_handle); } $options = $object->GetFieldOptions('ImportLocalFilename'); $options['options'] = $import_filenames; $object->SetFieldOptions('ImportLocalFilename', $options); } /** * Returns module folder * * @param kEvent $event * @return string */ function getModuleName(&$event) { $module_path = $this->Application->getUnitOption($event->Prefix, 'ModuleFolder') . '/'; $module_name = $this->Application->findModule('Path', $module_path, 'Name'); return mb_strtolower($module_name); } /** * Export form validation & processing * * @param kEvent $event */ function OnExportBegin(&$event) { $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if (!$items_info) { $items_info = unserialize( $this->Application->RecallVar($event->getPrefixSpecial().'_ItemsInfo') ); $this->Application->SetVar($event->getPrefixSpecial(true), $items_info); } list($item_id, $field_values) = each($items_info); $object =& $event->getObject( Array('skip_autoload' => true) ); $object->SetFieldsFromHash($field_values); $field_values['ImportFilename'] = $object->GetDBField('ImportFilename'); //if upload formatter has renamed the file during moving !!! $object->setID($item_id); $this->setRequiredFields($event); $export_object =& $this->Application->recallObject('CatItemExportHelper'); // save export/import options if ($event->Special == 'export') { $export_ids = $this->Application->RecallVar($event->Prefix.'_export_ids'); $export_cats_ids = $this->Application->RecallVar($event->Prefix.'_export_cats_ids'); // used for multistep export $field_values['export_ids'] = $export_ids ? explode(',', $export_ids) : false; $field_values['export_cats_ids'] = $export_cats_ids ? explode(',', $export_cats_ids) : Array( $this->Application->GetVar('m_cat_id') ); } $field_values['ExportColumns'] = $field_values['ExportColumns'] ? explode('|', substr($field_values['ExportColumns'], 1, -1) ) : Array(); $field_values['start_from'] = 0; $this->Application->HandleEvent($nevent, $event->Prefix.':OnBeforeExportBegin', array('options'=>$field_values)); $field_values = $nevent->getEventParam('options'); $export_object->saveOptions($event, $field_values); if( $export_object->verifyOptions($event) ) { if ($this->_getExportSavePreset($object)) { $name = $object->GetDBField('ExportPresetName'); $export_settings = $this->Application->RecallPersistentVar('export_settings'); $export_settings = $export_settings ? unserialize($export_settings) : array(); $export_settings[$event->Prefix][$name] = $field_values; $this->Application->StorePersistentVar('export_settings', serialize($export_settings)); } $progress_t = $this->Application->RecallVar('export_progress_t'); if ($progress_t) { $this->Application->RemoveVar('export_progress_t'); } else { $progress_t = $export_object->getModuleName($event).'/'.$event->Special.'_progress'; } $event->redirect = $progress_t; if ($event->Special == 'import') { $import_category = (int)$this->Application->RecallVar('ImportCategory'); // in future could use module root category if import category will be unavailable :) $event->SetRedirectParam('m_cat_id', $import_category); // for template permission checking $this->Application->StoreVar('m_cat_id', $import_category); // for event permission checking } } else { // make uploaded file local & change source selection $filename = getArrayValue($field_values, 'ImportFilename'); if ($filename) { $export_object->updateImportFiles($event); $object->SetDBField('ImportSource', 2); $field_values['ImportSource'] = 2; $object->SetDBField('ImportLocalFilename', $filename); $field_values['ImportLocalFilename'] = $filename; $export_object->saveOptions($event, $field_values); } $event->status = erFAIL; $event->redirect = false; } } /** * Returns export save preset name, when used at all * * @param kDBItem $object * @return string */ function _getExportSavePreset(&$object) { if (!array_key_exists('ExportSavePreset', $object->Fields)) { return ''; } return $object->GetDBField('ExportSavePreset'); } /** * set required fields based on import or export params * * @param kEvent $event */ function setRequiredFields(&$event) { $required_fields['common'] = Array('FieldsSeparatedBy', 'LineEndings', 'CategoryFormat'); $required_fields['export'] = Array('ExportFormat', 'ExportFilename','ExportColumns'); $object =& $event->getObject(); if ($this->_getExportSavePreset($object)) { $required_fields['export'][] = 'ExportPresetName'; } $required_fields['import'] = Array('FieldTitles', 'ImportSource', 'CheckDuplicatesMethod'); // ImportFilename, ImportLocalFilename if ($event->Special == 'import') { $import_source = Array(1 => 'ImportFilename', 2 => 'ImportLocalFilename'); $used_field = $import_source[ $object->GetDBField('ImportSource') ]; $required_fields[$event->Special][] = $used_field; $object->Fields[$used_field]['error_field'] = 'ImportSource'; if ($object->GetDBField('FieldTitles') == 2) $required_fields[$event->Special][] = 'ExportColumns'; // manual field titles } $required_fields = array_merge($required_fields['common'], $required_fields[$event->Special]); foreach ($required_fields as $required_field) { $object->setRequired($required_field, true); } } } \ No newline at end of file Index: branches/5.1.x/core/units/helpers/menu_helper.php =================================================================== --- branches/5.1.x/core/units/helpers/menu_helper.php (revision 13986) +++ branches/5.1.x/core/units/helpers/menu_helper.php (revision 13987) @@ -1,382 +1,382 @@ <?php /** * @version $Id: menu_helper.php 12951 2009-12-19 10:04:11Z alex $ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class MenuHelper extends kHelper { /** * Cached version of site menu * * @var Array */ var $Menu = null; /** * Parent path mapping used in CachedMenu tag * * @var Array */ var $parentPaths = Array (); /** * Builds site menu * * @param Array $params * @return string */ function menuTag($prefix_special, $params) { list ($menu, $root_path) = $this->_prepareMenu(); $cat = $this->_getCategoryId($params); $parent_path = array_key_exists($cat, $this->parentPaths) ? $this->parentPaths[$cat] : ''; $parent_path = str_replace($root_path, '', $parent_path); // menu starts from module path $levels = explode('|', trim($parent_path, '|')); if ($levels[0] === '') { $levels = Array (); } if (array_key_exists('level', $params) && $params['level'] > count($levels)) { // current level is deeper, then requested level return ; } $level = max(array_key_exists('level', $params) ? $params['level'] - 1 : count($levels) - 1, 0); $parent = array_key_exists($level, $levels) ? $levels[$level] : 0; $cur_menu =& $menu; $menu_path = array_slice($levels, 0, $level + 1); foreach ($menu_path as $elem) { $cur_menu =& $cur_menu['c' . $elem]['sub_items']; } $block_params = $this->prepareTagParams($prefix_special, $params); $block_params['name'] = $params['render_as']; $this->Application->SetVar('cur_parent_path', $parent_path); $real_cat_id = $this->Application->GetVar('m_cat_id'); if (!is_array($cur_menu) || !$cur_menu) { // no menus on this level return ''; } $ret = ''; $cur_item = 1; $cur_menu = $this->_removeNonMenuItems($cur_menu); $block_params['total_items'] = count($cur_menu); foreach ($cur_menu as $page) { $block_params = array_merge_recursive2( $block_params, $this->_prepareMenuItem($page, $real_cat_id, $root_path) ); $block_params['is_last'] = $cur_item == $block_params['total_items']; $block_params['is_first'] = $cur_item == 1; // bug #1: this breaks active section highlighting when 2 menu levels are printed on same page (both visible) // bug #2: people doesn't pass cat_id parameter to m_Link tags in their blocks, so this line helps them; when removed their links will lead to nowhere $this->Application->SetVar('m_cat_id', $page['CategoryId']); $ret .= $this->Application->ParseBlock($block_params); $cur_item++; } $this->Application->SetVar('m_cat_id', $real_cat_id); return $ret; } /** * Builds cached menu version * * @return Array */ function _prepareMenu() { static $root_cat = null; static $root_path = null; if (!$root_cat) { - $root_cat = $this->Application->ModuleInfo['Core']['RootCat']; + $root_cat = $this->Application->getBaseCategory(); $cache_key = 'parent_paths[%CIDSerial:' . $root_cat . '%]'; $root_path = $this->Application->getCache($cache_key); if ($root_path === false) { $this->Conn->nextQueryCachable = true; $sql = 'SELECT ParentPath FROM ' . TABLE_PREFIX . 'Category WHERE CategoryId = ' . $root_cat; $root_path = $this->Conn->GetOne($sql); $this->Application->setCache($cache_key, $root_path); } } if (!$this->Menu) { if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $menu = $this->Application->getCache('master:cms_menu', false); } else { $menu = $this->Application->getDBCache('cms_menu'); } if ($menu) { $menu = unserialize($menu); $this->parentPaths = $menu['parentPaths']; } else { $menu = $this->_altBuildMenuStructure(Array ('CategoryId' => $root_cat, 'ParentPath' => $root_path)); $menu['parentPaths'] = $this->parentPaths; if ($this->Application->isCachingType(CACHING_TYPE_MEMORY)) { $this->Application->setCache('master:cms_menu', serialize($menu)); } else { $to_cache = serialize($menu); // setDBCache's 2nd parameter passed by reference! $this->Application->setDBCache('cms_menu', $to_cache); } } unset($menu['parentPaths']); $this->Menu = $menu; } return Array ($this->Menu, $root_path); } /** * Returns category id based tag parameters * * @param Array $params * @return int */ function _getCategoryId($params) { $cat = isset($params['category_id']) && $params['category_id'] != '' ? $params['category_id'] : $this->Application->GetVar('m_cat_id'); if ("$cat" == 'parent') { $this_category =& $this->Application->recallObject('c'); /* @var $this_category kDBItem */ $cat = $this_category->GetDBField('ParentId'); } elseif ($cat == 0) { - $cat = $this->Application->ModuleInfo['Core']['RootCat']; + $cat = $this->Application->getBaseCategory(); } return $cat; } /** * Prepares cms menu item block parameters * * @param Array $page * @param int $real_cat_id * @param string $root_path * @return Array */ function _prepareMenuItem($page, $real_cat_id, $root_path) { static $language_id = null; static $primary_language_id = null; static $template = null; if (!isset($language_id)) { $language_id = $this->Application->GetVar('m_lang'); $primary_language_id = $this->Application->GetDefaultLanguageId(); $template = $this->Application->GetVar('t'); } $active = $category_active = false; $title = $page['l' . $language_id . '_ItemName'] ? $page['l' . $language_id . '_ItemName'] : $page['l' . $primary_language_id . '_ItemName']; if ($page['ItemType'] == 'cat') { if (array_key_exists($real_cat_id, $this->parentPaths)) { $active = strpos($this->parentPaths[$real_cat_id], $page['ParentPath']) !== false; } elseif ($page['ItemPath'] == $template) { // physical template in menu $active = true; } $category_active = $page['CategoryId'] == $real_cat_id; } /*if ($page['ItemType'] == 'cat_index') { $check_path = str_replace($root_path, '', $page['ParentPath']); $active = strpos($parent_path, $check_path) !== false; } if ($page['ItemType'] == 'page') { $active = $page['ItemPath'] == preg_replace('/^Content\//i', '', $this->Application->GetVar('t')); }*/ $block_params = Array ( 'title' => $title, 'template' => $page['ItemPath'], 'active' => $active, 'category_active' => $category_active, // new 'parent_path' => $page['ParentPath'], 'parent_id' => $page['ParentId'], 'cat_id' => $page['CategoryId'], 'item_type' => $page['ItemType'], 'page_id' => $page['ItemId'], 'use_section' => ($page['Type'] == PAGE_TYPE_TEMPLATE) && ($page['ItemPath'] != 'index'), 'has_sub_menu' => isset($page['sub_items']) && count($page['sub_items']) > 0, 'external_url' => $page['UseExternalUrl'] ? $page['ExternalUrl'] : false, // for backward compatibility 'menu_icon' => $page['UseMenuIconUrl'] ? $page['MenuIconUrl'] : false, ); return $block_params; } /** * Returns only items, that are visible in menu * * @param Array $menu * @return Array */ function _removeNonMenuItems($menu) { $theme_id = $this->Application->GetVar('m_theme'); foreach ($menu as $menu_index => $menu_item) { // $menu_index is in "cN" format, where N is category id if (!$menu_item['IsMenu'] || ($menu_item['ThemeId'] != $theme_id && $menu_item['ThemeId'] != 0)) { // don't show sections, that are not from menu OR system templates from other themes unset($menu[$menu_index]); } } return $menu; } /** * Builds cache for children of given category (no matter, what menu status is) * * @param Array $parent * @return Array */ function _altBuildMenuStructure($parent) { static $lang_part = null; if (!isset($lang_part)) { $ml_helper =& $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $lang_part = ''; for ($i = 1; $i <= $ml_helper->languageCount; $i++) { $lang_part .= 'c.l' . $i . '_MenuTitle AS l' . $i . '_ItemName,' . "\n"; } } // Sub-categories from current category $items = $this->getSubCategories( $parent['CategoryId'] ); // sort menu items uasort($items, Array (&$this, '_menuSort')); // store menu items $the_items = Array(); foreach ($items as $index => $an_item) { $the_items[ $an_item['ItemId'] ] = $an_item; $this->parentPaths[ $an_item['CategoryId'] ] = $an_item['ParentPath']; } // process submenus of each menu $items = $the_items; foreach ($items as $key => $menu_item) { if ($menu_item['CategoryId'] == $parent['CategoryId']) { // don't process myself - prevents recursion continue; } $sub_items = $this->_altBuildMenuStructure($menu_item); if ($sub_items) { $items[$key]['sub_items'] = $sub_items; } } return $items; } function getSubCategories($parent_id) { static $items_by_parent = null; if (!isset($items_by_parent)) { $ml_helper =& $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $lang_part = ''; $items_by_parent = Array (); for ($i = 1; $i <= $ml_helper->languageCount; $i++) { $lang_part .= 'c.l' . $i . '_MenuTitle AS l' . $i . '_ItemName,' . "\n"; } // Sub-categories from current category $sql = 'SELECT c.CategoryId AS CategoryId, CONCAT(\'c\', c.CategoryId) AS ItemId, c.Priority AS ItemPriority, ' . $lang_part . ' IF(c.`Type` = ' . PAGE_TYPE_TEMPLATE . ', c.Template, CONCAT("id:", c.CategoryId)) AS ItemPath, c.ParentPath AS ParentPath, c.ParentId As ParentId, \'cat\' AS ItemType, c.IsMenu, c.Type, c.ThemeId, c.UseExternalUrl, c.ExternalUrl, c.UseMenuIconUrl, c.MenuIconUrl FROM ' . TABLE_PREFIX . 'Category AS c WHERE c.Status = ' . STATUS_ACTIVE; $items = $this->Conn->Query($sql, 'ItemId'); foreach ($items as $item_id => $item_data) { $item_parent_id = $item_data['ParentId']; if ( !array_key_exists($item_parent_id, $items_by_parent) ) { $items_by_parent[$item_parent_id] = Array (); } $items_by_parent[$item_parent_id][$item_id] = $item_data; } } return array_key_exists($parent_id, $items_by_parent) ? $items_by_parent[$parent_id] : Array (); } /** * Method for sorting pages by priority in decending order * * @param Array $a * @param Array $b * @return int */ function _menuSort($a, $b) { if ($a['ItemPriority'] == $b['ItemPriority']) { return 0; } return ($a['ItemPriority'] < $b['ItemPriority']) ? 1 : -1; //descending } } Index: branches/5.1.x/core/units/helpers/permissions_helper.php =================================================================== --- branches/5.1.x/core/units/helpers/permissions_helper.php (revision 13986) +++ branches/5.1.x/core/units/helpers/permissions_helper.php (revision 13987) @@ -1,778 +1,781 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class kPermissionsHelper extends kHelper { /** * Current set of permissions for group being edited * * @var Array */ var $Permissions = Array(); function LoadPermissions($group_id, $cat_id, $type = 1, $prefix = '') { $perm_table = $this->Application->getUnitOption('perm', 'TableName'); $perm_table = $this->Application->GetTempName($perm_table, 'prefix:'.$prefix); $sql = 'SELECT * FROM '.$perm_table.' WHERE (GroupId = '.$group_id.') AND (CatId = '.$cat_id.') AND (Type = '.$type.')'; $permissions = $this->Conn->Query($sql, 'Permission'); $this->Permissions = Array(); foreach ($permissions as $perm_name => $perm_options) { $perm_record['value'] = $perm_options['PermissionValue']; $perm_record['id'] = $perm_options['PermissionId']; $this->Permissions[$perm_name] = $perm_record; } } function getPermissionValue($perm_name) { return isset($this->Permissions[$perm_name]) ? $this->Permissions[$perm_name]['value'] : 0; } function getPermissionID($perm_name) { return isset($this->Permissions[$perm_name]) ? $this->Permissions[$perm_name]['id'] : 0; } /** * This is old permission like ADMIN or LOGIN * * @param string $section_name * @param string $perm_name * @return bool */ function isOldPermission($section_name, $perm_name) { return $section_name == 'in-portal:root' && $perm_name != 'view'; } /** * Returns permission names to check based on event name and item prefix (main item or subitem) * * @param kEvent $event * @return Array */ function getPermissionByEvent(&$event, $perm_mapping) { $top_prefix = $event->getEventParam('top_prefix'); $pefix_type = ($top_prefix == $event->Prefix) ? 'self' : 'subitem'; $perm_mapping = getArrayValue($perm_mapping, $event->Name); if (!$perm_mapping[$pefix_type]) { trigger_error('Permission mappings not defined for event <b>'.$top_prefix.' <- '.$event->Prefix.':'.$event->Name.'</b>', E_USER_ERROR); } if ($perm_mapping[$pefix_type] === true) { // event is defined in mapping but is not checked by permissions return true; } return explode('|', $perm_mapping[$pefix_type]); } /** * Common event permission checking method * * @param kEvent $event */ function CheckEventPermission(&$event, $perm_mapping) { $section = $event->getSection(); if (preg_match('/^CATEGORY:(.*)/', $section)) { return $this->CheckEventCategoryPermission($event, $perm_mapping); } $top_prefix = $event->getEventParam('top_prefix'); $check_perms = $this->getPermissionByEvent($event, $perm_mapping); if ($check_perms === true) { // event is defined in mapping but is not checked by permissions return true; } $perm_status = false; foreach ($check_perms as $perm_name) { // check if at least one of required permissions is set if ($perm_name == 'debug' && $this->Application->isDebugMode(false)) { // universal "debug" permission return true; } $perm_name = $section.'.'.$perm_name; $perm_status = $this->CheckPermission($perm_name, 1); if (($perm_name == $section.'.add') && $perm_status && ($top_prefix == $event->Prefix)) { // main item, add permission allowed, but ID is > 0, then deny permission // how to get id here } if ($perm_status) { return $perm_status; } } return $this->finalizePermissionCheck($event, $perm_status); } /** * Returns owner + primary category for each item (used for permission checking) * * @param string $prefix * @param string $ids * @param bool $temp_mode * @return Array * @author Alex */ function GetCategoryItemData($prefix, $ids, $temp_mode = false) { if (is_array($ids)) { $ids = implode(',', $ids); } $id_field = $this->Application->getUnitOption($prefix, 'IDField'); $table_name = $this->Application->getUnitOption($prefix, 'TableName'); $ci_table = $this->Application->getUnitOption('ci', 'TableName'); if ($temp_mode) { $table_name = $this->Application->GetTempName($table_name, 'prefix:' . $prefix); $ci_table = $this->Application->GetTempName($ci_table, 'prefix:' . $prefix); } $owner_field = $this->Application->getUnitOption($prefix, 'OwnerField'); if (!$owner_field) { $owner_field = 'CreatedById'; } $sql = 'SELECT item_table.'.$id_field.', item_table.'.$owner_field.' AS CreatedById, ci.CategoryId FROM '.$table_name.' item_table LEFT JOIN '.$ci_table.' ci ON ci.ItemResourceId = item_table.ResourceId WHERE item_table.'.$id_field.' IN ('.$ids.') AND (ci.PrimaryCat = 1)'; return $this->Conn->Query($sql, $id_field); } /** * Check category-based permissions for category items * * @param kEvent $event */ function _frontCheckEventCategoryPermission(&$event, $event_perm_mapping) { // mapping between specific permissions and common permissions static $perm_mapping = Array( 'add' => 'ADD', 'add.pending' => 'ADD.PENDING', 'edit' => 'MODIFY', 'edit.pending' => 'MODIFY.PENDING', 'delete' => 'DELETE', 'view' => 'VIEW', 'debug' => 'DEBUG' ); $top_prefix = $event->getEventParam('top_prefix'); $event_handler =& $this->Application->recallObject($event->Prefix.'_EventHandler'); /* @var $event_handler kCatDBEventHandler */ $raise_warnings = $event->getEventParam('raise_warnings'); $event->setEventParam('raise_warnings', 0); if ($event->Prefix != $top_prefix) { $top_event = new kEvent($top_prefix.':'.$event->Name); $id = $event_handler->getPassedID($top_event); } else { $id = $event_handler->getPassedID($event); } $event->setEventParam('raise_warnings', $raise_warnings); $owner_id = USER_ROOT; // owner is root if not detected if (!$id) { // item being created -> check by current (before editing started, saved in OnPreCreate event) category permissions // note: category in session is placed on catalog data import start $category_id = $this->Application->isAdmin ? $this->Application->RecallVar('m_cat_id') : $this->Application->GetVar('m_cat_id'); } elseif ($top_prefix == 'c' || $top_prefix == 'st') { $category_id = $id; } else { // item being edited -> check by it's primary category permissions $items_info = $this->GetCategoryItemData($top_prefix, $id); if ($items_info) { $category_id = $items_info[$id]['CategoryId']; $owner_id = $items_info[$id]['CreatedById']; } else { // item wasn't found in database $category_id = $this->Application->GetVar('m_cat_id'); } } // specific permission check for pending & owner permissions: begin $uploader_events = Array ('OnUploadFile', 'OnDeleteFile', 'OnViewFile'); if (in_array($event->Name, $uploader_events)) { // don't recall target object during uploader-related, because OnItemLoad will use incorrect // $user_id in Firefox (during Flash problems session will be used from Internet Exploere) $new_item = false; } else { $new_item = $this->Application->isAdminUser && $event_handler->isNewItemCreate($event) ? true : false; $check_status = $this->checkCombinedPermissions($event, $owner_id, (int)$category_id, $new_item); } if (isset($check_status)) { return $this->finalizePermissionCheck($event, $check_status); } // specific permission check for pending & owner permissions: end $perm_status = false; $check_perms = $this->getPermissionByEvent($event, $event_perm_mapping); if ($check_perms === true) { // event is defined in mapping but is not checked by permissions return true; } $item_prefix = $this->Application->getUnitOption($top_prefix, 'PermItemPrefix'); foreach ($check_perms as $perm_name) { // check if at least one of required permissions is set if (!isset($perm_mapping[$perm_name])) { // not mapped permission (e.g. advanced:approve) -> skip continue; } if ($perm_name == 'debug' && $this->Application->isDebugMode(false)) { // universal "debug" permission return true; } $perm_name = $item_prefix.'.'.$perm_mapping[$perm_name]; $perm_status = $this->CheckPermission($perm_name, 0, (int)$category_id); if ($perm_status) { return $perm_status; } } return $this->finalizePermissionCheck($event, $perm_status); } /** * Finalizes permission checking (with additional debug output, when in debug mode) * * @param kEvent $event * @param bool $perm_status * @return bool */ function finalizePermissionCheck(&$event, $perm_status) { if (!$perm_status) { if (MOD_REWRITE) { // $event->SetRedirectParam('m_cat_id', 0); // category means nothing on admin login screen $event->SetRedirectParam('next_template', urlencode('external:' . $_SERVER['REQUEST_URI'])); } else { $event->SetRedirectParam('next_template', $this->Application->GetVar('t')); } if ($this->Application->isDebugMode()) { // for debugging purposes $event->SetRedirectParam('section', $event->getSection()); $event->SetRedirectParam('main_prefix', $event->getEventParam('top_prefix')); $event->SetRedirectParam('event_name', $event->Name); } $event->status = erPERM_FAIL; } return $perm_status; } /** * Allows to check combined permissions (*.owner, *.pending) for add/modify/delete operations from admin & front-end * * @param kEvent $event * @param int $owner_id * @param int $category_id * @param bool $new_item * @return mixed */ function checkCombinedPermissions(&$event, $owner_id, $category_id, $new_item = false) { $ret = null; // true/false when used, null when not used $top_prefix = $event->getEventParam('top_prefix'); // check admin permission if (substr($event->Name, 0, 9) == 'OnPreSave') { if ($new_item) { $ret = $this->AddCheckPermission($category_id, $top_prefix); } else { // add & modify because $new_item is false, when item is aready created & then saved in temp table (even with 0 id) $ret = $this->AddCheckPermission($category_id, $top_prefix) || $this->ModifyCheckPermission($owner_id, $category_id, $top_prefix); } } // check front-end permissions switch ($event->Name) { case 'OnCreate': $ret = $this->AddCheckPermission($category_id, $top_prefix); break; case 'OnUpdate': $ret = $this->ModifyCheckPermission($owner_id, $category_id, $top_prefix); break; case 'OnDelete': case 'OnMassDelete': $ret = $this->DeleteCheckPermission($owner_id, $category_id, $top_prefix); break; } if ($ret === 0) { // permission check failed (user has no permission) $event->status = erPERM_FAIL; } return $ret; } /** * Simplified permission check for category items, when adding/editing them from advanced view. * * @param kEvent $event * @return mixed */ function CheckEventCategoryPermission(&$event, $event_perm_mapping) { if (!$this->Application->isAdmin) { // check front-end permission by old scheme return $this->_frontCheckEventCategoryPermission($event, $event_perm_mapping); } if (substr($event->Name, 0, 9) == 'OnPreSave') { // check separately, because permission mapping is not defined for OnPreSave* events $check_perms = Array ('add', 'edit'); } else { $check_perms = $this->getPermissionByEvent($event, $event_perm_mapping); } if ($check_perms === true) { // event is defined in mapping but is not checked by permissions return true; } // 1. most of events does require admin login only $perm_status = $this->Application->isAdminUser; // 2. in case, when event require more, then "view" right, then restrict it to temporary tables only if (!in_array('view', $check_perms)) { $perm_status = $perm_status && $this->Application->IsTempMode($event->Prefix, $event->Special); } return $this->finalizePermissionCheck($event, $perm_status); } function TagPermissionCheck($params, $is_owner = false) { $perm_prefix = getArrayValue($params, 'perm_prefix'); $perm_event = getArrayValue($params, 'perm_event'); $permission_groups = getArrayValue($params, 'permissions'); if ($permission_groups && !$perm_event) { // check permissions by permission names in current category $permission_groups = explode('|', $permission_groups); $group_has_permission = false; $perm_category = isset($params['cat_id']) ? $params['cat_id'] : $this->Application->GetVar('m_cat_id'); if ($perm_prefix) { // use primary category of item with id from {perm_prefix}_id as base for permission checking $perm_category = $this->getPrimaryCategory($perm_prefix); } $is_system = isset($params['system']) && $params['system'] ? 1 : 0; foreach ($permission_groups as $permission_group) { $permissions = explode(',', $permission_group); $has_permission = true; foreach ($permissions as $permission) { $owner_checked = (strpos($permission, '.OWNER.') !== false) ? $is_owner : true; $has_permission = $has_permission && $this->CheckPermission($permission, $is_system, $perm_category) && $owner_checked; } $group_has_permission = $group_has_permission || $has_permission; if ($group_has_permission) { return true; } } return false; } elseif ($perm_event) { // check permission by event name list ($prefix, $event) = explode(':', $perm_event); $event_handler =& $this->Application->recallObject($prefix.'_EventHandler'); return $event_handler->CheckPermission( new kEvent($perm_event) ); } return true; } /** * Returns item's primary category (get item_id from request) * * @param string $prefix * @return int */ function getPrimaryCategory($prefix) { $id_field = $this->Application->getUnitOption($prefix, 'IDField'); $table_name = $this->Application->getUnitOption($prefix, 'TableName'); $id = $this->Application->GetVar($prefix.'_id'); if (!$id) { return $this->Application->GetVar('m_cat_id'); } $sql = 'SELECT ResourceId FROM '.$table_name.' WHERE '.$id_field.' = '.(int)$id; $resource_id = $this->Conn->GetOne($sql); $sql = 'SELECT CategoryId FROM '.$this->Application->getUnitOption('ci', 'TableName').' WHERE ItemResourceId = '.$resource_id.' AND PrimaryCat = 1'; return $this->Conn->GetOne($sql); } /** * Returns no permission template to redirect to * * @param Array $params * @return Array */ function getPermissionTemplate($params) { $t = $this->Application->GetVar('t'); if ($next_t = getArrayValue($params, 'next_template')) { $t = $next_t; } $redirect_params = $this->Application->HttpQuery->getRedirectParams(true); if (array_key_exists('pass_category', $params)) { $redirect_params['pass_category'] = $params['pass_cateogry']; } if (MOD_REWRITE) { // TODO: $next_t variable is ignored !!! (is anyone using m_RequireLogin tag with "next_template" parameter?) $redirect_params = Array ( 'm_cat_id' => 0, // category means nothing on admin login screen 'next_template' => urlencode('external:' . $_SERVER['REQUEST_URI']), ); } else { $redirect_params['next_template'] = $t; } if ($this->Application->isAdmin) { $redirect_params['m_wid'] = ''; // remove wid, otherwise parent window may add wid to its name breaking all the frameset (for <a> targets) $redirect_params['pass'] = 'm'; // don't pass any other (except "m") prefixes to admin login template } if (!$this->Application->LoggedIn()) { $redirect_template = array_key_exists('login_template', $params) ? $params['login_template'] : ''; if (!$redirect_template && $this->Application->isAdmin) { $redirect_template = 'login'; } } else { if (array_key_exists('no_permissions_template', $params)) { $redirect_template = $params['no_permissions_template']; } else { $redirect_template = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate'); } if ($this->Application->isDebugMode()) { $redirect_params['from_template'] = 1; $redirect_params['perms'] = $params[ isset($params['permissions']) ? 'permissions' : 'perm_event' ]; } } if (isset($params['index_file']) && $params['index_file']) { $redirect_params['index_file'] = $params['index_file']; } return Array ($redirect_template, $redirect_params); } /** * Check current user permissions based on it's group permissions in specified category (for non-system permissions) or just checks if system permission is set * * @param string $name permission name * @param int $cat_id category id, current used if not specified * @param int $type permission type {1 - system, 0 - per category} * @return int */ function CheckPermission($name, $type = 1, $cat_id = null) { $user_id = $this->Application->RecallVar('user_id'); return $this->CheckUserPermission($user_id, $name, $type, $cat_id); } function CheckUserPermission($user_id, $name, $type = 1, $cat_id = null) { if ($user_id == USER_ROOT) { // "root" is allowed anywhere return $name == 'SYSTEM_ACCESS.READONLY' ? 0 : 1; } + if (!isset($cat_id)) { + $cat_id = $this->Application->GetVar('m_cat_id'); + } + if ($type == 1) { // "system" permission are always checked per "Home" category (ID = 0) $cat_id = 0; } - - if (!isset($cat_id)) { - $cat_id = $this->Application->GetVar('m_cat_id'); + elseif ("$cat_id" === "0") { + $cat_id = $this->Application->getBaseCategory(); } // perm cache is build only based on records in db, that's why if permission is not explicitly denied, then // that (perm cache creator) code thinks that it is allowed & adds corresponding record and code below will // return incorrect results if ($user_id == $this->Application->RecallVar('user_id')) { $groups = explode(',', $this->Application->RecallVar('UserGroups')); } else { // checking not current user $sql = 'SELECT GroupId FROM '.TABLE_PREFIX.'UserGroup WHERE (PortalUserId = '.$user_id.') AND ( (MembershipExpires IS NULL) OR ( MembershipExpires >= UNIX_TIMESTAMP() ) )'; $groups = $this->Conn->GetCol($sql); array_push($groups, $this->Application->ConfigValue('User_LoggedInGroup') ); } $cache_key = $name . '|' . $type . '|' . $cat_id . '|' . implode(',', $groups); $perm_value = $this->Application->getCache('permissions[%' . ($type == 1 ? 'G' : 'C') . 'PermSerial%]:' . $cache_key); if ($perm_value !== false) { return $perm_value; } if (preg_match('/(.*)\.VIEW$/', $name) && ($type == 0)) { // cached view permission of category: begin if (strpos($cat_id, '|') !== false) { $category_path = explode('|', substr($cat_id, 1, -1)); $cat_id = end($category_path); } $sql = 'SELECT PermissionConfigId FROM '.TABLE_PREFIX.'PermissionConfig WHERE PermissionName = '.$this->Conn->qstr($name); $perm_id = $this->Conn->GetOne($sql); $sql = 'SELECT PermId FROM '.TABLE_PREFIX.'PermCache WHERE (PermId = '.$perm_id.') AND (CategoryId = '.(int)$cat_id.')'; $view_filters = Array(); foreach ($groups as $group) { $view_filters[] = 'FIND_IN_SET('.$group.', ACL)'; } $sql .= ' AND ('.implode(' OR ', $view_filters).')'; $perm_value = $this->Conn->GetOne($sql) ? 1 : 0; $this->Application->setCache('permissions[%CPermSerial%]:' . $cache_key, $perm_value); return $perm_value; // cached view permission of category: end } if (is_numeric($cat_id) && $cat_id == 0) { $cat_hierarchy = Array(0); } else { if (strpos($cat_id, '|') !== false) { $cat_hierarchy = $cat_id; } else { $sql = 'SELECT ParentPath FROM '.$this->Application->getUnitOption('c', 'TableName').' WHERE CategoryId = '.$cat_id; $cat_hierarchy = $this->Conn->GetOne($sql); if ($cat_hierarchy === false) { // category was deleted, but refrence to it stays in other tables -> data integrity is broken - $cat_hierarchy = '|' . $this->Application->findModule('Name', 'Core', 'RootCat') . '|'; + $cat_hierarchy = '|' . $this->Application->getBaseCategory() . '|'; } } $cat_hierarchy = explode('|', substr($cat_hierarchy, 1, -1)); $cat_hierarchy = array_reverse($cat_hierarchy); array_push($cat_hierarchy, 0); } $perm_value = 0; $groups = implode(',',$groups); foreach ($cat_hierarchy as $category_id) { $sql = 'SELECT SUM(PermissionValue) FROM '.TABLE_PREFIX.'Permissions WHERE Permission = "'.$name.'" AND CatId = '.$category_id.' AND GroupId IN ('.$groups.') AND Type = '.$type; $res = $this->Conn->GetOne($sql); if ($res !== false && !is_null($res)) { $perm_value = $res ? 1 : 0; break; } } $this->Application->setCache('permissions[%' . ($type == 1 ? 'G' : 'C') . 'PermSerial%]:' . $cache_key, $perm_value); return $perm_value; } /** * Returns categories, where given permission is set to "1" * * @param string $permission_name * @return Array */ function getPermissionCategories($permission_name) { $groups = $this->Application->RecallVar('UserGroups'); // get categories, where given permission is explicitely defined $sql = 'SELECT SUM(PermissionValue), CatId FROM ' . TABLE_PREFIX . 'Permissions WHERE Permission = "' . $permission_name . '" AND GroupId IN (' . $groups . ') AND Type = 0 GROUP BY CatId'; $permissions = $this->Conn->GetCol($sql, 'CatId'); // get all categories along with their parent path $sql = 'SELECT ParentPath, CategoryId FROM ' . TABLE_PREFIX . 'Category'; $parent_paths = $this->Conn->GetCol($sql, 'CategoryId'); foreach ($parent_paths as $category_id => $parent_path) { if (array_key_exists($category_id, $permissions)) { // permission for given category is set explicitly continue; } $perm_value = 0; $parent_path = explode('|', substr($parent_path, 1, -1)); $parent_path = array_reverse($parent_path); array_push($parent_path, 0); foreach ($parent_path as $parent_category_id) { if (array_key_exists($parent_category_id, $permissions)) { $perm_value = $permissions[$parent_category_id] ? 1 : 0; break; } } $permissions[$category_id] = $perm_value; } // remove categories, where given permissions is denied foreach ($permissions as $category_id => $perm_value) { if (!$perm_value) { unset($permissions[$category_id]); } } return array_keys($permissions); } /** * Allows to check MODIFY & OWNER.MODFY +/- PENDING permission combinations on item * * @param int $owner_id user_id, that is owner of the item * @param int $category_id primary category of item * @param string $prefix prefix of item * @return int {0 - no MODIFY permission, 1 - has MODIFY permission, 2 - has MODIFY.PENDING permission} */ function ModifyCheckPermission($owner_id, $category_id, $prefix) { $perm_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix'); $live_modify = $this->CheckPermission($perm_prefix.'.MODIFY', ptCATEGORY, $category_id); if ($live_modify) { return 1; } else if ($this->CheckPermission($perm_prefix.'.MODIFY.PENDING', ptCATEGORY, $category_id)) { return 2; } if ($owner_id == $this->Application->RecallVar('user_id')) { // user is item's OWNER -> check this permissions first $live_modify = $this->CheckPermission($perm_prefix.'.OWNER.MODIFY', ptCATEGORY, $category_id); if ($live_modify) { return 1; } else if ($this->CheckPermission($perm_prefix.'.OWNER.MODIFY.PENDING', ptCATEGORY, $category_id)) { return 2; } } return 0; } /** * Allows to check DELETE & OWNER.DELETE permission combinations on item * * @param int $owner_id user_id, that is owner of the item * @param int $category_id primary category of item * @param string $prefix prefix of item * @return int {0 - no DELETE permission, 1 - has DELETE/OWNER.DELETE permission} */ function DeleteCheckPermission($owner_id, $category_id, $prefix) { $perm_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix'); $live_delete = $this->CheckPermission($perm_prefix.'.DELETE', ptCATEGORY, $category_id); if ($live_delete) { return 1; } if ($owner_id == $this->Application->RecallVar('user_id')) { // user is item's OWNER -> check this permissions first $live_delete = $this->CheckPermission($perm_prefix.'.OWNER.DELETE', ptCATEGORY, $category_id); if ($live_delete) { return 1; } } return 0; } /** * Allows to check ADD +/- PENDING permission combinations on item * * @param int $category_id primary category of item * @param string $prefix prefix of item * @return int {0 - no ADD permission, 1 - has ADD permission, 2 - has ADD.PENDING permission} */ function AddCheckPermission($category_id, $prefix) { $perm_prefix = $this->Application->getUnitOption($prefix, 'PermItemPrefix'); $live_add = $this->CheckPermission($perm_prefix.'.ADD', ptCATEGORY, $category_id); if ($live_add) { return 1; } else if ($this->CheckPermission($perm_prefix.'.ADD.PENDING', ptCATEGORY, $category_id)) { return 2; } return 0; } } \ No newline at end of file Index: branches/5.1.x/core/units/content/content_eh.php =================================================================== --- branches/5.1.x/core/units/content/content_eh.php (revision 13986) +++ branches/5.1.x/core/units/content/content_eh.php (revision 13987) @@ -1,37 +1,37 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class ContentEventHandler extends kDBEventHandler { /** * Checks permissions of user * * @param kEvent $event */ function CheckPermission(&$event) { $perm_helper =& $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $user_id = $this->Application->RecallVar('user_id'); // user can change top category - $top_category = $this->Application->findModule('Name', 'Core', 'RootCat'); + $top_category = $this->Application->getBaseCategory(); $perm_status = $perm_helper->CheckUserPermission($user_id, 'CATEGORY.MODIFY', 0, $top_category); return $perm_helper->finalizePermissionCheck($event, $perm_status); } } \ No newline at end of file Index: branches/5.1.x/core/units/admin/admin_tag_processor.php =================================================================== --- branches/5.1.x/core/units/admin/admin_tag_processor.php (revision 13986) +++ branches/5.1.x/core/units/admin/admin_tag_processor.php (revision 13987) @@ -1,1135 +1,1135 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class AdminTagProcessor extends kDBTagProcessor { function SetConst($params) { $name = $this->SelectParam($params, 'name,const'); safeDefine($name, $params['value']); } /** * Allows to execute js script after the page is fully loaded * * @param Array $params * @return string */ function AfterScript($params) { $after_script = $this->Application->GetVar('after_script'); if ($after_script) { return '<script type="text/javascript">'.$after_script.'</script>'; } return ''; } /** * Returns section title with #section# keyword replaced with current section * * @param Array $params * @return string */ function GetSectionTitle($params) { if (array_key_exists('default', $params)) { return $params['default']; } return $this->Application->Phrase( replaceModuleSection($params['phrase']) ); } /** * Returns section icon with #section# keyword replaced with current section * * @param Array $params * @return string */ function GetSectionIcon($params) { return replaceModuleSection($params['icon']); } /** * Returns version of module by name * * @param Array $params * @return string */ function ModuleVersion($params) { return $this->Application->findModule('Name', $params['module'], 'Version'); } /** * Used in table form section drawing * * @param Array $params * @return string */ function DrawTree($params) { static $deep_level = 0; // when processings, then sort children by priority (key of children array) $ret = ''; $section_name = $params['section_name']; $params['name'] = $this->SelectParam($params, 'name,render_as,block'); $sections_helper =& $this->Application->recallObject('SectionsHelper'); /* @var $sections_helper kSectionsHelper */ $section_data =& $sections_helper->getSectionData($section_name); $params['children_count'] = isset($section_data['children']) ? count($section_data['children']) : 0; $params['deep_level'] = $deep_level++; $template = $section_data['url']['t']; unset($section_data['url']['t']); $section_data['section_url'] = $this->Application->HREF($template, '', $section_data['url']); $ret .= $this->Application->ParseBlock( array_merge_recursive2($params, $section_data) ); if (!isset($section_data['children'])) { return $ret; } ksort($section_data['children'], SORT_NUMERIC); foreach ($section_data['children'] as $section_name) { if (!$sections_helper->sectionVisible($section_name)) { continue; } $params['section_name'] = $section_name; $ret .= $this->DrawTree($params); $deep_level--; } return $ret; } function SectionInfo($params) { $section = $params['section']; if ($section == '#session#') { $section = $this->Application->RecallVar('section'); } $sections_helper =& $this->Application->recallObject('SectionsHelper'); /* @var $sections_helper kSectionsHelper */ $section_data =& $sections_helper->getSectionData($section); if (!$section_data) { trigger_error('Use of undefined section "<strong>' . $section . '</strong>" in "<strong>' . __METHOD__ . '</strong>"', E_USER_ERROR); return ''; } if (array_key_exists('parent', $params) && $params['parent']) { do { $section = $section_data['parent']; $section_data =& $sections_helper->getSectionData($section); } while (array_key_exists('use_parent_header', $section_data) && $section_data['use_parent_header']); } $info = $params['info']; switch ($info) { case 'module_path': if (isset($params['module']) && $params['module']) { $module = $params['module']; } elseif (isset($section_data['icon_module'])) { $module = $section_data['icon_module']; } else { $module = '#session#'; } $res = $this->ModulePath(array('module' => $module)); break; case 'perm_section': $res = $sections_helper->getPermSection($section); break; case 'label': if ($section && ($section != 'in-portal:root')) { // don't translate label for top section, because it's already translated $no_editing = array_key_exists('no_editing', $params) ? $params['no_editing'] : false; $res = $this->Application->Phrase($section_data['label'], !$no_editing); } else { $res = ''; } break; default: $res = $section_data[$info]; break; } if (array_key_exists('as_label', $params) && $params['as_label']) { $res = $this->Application->Phrase($res); } return $res; } function PrintSection($params) { $section_name = $params['section_name']; if ($section_name == '#session#') { $section_name = $this->Application->RecallVar('section'); } $sections_helper =& $this->Application->recallObject('SectionsHelper'); /* @var $sections_helper kSectionsHelper */ if (isset($params['use_first_child']) && $params['use_first_child']) { $section_name = $sections_helper->getFirstChild($section_name, true); } $section_data =& $sections_helper->getSectionData($section_name); $params['name'] = $this->SelectParam($params, 'name,render_as,block'); $params['section_name'] = $section_name; $template = $section_data['url']['t']; unset($section_data['url']['t']); $section_data['section_url'] = $this->Application->HREF($template, '', $section_data['url']); $ret = $this->Application->ParseBlock( array_merge_recursive2($params, $section_data) ); return $ret; } /** * Used in XML drawing for tree * * @param Array $params * @return string */ function PrintSections($params) { // when processings, then sort children by priority (key of children array) $ret = ''; $section_name = $params['section_name']; if ($section_name == '#session#') { $section_name = $this->Application->RecallVar('section'); } $sections_helper =& $this->Application->recallObject('SectionsHelper'); /* @var $sections_helper kSectionsHelper */ $section_data =& $sections_helper->getSectionData($section_name); $params['name'] = $this->SelectParam($params, 'name,render_as,block'); if (!isset($section_data['children'])) { return ''; } ksort($section_data['children'], SORT_NUMERIC); foreach ($section_data['children'] as $section_name) { $params['section_name'] = $section_name; $section_data =& $sections_helper->getSectionData($section_name); if (!$sections_helper->sectionVisible($section_name)) { continue; } else { $show_mode = $section_data['show_mode']; $section_data['debug_only'] = ($show_mode == smDEBUG) || ($show_mode == smSUPER_ADMIN) ? 1 : 0; } if (isset($section_data['tabs_only']) && $section_data['tabs_only']) { $perm_status = false; $folder_label = $section_data['label']; ksort($section_data['children'], SORT_NUMERIC); foreach ($section_data['children'] as $priority => $section_name) { // if only tabs in this section & none of them have permission, then skip section too $section_name = $sections_helper->getPermSection($section_name); $perm_status = $this->Application->CheckPermission($section_name.'.view', 1); if ($perm_status) { break; } } if (!$perm_status) { // no permission for all tabs -> don't display tree node either continue; } $params['section_name'] = $section_name; $section_data =& $sections_helper->getSectionData($section_name); $section_data['label'] = $folder_label; // use folder label in tree $section_data['is_tab'] = 1; } else { $section_name = $sections_helper->getPermSection($section_name); if (!$this->Application->CheckPermission($section_name.'.view', 1)) continue; } $params['children_count'] = isset($section_data['children']) ? count($section_data['children']) : 0; // remove template, so it doesn't appear as additional parameter in url $template = $section_data['url']['t']; unset($section_data['url']['t']); $section_data['section_url'] = $this->Application->HREF($template, '', $section_data['url']); $late_load = getArrayValue($section_data, 'late_load'); if ($late_load) { $t = $late_load['t']; unset($late_load['t']); $section_data['late_load'] = $this->Application->HREF($t, '', $late_load); $params['children_count'] = 99; } else { $section_data['late_load'] = ''; } // restore template $section_data['url']['t'] = $template; $ret .= $this->Application->ParseBlock( array_merge_recursive2($params, $section_data) ); $params['section_name'] = $section_name; } return preg_replace("/\r\n|\n/", '', $ret); } function ListSectionPermissions($params) { $section_name = isset($params['section_name']) ? $params['section_name'] : $this->Application->GetVar('section_name'); $sections_helper =& $this->Application->recallObject('SectionsHelper'); $section_data =& $sections_helper->getSectionData($section_name); $block_params = array_merge_recursive2($section_data, Array('name' => $params['render_as'], 'section_name' => $section_name)); $ret = ''; foreach ($section_data['permissions'] as $perm_name) { if (preg_match('/^advanced:(.*)/', $perm_name) != $params['type']) continue; $block_params['perm_name'] = $perm_name; $ret .= $this->Application->ParseBlock($block_params); } return $ret; } function ModuleInclude($params) { foreach ($params as $param_name => $param_value) { $params[$param_name] = replaceModuleSection($param_value); } $m =& $this->Application->recallObject('m_TagProcessor'); return $m->ModuleInclude($params); } function TodayDate($params) { return date($params['format']); } function TreeEditWarrning($params) { $ret = $this->Application->Phrase($params['label']); $ret = str_replace(Array('<', '>', 'br/', 'br /', "\n", "\r"), Array('<', '>', 'br', 'br', '', ''), $ret); if (getArrayValue($params, 'escape')) { $ret = addslashes($ret); } $ret = str_replace('<br>', '\n', $ret); return $ret; } /** * Draws section tabs using block name passed * * @param Array $params */ function ListTabs($params) { $sections_helper =& $this->Application->recallObject('SectionsHelper'); $section_data =& $sections_helper->getSectionData($params['section_name']); $ret = ''; $block_params = Array('name' => $params['render_as']); ksort($section_data['children'], SORT_NUMERIC); foreach ($section_data['children'] as $priority => $section_name) { if (!$this->Application->CheckPermission($section_name.'.view', 1)) continue; $tab_data =& $sections_helper->getSectionData($section_name); $block_params['t'] = $tab_data['url']['t']; $block_params['title'] = $tab_data['label']; $block_params['main_prefix'] = $section_data['SectionPrefix']; $ret .= $this->Application->ParseBlock($block_params); } return $ret; } /** * Returns list of module item tabs that have view permission in current category * * @param Array $params */ function ListCatalogTabs($params) { $ret = ''; $special = isset($params['special']) ? $params['special'] : ''; $replace_main = isset($params['replace_m']) && $params['replace_m']; $skip_prefixes = isset($params['skip_prefixes']) ? explode(',', $params['skip_prefixes']) : Array(); $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['render_as']; foreach ($this->Application->ModuleInfo as $module_name => $module_info) { $prefix = $module_info['Var']; if ($prefix == 'm' && $replace_main) { $prefix = 'c'; } if (in_array($prefix, $skip_prefixes) || !$this->Application->prefixRegistred($prefix) || !$this->Application->getUnitOption($prefix, 'CatalogItem')) { continue; } $icon = $this->Application->getUnitOption($prefix, 'CatalogTabIcon'); if (strpos($icon, ':') !== false) { list ($icon_module, $icon) = explode(':', $icon, 2); } else { $icon_module = 'core'; } $label = $this->Application->getUnitOption($prefix, $params['title_property']); $block_params['title'] = $label; $block_params['prefix'] = $prefix; $block_params['icon_module'] = $icon_module; $block_params['icon'] = $icon; $ret .= $this->Application->ParseBlock($block_params); } return $ret; } /** * Renders inividual catalog tab based on prefix and title_property given * * @param Array $params * @return string */ function CatalogTab($params) { $icon = $this->Application->getUnitOption($params['prefix'], 'CatalogTabIcon'); if (strpos($icon, ':') !== false) { list ($icon_module, $icon) = explode(':', $icon, 2); } else { $icon_module = 'core'; } $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['render_as']; $block_params['icon_module'] = $icon_module; $block_params['icon'] = $icon; $block_params['title'] = $this->Application->getUnitOption($params['prefix'], $params['title_property']); return $this->Application->ParseBlock($block_params); } /** * Allows to construct link for opening any type of catalog item selector * * @param Array $params * @return string */ function SelectorLink($params) { $mode = 'catalog'; if (isset($params['mode'])) { // {catalog, advanced_view} $mode = $params['mode']; unset($params['mode']); } $params['t'] = 'catalog/item_selector/item_selector_'.$mode; - $params['m_cat_id'] = $this->Application->findModule('Name', 'Core', 'RootCat'); + $params['m_cat_id'] = $this->Application->getBaseCategory(); $default_params = Array('no_amp' => 1, 'pass' => 'all,'.$params['prefix']); unset($params['prefix']); $pass_through = Array(); if (isset($params['tabs_dependant'])) { // {yes, no} $pass_through['td'] = $params['tabs_dependant']; unset($params['tabs_dependant']); } if (isset($params['selection_mode'])) { // {single, multi} $pass_through['tm'] = $params['selection_mode']; unset($params['selection_mode']); } if (isset($params['tab_prefixes'])) { // {all, none, <comma separated prefix list>} $pass_through['tp'] = $params['tab_prefixes']; unset($params['tab_prefixes']); } if ($pass_through) { // add pass_through to selector url if any $params['pass_through'] = implode(',', array_keys($pass_through)); $params = array_merge_recursive2($params, $pass_through); } // user can override default parameters (except pass_through of course) $params = array_merge_recursive2($default_params, $params); $main_processor =& $this->Application->recallObject('m_TagProcessor'); return $main_processor->T($params); } function TimeFrame($params) { $w = adodb_date('w'); $m = adodb_date('m'); $y = adodb_date('Y'); //FirstDayOfWeek is 0 for Sunday and 1 for Monday $fdow = $this->Application->ConfigValue('FirstDayOfWeek'); if ($fdow && $w == 0) $w = 7; $today_start = adodb_mktime(0,0,0,adodb_date('m'),adodb_date('d'),$y); $first_day_of_this_week = $today_start - ($w - $fdow)*86400; $first_day_of_this_month = adodb_mktime(0,0,0,$m,1,$y); $this_quater = ceil($m/3); $this_quater_start = adodb_mktime(0,0,0,$this_quater*3-2,1,$y); switch ($params['type']) { case 'last_week_start': $timestamp = $first_day_of_this_week - 86400*7; break; case 'last_week_end': $timestamp = $first_day_of_this_week - 1; break; case 'last_month_start': $timestamp = $m == 1 ? adodb_mktime(0,0,0,12,1,$y-1) : adodb_mktime(0,0,0,$m-1,1,$y); break; case 'last_month_end': $timestamp = $first_day_of_this_month = adodb_mktime(0,0,0,$m,1,$y) - 1; break; case 'last_quater_start': $timestamp = $this_quater == 1 ? adodb_mktime(0,0,0,10,1,$y-1) : adodb_mktime(0,0,0,($this_quater-1)*3-2,1,$y); break; case 'last_quater_end': $timestamp = $this_quater_start - 1; break; case 'last_6_months_start': $timestamp = $m <= 6 ? adodb_mktime(0,0,0,$m+6,1,$y-1) : adodb_mktime(0,0,0,$m-6,1,$y); break; case 'last_year_start': $timestamp = adodb_mktime(0,0,0,1,1,$y-1); break; case 'last_year_end': $timestamp = adodb_mktime(23,59,59,12,31,$y-1); break; } if (isset($params['format'])) { $format = $params['format']; if(preg_match("/_regional_(.*)/", $format, $regs)) { $lang =& $this->Application->recallObject('lang.current'); $format = $lang->GetDBField($regs[1]); } return adodb_date($format, $timestamp); } return $timestamp; } /** * Redirect to cache rebuild template, when required by installator * * @param Array $params */ function CheckPermCache($params) { // we have separate session between install wizard and admin console, so store in cache $global_mark = $this->Application->getDBCache('ForcePermCacheUpdate'); $local_mark = $this->Application->RecallVar('PermCache_UpdateRequired'); if ($global_mark || $local_mark) { $this->Application->RemoveVar('PermCache_UpdateRequired'); if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild')) { $updater =& $this->Application->recallObject('kPermCacheUpdater'); /* @var $updater kPermCacheUpdater */ $updater->OneStepRun(); } else { // update with progress bar return true; } } return false; } /** * Checks if current protocol is SSL * * @param Array $params * @return int */ function IsSSL($params) { return (PROTOCOL == 'https://')? 1 : 0; } function PrintColumns($params) { $picker_helper =& $this->Application->RecallObject('ColumnPickerHelper'); $picker_helper->SetGridName($this->Application->GetLinkedVar('grid_name')); /* @var $picker_helper kColumnPickerHelper */ $main_prefix = $this->Application->RecallVar('main_prefix'); $cols = $picker_helper->LoadColumns($main_prefix); $this->Application->Phrases->AddCachedPhrase('__FREEZER__', '-------------'); $o = ''; if (isset($params['hidden']) && $params['hidden']) { foreach ($cols['hidden_fields'] as $col) { $title = $this->Application->Phrase($cols['titles'][$col]); $o .= "<option value='$col'>".$title; } } else { foreach ($cols['order'] as $col) { if (in_array($col, $cols['hidden_fields'])) continue; $title = $this->Application->Phrase($cols['titles'][$col]); $o .= "<option value='$col'>".$title; } } return $o; } /** * Allows to set popup size (key - current template name) * * @param Array $params */ function SetPopupSize($params) { $width = $params['width']; $height = $params['height']; if ($this->Application->GetVar('ajax') == 'yes') { // during AJAX request just output size die($width.'x'.$height); } if (!$this->UsePopups($params)) { return ; } $t = $this->Application->GetVar('t'); $sql = 'SELECT * FROM '.TABLE_PREFIX.'PopupSizes WHERE TemplateName = '.$this->Conn->qstr($t); $popup_info = $this->Conn->GetRow($sql); if (!$popup_info) { // create new popup size record $fields_hash = Array ( 'TemplateName' => $t, 'PopupWidth' => $width, 'PopupHeight' => $height, ); $this->Conn->doInsert($fields_hash, TABLE_PREFIX.'PopupSizes'); } elseif ($popup_info['PopupWidth'] != $width || $popup_info['PopupHeight'] != $height) { // popup found and size in tag differs from one in db -> update in db $fields_hash = Array ( 'PopupWidth' => $width, 'PopupHeight' => $height, ); $this->Conn->doUpdate($fields_hash, TABLE_PREFIX.'PopupSizes', 'PopupId = '.$popup_info['PopupId']); } } /** * Returns popup size (by template), if not cached, then parse template to get value * * @param Array $params * @return string */ function GetPopupSize($params) { $t = $this->Application->GetVar('template_name'); $sql = 'SELECT * FROM '.TABLE_PREFIX.'PopupSizes WHERE TemplateName = '.$this->Conn->qstr($t); $popup_info = $this->Conn->GetRow($sql); if (!$popup_info) { $this->Application->InitParser(); $this->Application->ParseBlock(array('name' => $t)); // dies when SetPopupSize tag found & in ajax requrest return '750x400'; // tag SetPopupSize not found in template -> use default size } return $popup_info['PopupWidth'].'x'.$popup_info['PopupHeight']; } /** * Allows to check if popups are generally enabled OR to check for "popup" or "modal" mode is enabled * * @param Array $params * @return bool */ function UsePopups($params) { if ($this->Application->GetVar('_force_popup')) { return true; } $use_popups = (int)$this->Application->ConfigValue('UsePopups'); if (array_key_exists('mode', $params)) { $mode_mapping = Array ('popup' => 1, 'modal' => 2); return $use_popups == $mode_mapping[ $params['mode'] ]; } return $use_popups; } function UseToolbarLabels($params) { return (int)$this->Application->ConfigValue('UseToolbarLabels'); } /** * Checks if debug mode enabled (optionally) and specified constant is on * * @param Array $params * @return bool */ function ConstOn($params) { $constant_name = $this->SelectParam($params, 'name,const'); $debug_mode = isset($params['debug_mode']) && $params['debug_mode'] ? $this->Application->isDebugMode() : true; return $debug_mode && constOn($constant_name); } /** * Builds link to last template in main frame of admin * * @param Array $params * @return string */ function MainFrameLink($params) { $persistent = isset($params['persistent']) && $params['persistent']; if ($persistent && $this->Application->ConfigValue('RememberLastAdminTemplate')) { // check last_template in persistent session $last_template = $this->Application->RecallPersistentVar('last_template_popup'); } else { // check last_template in session $last_template = $this->Application->RecallVar('last_template_popup'); // because of m_opener=s there } if (!$last_template) { $params['persistent'] = 1; return $persistent ? false : $this->MainFrameLink($params); } list($index_file, $env) = explode('|', $last_template); $vars = $this->Application->HttpQuery->processQueryString($env, 'pass'); $recursion_templates = Array ('login', 'index', 'no_permission'); if (isset($vars['admin']) && $vars['admin'] == 1) { // index template doesn't begin recursion on front-end (in admin frame) $vars['m_theme'] = ''; if (isset($params['m_opener']) && $params['m_opener'] == 'r') { // front-end link for highlighting purposes $vars['t'] = 'index'; - $vars['m_cat_id'] = $this->Application->findModule('Name', 'Core', 'RootCat'); + $vars['m_cat_id'] = $this->Application->getBaseCategory(); } unset($recursion_templates[ array_search('index', $recursion_templates)]); } if (in_array($vars['t'], $recursion_templates)) { // prevents redirect recursion OR old in-portal pages $params['persistent'] = 1; return $persistent ? false : $this->MainFrameLink($params); } $vars = array_merge_recursive2($vars, $params); $t = $vars['t']; unset($vars['t'], $vars['persistent']); // substitute language in link to current (link will work, even when language will be changed) $vars['m_lang'] = $this->Application->GetVar('m_lang'); return $this->Application->HREF($t, '', $vars, $index_file); } /** * Returns menu frame width or 200 in case, when invalid width specified in config * * @param Array $params * @return string */ function MenuFrameWidth($params) { $width = (int)$this->Application->ConfigValue('MenuFrameWidth'); return $width > 0 ? $width : 200; } function AdminSkin($params) { $skin_helper =& $this->Application->recallObject('SkinHelper'); /* @var $skin_helper SkinHelper */ return $skin_helper->AdminSkinTag($params); } function PrintCompileErrors($params) { $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['render_as']; $errors = $this->Application->RecallVar('compile_errors'); if (!$errors) { return ; } $ret = ''; $errors = unserialize($errors); foreach ($errors as $an_error) { $block_params['file'] = str_replace(FULL_PATH, '', $an_error['file']); $block_params['line'] = $an_error['line']; $block_params['message'] = $an_error['msg']; $ret .= $this->Application->ParseBlock($block_params); } $this->Application->RemoveVar('compile_errors'); return $ret; } function CompileErrorCount($params) { $errors = $this->Application->RecallVar('compile_errors'); if (!$errors) { return 0; } return count( unserialize($errors) ); } function ExportData($params) { $export_helper =& $this->Application->recallObject('CSVHelper'); /* @var $export_helper kCSVHelper */ $result = $export_helper->ExportData( $this->SelectParam($params, 'var,name,field') ); return ($result === false) ? '' : $result; } function ImportData($params) { $import_helper =& $this->Application->recallObject('CSVHelper'); /* @var $import_helper kCSVHelper */ $result = $import_helper->ImportData( $this->SelectParam($params, 'var,name,field') ); return ($result === false) ? '' : $result; } function PrintCSVNotImportedLines($params) { $import_helper =& $this->Application->recallObject('CSVHelper'); /* @var $import_helper kCSVHelper */ return $import_helper->GetNotImportedLines(); } /** * Returns input field name to * be placed on form (for correct * event processing) * * @param Array $params * @return string * @access public */ function InputName($params) { list($id, $field) = $this->prepareInputName($params); $ret = $this->getPrefixSpecial().'[0]['.$field.']'; // 0 always, as has no idfield if( getArrayValue($params, 'as_preg') ) $ret = preg_quote($ret, '/'); return $ret; } /** * Returns list of all backup file dates formatted * in passed block * * @param Array $params * @return string * @access public */ function PrintBackupDates($params) { $datearray = $this->getDirList($this->Application->ConfigValue('Backup_Path')); $ret = ''; foreach($datearray as $key => $value) { $params['backuptimestamp'] = $value['filedate']; $params['backuptime'] = date('F j, Y, g:i a', $value['filedate']); $params['backupsize'] = round($value['filesize']/1024/1024, 2); // MBytes $ret .= $this->Application->ParseBlock($params); } return $ret; } function getDirList ($dirName) { $file_helper =& $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ $file_helper->CheckFolder($dirName); $fileinfo = array(); $d = dir($dirName); while($entry = $d->read()) { if ($entry != "." && $entry != "..") { if (!is_dir($dirName."/".$entry) && strpos($entry, 'dump') !== false) { $fileinfo[]= Array('filedate' => $this->chopchop($entry), 'filesize' => filesize($dirName. '/'. $entry) ); } } } $d->close(); rsort($fileinfo); return $fileinfo; } function chopchop ($filename) { $p = pathinfo($filename); $ext = '.'.$p['extension']; $filename; $filename = str_replace('dump', '',$filename); $filename = str_replace($ext, '', $filename); return $filename; } /** * Returns phpinfo() output * * @param Array $params * @return string */ function PrintPHPinfo($params) { ob_start(); phpinfo(); return ob_get_clean(); } function PrintSqlCols($params) { $a_data = unserialize($this->Application->GetVar('sql_rows')); $ret = ''; $block = $params['render_as']; foreach ($a_data AS $a_row) { foreach ($a_row AS $col => $value) { $ret .= $this->Application->ParseBlock(Array('name'=>$block, 'value'=>$col)); } break; } return $ret; } function PrintSqlRows($params) { $a_data = unserialize($this->Application->GetVar('sql_rows')); $ret = ''; $block = $params['render_as']; foreach ($a_data AS $a_row) { $cells = ''; foreach ($a_row AS $col => $value) { $cells .= '<td>'.$value.'</td>'; } $ret .= $this->Application->ParseBlock(Array('name'=>$block, 'cells'=>$cells)); } return $ret; } /** * Prints available and enabled import sources using given block * * @param Array $params * @return string */ function PrintImportSources($params) { $sql = 'SELECT * FROM ' . TABLE_PREFIX . 'ImportScripts WHERE (Status = ' . STATUS_ACTIVE . ') AND (Type = "CSV")'; $import_sources = $this->Conn->Query($sql); $block_params = $this->prepareTagParams($params); $block_params['name'] = $params['render_as']; $ret = ''; foreach ($import_sources as $import_source) { $block_params['script_id'] = $import_source['ImportId']; $block_params['script_module'] = mb_strtolower($import_source['Module']); $block_params['script_name'] = $import_source['Name']; $block_params['script_prefix'] = $import_source['Prefix']; $block_params['module_path'] = $this->Application->findModule('Name', $import_source['Module'], 'Path'); $ret .= $this->Application->ParseBlock($block_params); } return $ret; } /** * Checks, that new window should be opened in "incs/close_popup" template instead of refreshing parent window * * @param Array $params * @return bool */ function OpenNewWindow($params) { if (!$this->UsePopups($params)) { return false; } $diff = array_key_exists('diff', $params) ? $params['diff'] : 0; $wid = $this->Application->GetVar('m_wid'); $stack_name = rtrim('opener_stack_' . $wid, '_'); $opener_stack = $this->Application->RecallVar($stack_name); $opener_stack = $opener_stack ? unserialize($opener_stack) : Array (); return count($opener_stack) >= 2 - $diff; } /** * Allows to dynamically change current language in template * * @param Array $params */ function SetLanguage($params) { $this->Application->SetVar('m_lang', $params['language_id']); $this->Application->Phrases->Init(''); $this->Application->Phrases->LanguageId = $params['language_id']; $this->Application->Phrases->LoadPhrases( $this->Application->Caches['PhraseList'] ); } /** * Performs HTTP Authentification for administrative console * * @param Array $params */ function HTTPAuth($params) { if (!$this->Application->ConfigValue('UseHTTPAuth')) { // http authentification not required return true; } $super_admin_ips = defined('SA_IP') ? SA_IP : false; $auth_bypass_ips = $this->Application->ConfigValue('HTTPAuthBypassIPs'); if (($auth_bypass_ips && ipMatch($auth_bypass_ips)) || ($super_admin_ips && ipMatch($super_admin_ips))) { // user ip is in ip bypass list return true; } if (!array_key_exists('PHP_AUTH_USER', $_SERVER)) { // ask user to authentificate, when not authentificated before return $this->_httpAuthentificate(); } else { // validate user credentials (browsers remembers user/password // and sends them each time page is visited, so no need to save // authentification result in session) if ($this->Application->ConfigValue('HTTPAuthUsername') != $_SERVER['PHP_AUTH_USER']) { // incorrect username return $this->_httpAuthentificate(); } $password_formatter =& $this->Application->recallObject('kPasswordFormatter'); /* @var $password_formatter kPasswordFormatter */ $password = $password_formatter->EncryptPassword($_SERVER['PHP_AUTH_PW'], 'b38'); if ($this->Application->ConfigValue('HTTPAuthPassword') != $password) { // incorrect password return $this->_httpAuthentificate(); } } return true; } /** * Ask user to authentificate * * @return false */ function _httpAuthentificate() { $realm = strip_tags( $this->Application->ConfigValue('Site_Name') ); header('WWW-Authenticate: Basic realm="' . $realm . '"'); header('HTTP/1.0 401 Unauthorized'); return false; } /** * Checks, that we are using memory cache * * @param Array $params * @return bool */ function MemoryCacheEnabled($params) { return $this->Application->isCachingType(CACHING_TYPE_MEMORY); } } \ No newline at end of file Index: branches/5.1.x/core/install/install_toolkit.php =================================================================== --- branches/5.1.x/core/install/install_toolkit.php (revision 13986) +++ branches/5.1.x/core/install/install_toolkit.php (revision 13987) @@ -1,1037 +1,1037 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); /** * Upgrade sqls are located using this mask * */ define('UPGRADES_FILE', FULL_PATH.'/%sinstall/upgrades.%s'); /** * Prerequisit check classes are located using this mask * */ define('PREREQUISITE_FILE', FULL_PATH.'/%sinstall/prerequisites.php'); /** * Format of version identificator in upgrade files (normal, beta, release candidate) * */ define('VERSION_MARK', '# ===== v ([\d]+\.[\d]+\.[\d]+|[\d]+\.[\d]+\.[\d]+-B[\d]+|[\d]+\.[\d]+\.[\d]+-RC[\d]+) ====='); if (!defined('GET_LICENSE_URL')) { /** * Url used for retrieving user licenses from Intechnic licensing server * */ define('GET_LICENSE_URL', 'http://www.intechnic.com/myaccount/license.php'); } /** * Misc functions, that are required during installation, when * */ class kInstallToolkit { /** * Reference to kApplication class object * * @var kApplication */ var $Application = null; /** * Connection to database * * @var kDBConnection */ var $Conn = null; /** * Path to config.php * * @var string */ var $INIFile = ''; /** * Parsed data from config.php * * @var Array */ var $systemConfig = Array (); /** * Path, used by system to store data on filesystem * * @var string */ var $defaultWritablePath = ''; /** * Installator instance * * @var kInstallator */ var $_installator = null; function kInstallToolkit() { $this->defaultWritablePath = DIRECTORY_SEPARATOR . 'system'; if (class_exists('kApplication')) { // auto-setup in case of separate module install $this->Application =& kApplication::Instance(); $this->Conn =& $this->Application->GetADODBConnection(); } $this->INIFile = FULL_PATH . $this->defaultWritablePath . DIRECTORY_SEPARATOR . 'config.php'; $this->systemConfig = $this->ParseConfig(true); } /** * Sets installator * * @param kInstallator $instance */ function setInstallator(&$instance) { $this->_installator =& $instance; } /** * Checks prerequisities before module install or upgrade * * @param string $module_path * @param string $versions * @param string $mode upgrade mode = {install, standalone, upgrade} */ function CheckPrerequisites($module_path, $versions, $mode) { static $prerequisit_classes = Array (); $prerequisites_file = sprintf(PREREQUISITE_FILE, $module_path); if (!file_exists($prerequisites_file) || !$versions) { return Array (); } if (!isset($prerequisit_classes[$module_path])) { // save class name, because 2nd time // (in after call $prerequisite_class variable will not be present) include_once $prerequisites_file; $prerequisit_classes[$module_path] = $prerequisite_class; } $prerequisite_object = new $prerequisit_classes[$module_path](); if (method_exists($prerequisite_object, 'setToolkit')) { $prerequisite_object->setToolkit($this); } // some errors possible return $prerequisite_object->CheckPrerequisites($versions, $mode); } /** * Processes one license, received from server * * @param string $file_data */ function processLicense($file_data) { $modules_helper =& $this->Application->recallObject('ModulesHelper'); /* @var $modules_helper kModulesHelper */ $file_data = explode('Code==:', $file_data); $file_data[0] = str_replace('In-Portal License File - do not edit!' . "\n", '', $file_data[0]); $file_data = array_map('trim', $file_data); if ($modules_helper->verifyLicense($file_data[0])) { $this->setSystemConfig('Intechnic', 'License', $file_data[0]); if (array_key_exists(1, $file_data)) { $this->setSystemConfig('Intechnic', 'LicenseCode', $file_data[1]); } else { $this->setSystemConfig('Intechnic', 'LicenseCode'); } $this->SaveConfig(); } else { // invalid license received from licensing server $this->_installator->errorMessage = 'Invalid License File'; } } /** * Saves given configuration values to database * * @param Array $config */ function saveConfigValues($config) { foreach ($config as $config_var => $value) { $sql = 'UPDATE ' . TABLE_PREFIX . 'ConfigurationValues SET VariableValue = ' . $this->Conn->qstr($value) . ' WHERE VariableName = ' . $this->Conn->qstr($config_var); $this->Conn->Query($sql); } } /** * Sets module version to passed * * @param string $module_name * @param string $module_path * @param string $version */ function SetModuleVersion($module_name, $module_path = false, $version = false) { if ($version === false) { if (!$module_path) { trigger_error('Module path must be given to "SetModuleVersion" method to auto-detect version', E_USER_ERROR); return ; } $version = $this->GetMaxModuleVersion($module_path); } // get table prefix from config, because application may not be available here $table_prefix = $this->getSystemConfig('Database', 'TablePrefix'); if ($module_name == 'kernel') { $module_name = 'in-portal'; } // don't use "adodb_mktime" here, because it's not yet included $sql = 'UPDATE ' . $table_prefix . 'Modules SET Version = "' . $version . '", BuildDate = ' . time() . ' WHERE LOWER(Name) = "' . strtolower($module_name) . '"'; $this->Conn->Query($sql); } /** * Sets module root category to passed * * @param string $module_name * @param string $category_id */ function SetModuleRootCategory($module_name, $category_id = 0) { // get table prefix from config, because application may not be available here $table_prefix = $this->getSystemConfig('Database', 'TablePrefix'); if ($module_name == 'kernel') { $module_name = 'in-portal'; } $sql = 'UPDATE ' . $table_prefix . 'Modules SET RootCat = ' . $category_id . ' WHERE LOWER(Name) = "' . strtolower($module_name) . '"'; $this->Conn->Query($sql); } /** * Returns maximal version of given module by scanning it's upgrade scripts * * @param string $module_name * @return string */ function GetMaxModuleVersion($module_path) { $module_path = rtrim(mb_strtolower($module_path), '/'); $upgrades_file = sprintf(UPGRADES_FILE, $module_path . '/', 'sql'); if (!file_exists($upgrades_file)) { // no upgrade file return '5.0.0'; } $sqls = file_get_contents($upgrades_file); $versions_found = preg_match_all('/'.VERSION_MARK.'/s', $sqls, $regs); if (!$versions_found) { // upgrades file doesn't contain version definitions return '5.0.0'; } return end($regs[1]); } /** * Runs SQLs from file * * @param string $filename * @param mixed $replace_from * @param mixed $replace_to */ function RunSQL($filename, $replace_from = null, $replace_to = null) { if (!file_exists(FULL_PATH.$filename)) { return ; } $sqls = file_get_contents(FULL_PATH.$filename); if (!$this->RunSQLText($sqls, $replace_from, $replace_to)) { if (is_object($this->_installator)) { $this->_installator->Done(); } else { if (isset($this->Application)) { $this->Application->Done(); } exit; } } } /** * Runs SQLs from string * * @param string $sqls * @param mixed $replace_from * @param mixed $replace_to */ function RunSQLText(&$sqls, $replace_from = null, $replace_to = null, $start_from = 0) { $table_prefix = $this->getSystemConfig('Database', 'TablePrefix'); // add prefix to all tables if (strlen($table_prefix) > 0) { $replacements = Array ('INSERT INTO ', 'UPDATE ', 'ALTER TABLE ', 'DELETE FROM ', 'REPLACE INTO '); foreach ($replacements as $replacement) { $sqls = str_replace($replacement, $replacement . $table_prefix, $sqls); } } $sqls = str_replace('CREATE TABLE ', 'CREATE TABLE IF NOT EXISTS ' . $table_prefix, $sqls); $sqls = str_replace('DROP TABLE ', 'DROP TABLE IF EXISTS ' . $table_prefix, $sqls); $sqls = str_replace('<%TABLE_PREFIX%>', $table_prefix, $sqls); $primary_language = is_object($this->Application) ? $this->Application->GetDefaultLanguageId() : 1; $sqls = str_replace('<%PRIMARY_LANGUAGE%>', $primary_language, $sqls); if (isset($replace_from) && isset($replace_to)) { // replace something additionally, e.g. module root category $sqls = str_replace($replace_from, $replace_to, $sqls); } $sqls = str_replace("\r\n", "\n", $sqls); // convert to linux line endings $no_comment_sqls = preg_replace("/#\s([^;]*?)\n/is", '', $sqls); // remove all comments "#" on new lines if ($no_comment_sqls === null) { // "ini.pcre.backtrack-limit" reached and error happened $sqls = explode(";\n", $sqls . "\n"); // ensures that last sql won't have ";" in it $sqls = array_map('trim', $sqls); // remove all comments "#" on new lines (takes about 2 seconds for 53000 sqls) $sqls = preg_replace("/#\s([^;]*?)/", '', $sqls); } else { $sqls = explode(";\n", $no_comment_sqls . "\n"); // ensures that last sql won't have ";" in it $sqls = array_map('trim', $sqls); } $sql_count = count($sqls); $db_collation = $this->getSystemConfig('Database', 'DBCollation'); for ($i = $start_from; $i < $sql_count; $i++) { $sql = $sqls[$i]; if (!$sql || (substr($sql, 0, 1) == '#')) { continue; // usually last line } if (substr($sql, 0, 13) == 'CREATE TABLE ' && $db_collation) { // it is CREATE TABLE statement -> add collation $sql .= ' COLLATE \'' . $db_collation . '\''; } $this->Conn->Query($sql); if ($this->Conn->getErrorCode() != 0) { if (is_object($this->_installator)) { $this->_installator->errorMessage = 'Error: ('.$this->Conn->getErrorCode().') '.$this->Conn->getErrorMsg().'<br /><br />Last Database Query:<br /><textarea cols="70" rows="10" readonly>'.htmlspecialchars($sql).'</textarea>'; $this->_installator->LastQueryNum = $i + 1; } return false; } } return true; } /** * Performs clean language import from given xml file * * @param string $lang_file * @param bool $upgrade * @todo Import for "core/install/english.lang" (322KB) takes 18 seconds to work on Windows */ function ImportLanguage($lang_file, $upgrade = false) { $lang_file = FULL_PATH.$lang_file.'.lang'; if (!file_exists($lang_file)) { return ; } $language_import_helper =& $this->Application->recallObject('LanguageImportHelper'); /* @var $language_import_helper LanguageImportHelper */ $language_import_helper->performImport($lang_file, '|0|1|2|', '', $upgrade ? LANG_SKIP_EXISTING : LANG_OVERWRITE_EXISTING); } /** * Converts module version in format X.Y.Z[-BN/-RCM] to signle integer * * @param string $version * @return int */ function ConvertModuleVersion($version) { if (preg_match('/(.*)-(B|RC)([\d]+)/', $version, $regs)) { // -B<M> or RC-<N> $parts = explode('.', $regs[1]); $parts[] = $regs[2] == 'B' ? 1 : 2; // B reliases goes before RC releases $parts[] = $regs[3]; } else { // releases without B/RC marks go after any B/RC releases $parts = explode('.', $version . '.3.100'); } $bin = ''; foreach ($parts as $part_index => $part) { if ($part_index == 3) { // version type only can be 1/2/3 (11 in binary form), so don't use padding at all $pad_count = 2; } else { $pad_count = 8; } $bin .= str_pad(decbin($part), $pad_count, '0', STR_PAD_LEFT); } return bindec($bin); } /** * Returns themes, found in system * * @param bool $rebuild * @return int */ function getThemes($rebuild = false) { if ($rebuild) { $this->rebuildThemes(); } $id_field = $this->Application->getUnitOption('theme', 'IDField'); $table_name = $this->Application->getUnitOption('theme', 'TableName'); $sql = 'SELECT Name, ' . $id_field . ' FROM ' . $table_name . ' ORDER BY Name ASC'; return $this->Conn->GetCol($sql, $id_field); } function ParseConfig($parse_section = false) { if (!file_exists($this->INIFile)) { return Array (); } if (file_exists($this->INIFile) && !is_readable($this->INIFile)) { die('Could Not Open Ini File'); } $contents = file($this->INIFile); if ($contents && $contents[0] == '<' . '?' . 'php die() ?' . ">\n") { // format of "config.php" file before 5.1.0 version array_shift($contents); return $this->parseIniString(implode('', $contents), $parse_section); } $_CONFIG = Array (); require($this->INIFile); if ($parse_section) { return $_CONFIG; } $ret = Array (); foreach ($_CONFIG as $section => $section_variables) { $ret = array_merge($ret, $section_variables); } return $ret; } /** * Equivalent for "parse_ini_string" function available since PHP 5.3.0 * * @param string $ini * @param bool $process_sections * @param int $scanner_mode * @return Array */ function parseIniString($ini, $process_sections = false, $scanner_mode = null) { # Generate a temporary file. $tempname = tempnam('/tmp', 'ini'); $fp = fopen($tempname, 'w'); fwrite($fp, $ini); $ini = parse_ini_file($tempname, !empty($process_sections)); fclose($fp); @unlink($tempname); return $ini; } function SaveConfig($silent = false) { if (!is_writable($this->INIFile) && !is_writable(dirname($this->INIFile))) { trigger_error('Cannot write to "' . $this->INIFile . '" file.', $silent ? E_USER_WARNING : E_USER_ERROR); return ; } $fp = fopen($this->INIFile, 'w'); fwrite($fp, '<' . '?' . 'php' . "\n\n"); foreach ($this->systemConfig as $section_name => $section_data) { foreach ($section_data as $key => $value) { fwrite($fp, '$_CONFIG[\'' . $section_name . '\'][\'' . $key . '\'] = \'' . addslashes($value) . '\';' . "\n"); } fwrite($fp, "\n"); } fclose($fp); } /** * Sets value to system config (yet SaveConfig must be called to write it to file) * * @param string $section * @param string $key * @param string $value */ function setSystemConfig($section, $key, $value = null) { if (isset($value)) { if (!array_key_exists($section, $this->systemConfig)) { // create section, when missing $this->systemConfig[$section] = Array (); } // create key in section $this->systemConfig[$section][$key] = $value; return ; } unset($this->systemConfig[$section][$key]); } /** * Returns information from system config * * @return string */ function getSystemConfig($section, $key) { if (!array_key_exists($section, $this->systemConfig)) { return false; } if (!array_key_exists($key, $this->systemConfig[$section])) { return false; } return $this->systemConfig[$section][$key] ? $this->systemConfig[$section][$key] : false; } /** * Checks if system config is present and is not empty * * @return bool */ function systemConfigFound() { return file_exists($this->INIFile) && $this->systemConfig; } /** * Checks if given section is present in config * * @param string $section * @return bool */ function sectionFound($section) { return array_key_exists($section, $this->systemConfig); } /** * Returns formatted module name based on it's root folder * * @param string $module_folder * @return string */ function getModuleName($module_folder) { return implode('-', array_map('ucfirst', explode('-', $module_folder))); } /** * Returns information about module (based on "install/module_info.xml" file) * * @param string $module_name * @return Array */ function getModuleInfo($module_name) { if ($module_name == 'core') { $info_file = FULL_PATH . '/' . $module_name . '/install/module_info.xml'; } else { $info_file = MODULES_PATH . '/' . $module_name . '/install/module_info.xml'; } if (!file_exists($info_file)) { return Array (); } $xml_helper =& $this->Application->recallObject('kXMLHelper'); /* @var $xml_helper kXMLHelper */ $root_node =& $xml_helper->Parse( file_get_contents($info_file) ); if (!is_object($root_node) || !preg_match('/^kxmlnode/i', get_class($root_node)) || ($root_node->Name == 'ERROR')) { // non-valid xml file return Array (); } $ret = Array (); $current_node =& $root_node->firstChild; do { $ret[ strtolower($current_node->Name) ] = trim($current_node->Data); } while (($current_node =& $current_node->NextSibling())); return $ret; } /** * Returns nice module string to be used on install/upgrade screens * * @param string $module_name * @param string $version_string * @return string */ function getModuleString($module_name, $version_string) { // image (if exists) <description> (<name> <version>) $ret = Array (); $module_info = $this->getModuleInfo($module_name); if (array_key_exists('name', $module_info) && $module_info['name']) { $module_name = $module_info['name']; } else { $module_name = $this->getModuleName($module_name); } if (array_key_exists('image', $module_info) && $module_info['image']) { $image_src = $module_info['image']; if (!preg_match('/^(http|https):\/\//', $image_src)) { // local image -> make absolute url $image_src = $this->Application->BaseURL() . $image_src; } $ret[] = '<img src="' . $image_src . '" alt="' . htmlspecialchars($module_name) . '" title="' . htmlspecialchars($module_name) . '" style="vertical-align:middle; margin: 3px 0 3px 5px"/>'; } if (array_key_exists('description', $module_info) && $module_info['description']) { $ret[] = $module_info['description']; } else { $ret[] = $module_name; } $ret[] = '(' . $module_name . ' ' . $version_string . ')'; return implode(' ', $ret); } /** * Creates module root category in "Home" category using given data and returns it * * @param string $name * @param string $description * @param string $category_template * @param string $category_icon * @return kDBItem */ function &createModuleCategory($name, $description, $category_template = null, $category_icon = null) { static $fields = null; if (!isset($fields)) { $ml_formatter =& $this->Application->recallObject('kMultiLanguage'); $fields['name'] = $ml_formatter->LangFieldName('Name'); $fields['description'] = $ml_formatter->LangFieldName('Description'); } $category =& $this->Application->recallObject('c', null, Array ('skip_autoload' => true)); /* @var $category kDBItem */ $category_fields = Array ( $fields['name'] => $name, 'Filename' => $name, 'AutomaticFilename' => 1, $fields['description'] => $description, 'Status' => STATUS_ACTIVE, 'Priority' => -9999, // prevents empty link to module category on spearate module install 'NamedParentPath' => 'Content/' . $name, ); - $category_fields['ParentId'] = $this->Application->findModule('Name', 'Core', 'RootCat'); + $category_fields['ParentId'] = $this->Application->getBaseCategory(); if (isset($category_template)) { $category_fields['Template'] = $category_template; $category_fields['CachedTemplate'] = $category_template; } if (isset($category_icon)) { $category_fields['UseMenuIconUrl'] = 1; $category_fields['MenuIconUrl'] = $category_icon; } $category->Clear(); $category->SetDBFieldsFromHash($category_fields); $category->Create(); $priority_helper =& $this->Application->recallObject('PriorityHelper'); /* @var $priority_helper kPriorityHelper */ $event = new kEvent('c:OnListBuild'); // ensure, that newly created category has proper value in Priority field $priority_helper->recalculatePriorities($event, 'ParentId = ' . $category_fields['ParentId']); // update Priority field in object, becase "CategoriesItem::Update" method will be called // from "kInstallToolkit::setModuleItemTemplate" and otherwise will set 0 to Priority field $sql = 'SELECT Priority FROM ' . $category->TableName . ' WHERE ' . $category->IDField . ' = ' . $category->GetID(); $category->SetDBField('Priority', $this->Conn->GetOne($sql)); return $category; } /** * Sets category item template into custom field for given prefix * * @param kDBItem $category * @param string $prefix * @param string $item_template */ function setModuleItemTemplate(&$category, $prefix, $item_template) { $this->Application->removeObject('c-cdata'); // recreate all fields, because custom fields are added during install script $category->defineFields(); $category->prepareConfigOptions(); // creates ml fields $category->SetDBField('cust_' . $prefix .'_ItemTemplate', $item_template); $category->Update(); } /** * Link custom field records with search config records + create custom field columns * * @param string $module_folder * @param int $item_type */ function linkCustomFields($module_folder, $prefix, $item_type) { $module_folder = strtolower($module_folder); $module_name = $module_folder; if ($module_folder == 'kernel') { $module_name = 'in-portal'; $module_folder = 'core'; } $db =& $this->Application->GetADODBConnection(); $sql = 'SELECT FieldName, CustomFieldId FROM ' . TABLE_PREFIX . 'CustomField WHERE Type = ' . $item_type . ' AND IsSystem = 0'; // config is not read here yet :( $this->Application->getUnitOption('p', 'ItemType'); $custom_fields = $db->GetCol($sql, 'CustomFieldId'); foreach ($custom_fields as $cf_id => $cf_name) { $sql = 'UPDATE ' . TABLE_PREFIX . 'SearchConfig SET CustomFieldId = ' . $cf_id . ' WHERE (TableName = "CustomField") AND (LOWER(ModuleName) = "' . $module_name . '") AND (FieldName = ' . $db->qstr($cf_name) . ')'; $db->Query($sql); } $this->Application->refreshModuleInfo(); // this module configs are now processed // because of configs was read only from installed before modules (in-portal), then reread configs $unit_config_reader =& $this->Application->recallObject('kUnitConfigReader'); /* @var $unit_config_reader kUnitConfigReader */ $unit_config_reader->scanModules(MODULES_PATH . DIRECTORY_SEPARATOR . $module_folder); // create correct columns in CustomData table $ml_helper =& $this->Application->recallObject('kMultiLanguageHelper'); $ml_helper->createFields($prefix . '-cdata', true); } /** * Deletes cache, useful after separate module install and installator last step * */ function deleteCache($refresh_permissions = false) { $this->Application->HandleEvent($event, 'adm:OnResetConfigsCache'); $this->Application->HandleEvent($event, 'adm:OnResetSections'); $this->Application->HandleEvent($event, 'c:OnResetCMSMenuCache'); $this->Conn->Query('DELETE FROM ' . TABLE_PREFIX . 'CachedUrls'); if ($refresh_permissions) { if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild')) { // refresh permission without progress bar $updater =& $this->Application->recallObject('kPermCacheUpdater'); /* @var $updater kPermCacheUpdater */ $updater->OneStepRun(); } else { // refresh permissions with ajax progress bar (when available) $this->Application->setDBCache('ForcePermCacheUpdate', 1); } } } /** * Deletes all temp tables (from active sessions too) * */ function deleteEditTables() { $table_prefix = $this->getSystemConfig('Database', 'TablePrefix'); $tables = $this->Conn->GetCol('SHOW TABLES'); $mask_edit_table = '/' . $table_prefix . 'ses_(.*)_edit_(.*)/'; $mask_search_table = '/' . $table_prefix . 'ses_(.*?)_(.*)/'; foreach ($tables as $table) { if ( preg_match($mask_edit_table, $table, $rets) || preg_match($mask_search_table, $table, $rets) ) { $this->Conn->Query('DROP TABLE IF EXISTS ' . $table); } } } /** * Perform redirect after separate module install * * @param string $module_folder * @param bool $refresh_permissions */ function finalizeModuleInstall($module_folder, $refresh_permissions = false) { $this->SetModuleVersion(basename($module_folder), $module_folder); if (!$this->Application->GetVar('redirect')) { return ; } $themes_helper =& $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ $module_name = $this->Application->findModule('Path', rtrim($module_folder, '/') . '/', 'Name'); $themes_helper->syncronizeModule($module_name); $this->deleteCache($refresh_permissions); $url_params = Array ( 'pass' => 'm', 'admin' => 1, 'RefreshTree' => 1, 'index_file' => 'index.php', ); $this->Application->Redirect('modules/modules_list', $url_params); } /** * Performs rebuild of themes * */ function rebuildThemes() { $this->Application->HandleEvent($themes_event, 'adm:OnRebuildThemes'); } /** * Checks that file is writable by group or others * * @param string $file * @return boolean */ function checkWritePermissions($file) { if (DIRECTORY_SEPARATOR == '\\') { // windows doen't allow to check permissions (always returns null) return null; } $permissions = fileperms($file); return $permissions & 0x0010 || $permissions & 0x0002; } /** * Upgrades primary skin to the latest version * * @param Array $module_info * @return string */ function upgradeSkin($module_info) { $upgrades_file = sprintf(UPGRADES_FILE, $module_info['Path'], 'css'); $data = file_get_contents($upgrades_file); // get all versions with their positions in file $versions = Array (); preg_match_all('/(' . VERSION_MARK . ')/s', $data, $matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE); $from_version_int = $this->ConvertModuleVersion($module_info['FromVersion']); foreach ($matches as $index => $match) { $version_int = $this->ConvertModuleVersion($match[2][0]); if ($version_int < $from_version_int) { // only process versions, that were released after currently used version continue; } $start_pos = $match[0][1] + strlen($match[0][0]); $end_pos = array_key_exists($index + 1, $matches) ? $matches[$index + 1][0][1] : mb_strlen($data); $patch_data = str_replace("\r\n", "\n", substr($data, $start_pos, $end_pos - $start_pos)); $versions[] = Array ( 'Version' => $match[2][0], // fixes trimmed leading spaces by modern text editor 'Data' => ltrim( str_replace("\n\n", "\n \n", $patch_data) ), ); } if (!$versions) { // not skin changes -> quit return true; } $primary_skin =& $this->Application->recallObject('skin.primary', null, Array ('skip_autoload' => true)); /* @var $primary_skin kDBItem */ $primary_skin->Load(1, 'IsPrimary'); if (!$primary_skin->isLoaded()) { // we always got primary skin, but just in case return ; } $temp_handler =& $this->Application->recallObject('skin_TempHandler', 'kTempTablesHandler'); /* @var $temp_handler kTempTablesHandler */ // clone current skin $cloned_ids = $temp_handler->CloneItems('skin', '', Array ($primary_skin->GetID())); if (!$cloned_ids) { // can't clone return ; } $skin =& $this->Application->recallObject('skin.tmp', null, Array ('skip_autoload' => true)); /* @var $skin kDBItem */ $skin->Load( $cloned_ids[0] ); // save css to temp file (for patching) $skin_file = tempnam('/tmp', 'skin_css_'); $fp = fopen($skin_file, 'w'); fwrite($fp, str_replace("\r\n", "\n", $skin->GetDBField('CSS'))); fclose($fp); $output = Array (); $patch_file = tempnam('/tmp', 'skin_patch_'); foreach ($versions as $version_info) { // for each left version get it's patch and apply to temp file $fp = fopen($patch_file, 'w'); fwrite($fp, $version_info['Data']); fclose($fp); $output[ $version_info['Version'] ] = shell_exec('patch ' . $skin_file . ' ' . $patch_file . ' 2>&1') . "\n"; } // place temp file content into cloned skin $skin->SetDBField('Name', 'Upgraded to ' . $module_info['ToVersion']); $skin->SetDBField('CSS', file_get_contents($skin_file)); $skin->Update(); unlink($skin_file); unlink($patch_file); $has_errors = false; foreach ($output as $version => $version_output) { $version_errors = trim( preg_replace("/(^|\n)(patching file .*?|Hunk #.*?\.)(\n|$)/m", '', $version_output) ); if ($version_errors) { $has_errors = true; $output[$version] = trim( preg_replace("/(^|\n)(patching file .*?)(\n|$)/m", '', $output[$version]) ); } else { unset($output[$version]); } } if (!$has_errors) { // copy patched css back to primary skin $primary_skin->SetDBField('CSS', $skin->GetDBField('CSS')); $primary_skin->Update(); // delete temporary skin record $temp_handler->DeleteItems('skin', '', Array ($skin->GetID())); return true; } // put clean skin from new version $skin->SetDBField('CSS', file_get_contents(FULL_PATH . '/core/admin_templates/incs/style_template.css')); $skin->Update(); // return output in case of errors return $output; } } \ No newline at end of file