Index: branches/RC/core/units/categories/categories_event_handler.php =================================================================== diff -u -r11119 -r11495 --- branches/RC/core/units/categories/categories_event_handler.php (.../categories_event_handler.php) (revision 11119) +++ branches/RC/core/units/categories/categories_event_handler.php (.../categories_event_handler.php) (revision 11495) @@ -9,16 +9,41 @@ function mapPermissions() { parent::mapPermissions(); - $permissions = Array( - 'OnRebuildCache' => Array('self' => 'add|edit'), - 'OnPasteClipboard' => Array('self' => 'add|edit'), - 'OnPaste' => array('self'=>'add|edit', 'subitem' => 'edit'), -// 'OnSave' => Array('self' => 'add|edit') - ); + + $permissions = Array ( + 'OnRebuildCache' => Array ('self' => 'add|edit'), + 'OnCopy' => Array ('self' => 'add|edit'), + 'OnCut' => Array ('self' => 'edit'), + 'OnPasteClipboard' => Array ('self' => 'add|edit'), + '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 + + 'OnSaveBlock' => Array ('self' => true), // saves RenderElements from Front-End + ); + $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 @@ -137,9 +162,14 @@ $object =& $event->getObject(); /* @var $object kDBList */ - // hide categories with status = 4 (system categories) - $object->addFilter('system_categories', '%1$s.Status <> 4'); + /*if (!$this->Application->IsAdmin()) { + // don't show categories visible in menu as sub-categories + $object->addFilter('system_categories', '%1$s.IsMenu = 0'); + }*/ + // 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'); @@ -358,25 +388,95 @@ $type_clauses['product_related']['having_filter'] = false; } + $type_clauses['menu']['include'] = '%1$s.IsMenu = 1'; + $type_clauses['menu']['except'] = '%1$s.IsMenu = 0'; + $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 $themes_helper->getCurrentThemeId(); + } + + /** * Enter description here... * * @param kEvent $event * @return int */ function getPassedID(&$event) { - if ( $this->Application->IsAdmin()) { - return parent::getPassedID($event); + static $page_by_template = Array (); + + if ($event->Special == 'current') { + return $this->Application->GetVar('m_cat_id'); } - 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 remplace) 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); + $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 . '.-new', null, Array('skip_autoload' => true)); + /* @var $object kDBItem */ + + $this->_prepareAutoPage($object, $template); // create virtual page + + if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild') || !$this->Application->IsAdmin()) { + $updater =& $this->Application->recallObject('kPermCacheUpdater'); + /* @var $updater kPermCacheUpdater */ + + $updater->OneStepRun(); + } + + $event->CallSubEvent('OnResetMenuCache'); + + $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) @@ -425,6 +525,7 @@ else { $parent_path = $object->GetDBField('ParentPath'); } + if ($parent_path) { $cache_updater =& $this->Application->recallObject('kPermCacheUpdater', null, array('strict_path' => $parent_path)); $cache_updater->OneStepRun(); @@ -436,11 +537,27 @@ * Set cache modification mark if needed * * @param kEvent $event + * @todo Path field is empty for each of virtual pages. Any logic here? */ function OnBeforeDeleteFromLive(&$event) { + // 1. update paths (cms part) $id = $event->getEventParam('id'); + $old_paths = $this->Application->RecallVar('old_paths'); + $old_paths = $old_paths ? unserialize($old_paths) : Array (); + + $live_object =& $this->Application->recallObject($event->Prefix.'.-live', null, Array('live_table' => true, 'skip_autoload' => true)); + /* @var $live_object kDBItem */ + + $live_object->Load($id); + $live_path = $live_object->GetField('Path'); + $old_paths[$id] = $live_path; + + $this->Application->StoreVar('old_paths', serialize($old_paths)); + + // 2. set perm cache update mark + // loding anyway, because this object is needed by "c-perm:OnBeforeDeleteFromLive" event $temp_object =& $event->getObject( Array('skip_autoload' => true) ); $temp_object->Load($id); @@ -454,9 +571,6 @@ } // 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('Name', 'Filename', 'CategoryTemplate', 'ParentId', 'Priority'); foreach ($cached_fields as $cached_field) { @@ -487,9 +601,22 @@ */ function OnPreCreate(&$event) { - $this->Application->RemoveVar('IsRootCategory_'.$this->Application->GetVar('m_wid')); + // 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 + $object->SetDBField('Template', $this->_getDefaultDesign()); + + $priority_helper =& $this->Application->recallObject('PriorityHelper'); + /* @var $priority_helper kPriorityHelper */ + + // 3. prepare priorities dropdown + $category_id = $this->Application->GetVar('m_cat_id'); + $priority_helper->preparePriorities($event, true, 'ParentId = ' . $category_id); } /** @@ -506,17 +633,44 @@ } parent::OnSave($event); - if ($event->status == erSUCCESS && $this->Application->RecallVar('PermCache_UpdateRequired')) { - // "catalog" should be in opener stack by now - $wid = $this->Application->GetVar('m_wid'); - $stack_name = rtrim('opener_stack_'.$wid, '_'); - $this->Application->RemoveVar('IsRootCategory_'.$wid); - $opener_stack = unserialize($this->Application->RecallVar($stack_name)); - $opener_stack[0] = str_replace('catalog/catalog', 'categories/cache_updater', $opener_stack[0]); - $this->Application->StoreVar($stack_name, serialize($opener_stack)); + 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')) { + if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild')) { + $updater =& $this->Application->recallObject('kPermCacheUpdater'); + /* @var $updater kPermCacheUpdater */ + + $updater->OneStepRun(); + } + else { + // "catalog" should be in opener stack by now + $wid = $this->Application->GetVar('m_wid'); + $stack_name = rtrim('opener_stack_'.$wid, '_'); + $this->Application->RemoveVar('IsRootCategory_'.$wid); + + $opener_stack = unserialize($this->Application->RecallVar($stack_name)); + $opener_stack[0] = str_replace('catalog/catalog', 'categories/cache_updater', $opener_stack[0]); + $this->Application->StoreVar($stack_name, serialize($opener_stack)); + } + $this->Application->RemoveVar('PermCache_UpdateRequired'); } + + $this->Application->StoreVar('RefreshStructureTree', 1); + $event->CallSubEvent('OnResetMenuCache'); } /** @@ -535,6 +689,12 @@ 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); } @@ -582,9 +742,64 @@ $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) + { + if (!defined('DBG_NPARSER_FORCE_COMPILE')) { + $this->Application->InitParser(); + define('DBG_NPARSER_FORCE_COMPILE', 1); + } + + 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 @@ -629,6 +844,17 @@ } } $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); + $event->CallSubEvent('OnResetMenuCache'); } /** @@ -692,6 +918,17 @@ return false; } + + // 1. get ParentId of moved category(-es) before it gets updated!!!) + $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); + + if ($clipboard_data['cut']) { + $sql = 'SELECT ParentId + FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' + WHERE ' . $id_field . ' = ' . $clipboard_data['cut'][0]; + $source_category_id = $this->Conn->GetOne($sql); + } + $recursive_helper =& $this->Application->recallObject('RecursiveHelper'); /* @var $recursive_helper kRecursiveHelper */ @@ -705,8 +942,32 @@ } } + $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']) { - $event->redirect = 'categories/cache_updater'; + // 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'; + } + + $event->CallSubEvent('OnResetMenuCache'); + $this->Application->StoreVar('RefreshStructureTree', 1); } } @@ -765,6 +1026,8 @@ */ function OnBeforeItemCreate(&$event) { + $this->_beforeItemChange($event); + if ($this->Application->IsAdmin() || $event->Prefix == 'st') { // don't check category permissions when auto-creating structure pages return ; @@ -795,6 +1058,18 @@ } /** + * Sets correct status for new categories created on front-end + * + * @param kEvent $event + */ + function OnBeforeItemUpdate(&$event) + { + parent::OnBeforeItemUpdate($event); + + $this->_beforeItemChange($event); + } + + /** * Performs redirect to correct suggest confirmation template * * @param kEvent $event @@ -946,6 +1221,723 @@ } return true; } + + // ============= for cms page processing ======================= + + /** + * Returns default design template + * + * @return string + */ + function _getDefaultDesign() + { + $default_design = $this->Application->ConfigValue('cms_DefaultDesign'); + return '/' . trim($default_design ? $default_design : 'designs/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('Template'); + $this->Application->SetVar('m_cat_id', $page->GetDBField('CategoryId') ); + if ($page->GetDBField('FormId')) { + $this->Application->SetVar('form_id', $page->GetDBField('FormId')); + } + } + else { + $real_t = $this->_getDefaultDesign(); + } + +// $this->Application->SetVar('t', $t); + + 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 */ + + $object->SetDBField('Template', $this->_stripTemplateExtension( $object->GetDBField('Template') )); + $object->SetDBField('Path', $this->_stripTemplateExtension( $object->GetDBField('Path') )); + + if ($object->GetDBField('IsSystem') == 1) { + if (!$this->_templateFound($object->GetDBField('Template'), $object->GetDBField('ThemeId'))) { + $object->SetError('Template', 'template_file_missing', 'la_err_TemplateFileMissing'); + } + else { + $object->SetDBField('Filename', 'Content/' . $object->GetDBField('Template')); + } + } + + $this->_saveTitleField($object, 'Title'); + $this->_saveTitleField($object, 'MenuTitle'); + } + + /** + * 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); + if ($value == '' || preg_match('/^_Auto: (.*)/', $value)) { + $ml_formatter =& $this->Application->recallObject('kMultiLanguage'); + /* @var $ml_formatter kMultiLanguage */ + + $object->SetField( + $ml_formatter->LangFieldName($field), + $object->GetField( $ml_formatter->LangFieldName('Name') ) + ); + } + } + + /** + * When page is renamed, then update all links to it in content blocks + * + * @param kEvent $event + * @todo Path field empty for each virtual page. Any logic here? + */ + function OnBeforeCopyToLive(&$event) + { + $id = $event->getEventParam('id'); + + $temp =& $this->Application->recallObject($event->Prefix.'.-temp', $event->Prefix); + $temp->SwitchToTemp(); + $temp->Load($id); + + $old_paths = $this->Application->RecallVar('old_paths'); + $old_paths = $old_paths ? unserialize($old_paths) : array(); + if ($old_paths[$id]) { + $live_path = $old_paths[$id]; + } + else { + return; + } + + $temp_path = $temp->GetField('Path'); + if ($temp_path !== $live_path) { + $languages_count = ceil($this->Conn->GetOne('SELECT COUNT(*) FROM '.TABLE_PREFIX.'Language')/5)*5; + $lang_parts = array(); + for ($i=1;$i<=$languages_count;$i++) { + $lang_parts[] = + 'l'.$i.'_Content = + REPLACE(l'.$i.'_Content, + '.$this->Conn->qstr($live_path).', + '.$this->Conn->qstr($temp_path).')'; + } + $query = 'UPDATE '.TABLE_PREFIX.'PageContent SET '.join(',', $lang_parts); + $this->Conn->Query($query); + } + } + + /** + * Don't allow to delete system pages, when not in debug mode + * + * @param kEvent $event + */ + function OnBeforeItemDelete(&$event) + { + $object =& $event->getObject(); + if ($object->GetDBField('IsSystem') && !$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 = $this->_stripTemplateExtension($template); + + if ($system_mode == SMS_MODE_AUTO) { + $system = $this->_templateFound($template, $theme_id) ? 1 : 0; + } + else { + $system = $system_mode == SMS_MODE_FORCE ? 1 : 0; + } + + if (!isset($theme_id)) { + $theme_id = $this->_getCurrentThemeId(); + } + + $root_category = $this->Application->findModule('Name', 'Proj-CMS', 'RootCat'); + $page_category = $this->Application->GetVar('m_cat_id'); + if (!$page_category) { + $page_category = $root_category; + $this->Application->SetVar('m_cat_id', $page_category); + } + + if (!$system && 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 = $system ? '_Auto: ' . $template : $template; + $page_description = ''; + + if ($system) { + $design_template = $template; + $template_info = $this->_getTemplateInfo($template, $theme_id); + 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(); + } + + // put all templates to then end of list (in their category) + $sql = 'SELECT MIN(Priority) + FROM ' . $object->TableName . ' + WHERE ParentId = ' . $page_category; + $min_priority = (int)$this->Conn->GetOne($sql); + + $object->Clear(); + $object->SetDBField('ParentId', $page_category); + $object->SetDBField('Filename', $system ? 'Content/' . $template : $template); + $object->SetDBField('IsSystem', $system); + + + // system templates don't build their NamedParentPath based on their location because they are all added under + // "Content" when theme file structure is rebuilded and for ex. file "in-edit/designs/general" will have filename + // (after stripDisallowed) like "in_edit_designs_general" witch is less readable like "in-edit/designs/general" + // as for now. + + // TODO: 1. make system template NamedParentPath dependent on their location in structure. + // 2. load cms-page not only by NamedParentPath, but also by 'OR (Template = "$template" AND IsSystem = 1)' + // This way we can store CMS-blocks based on system template name on HDD no matter where it's location is + // and we can address him from template using his system path OR structure path (CMS-blocks should work too). + + $object->SetDBField('AutomaticFilename', 0); + $object->SetDBField('IsMenu', 0); + $object->SetDBField('ThemeId', $system ? $theme_id : 0); + $object->SetDBField('Priority', $min_priority - 1); + $object->SetDBField('Template', $design_template); // bug: possible case, when leading "/" is missing + + $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 . '.-item', null, Array ('skip_autoload' => true)); + /* @var $object kDBItem */ + + if (isset($theme_id)) { + // extract system page from category path (if any) + $template_path = $category_path; + while (!$this->_templateFound(implode('/', $template_path), $theme_id) && $template_path) { + array_pop($template_path); + } + + if ($template_path) { + $template = implode('/', $template_path); + $object->Load( Array ('Filename' => 'Content/' . $template, 'ParentId' => $base_category, 'ThemeId' => $theme_id) ); + if (!$object->isLoaded()) { + // force creating system page + $this->_prepareAutoPage($object, $template, $theme_id, SMS_MODE_FORCE); + } + + $category_path = array_slice($category_path, count($template_path)); + + return $this->_getParentCategoryFromPath($category_path, $object->GetID()); + } + } + + $parent_id = $base_category; + + foreach ($category_path as $category_order => $category_name) { + $this->Application->SetVar('m_cat_id', $parent_id); + + $object->Load( Array ('Filename' => $category_name, 'ParentId' => $parent_id, 'ThemeId' => 0) ); + if ($object->isLoaded()) { + // page found -> use it's id + $parent_id = $object->GetID(); + } + else { + // page not found -> force creating virtual page + if (!$this->_prepareAutoPage($object, $category_name, null, 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 template information (name, description, path) from it's header comment + * + * @param string $template + * @param int $theme_id + * @return Array + */ + function _getTemplateInfo($template, $theme_id = null) + { + if (!defined('DBG_NPARSER_FORCE_COMPILE')) { + $this->Application->InitParser(); + define('DBG_NPARSER_FORCE_COMPILE', 1); + } + + if (!isset($theme_id)) { + $theme_id = $this->_getCurrentThemeId(); + } + + $template = 'theme:' . $this->_getThemeName($theme_id) . '/' . $template; + $template_file = $this->Application->TemplatesCache->GetRealFilename($template) . '.tpl'; + $template_data = file_get_contents($template_file); + + if (substr($template_data, 0, 6) == '*/ + + $comment_end = strpos($template_data, '##-->'); + if ($comment_end === false) { + // badly formatted comment + return Array (); + } + + $comment = trim( substr($template_data, 6, $comment_end - 6) ); + if (preg_match_all('/<(NAME|DESC|SECTION)>(.*?)<\/(NAME|DESC|SECTION)>/is', $comment, $regs)) { + $ret = Array (); + foreach ($regs[1] as $param_order => $param_name) { + $ret[ strtolower($param_name) ] = trim($regs[2][$param_order]); + } + + if (array_key_exists('section', $ret) && $ret['section']) { + $category_path = explode('||', $ret['section']); + $category_path = array_map('trim', $category_path); + $ret['section'] = implode('||', $category_path); + } + + return $ret; + } + } + + return Array (); + } + + /** + * 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 OnResetMenuCache(&$event) + { + $this->Conn->Query('DELETE FROM '.TABLE_PREFIX.'Cache WHERE VarName IN ("cms_menu", "StructureTree")'); + } + + /** + * 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 ; + } + + $root_category = $this->Application->findModule('Name', 'Proj-CMS', 'RootCat'); + + // 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 . ')', + ); + + $this->Application->setUnitOption($event->Prefix, 'SectionAdjustments', $section_ajustments); + + // prepare structure dropdown + $sql = 'SELECT Data + FROM ' . TABLE_PREFIX . 'Cache + WHERE VarName = "StructureTree"'; + $data = $this->Conn->GetOne($sql); + if ($data) { + $data = unserialize($data); + } + else { + $data = $this->_getChildren($event, $root_category); + + $fields_hash = Array ( + 'VarName' => 'StructureTree', + 'Data' => serialize($data), + 'Cached' => adodb_mktime(), + ); + + $this->Conn->doInsert($fields_hash, TABLE_PREFIX.'Cache', 'REPLACE'); + } + + + $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); + $theme_id = $this->_getCurrentThemeId(); + $fields['ParentId']['default'] = $this->Application->GetVar('m_cat_id'); + $fields['ParentId']['options'] = $this->_printChildren($data, $root_category, $this->Application->GetVar('m_lang'), $theme_id); + + // limit design list by theme + $design_folders = Array ('tf.FilePath = "/designs"', 'tf.FilePath = "/platform/designs"', 'tf.FilePath = "/in-edit/designs"'); + foreach ($this->Application->ModuleInfo as $module_name => $module_info) { + $design_folders[] = 'tf.FilePath = "/' . $module_info['TemplatePath'] . 'designs"'; + } + $design_folders = array_unique($design_folders); + + $design_sql = $fields['Template']['options_sql']; + $design_sql = str_replace('(tf.FilePath = "/designs")', '(' . implode(' OR ', $design_folders) . ')', $design_sql); + $design_sql .= ' AND (t.ThemeId = ' . $theme_id . ')'; + $fields['Template']['options_sql'] = $design_sql; + + $this->Application->setUnitOption($event->Prefix, 'Fields', $fields); + } + + 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 (); + } + + $ret = Array($parent_category_id => str_repeat('-', $level).' '.$data['l'.$language_id.'_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 kEvent $event + * @param int $parent_category_id + * @param int $language_count + */ + function _getChildren(&$event, $parent_category_id) + { + static $language_count = null; + if (!isset($language_count)) { + $ml_helper =& $this->Application->recallObject('kMultiLanguageHelper'); + /* @var $ml_helper kMultiLanguageHelper */ + + $language_count = $ml_helper->getLanguageCount(); + } + + $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); + $table_name = $this->Application->getUnitOption($event->Prefix, 'TableName'); + + // get category children + parent category + $sql = 'SELECT * + FROM '.$table_name.' + WHERE ParentId = '.$parent_category_id.' OR '.$id_field.' = '.$parent_category_id.' + ORDER BY Priority DESC'; + $categories = $this->Conn->Query($sql, $id_field); + + $parent_data = $categories[$parent_category_id]; + unset($categories[$parent_category_id]); + + // no children for this category + $data = Array ('id' => $parent_data[$id_field], 'children' => false, 'ThemeId' => $parent_data['ThemeId']); + for ($i = 1; $i <= $language_count; $i++) { + $data['l'.$i.'_Name'] = $parent_data['l'.$i.'_Name']; + } + + if (!$categories) { + // no children + return $data; + } + + // category has children + foreach ($categories as $category_id => $category_data) { + $data['children'][$category_id] = $this->_getChildren($event, $category_id); + } + return $data; + } + + /** + * Removes this item and it's children (recursive) from structure dropdown + * + * @param kEvent $event + */ + function OnAfterItemLoad(&$event) + { + parent::OnAfterItemLoad($event); + + if (!$this->Application->IsAdmin()) { + 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')); + } + + /** + * Enter description here... + * + * @param kEvent $event + */ + function OnAfterRebuildThemes(&$event) + { + $sql = 'SELECT t.ThemeId, CONCAT( tf.FilePath, \'/\', tf.FileName ) AS Path + 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 + WHERE CONCAT(\'/\', ' . TABLE_PREFIX . 'Category.Template, \'.tpl\') = CONCAT( tf.FilePath, \'/\', tf.FileName ) + ) = 0 '; + $files = $this->Conn->GetCol($sql, 'Path'); + if (!$files) { + // all possible pages are already created + return ; + } + + $dummy =& $this->Application->recallObject($event->Prefix . '.-dummy', null, Array ('skip_autoload' => true)); + /* @var $dummy kDBItem */ + + $error_count = 0; + foreach ($files as $a_file => $theme_id) { + $status = $this->_prepareAutoPage($dummy, $a_file, $theme_id, SMS_MODE_FORCE); // create system page + if (!$status) { + $error_count++; + } + } + + if ($this->Application->ConfigValue('QuickCategoryPermissionRebuild')) { + $updater =& $this->Application->recallObject('kPermCacheUpdater'); + /* @var $updater kPermCacheUpdater */ + + $updater->OneStepRun(); + } + + $event->CallSubEvent('OnResetMenuCache'); + + 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) + { + $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); + } + + /** + * Completely recalculates priorities in current category + * + * @param kEvent $event + */ + function OnRecalculatePriorities(&$event) + { + $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); + } + + /** + * Update Preview Block for FCKEditor + * + * @param kEvent $event + */ + function OnUpdatePreviewBlock(&$event) + { + $event->status = erSTOP; + $string = unhtmlentities($this->Application->GetVar('preview_content')); + + $this->Application->StoreVar('_editor_preview_content_', $string); + } + + /** + * Saves changed template block + * + * @param kEvent $event + */ + function OnSaveBlock(&$event) + { + if (!EDITING_MODE) { + $event->status = erSTOP; + echo '0'; + return ; + } + + $template_helper =& $this->Application->recallObject('TemplateHelper'); + /* @var $template_helper TemplateHelper */ + + $event->status = $template_helper->saveBlock($event) ? erSTOP : erFAIL; + } } ?> \ No newline at end of file