* @license PHP License * @package WB * @subpackage content */ /** * Load classes */ WBClass::load('WBContent' , 'patI18n' . 'WBCache'); /** * Content component: Menu Maker * * Display and maintain site menu. * * @version 1.1.2 * @package WB * @subpackage content */ class WBContent_MenuMaker extends WBContent { /** * my parameter list * * - action tell class what shall be done * - menu main menu id * - tmpl name of template file to load for displaying menus * - requiredgroup * - treemenu tree menu sub class, if any * - children list of automated child trees * * @var array */ protected $config = array( 'action' => 'display', 'menu' => '', 'tmpl' => 'default', 'requiredgroup' => 'menumaker', 'treemenu' => '', 'children' => array() ); /** * Tree datasource object * @var WBDatasource_Tree */ private $tree; /** * Tree datasource object * @var WBDatasource_TreeMenu */ private $treeMenu; /** * selected node * @var array */ private $node = array(); /** * current root node * @var array */ private $root = array(); /** * root menu items * @var array */ private $ri = array(); /** * file to flush menu cache * @var string */ private $cacheFlushFile = '%s/var/tmp/menumakercache'; /** * run * * run component * * @return array parameter list */ public function run() { // setup cache flush file $this->cacheFlushFile = sprintf($this->cacheFlushFile, WBParam::get('wb/dir/base')); if (!file_exists($this->cacheFlushFile)) { touch($this->cacheFlushFile); chmod($this->cacheFlushFile, 0666); } if ($this->config['action'] == 'display') { $treeMenu = 'WBDatasource_TreeMenu'; if (!empty($this->config['treemenu'])) { $treeMenu .= '_' . $this->config['treemenu']; } $this->treeMenu = WBClass::Create($treeMenu); $this->treeMenu->setRoot($this->config['menu']); $this->treeMenu->useChildTrees($this->config['children']); return $this->runDisplay(); } // use standard tree menu $params = array( 'treetable' => 'menu', 'alphalength' => 4, ); $this->tree = WBClass::create('WBDatasource_Tree', $params); // if not in display mode, don't translate anything $this->tree->switchTranslation(false); // group required if (!$this->isUserInGroup($this->config['requiredgroup'])) { $this->config['action'] = 'anonymous'; $this->loadTemplates('anon'); return $this->config; } $displayOverview = false; // verify root node $this->loadRoot(); if (empty($this->root) && 'overview' != $this->config['action']) { $this->config['action'] = 'overview'; } $this->tmpl->addGlobalVars($this->config); switch ($this->config['action']) { case 'flushcache': touch($this->cacheFlushFile); $this->loadNode(); $this->loadTemplates('list'); break; case 'edit': $this->loadNode(); $this->processForm('edit', $this->node); break; case 'add': $this->loadNode(); $this->processForm('edit', array()); break; case 'rm': $this->loadNode(); if ('yes' == $this->req->get('force', 'no')) { $parent = $this->tree->getParent($this->req->get('id')); $this->tree->delete($this->req->get('id')); $this->loadTemplates('list'); $this->loadNode($parent); break; } $this->loadTemplates('rm'); break; case 'list': $this->loadNode(); $this->loadTemplates('list'); break; case 'movetop': case 'moveup': case 'movedown': case 'movebottom': case 'moverollup': case 'moverolldown': $this->loadNode(); $this->moveNode(substr($this->config['action'], 4)); $this->loadTemplates('list'); break; case 'rootrm': if ('yes' == $this->req->get('force', 'no')) { $this->tree->delete($this->config['menu']); $this->config['menu'] = ''; $displayOverview = true; break; } $this->loadTemplates('rootrm'); break; default: case 'overview': $displayOverview = true; break; } if ($displayOverview) { // process root $this->processForm('root', array(), 'saveroot'); $this->loadTemplates('overview'); } $this->displayMenu(); return $this->config; } /** * receive output * * fetch output of this content component * * @return string */ public function getString() { return $this->tmpl->getParsedTemplate('snippet'); } /** * display root and selected menu * */ private function displayMenu() { $options = array( 'order' => array( array('field' => 'menu') ) ); $this->ri = $this->tree->getChildren(null, 1, $options); $this->tmpl->addGlobalVar('menu_root_count', count($this->ri)); $this->tmpl->addGlobalVar('menu_root', $this->config['menu']); // add root records if ($this->tmpl->exists('root_list')) { $this->tmpl->addRows('root_list_entry', $this->ri); } // selected menu item $id = $this->req->get('id', ''); if (empty($id)) { $id = $this->config['menu']; } try{ $node = $this->tree->get($id); } catch (WBException_Datasource $e) { $node = array(); } // add list of children $children = $this->tree->getChildren($id); $this->tmpl->addGlobalVar('list_count', count($children)); if ($this->tmpl->exists('list_entry')) { $this->tmpl->addRows('list_entry', $children); } // add list of parent nodes $parents = $this->tree->getParent($id, 0); if (empty($parents)) { return; } $this->tmpl->addGlobalVar('parent_count', count($parents)); if ($this->tmpl->exists('parent_entry')) { $this->tmpl->addRows('parent_entry', $parents); } } /** * 2nd run method to display actual menu item. * * Select menu item according to current URL and display. * * @return array */ protected function runDisplay() { $urlId = $this->getUrlId(); WBClass::load('WBCache'); $cacheId = $this->getCacheId($urlId); $menu = WBCache::get($cacheId); if (!empty($menu)) { $this->loadTemplates('display/' . $this->config['tmpl']); $this->tmpl->addGlobalVar('menu', $menu); return $this->config; } $current = $this->treeMenu->getMenuCurrent($urlId); $current['current'] = 'yes'; $parents = $this->treeMenu->getMenuParents($current); // load templates $this->loadTemplates('display/' . $this->config['tmpl']); // build menu HTML string $menu = ''; $cacheable = true; for ($i = count($parents) - 1; $i >= 0; --$i) { $current['level'] = $i; // either use levelX and levelX_item or leveldefault and leveldefault_item $no = $i; if (!$this->tmpl->exists('level' . $no)) { $no = 'default'; } // child records $children = $this->treeMenu->getMenuChildren($parents[$i]); if (empty($children)) { $current = $parents[$i]; continue; } // apply authorisation settings foreach ($children as &$child) { if ($child['urlid'] == $urlId || $child['id'] == $current['id']) { $child['status'] = 'selected'; } // check group permissions if (!empty($child['requiregroup'])) { $cacheable = false; if (!$this->isUserInGroup($child['requiregroup'], true)) { $child['status'] = 'grouprequired'; } continue; } // check user login permissions switch ($child['requireuser']) { case 'user': $cacheable = false; if (!$this->user->isAuthenticated()) { $child['status'] = 'userrequired'; } break; case 'anonymous': $cacheable = false; if ($this->user->isAuthenticated()) { $child['status'] = 'userdenied'; } break; case 'anybody': default: break; } } // add stuff to template ... $this->tmpl->addVar('level' . $no, 'children', $menu); $this->tmpl->addVars('level' . $no, $current); $this->tmpl->addRows('level' . $no . '_item', $children); // ... render template and prepare for next step - one level up. $menu = $this->tmpl->getParsedTemplate('level' . $no); $this->tmpl->clearTemplate('level' . $no); $this->tmpl->clearTemplate('level' . $no . '_item'); $current = $parents[$i]; } $this->tmpl->addGlobalVar('menu', trim($menu)); if ($cacheable) { $expire = array( 'filemtime' => $this->cacheFlushFile ); WBCache::set($cacheId, $menu, $expire); } return $this->config; } /** * Get cache id for current menu item * * @param string $urlid * @return string $cacheId */ private function getCacheId($urlid) { $params = array( 'treemenu' => $this->config['treemenu'], 'menu' => $this->config['menu'], 'tmplDir' => $this->config['tmplDir'], 'tmpl' => $this->config['tmpl'], 'lang' => patI18n::getLocale(patI18n::LOCALE_TYPE_LANG), 'urlid' => $urlid ); $this->treeMenu->addCacheIdParams($params); $id = array(__CLASS__); foreach ($params as $k => $v) { $id[] = $k . '-' . $v; } return implode('_', $id); } /** * Get URL id of current page * * Auxilliary method, can be overwritten * * @return string $urlid */ private function getUrlId() { return $this->treeMenu->getUrlId(rtrim($this->req->getMeta('PHP_SELF'), '/')); } /** * move node's position * * Calculate new position for each node and save it. This method always * saves all the siblings, regardless whether their position is unchanged. * * Direction can be one of: up, down, top, bottom, rollup and rolldown * * @param string $dir move direction */ private function moveNode($dir) { // swap node with parent $node = $this->node; $tmp = $this->tree->getParent($node['id']); $this->loadNode($tmp[0]); $siblings = $this->tree->getChildren($this->node['id']); // find current position $pos = 0; foreach ($siblings as $sib) { if ($sib['id'] == $node['id']) { break; } ++$pos; } $top = array_slice($siblings, 0, $pos); $bot = array_slice($siblings, $pos + 1); switch ($dir) { case 'up': if (empty($top)) { return; } array_unshift($bot, array_pop($top)); $top[] = $node; break; case 'down': if (empty($bot)) { return; } $top[] = array_shift($bot); $top[] = $node; break; case 'top': if (empty($top)) { return; } array_unshift($top, $node); break; case 'bottom': if (empty($bot)) { return; } $bot[] = $node; break; case 'rollup': $top[] = $node; $bot[] = array_shift($top); break; case 'rolldown': $top[] = $node; array_unshift($top, array_pop($bot)); break; default: return; break; } $pos = 0; foreach ($top as $sib) { $this->tree->set($sib['id'], array('position' => $pos++)); } foreach ($bot as $sib) { $this->tree->set($sib['id'], array('position' => $pos++)); } } /** * store menu record * * Menu record is valid, sava either as new child or override existing one. * * @param patForms $form * @param array $values * @return bool whether to continue with template engine */ protected function onEditValid($form, $values) { switch ($this->config['action']) { case 'add': $parent = $this->config['menu']; if (!empty($this->node)) { $parent = $this->node['id']; } // count siblings to get new position $siblings = $this->tree->getChildren($parent); $values['position'] = count($siblings); $this->tree->addChild($parent, $values); break; case 'edit': $this->tree->set($this->node['id'], $values); break; default: break; } $this->loadTemplates('list'); return false; } /** * save new menu record * * * * @param patForms $form * @param array $values * @return bool whether to continue with template engine */ protected function onRootValid($form, $values) { $data = array( 'menu' => $values['menuname'] ); $this->tree->addChild(null, $data); // force to render again $this->req->set('saveroot', null); $this->processForm('root', array(), 'saveroot'); return false; } /** * Fetch list of form elements * * Load form element definition using parent class and add project ids * * @param string name of the xml- and template-filename * @return array $elements */ protected function getFormElementList($name) { $list = parent::getFormElementList($name); return $list; } /** * location of form config * * Return sub directory where form element definitions are located * * @return string folder */ protected function getFormConfigDir() { return 'menumaker/form'; } /** * load root menu node * */ private function loadRoot() { $this->root = array(); if (empty($this->config['menu'])) { return; } try{ $this->root = $this->tree->get($this->config['menu']); } catch (WBException_Datasource $e) { $this->node = array(); } if (empty($this->root) || 4 != strlen($this->root['path'])) { $this->node = array(); } $this->tmpl->addGlobalVars($this->root, 'menu_'); } /** * load menu item * * Load node from tree table or simply inject raw data * * @param array raw data */ private function loadNode($raw = null) { if (!empty($raw)) { $this->node = $raw; $this->req->set('id', $raw['id']); $this->tmpl->addGlobalVars($this->node, 'node_'); return; } // selected menu item $id = $this->req->get('id', ''); $this->node = array(); if(empty($id)) { return; } try{ $this->node = $this->tree->get($id); } catch (WBException_Datasource $e) { $this->node = array(); } $this->tmpl->addGlobalVars($this->node, 'node_'); } } ?>