diff --git a/administrator/components/com_admin/script.php b/administrator/components/com_admin/script.php index 2a686396116a0..b1bb47099692f 100644 --- a/administrator/components/com_admin/script.php +++ b/administrator/components/com_admin/script.php @@ -1907,6 +1907,8 @@ public function deleteUnexistingFiles() '/libraries/legacy/view/legacy.php', '/libraries/legacy/web/client.php', '/libraries/legacy/web/web.php', + '/administrator/modules/mod_menu/preset/enabled.php', + '/administrator/modules/mod_menu/preset/disabled.php', ); // TODO There is an issue while deleting folders using the ftp mode @@ -2129,6 +2131,7 @@ public function deleteUnexistingFiles() '/libraries/legacy/model', '/libraries/legacy/view', '/libraries/legacy/web', + '/administrator/modules/mod_menu/preset', ); jimport('joomla.filesystem.file'); diff --git a/administrator/components/com_admin/views/help/tmpl/langforum.php b/administrator/components/com_admin/views/help/tmpl/langforum.php new file mode 100644 index 0000000000000..f657d816df8be --- /dev/null +++ b/administrator/components/com_admin/views/help/tmpl/langforum.php @@ -0,0 +1,15 @@ +redirect($forum_url); diff --git a/administrator/components/com_menus/controllers/menu.php b/administrator/components/com_menus/controllers/menu.php index 3f4ccf1b20a1b..9115aae674324 100644 --- a/administrator/components/com_menus/controllers/menu.php +++ b/administrator/components/com_menus/controllers/menu.php @@ -78,10 +78,10 @@ public function save($key = null, $urlVar = null) return false; } - $data = $model->validate($form, $data); + $validData = $model->validate($form, $data); // Check for validation errors. - if ($data === false) + if ($validData === false) { // Get the validation messages. $errors = $model->getErrors(); @@ -98,8 +98,9 @@ public function save($key = null, $urlVar = null) $app->enqueueMessage($errors[$i], 'warning'); } } + // Save the data in the session. - $app->setUserState('com_menus.edit.menu.data', $data); + $app->setUserState($context . '.data', $data); // Redirect back to the edit screen. $this->setRedirect(JRoute::_('index.php?option=com_menus&view=menu&layout=edit', false)); @@ -107,11 +108,18 @@ public function save($key = null, $urlVar = null) return false; } + if (isset($validData['preset'])) + { + $preset = trim($validData['preset']) ?: null; + + unset($validData['preset']); + } + // Attempt to save the data. - if (!$model->save($data)) + if (!$model->save($validData)) { // Save the data in the session. - $app->setUserState('com_menus.edit.menu.data', $data); + $app->setUserState($context . '.data', $validData); // Redirect back to the edit screen. $this->setMessage(JText::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()), 'error'); @@ -120,7 +128,25 @@ public function save($key = null, $urlVar = null) return false; } - $this->setMessage(JText::_('COM_MENUS_MENU_SAVE_SUCCESS')); + // Import the preset selected + if (isset($preset) && $data['client_id'] == 1) + { + try + { + MenusHelper::installPreset($preset, $data['menutype']); + + $this->setMessage(JText::_('COM_MENUS_PRESET_IMPORT_SUCCESS')); + } + catch (Exception $e) + { + // Save was successful but the preset could not be loaded. Let it through with just a warning + $this->setMessage(JText::sprintf('COM_MENUS_PRESET_IMPORT_FAILED', $e->getMessage())); + } + } + else + { + $this->setMessage(JText::_('COM_MENUS_MENU_SAVE_SUCCESS')); + } // Redirect the user and adjust session state based on the chosen task. switch ($task) @@ -153,4 +179,34 @@ public function save($key = null, $urlVar = null) break; } } + + /** + * Method to display a menu as preset xml. + * + * @return boolean True if successful, false otherwise. + * + * @since __DEPLOY_VERSION__ + */ + public function exportXml() + { + // Check for request forgeries. + $this->checkToken(); + + $cid = $this->input->get('cid', array(), 'array'); + $model = $this->getModel('Menu'); + $item = $model->getItem(reset($cid)); + + if (!$item->menutype) + { + $this->setMessage(JText::_('COM_MENUS_SELECT_MENU_FIRST_EXPORT'), 'warning'); + + $this->setRedirect(JRoute::_('index.php?option=com_menus&view=menus', false)); + + return false; + } + + $this->setRedirect(JRoute::_('index.php?option=com_menus&view=menu&menutype=' . $item->menutype . '&format=xml', false)); + + return true; + } } diff --git a/administrator/components/com_menus/helpers/menus.php b/administrator/components/com_menus/helpers/menus.php index c255900616ddb..f2be9931171af 100644 --- a/administrator/components/com_menus/helpers/menus.php +++ b/administrator/components/com_menus/helpers/menus.php @@ -7,6 +7,10 @@ * @license GNU General Public License version 2 or later; see LICENSE.txt */ +use Joomla\CMS\Menu\MenuHelper; +use Joomla\Registry\Registry; +use Joomla\Utilities\ArrayHelper; + defined('_JEXEC') or die; /** @@ -18,6 +22,8 @@ class MenusHelper { /** * Defines the valid request variables for the reverse lookup. + * + * @since 1.6 */ protected static $_filter = array('option', 'view', 'layout'); @@ -321,4 +327,228 @@ public static function getAssociations($pk) return $associations; } + + /** + * Load the menu items from database for the given menutype + * + * @param string $menutype The selected menu type + * @param boolean $enabledOnly Whether to load only enabled/published menu items. + * @param int[] $exclude The menu items to exclude from the list + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public static function getMenuItems($menutype, $enabledOnly = false, $exclude = array()) + { + $db = JFactory::getDbo(); + $query = $db->getQuery(true); + + // Prepare the query. + $query->select('m.*') + ->from('#__menu AS m') + ->where('m.menutype = ' . $db->q($menutype)) + ->where('m.client_id = 1') + ->where('m.id > 1'); + + if ($enabledOnly) + { + $query->where('m.published = 1'); + } + + // Filter on the enabled states. + $query->select('e.element') + ->join('LEFT', '#__extensions AS e ON m.component_id = e.extension_id') + ->where('(e.enabled = 1 OR e.enabled IS NULL)'); + + if (count($exclude)) + { + $exId = array_filter($exclude, 'is_numeric'); + $exEl = array_filter($exclude, 'is_string'); + + if ($exId) + { + $query->where('m.id NOT IN (' . implode(', ', array_map('intval', $exId)) . ')'); + $query->where('m.parent_id NOT IN (' . implode(', ', array_map('intval', $exId)) . ')'); + } + + if ($exEl) + { + $query->where('e.element NOT IN (' . implode(', ', $db->quote($exEl)) . ')'); + } + } + + // Order by lft. + $query->order('m.lft'); + + $db->setQuery($query); + + try + { + $menuItems = $db->loadObjectList(); + + foreach ($menuItems as &$menuitem) + { + $menuitem->params = new Registry($menuitem->params); + } + } + catch (RuntimeException $e) + { + $menuItems = array(); + + JFactory::getApplication()->enqueueMessage(JText::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error'); + } + + return $menuItems; + } + + /** + * Method to install a preset menu into database and link them to the given menutype + * + * @param string $preset The preset name + * @param string $menutype The target menutype + * + * @return void + * + * @throws Exception + * + * @since __DEPLOY_VERSION__ + */ + public static function installPreset($preset, $menutype) + { + $items = MenuHelper::loadPreset($preset, false); + + if (count($items) == 0) + { + throw new Exception(JText::_('COM_MENUS_PRESET_LOAD_FAILED')); + } + + static::installPresetItems($items, $menutype, 1); + } + + /** + * Method to install a preset menu item into database and link it to the given menutype + * + * @param stdClass[] &$items The single menuitem instance with a list of its descendants + * @param string $menutype The target menutype + * @param int $parent The parent id or object + * + * @return void + * + * @throws Exception + * + * @since __DEPLOY_VERSION__ + */ + protected static function installPresetItems(&$items, $menutype, $parent = 1) + { + $db = JFactory::getDbo(); + $query = $db->getQuery(true); + + static $components = array(); + + if (!$components) + { + $query->select('extension_id, element')->from('#__extensions')->where('type = ' . $db->q('component')); + $components = $db->setQuery($query)->loadObjectList(); + $components = ArrayHelper::getColumn((array) $components, 'element', 'extension_id'); + } + + foreach ($items as &$item) + { + /** @var JTableMenu $table */ + $table = JTable::getInstance('Menu'); + + $item->alias = $menutype . '-' . $item->title; + + if ($item->type == 'separator') + { + // Do not reuse a separator + $item->title = $item->title ?: '-'; + $item->alias = microtime(true); + } + elseif ($item->type == 'heading' || $item->type == 'container') + { + // Try to match an existing record to have minimum collision for a heading + $keys = array( + 'menutype' => $menutype, + 'type' => $item->type, + 'title' => $item->title, + 'parent_id' => $parent, + 'client_id' => 1, + ); + $table->load($keys); + } + elseif ($item->type == 'url' || $item->type == 'component') + { + // Try to match an existing record to have minimum collision for a link + $keys = array( + 'menutype' => $menutype, + 'type' => $item->type, + 'link' => $item->link, + 'parent_id' => $parent, + 'client_id' => 1, + ); + $table->load($keys); + } + + // Translate "hideitems" param value from "element" into "menu-item-id" + if ($item->type == 'container' && count($hideitems = (array) $item->params->get('hideitems'))) + { + foreach ($hideitems as &$hel) + { + if (!is_numeric($hel)) + { + $hel = array_search($hel, $components); + } + } + + $query->clear()->select('id')->from('#__menu')->where('component_id IN (' . implode(', ', $hideitems) . ')'); + $hideitems = $db->setQuery($query)->loadColumn(); + + $item->params->set('hideitems', $hideitems); + } + + $record = array( + 'menutype' => $menutype, + 'title' => $item->title, + 'alias' => $item->alias, + 'type' => $item->type, + 'link' => $item->link, + 'browserNav' => $item->browserNav ? 1 : 0, + 'img' => $item->class, + 'access' => $item->access, + 'component_id' => array_search($item->element, $components), + 'parent_id' => $parent, + 'client_id' => 1, + 'published' => 1, + 'language' => '*', + 'home' => 0, + 'params' => (string) $item->params, + ); + + if (!$table->bind($record)) + { + throw new Exception('Bind failed: ' . $table->getError()); + } + + $table->setLocation($parent, 'last-child'); + + if (!$table->check()) + { + throw new Exception('Check failed: ' . $table->getError()); + } + + if (!$table->store()) + { + throw new Exception('Saved failed: ' . $table->getError()); + } + + $item->id = $table->get('id'); + + if (!empty($item->submenu)) + { + static::installPresetItems($item->submenu, $menutype, $item->id); + } + } + } } diff --git a/administrator/components/com_menus/menus.xml b/administrator/components/com_menus/menus.xml index 285432982d620..ed40066654c09 100644 --- a/administrator/components/com_menus/menus.xml +++ b/administrator/components/com_menus/menus.xml @@ -18,6 +18,7 @@ helpers models views + presets language/en-GB.com_menus.ini diff --git a/administrator/components/com_menus/models/fields/menupreset.php b/administrator/components/com_menus/models/fields/menupreset.php new file mode 100644 index 0000000000000..9992bf001f6b0 --- /dev/null +++ b/administrator/components/com_menus/models/fields/menupreset.php @@ -0,0 +1,51 @@ +name, JText::_($preset->title)); + } + + return array_merge(parent::getOptions(), $options); + } +} diff --git a/administrator/components/com_menus/models/forms/menu.xml b/administrator/components/com_menus/models/forms/menu.xml index 7db713f400f60..683e79fa6dc69 100644 --- a/administrator/components/com_menus/models/forms/menu.xml +++ b/administrator/components/com_menus/models/forms/menu.xml @@ -56,6 +56,15 @@ + + + getItem(); } + else + { + unset($data['preset']); + } $this->preprocessData('com_menus.menu', $data); diff --git a/administrator/components/com_menus/presets/joomla.xml b/administrator/components/com_menus/presets/joomla.xml new file mode 100644 index 0000000000000..10b93ad3a3a49 --- /dev/null +++ b/administrator/components/com_menus/presets/joomla.xml @@ -0,0 +1,558 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/administrator/components/com_menus/presets/menu.xsd b/administrator/components/com_menus/presets/menu.xsd new file mode 100644 index 0000000000000..6338b8db992c8 --- /dev/null +++ b/administrator/components/com_menus/presets/menu.xsd @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/administrator/components/com_menus/presets/modern.xml b/administrator/components/com_menus/presets/modern.xml new file mode 100644 index 0000000000000..3c56af59b54af --- /dev/null +++ b/administrator/components/com_menus/presets/modern.xml @@ -0,0 +1,585 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/administrator/components/com_menus/views/menu/tmpl/edit.php b/administrator/components/com_menus/views/menu/tmpl/edit.php index 80d88e941df71..fee122017ff74 100644 --- a/administrator/components/com_menus/views/menu/tmpl/edit.php +++ b/administrator/components/com_menus/views/menu/tmpl/edit.php @@ -37,30 +37,17 @@ 'details')); ?> -
-
- form->getLabel('menutype'); ?> -
-
- form->getInput('menutype'); ?> -
-
-
-
- form->getLabel('description'); ?> -
-
- form->getInput('description'); ?> -
-
-
-
- form->getLabel('client_id'); ?> -
-
- form->getInput('client_id'); ?> -
-
+ + form->renderField('menutype'); + + echo $this->form->renderField('description'); + + echo $this->form->renderField('client_id'); + + echo $this->form->renderField('preset'); + ?> + canDo->get('core.admin')) : ?> @@ -73,6 +60,5 @@ - diff --git a/administrator/components/com_menus/views/menu/view.xml.php b/administrator/components/com_menus/views/menu/view.xml.php new file mode 100644 index 0000000000000..6701d75af38a1 --- /dev/null +++ b/administrator/components/com_menus/views/menu/view.xml.php @@ -0,0 +1,170 @@ +input->getCmd('menutype'); + + if ($menutype) + { + $items = MenusHelper::getMenuItems($menutype, true); + } + + if (empty($items)) + { + JLog::add(JText::_('COM_MENUS_SELECT_MENU_FIRST_EXPORT'), JLog::WARNING, 'jerror'); + + $app->redirect(JRoute::_('index.php?option=com_menus&view=menus', false)); + + return; + } + + $this->items = MenuHelper::createLevels($items); + + $xml = new SimpleXMLElement('' + ); + + foreach ($this->items as $item) + { + $this->addXmlChild($xml, $item); + } + + if (headers_sent($file, $line)) + { + JLog::add("Headers already sent at $file:$line.", JLog::ERROR, 'jerror'); + + return; + } + + header('content-type: application/xml'); + header('content-disposition: attachment; filename="' . $menutype . '.xml"'); + header("Cache-Control: no-cache, must-revalidate"); + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + header('Pragma: private'); + + $dom = new DOMDocument; + $dom->preserveWhiteSpace = true; + $dom->formatOutput = true; + $dom->loadXML($xml->asXML()); + + echo $dom->saveXML(); + + $app->close(); + } + + /** + * Add a child node to the xml + * + * @param SimpleXMLElement $xml The current XML node which would become the parent to the new node + * @param stdClass $item The menuitem object to create the child XML node from + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function addXmlChild($xml, $item) + { + $node = $xml->addChild('menuitem'); + + $node['type'] = $item->type; + + if ($item->title) + { + $node['title'] = $item->title; + } + + if ($item->link) + { + $node['link'] = $item->link; + } + + if ($item->element) + { + $node['element'] = $item->element; + } + + if ($item->class) + { + $node['class'] = $item->class; + } + + if ($item->access) + { + $node['access'] = $item->access; + } + + if ($item->browserNav) + { + $node['target'] = '_blank'; + } + + if (count($item->params)) + { + $hideitems = $item->params->get('hideitems'); + + if (count($hideitems)) + { + $db = JFactory::getDbo(); + $query = $db->getQuery(true); + + $query->select('e.element')->from('#__extensions e') + ->join('inner', '#__menu m ON m.component_id = e.extension_id') + ->where('m.id IN (' . implode(', ', $db->quote($hideitems)) . ')'); + + $hideitems = $db->setQuery($query)->loadColumn(); + + $item->params->set('hideitems', $hideitems); + } + + $node->addChild('params', (string) $item->params); + } + + foreach ($item->submenu as $sub) + { + $this->addXmlChild($node, $sub); + } + } +} diff --git a/administrator/components/com_menus/views/menus/tmpl/default.php b/administrator/components/com_menus/views/menus/tmpl/default.php index 49b8e661ba9ca..3fcd852f176ee 100644 --- a/administrator/components/com_menus/views/menus/tmpl/default.php +++ b/administrator/components/com_menus/views/menus/tmpl/default.php @@ -41,6 +41,16 @@ $script[] = ' },1000);'; $script[] = ' });'; $script[] = '});'; +$script[] = ' + (function (originalFn) { + Joomla.submitform = function(task, form) { + originalFn(task, form); + if (task == "menu.exportXml") { + document.adminForm.task.value = ""; + } + }; + })(Joomla.submitform); +'; JFactory::getDocument()->addScriptDeclaration(implode("\n", $script)); ?> diff --git a/administrator/components/com_menus/views/menus/view.html.php b/administrator/components/com_menus/views/menus/view.html.php index 39b74c1ab86a1..4bc2505c440ac 100644 --- a/administrator/components/com_menus/views/menus/view.html.php +++ b/administrator/components/com_menus/views/menus/view.html.php @@ -102,6 +102,11 @@ protected function addToolbar() JToolbarHelper::custom('menus.rebuild', 'refresh.png', 'refresh_f2.png', 'JTOOLBAR_REBUILD', false); + if ($canDo->get('core.admin') && $this->state->get('client_id') == 1) + { + JToolbarHelper::custom('menu.exportXml', 'download', 'download', 'COM_MENUS_MENU_EXPORT_BUTTON', true); + } + if ($canDo->get('core.admin') || $canDo->get('core.options')) { JToolbarHelper::divider(); diff --git a/administrator/language/en-GB/en-GB.com_admin.ini b/administrator/language/en-GB/en-GB.com_admin.ini index 09c48dddd04ea..cf893d048b7a8 100644 --- a/administrator/language/en-GB/en-GB.com_admin.ini +++ b/administrator/language/en-GB/en-GB.com_admin.ini @@ -99,6 +99,7 @@ COM_ADMIN_HELP_SITE_MAINTENANCE_GLOBAL_CHECK-IN="Global Check-in" COM_ADMIN_HELP_SITE_MAINTENANCE_PURGE_EXPIRED_CACHE="Cache: Clear Expired Cache" COM_ADMIN_HELP_SITE_SYSTEM_INFORMATION="System Information" COM_ADMIN_HELP_START_HERE="Start Here" +COM_ADMIN_HELP_SUPPORT_OFFICIAL_LANGUAGE_FORUM_VALUE="511" COM_ADMIN_HELP_USERS_ACCESS_LEVELS="Users: Access Levels" COM_ADMIN_HELP_USERS_ACCESS_LEVELS_EDIT="Users: Access Levels - New/Edit" COM_ADMIN_HELP_USERS_DEBUG_USER="Users: Debug Users Permissions" diff --git a/administrator/language/en-GB/en-GB.com_menus.ini b/administrator/language/en-GB/en-GB.com_menus.ini index 46bd9604113c4..1e3fb8220043f 100644 --- a/administrator/language/en-GB/en-GB.com_menus.ini +++ b/administrator/language/en-GB/en-GB.com_menus.ini @@ -32,6 +32,8 @@ COM_MENUS_EXTENSION_UNPUBLISHED_DISABLED="Component disabled and menu item unpub COM_MENUS_EXTENSION_UNPUBLISHED_ENABLED="Component enabled and menu item unpublished." COM_MENUS_FIELD_FEEDLINK_DESC="Display a feed link for this menu item." COM_MENUS_FIELD_FEEDLINK_LABEL="Feed link" +COM_MENUS_FIELD_PRESET_LABEL="Import a preset" +COM_MENUS_FIELD_PRESET_DESC="Select a preset if you want to populate this menu with the menu items in that preset. Otherwise leave this empty." COM_MENUS_FIELD_VALUE_IGNORE="Ignore" COM_MENUS_FIELD_VALUE_NEW_WITH_NAV="New Window With Navigation" COM_MENUS_FIELD_VALUE_NEW_WITHOUT_NAV="New Without Navigation" @@ -155,6 +157,7 @@ COM_MENUS_MENU_CLIENT_ID_DESC="Select if this Menu is to be used in the Site or COM_MENUS_MENU_CONFIRM_DELETE="Are you sure you want to delete these menus? Confirming will delete the selected menu types, all their menu items and the associated menu modules." COM_MENUS_MENU_DESCRIPTION_DESC="A description about the purpose of the menu." COM_MENUS_MENU_DETAILS="Menu Details" +COM_MENUS_MENU_EXPORT_BUTTON="Download as Preset" COM_MENUS_MENU_ITEM_SAVE_SUCCESS="Menu item saved." COM_MENUS_MENU_MENUTYPE_DESC="The system name of the menu." COM_MENUS_MENU_MENUTYPE_LABEL="Menu Type" @@ -192,12 +195,16 @@ COM_MENUS_NO_MENUS_SELECTED="No menu selected." COM_MENUS_OPTION_SELECT_COMPONENT="- Select Component -" COM_MENUS_OPTION_SELECT_LEVEL="- Select Max Levels -" COM_MENUS_PAGE_OPTIONS_LABEL="Page Display" +COM_MENUS_PRESET_IMPORT_SUCCESS="Menu was saved and the menu items was imported from the selected preset." +COM_MENUS_PRESET_IMPORT_FAILED="Menu was saved but failed to import the preset: %s" +COM_MENUS_PRESET_LOAD_FAILED="Failed to load the specified preset." COM_MENUS_REQUEST_FIELDSET_LABEL="Required Settings" COM_MENUS_SAVE_SUCCESS="Menu item saved." COM_MENUS_SELECT_A_MENUITEM="Select a Menu Item" COM_MENUS_SELECT_MENU="- Select Menu -" COM_MENUS_SELECT_MENU_FILTER_NOT_TRASHED="Filter the list by a state other than trashed or clear the filter." COM_MENUS_SELECT_MENU_FIRST="To use batch processing, please first select a Menu in the manager." +COM_MENUS_SELECT_MENU_FIRST_EXPORT="To use export, please first select a valid Menu in the manager." COM_MENUS_SUBMENU_ITEMS="Menu Items" COM_MENUS_SUBMENU_MENUS="Menus" COM_MENUS_SUCCESS_REORDERED="Menu item reordered." diff --git a/administrator/language/en-GB/en-GB.lib_joomla.ini b/administrator/language/en-GB/en-GB.lib_joomla.ini index 5aa2d69e3ff5c..234e304d16539 100644 --- a/administrator/language/en-GB/en-GB.lib_joomla.ini +++ b/administrator/language/en-GB/en-GB.lib_joomla.ini @@ -674,6 +674,9 @@ JLIB_MEDIA_ERROR_WARNINVALID_MIME="Invalid mime type detected." JLIB_MEDIA_ERROR_WARNINVALID_MIMETYPE="Illegal mime type detected: %s" JLIB_MEDIA_ERROR_WARNNOTADMIN="Uploaded file is not an image file and you do not have permission." +JLIB_MENUS_PRESET_JOOMLA="Preset - Joomla" +JLIB_MENUS_PRESET_MODERN="Preset - Modern" + JLIB_NO_EDITOR_PLUGIN_PUBLISHED="Unable to display an editor because no editor plugin is published." JLIB_PLUGIN_ERROR_LOADING_PLUGINS="Error loading Plugins: %s" diff --git a/administrator/language/en-GB/en-GB.mod_menu.ini b/administrator/language/en-GB/en-GB.mod_menu.ini index 90675c99d1264..e16ddf74f3595 100644 --- a/administrator/language/en-GB/en-GB.mod_menu.ini +++ b/administrator/language/en-GB/en-GB.mod_menu.ini @@ -42,7 +42,9 @@ MOD_MENU_FIELD_FORUMURL_DESC="Enter the URL to a forum other than the default." MOD_MENU_FIELD_FORUMURL_LABEL="Custom Support Forum" MOD_MENU_FIELD_MENUTYPE_LABEL="Menu to Show" MOD_MENU_FIELD_MENUTYPE_DESC="Choose which menu should be rendered with this instance of module." -MOD_MENU_FIELD_MENUTYPE_OPTION_PREDEFINED="Use System Preset" +MOD_MENU_FIELD_MENUTYPE_OPTION_PREDEFINED="Use a Preset" +MOD_MENU_FIELD_PRESET_LABEL="Choose Preset" +MOD_MENU_FIELD_PRESET_DESC="Choose a preset to use as the backend menu" MOD_MENU_FIELD_SHOWHELP="Help Menu" MOD_MENU_FIELD_SHOWHELP_DESC="Show or hide the Help menu which includes links to various joomla.org sites useful to users." MOD_MENU_FIELD_SHOWNEW="Add New Shortcuts" @@ -66,7 +68,6 @@ MOD_MENU_HELP_SUPPORT_OFFICIAL_FORUM="Official Support Forum" MOD_MENU_HELP_SUPPORT_CUSTOM_FORUM="Custom Support Forum" ; The string below will be used if MOD_MENU_HELP_SUPPORT_OFFICIAL_LANGUAGE_FORUM_VALUE has a value, i.e the # of the specific language forum in https://forum.joomla.org/. Use something like 'Official english forum'. MOD_MENU_HELP_SUPPORT_OFFICIAL_LANGUAGE_FORUM="Official Language Forums" -MOD_MENU_HELP_SUPPORT_OFFICIAL_LANGUAGE_FORUM_VALUE="511" MOD_MENU_HELP_TRANSLATIONS="Joomla! Translations" MOD_MENU_HELP_XCHANGE="Stack Exchange" MOD_MENU_HOME_DEFAULT="Home" diff --git a/administrator/modules/mod_menu/helper.php b/administrator/modules/mod_menu/helper.php index 4f62809cc6a00..28d6d527d0df2 100644 --- a/administrator/modules/mod_menu/helper.php +++ b/administrator/modules/mod_menu/helper.php @@ -9,9 +9,6 @@ defined('_JEXEC') or die; -use Joomla\Registry\Registry; -use Joomla\Utilities\ArrayHelper; - /** * Helper for mod_menu * @@ -28,7 +25,7 @@ abstract class ModMenuHelper */ public static function getMenus() { - $db = JFactory::getDbo(); + $db = JFactory::getDbo(); // Search for home menu and language if exists $subQuery = $db->getQuery(true) @@ -55,323 +52,9 @@ public static function getMenus() catch (RuntimeException $e) { $result = array(); - JFactory::getApplication()->enqueueMessage(JText::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error'); + JFactory::getApplication()->enqueueMessage(JText::sprintf('JERROR_LOADING_MENUS', $e->getMessage()), 'error'); } return $result; } - - /** - * Get a list of the authorised, non-special components to display in the components menu. - * - * @param boolean $authCheck An optional switch to turn off the auth check (to support custom layouts 'grey out' behaviour). - * @param boolean $enabledOnly Whether to load only enabled/published menu items. - * @param int[] $exclude The menu items to exclude from the list - * - * @return array A nest array of component objects and submenus - * - * @since 1.6 - */ - public static function getComponents($authCheck = true, $enabledOnly = false, $exclude = array()) - { - $lang = JFactory::getLanguage(); - $user = JFactory::getUser(); - $db = JFactory::getDbo(); - $query = $db->getQuery(true); - $result = array(); - - // Prepare the query. - $query->select('m.id, m.title, m.alias, m.link, m.parent_id, m.img, e.element, m.menutype') - ->from('#__menu AS m') - ->where('m.menutype = ' . $db->q('main')) - ->where('m.client_id = 1') - ->where('m.id > 1'); - - if ($enabledOnly) - { - $query->where('m.published = 1'); - } - - if (count($exclude)) - { - $query->where('m.id NOT IN (' . implode(', ', array_map('intval', $exclude)) . ')'); - $query->where('m.parent_id NOT IN (' . implode(', ', array_map('intval', $exclude)) . ')'); - } - - // Filter on the enabled states. - $query->join('INNER', '#__extensions AS e ON m.component_id = e.extension_id') - ->where('e.enabled = 1'); - - // Order by lft. - $query->order('m.lft'); - - $db->setQuery($query); - - // Component list - try - { - $components = $db->loadObjectList(); - } - catch (RuntimeException $e) - { - $components = array(); - JFactory::getApplication()->enqueueMessage(JText::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error'); - } - - // Parse the list of extensions. - foreach ($components as &$component) - { - // Trim the menu link. - $component->link = trim($component->link); - - if ($component->parent_id == 1) - { - // Only add this top level if it is authorised and enabled. - if ($authCheck == false || ($authCheck && $user->authorise('core.manage', $component->element))) - { - // Root level. - $result[$component->id] = $component; - - if (!isset($result[$component->id]->submenu)) - { - $result[$component->id]->submenu = array(); - } - - // If the root menu link is empty, add it in. - if (empty($component->link)) - { - $component->link = 'index.php?option=' . $component->element; - } - - if (!empty($component->element)) - { - // Load the core file then - // Load extension-local file. - $lang->load($component->element . '.sys', JPATH_BASE, null, false, true) - || $lang->load($component->element . '.sys', JPATH_ADMINISTRATOR . '/components/' . $component->element, null, false, true); - } - - $component->text = JText::_(strtoupper($component->title)); - } - } - // Sub-menu level. - // Add the submenu link if it is defined. - elseif (isset($result[$component->parent_id]) && isset($result[$component->parent_id]->submenu) && !empty($component->link)) - { - $component->text = JText::_(strtoupper($component->title)); - - $result[$component->parent_id]->submenu[] = &$component; - } - } - - return ArrayHelper::sortObjects($result, 'text', 1, false, true); - } - - /** - * Load the menu items from database for the given menutype - * - * @param string $menutype The selected menu type - * - * @return array - * - * @since 3.7.0 - */ - public static function getMenuItems($menutype) - { - $db = JFactory::getDbo(); - $query = $db->getQuery(true); - - // Prepare the query. - $query->select('m.*') - ->from('#__menu AS m') - ->where('m.menutype = ' . $db->q($menutype)) - ->where('m.client_id = 1') - ->where('m.published = 1') - ->where('m.id > 1'); - - // Filter on the enabled states. - $query->select('e.element') - ->join('LEFT', '#__extensions AS e ON m.component_id = e.extension_id') - ->where('(e.enabled = 1 OR e.enabled IS NULL)'); - - // Order by lft. - $query->order('m.lft'); - - $db->setQuery($query); - - // Component list - try - { - $menuItems = $db->loadObjectList(); - - foreach ($menuItems as &$menuitem) - { - $menuitem->params = new Registry($menuitem->params); - } - } - catch (RuntimeException $e) - { - $menuItems = array(); - JFactory::getApplication()->enqueueMessage(JText::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error'); - } - - return $menuItems; - } - - /** - * Parse the list of extensions. - * - * @param array $menuItems List of loaded components - * @param bool $authCheck An optional switch to turn off the auth check (to support custom layouts 'grey out' behaviour). - * - * @return array - * - * @since 3.7.0 - */ - public static function parseItems($menuItems, $authCheck = true) - { - $result = array(); - $user = JFactory::getUser(); - $lang = JFactory::getLanguage(); - $levels = $user->getAuthorisedViewLevels(); - - // Process each item - foreach ($menuItems as $i => &$menuitem) - { - /* - * Exclude item with menu item option set to exclude from menu modules - * Exclude item if the component is not authorised - * Exclude item if menu item set access level is not met - */ - if (($menuitem->params->get('menu_show', 1) == 0) - || ($menuitem->element && $authCheck && !$user->authorise('core.manage', $menuitem->element)) - || ($menuitem->access && !in_array($menuitem->access, $levels))) - { - continue; - } - - // Evaluate link url - switch ($menuitem->type) - { - case 'url': - case 'component': - $menuitem->link = trim($menuitem->link); - break; - case 'separator': - case 'heading': - case 'container': - $menuitem->link = '#'; - break; - case 'alias': - $aliasTo = $menuitem->params->get('aliasoptions'); - $menuitem->link = static::getLink($aliasTo); - break; - default: - } - - if ($menuitem->link == '') - { - continue; - } - - // Translate Menu item label, if needed - if (!empty($menuitem->element)) - { - $lang->load($menuitem->element . '.sys', JPATH_BASE, null, false, true) - || $lang->load($menuitem->element . '.sys', JPATH_ADMINISTRATOR . '/components/' . $menuitem->element, null, false, true); - } - - $menuitem->text = $lang->hasKey($menuitem->title) ? JText::_($menuitem->title) : $menuitem->title; - $menuitem->submenu = array(); - - $result[$menuitem->parent_id][$menuitem->id] = $menuitem; - } - - // Do an early exit if there are no top level menu items. - if (!isset($result[1])) - { - return array(); - } - - // Put the items under respective parent menu items. - foreach ($result as $parentId => &$mItems) - { - foreach ($mItems as &$mItem) - { - if (isset($result[$mItem->id])) - { - static::cleanup($result[$mItem->id]); - - $mItem->submenu = &$result[$mItem->id]; - } - } - } - - // Return only top level items - return $result[1]; - } - - /** - * Method to get a link to the aliased menu item - * - * @param int $menuId The record id of the referencing menu item - * - * @return string - * - * @since 3.7.0 - */ - protected static function getLink($menuId) - { - $table = JTable::getInstance('Menu'); - $table->load($menuId); - - // Look for an alias-to-alias - if ($table->type == 'alias') - { - $params = new Registry($table->params); - $aliasTo = $params->get('aliasoptions'); - - return static::getLink($aliasTo); - } - - return $table->link; - } - - /** - * Method to cleanup the menu items for repeated, leading or trailing separators in a given menu level - * - * @param array &$items The list of menu items in the selected level - * - * @return void - * - * @since 3.7.0 - */ - protected static function cleanup(&$items) - { - $b = true; - - foreach ($items as $k => &$item) - { - if ($item->type == 'separator') - { - if ($b) - { - $item = false; - } - - $b = true; - } - else - { - $b = false; - } - } - - if ($b) - { - $item = false; - } - - $items = array_filter($items); - } } diff --git a/administrator/modules/mod_menu/menu.php b/administrator/modules/mod_menu/menu.php index ca2095d9a1dc3..958df090eaa4e 100644 --- a/administrator/modules/mod_menu/menu.php +++ b/administrator/modules/mod_menu/menu.php @@ -9,6 +9,9 @@ defined('_JEXEC') or die; +use Joomla\CMS\Menu\Node; +use Joomla\CMS\Menu\Tree; +use Joomla\CMS\Menu\MenuHelper; use Joomla\Registry\Registry; use Joomla\Utilities\ArrayHelper; @@ -20,679 +23,385 @@ class JAdminCssMenu { /** - * CSS string to add to document head + * The Menu tree object * - * @var string - */ - protected $_css = null; - - /** - * Root node + * @var Tree * - * @var object + * @since __DEPLOY_VERSION__ */ - protected $_root = null; + protected $tree; /** - * Current working node + * The module options * - * @var object + * @var Registry + * + * @since _DEPLOY_VERSION__ */ - protected $_current = null; + protected $params; /** - * Constructor + * The menu bar state + * + * @var bool + * + * @since _DEPLOY_VERSION__ */ - public function __construct() - { - $this->_root = new JMenuNode('ROOT'); - $this->_current = &$this->_root; - } + protected $enabled; /** - * Method to add a child + * Get the current menu tree * - * @param JMenuNode $node The node to process - * @param boolean $setCurrent True to set as current working node + * @return Tree * - * @return void + * @since __DEPLOY_VERSION__ */ - public function addChild(JMenuNode $node, $setCurrent = false) + public function getTree() { - $this->_current->addChild($node); - - if ($setCurrent) + if (!$this->tree) { - $this->_current = &$node; + $this->tree = new Tree; } - } - /** - * Method to get the parent - * - * @return void - */ - public function getParent() - { - $this->_current = &$this->_current->getParent(); + return $this->tree; } /** - * Method to get the parent + * Populate the menu items in the menu tree object * - * @param bool $clear Whether to clear the existing menu items or just reset the pointer to root element + * @param Registry $params Menu configuration parameters + * @param bool $enabled Whether the menu should be enabled or disabled * * @return void + * + * @since 3.7.0 */ - public function reset($clear = false) + public function load($params, $enabled) { - if ($clear) + $this->tree = $this->getTree(); + $this->params = $params; + $this->enabled = $enabled; + $menutype = $this->params->get('menutype', '*'); + + if ($menutype == '*') { - $this->_root = new JMenuNode('ROOT'); + $name = $this->params->get('preset', 'joomla'); + $levels = MenuHelper::loadPreset($name); } + else + { + $items = MenusHelper::getMenuItems($menutype, true); - $this->_current = &$this->_root; - } + if ($this->enabled && $this->params->get('check')) + { + if ($this->check($items, $this->params)) + { + $this->params->set('recovery', true); - /** - * Method to add a separator node - * - * @param string $title The separator label text. A dash "-" can be used to use a horizontal bar instead of text label. - * - * @return void - */ - public function addSeparator($title = null) - { - if ($title == '-' || $title == '') - { - $title = null; + // In recovery mode, load the preset inside a special root node. + $this->tree->addChild(new Node\Heading('MOD_MENU_RECOVERY_MENU_ROOT'), true); + + $levels = MenuHelper::loadPreset('joomla'); + $levels = $this->preprocess($levels); + + $this->populateTree($levels); + + $this->tree->addChild(new Node\Separator); + + // Add link to exit recovery mode + $uri = clone JUri::getInstance(); + $uri->setVar('recover_menu', 0); + + $this->tree->addChild(new Node\Url('MOD_MENU_RECOVERY_EXIT', $uri->toString())); + + $this->tree->getParent(); + } + } + + $levels = MenuHelper::createLevels($items); } - $this->addChild(new JMenuNode($title, null, 'separator', false)); + $levels = $this->preprocess($levels); + + $this->populateTree($levels); } /** - * Method to render the menu + * Method to render a given level of a menu using provided layout file * - * @param string $id The id of the menu to be rendered - * @param string $class The class of the menu to be rendered + * @param string $layoutFile The layout file to be used to render * * @return void + * + * @since __DEPLOY_VERSION__ */ - public function renderMenu($id = 'menu', $class = '') + public function renderSubmenu($layoutFile) { - $depth = 1; - - if (!empty($id)) + if (is_file($layoutFile)) { - $id = 'id="' . $id . '"'; - } + $children = $this->tree->getCurrent()->getChildren(); - if (!empty($class)) - { - $class = 'class="' . $class . '"'; - } - - // Recurse through children if they exist - while ($this->_current->hasChildren()) - { - echo '\n"; - - echo ''; - } - - if ($this->_css) - { - // Add style to document head - JFactory::getDocument()->addStyleDeclaration($this->_css); + // This sets the scope to this object for the layout file and also isolates other `include`s + require $layoutFile; + } } } /** - * Method to render a given level of a menu + * Check the flat list of menu items for important links * - * @param integer $depth The level of the menu to be rendered + * @param array $items The menu items array + * @param Registry $params Module options * - * @return void + * @return bool Whether to show recovery menu + * + * @since __DEPLOY_VERSION__ */ - public function renderLevel($depth) + protected function check($items, Registry $params) { - // Build the CSS class suffix - $class = ''; + $me = JFactory::getUser(); + $authMenus = $me->authorise('core.manage', 'com_menus'); + $authModules = $me->authorise('core.manage', 'com_modules'); - if ($this->_current->hasChildren()) + if (!$authMenus && !$authModules) { - $class = ' class="dropdown"'; + return false; } - if ($this->_current->class == 'separator') - { - $class = $this->_current->title ? ' class="menuitem-group"' : ' class="divider"'; - } + $app = JFactory::getApplication(); + $types = ArrayHelper::getColumn($items, 'type'); + $elements = ArrayHelper::getColumn($items, 'element'); + $rMenu = $authMenus && !in_array('com_menus', $elements); + $rModule = $authModules && !in_array('com_modules', $elements); + $rContainer = !in_array('container', $types); - if ($this->_current->hasChildren() && $this->_current->class) + if ($rMenu || $rModule || $rContainer) { - $class = ' class="dropdown-submenu"'; + $recovery = $app->getUserStateFromRequest('mod_menu.recovery', 'recover_menu', 0, 'int'); - if ($this->_current->class == 'scrollable-menu') + if ($recovery) { - $class = ' class="dropdown scrollable-menu"'; + return true; } - } - - if ($this->_current->class == 'disabled') - { - $class = ' class="disabled"'; - } - // Print the item - echo ''; + $missing = array(); - // Print a link if it exists - $linkClass = array(); - $dataToggle = ''; - $dropdownCaret = ''; - - if ($this->_current->hasChildren()) - { - $linkClass[] = 'dropdown-toggle'; - $dataToggle = ' data-toggle="dropdown"'; - - if (!$this->_current->getParent()->hasParent()) + if ($rMenu) { - $dropdownCaret = ' '; + $missing[] = JText::_('MOD_MENU_IMPORTANT_ITEM_MENU_MANAGER'); } - } - else - { - $linkClass[] = 'no-dropdown'; - } - - if ($this->_current->link != null && $this->_current->getParent()->title != 'ROOT') - { - $iconClass = $this->getIconClass($this->_current->class); - if (!empty($iconClass)) + if ($rModule) { - $linkClass[] = $iconClass; + $missing[] = JText::_('MOD_MENU_IMPORTANT_ITEM_MODULE_MANAGER'); } - } - - // Implode out $linkClass for rendering - $linkClass = ' class="' . implode(' ', $linkClass) . '"'; - - if ($this->_current->link != null && $this->_current->target != null) - { - echo '' - . $this->_current->title . $dropdownCaret . ''; - } - elseif ($this->_current->link != null && $this->_current->target == null) - { - echo '' . $this->_current->title . $dropdownCaret . ''; - } - elseif ($this->_current->title != null && $this->_current->class != 'separator') - { - echo '' . $this->_current->title . $dropdownCaret . ''; - } - else - { - echo '' . $this->_current->title . ''; - } - // Recurse through children if they exist - while ($this->_current->hasChildren()) - { - if ($this->_current->class) + if ($rContainer) { - $id = ''; + $missing[] = JText::_('MOD_MENU_IMPORTANT_ITEM_COMPONENTS_CONTAINER'); + } - if (!empty($this->_current->id)) - { - $id = ' id="menu-' . strtolower($this->_current->id) . '"'; - } + $uri = clone JUri::getInstance(); + $uri->setVar('recover_menu', 1); - echo '' . "\n"; - } - else - { - echo '\n"; + $menutype = $table->get('title', $menutype); + $message = JText::sprintf('MOD_MENU_IMPORTANT_ITEMS_INACCESSIBLE_LIST_WARNING', $menutype, implode(', ', $missing), $uri); + + $app->enqueueMessage($message, 'warning'); } - echo "\n"; + return false; } /** - * Method to get the CSS class name for an icon identifier or create one if - * a custom image path is passed as the identifier + * Filter and perform other preparatory tasks for loaded menu items based on access rights and module configurations for display * - * @param string $identifier Icon identification string + * @param \stdClass[] $items The levelled array of menu item objects * - * @return string CSS class name + * @return array * - * @since 1.5 + * @since __DEPLOY_VERSION__ */ - public function getIconClass($identifier) + protected function preprocess($items) { - static $classes; + $result = array(); + $user = JFactory::getUser(); + $authLevels = $user->getAuthorisedViewLevels(); + $language = JFactory::getLanguage(); - // Initialise the known classes array if it does not exist - if (!is_array($classes)) - { - $classes = array(); - } + $noSeparator = true; - /* - * If we don't already know about the class... build it and mark it - * known so we don't have to build it again - */ - if (!isset($classes[$identifier])) + foreach ($items as $i => &$item) { - if (substr($identifier, 0, 6) == 'class:') + // Exclude item with menu item option set to exclude from menu modules + if ($item->params->get('menu_show', 1) == 0) { - // We were passed a class name - $class = substr($identifier, 6); - $classes[$identifier] = "menu-$class"; + continue; } - else - { - if ($identifier == null) - { - return null; - } - // Build the CSS class for the icon - $class = preg_replace('#\.[^.]*$#', '', basename($identifier)); - $class = preg_replace('#\.\.[^A-Za-z0-9\.\_\- ]#', '', $class); + $item->scope = isset($item->scope) ? $item->scope : 'default'; - $this->_css .= "\n.menu-$class {\n" . - " background: url($identifier) no-repeat;\n" . - "}\n"; + // Whether this scope can be displayed. Applies only to preset items. Db driven items should use un/published state. + if (($item->scope == 'help' && !$this->params->get('showhelp')) || ($item->scope == 'edit' && !$this->params->get('shownew'))) + { + continue; + } - $classes[$identifier] = "menu-$class"; + // Exclude item if the component is not authorised + if ($item->element && !$user->authorise(($item->scope == 'edit') ? 'core.create' : 'core.manage', $item->element)) + { + continue; } - } - return $classes[$identifier]; - } + // Exclude if menu item set access level is not met + if ($item->access && !in_array($item->access, $authLevels)) + { + continue; + } - /** - * Populate the menu items in the menu object for disabled state - * - * @param Registry $params Menu configuration parameters - * @param bool $enabled Whether the menu should be enabled or disabled - * - * @return void - * - * @since 3.7.0 - */ - public function load($params, $enabled) - { - $menutype = $params->get('menutype', '*'); + // Exclude if link is invalid + if (!in_array($item->type, array('separator', 'heading', 'container')) && trim($item->link) == '') + { + continue; + } - $this->reset(true); + // Process any children if exists + $item->submenu = $this->preprocess($item->submenu); - if ($menutype == '*') - { - require __DIR__ . '/preset/' . ($enabled ? 'enabled.php' : 'disabled.php'); - } - else - { - $items = ModMenuHelper::getMenuItems($menutype); - $types = ArrayHelper::getColumn($items, 'type'); - $app = JFactory::getApplication(); - $me = JFactory::getUser(); + // Populate automatic children for container items + if ($item->type == 'container') + { + $exclude = (array) $item->params->get('hideitems') ?: array(); + $components = MenusHelper::getMenuItems('main', false, $exclude); - $authMenus = $me->authorise('core.manage', 'com_menus'); - $authModules = $me->authorise('core.manage', 'com_modules'); + $item->components = MenuHelper::createLevels($components); + $item->components = $this->preprocess($item->components); + $item->components = ArrayHelper::sortObjects($item->components, 'text', 1, false, true); + } - if ($enabled && $params->get('check') && ($authMenus || $authModules)) + // Exclude if there are no child items under heading or container + if (in_array($item->type, array('heading', 'container')) && empty($item->submenu) && empty($item->components)) { - $elements = ArrayHelper::getColumn($items, 'element'); - - $rMenu = $authMenus && !in_array('com_menus', $elements); - $rModule = $authModules && !in_array('com_modules', $elements); - $rContainer = !in_array('container', $types); + continue; + } - if ($rMenu || $rModule || $rContainer) + // Remove repeated and edge positioned separators, It is important to put this check at the end of any logical filtering. + if ($item->type == 'separator') + { + if ($noSeparator) { - $recovery = $app->getUserStateFromRequest('mod_menu.recovery', 'recover_menu', 0, 'int'); - - if ($recovery) - { - $params->set('recovery', true); - - // In recovery mode, load the preset inside a special root node. - $this->addChild(new JMenuNode(JText::_('MOD_MENU_RECOVERY_MENU_ROOT'), '#'), true); - - require __DIR__ . '/preset/enabled.php'; - - $this->addSeparator(); - - $uri = clone JUri::getInstance(); - $uri->setVar('recover_menu', 0); - - $this->addChild(new JMenuNode(JText::_('MOD_MENU_RECOVERY_EXIT'), $uri->toString())); - - $this->getParent(); - } - else - { - $missing = array(); - - if ($rMenu) - { - $missing[] = JText::_('MOD_MENU_IMPORTANT_ITEM_MENU_MANAGER'); - } - - if ($rModule) - { - $missing[] = JText::_('MOD_MENU_IMPORTANT_ITEM_MODULE_MANAGER'); - } - - if ($rContainer) - { - $missing[] = JText::_('MOD_MENU_IMPORTANT_ITEM_COMPONENTS_CONTAINER'); - } - - $uri = clone JUri::getInstance(); - $uri->setVar('recover_menu', 1); - - $table = JTable::getInstance('MenuType'); - $table->load(array('menutype' => $menutype)); - $mType = $table->get('title', $menutype); + continue; + } - $msg = JText::sprintf('MOD_MENU_IMPORTANT_ITEMS_INACCESSIBLE_LIST_WARNING', $mType, implode(', ', $missing), $uri); + $noSeparator = true; + } + else + { + $noSeparator = false; + } - $app->enqueueMessage($msg, 'warning'); - } - } + // Ok we passed everything, load language at last only + if ($item->element) + { + $language->load($item->element . '.sys', JPATH_ADMINISTRATOR, null, false, true) || + $language->load($item->element . '.sys', JPATH_ADMINISTRATOR . '/components/' . $item->element, null, false, true); } - // Create levels - $items = ModMenuHelper::parseItems($items); + $item->text = JText::_($item->title); - // Menu items for dynamic db driven setup to load here - $this->loadItems($items, $enabled); + $result[$i] = $item; } + + // If last one was a separator remove it too. + if ($noSeparator && isset($i)) + { + unset($result[$i]); + } + + return $result; } /** - * Load the menu items from an array + * Load the menu items from a hierarchical list of items into the menu tree * - * @param array $items Menu items loaded from database - * @param bool $enabled Whether the menu should be enabled or disabled + * @param stdClass[] $levels Menu items as a hierarchical list format * * @return void * - * @since 3.7.0 + * @since __DEPLOY_VERSION__ */ - protected function loadItems($items, $enabled = true) + protected function populateTree($levels) { - foreach ($items as $item) + foreach ($levels as $item) { + $class = $this->enabled ? $item->class : 'disabled'; + if ($item->type == 'separator') { - $this->addSeparator($item->text); - } - elseif ($item->type == 'heading' && !count($item->submenu)) - { - // Exclude if it is a heading type menu item, and has no children. + $this->tree->addChild(new Node\Separator($item->title)); } - elseif ($item->type == 'container') + elseif ($item->type == 'heading') { - $exclude = (array) $item->params->get('hideitems') ?: array(); - $components = ModMenuHelper::getComponents(true, false, $exclude); + // We already excluded heading type menu item with no children. + $this->tree->addChild(new Node\Heading($item->title, $class), $this->enabled); - // Exclude if it is a container type menu item, and has no children. - if (count($item->submenu) || count($components)) + if ($this->enabled) { - $this->addChild(new JMenuNode($item->text, $item->link, $item->parent_id == 1 ? null : 'class:'), true); - - if ($enabled) - { - // Load explicitly assigned child items first. - $this->loadItems($item->submenu); - - // Add a separator between dynamic menu items and components menu items - if (count($item->submenu) && count($components)) - { - $this->addSeparator($item->text); - } - - // Adding component submenu the old way, this assumes 2-level menu only - foreach ($components as $component) - { - if (empty($component->submenu)) - { - $this->addChild(new JMenuNode($component->text, $component->link, $component->img)); - } - else - { - $this->addChild(new JMenuNode($component->text, $component->link, $component->img), true); - - foreach ($component->submenu as $sub) - { - $this->addChild(new JMenuNode($sub->text, $sub->link, $sub->img)); - } - - $this->getParent(); - } - } - } - - $this->getParent(); + $this->populateTree($item->submenu); + $this->tree->getParent(); } } - elseif (!$enabled) + elseif ($item->type == 'url') { - $this->addChild(new JMenuNode($item->text, $item->link, 'disabled')); + $cNode = new Node\Url($item->title, $item->link, $item->browserNav, $class); + $this->tree->addChild($cNode, $this->enabled); + + if ($this->enabled) + { + $this->populateTree($item->submenu); + $this->tree->getParent(); + } } - else + elseif ($item->type == 'component') { - $target = $item->browserNav ? '_blank' : null; + $cNode = new Node\Component($item->title, $item->element, $item->link, $item->browserNav, $class); + $this->tree->addChild($cNode, $this->enabled); - $this->addChild(new JMenuNode($item->text, $item->link, $item->parent_id == 1 ? null : 'class:', false, $target), true); - $this->loadItems($item->submenu); - $this->getParent(); + if ($this->enabled) + { + $this->populateTree($item->submenu); + $this->tree->getParent(); + } } - } - } -} - -/** - * A Node for JAdminCssMenu - * - * @see JAdminCssMenu - * @since 1.5 - */ -class JMenuNode -{ - /** - * Node Title - * - * @var string - */ - public $title = null; - - /** - * Node Id - * - * @var string - */ - public $id = null; - - /** - * Node Link - * - * @var string - */ - public $link = null; - - /** - * Link Target - * - * @var string - */ - public $target = null; - - /** - * CSS Class for node - * - * @var string - */ - public $class = null; - - /** - * Active Node? - * - * @var boolean - */ - public $active = false; - - /** - * Parent node - * - * @var JMenuNode - */ - protected $_parent = null; - - /** - * Array of Children - * - * @var array - */ - protected $_children = array(); - - /** - * Constructor for the class. - * - * @param string $title The title of the node - * @param string $link The node link - * @param string $class The CSS class for the node - * @param boolean $active True if node is active, false otherwise - * @param string $target The link target - * @param string $titleicon The title icon for the node - */ - public function __construct($title, $link = null, $class = null, $active = false, $target = null, $titleicon = null) - { - $this->title = $titleicon ? $title . $titleicon : $title; - $this->link = JFilterOutput::ampReplace($link); - $this->class = $class; - $this->active = $active; - - $this->id = null; - - if (!empty($link) && $link !== '#') - { - $uri = new JUri($link); - $params = $uri->getQuery(true); - $parts = array(); - - foreach ($params as $value) + elseif ($item->type == 'container') { - $parts[] = str_replace(array('.', '_'), '-', $value); - } + // We already excluded container type menu item with no children. + $this->tree->addChild(new Node\Container($item->title, $item->class), $this->enabled); - $this->id = implode('-', $parts); - } - - $this->target = $target; - } - - /** - * Add child to this node - * - * If the child already has a parent, the link is unset - * - * @param JMenuNode &$child The child to be added - * - * @return void - */ - public function addChild(JMenuNode &$child) - { - $child->setParent($this); - } + if ($this->enabled) + { + $this->populateTree($item->submenu); - /** - * Set the parent of a this node - * - * If the node already has a parent, the link is unset - * - * @param JMenuNode &$parent The JMenuNode for parent to be set or null - * - * @return void - */ - public function setParent(JMenuNode &$parent = null) - { - $hash = spl_object_hash($this); + // Add a separator between dynamic menu items and components menu items + if (count($item->submenu) && count($item->components)) + { + $this->tree->addChild(new Node\Separator); + } - if (!is_null($this->_parent)) - { - unset($this->_parent->_children[$hash]); - } + $this->populateTree($item->components); - if (!is_null($parent)) - { - $parent->_children[$hash] = &$this; + $this->tree->getParent(); + } + } } - - $this->_parent = &$parent; - } - - /** - * Get the children of this node - * - * @return array The children - */ - public function &getChildren() - { - return $this->_children; - } - - /** - * Get the parent of this node - * - * @return mixed JMenuNode object with the parent or null for no parent - */ - public function &getParent() - { - return $this->_parent; - } - - /** - * Test if this node has children - * - * @return boolean True if there are children - */ - public function hasChildren() - { - return (bool) count($this->_children); - } - - /** - * Test if this node has a parent - * - * @return boolean True if there is a parent - */ - public function hasParent() - { - return $this->getParent() != null; } } diff --git a/administrator/modules/mod_menu/mod_menu.php b/administrator/modules/mod_menu/mod_menu.php index a625f99b0b11e..85a2a1e419d13 100644 --- a/administrator/modules/mod_menu/mod_menu.php +++ b/administrator/modules/mod_menu/mod_menu.php @@ -12,6 +12,7 @@ use Joomla\Registry\Registry; // Include the module helper classes. +JLoader::register('MenusHelper', JPATH_ADMINISTRATOR . '/components/com_menus/helpers/menus.php'); JLoader::register('ModMenuHelper', __DIR__ . '/helper.php'); JLoader::register('JAdminCssMenu', __DIR__ . '/menu.php'); diff --git a/administrator/modules/mod_menu/mod_menu.xml b/administrator/modules/mod_menu/mod_menu.xml index 3721d11415ba6..2f896d54aec44 100644 --- a/administrator/modules/mod_menu/mod_menu.xml +++ b/administrator/modules/mod_menu/mod_menu.xml @@ -23,7 +23,7 @@ -
+
- + + + - - - - @@ -80,10 +76,28 @@ description="MOD_MENU_FIELD_SHOWHELP_DESC" class="btn-group btn-group-yesno" default="1" + showon="menutype:*" > +
+ +
+ + + addChild(new JMenuNode(JText::_('MOD_MENU_SYSTEM'), null, 'disabled')); - -/** - * Users Submenu - */ -if ($user->authorise('core.manage', 'com_users')) -{ - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_USERS'), null, 'disabled')); -} - -/** - * Menus Submenu - */ -if ($user->authorise('core.manage', 'com_menus')) -{ - $this->addChild(new JMenuNode(JText::_('MOD_MENU_MENUS'), null, 'disabled')); -} - -/** - * Content Submenu - */ -if ($user->authorise('core.manage', 'com_content')) -{ - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_CONTENT'), null, 'disabled')); -} - -/** - * Components Submenu - */ - -// Get the authorised components and sub-menus. -$components = ModMenuHelper::getComponents(true); - -// Check if there are any components, otherwise, don't display the components menu item -if ($components) -{ - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COMPONENTS'), null, 'disabled')); -} - -/** - * Extensions Submenu - */ -$im = $user->authorise('core.manage', 'com_installer'); -$mm = $user->authorise('core.manage', 'com_modules'); -$pm = $user->authorise('core.manage', 'com_plugins'); -$tm = $user->authorise('core.manage', 'com_templates'); -$lm = $user->authorise('core.manage', 'com_languages'); - -if ($im || $mm || $pm || $tm || $lm) -{ - $this->addChild(new JMenuNode(JText::_('MOD_MENU_EXTENSIONS_EXTENSIONS'), null, 'disabled')); -} - -/** - * Help Submenu - */ -if ($params->get('showhelp', 1)) -{ - $this->addChild(new JMenuNode(JText::_('MOD_MENU_HELP'), null, 'disabled')); -} diff --git a/administrator/modules/mod_menu/preset/enabled.php b/administrator/modules/mod_menu/preset/enabled.php deleted file mode 100644 index 610c4e1a50805..0000000000000 --- a/administrator/modules/mod_menu/preset/enabled.php +++ /dev/null @@ -1,450 +0,0 @@ -get('recovery', 0); -$shownew = (boolean) $params->get('shownew', 1); -$showhelp = (boolean) $params->get('showhelp', 1); -$user = JFactory::getUser(); -$lang = JFactory::getLanguage(); -$rootClass = $recovery ? 'class:' : null; - -// Is com_fields installed and enabled? -$comFieldsEnabled = JComponentHelper::isInstalled('com_fields') && JComponentHelper::isEnabled('com_fields'); - -/** - * Site Submenu - */ -$this->addChild(new JMenuNode(JText::_('MOD_MENU_SYSTEM'), '#', $rootClass), true); -$this->addChild(new JMenuNode(JText::_('MOD_MENU_CONTROL_PANEL'), 'index.php', 'class:cpanel')); - -if ($user->authorise('core.admin')) -{ - $this->addSeparator(); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_CONFIGURATION'), 'index.php?option=com_config', 'class:config')); -} - -if ($user->authorise('core.manage', 'com_checkin')) -{ - $this->addSeparator(); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_GLOBAL_CHECKIN'), 'index.php?option=com_checkin', 'class:checkin')); -} - -if ($user->authorise('core.manage', 'com_cache')) -{ - $this->addChild(new JMenuNode(JText::_('MOD_MENU_CLEAR_CACHE'), 'index.php?option=com_cache', 'class:clear')); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_PURGE_EXPIRED_CACHE'), 'index.php?option=com_cache&view=purge', 'class:purge')); -} - -if ($user->authorise('core.admin')) -{ - $this->addSeparator(); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_SYSTEM_INFORMATION'), 'index.php?option=com_admin&view=sysinfo', 'class:info')); -} - -$this->getParent(); - -/** - * Users Submenu - */ -if ($user->authorise('core.manage', 'com_users')) -{ - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_USERS_USERS'), '#', $rootClass), true); - $createUser = $shownew && $user->authorise('core.create', 'com_users'); - $createGrp = $user->authorise('core.admin', 'com_users'); - - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_USERS_USER_MANAGER'), 'index.php?option=com_users&view=users', 'class:user'), $createUser); - - if ($createUser) - { - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_USERS_ADD_USER'), 'index.php?option=com_users&task=user.add', 'class:newarticle')); - $this->getParent(); - } - - if ($createGrp) - { - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_USERS_GROUPS'), 'index.php?option=com_users&view=groups', 'class:groups'), $createUser); - - if ($createUser) - { - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_USERS_ADD_GROUP'), 'index.php?option=com_users&task=group.add', 'class:newarticle')); - $this->getParent(); - } - - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_USERS_LEVELS'), 'index.php?option=com_users&view=levels', 'class:levels'), $createUser); - - if ($createUser) - { - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_USERS_ADD_LEVEL'), 'index.php?option=com_users&task=level.add', 'class:newarticle')); - $this->getParent(); - } - } - - if ($comFieldsEnabled && JComponentHelper::getParams('com_users')->get('custom_fields_enable', '1')) - { - $this->addSeparator(); - $this->addChild( - new JMenuNode( - JText::_('MOD_MENU_FIELDS'), 'index.php?option=com_fields&context=com_users.user', 'class:fields') - ); - - $this->addChild( - new JMenuNode( - JText::_('MOD_MENU_FIELDS_GROUP'), 'index.php?option=com_fields&view=groups&context=com_users.user', 'class:category') - ); - } - - $this->addSeparator(); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_USERS_NOTES'), 'index.php?option=com_users&view=notes', 'class:user-note'), $createUser); - - if ($createUser) - { - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_USERS_ADD_NOTE'), 'index.php?option=com_users&task=note.add', 'class:newarticle')); - $this->getParent(); - } - - $this->addChild( - new JMenuNode( - JText::_('MOD_MENU_COM_USERS_NOTE_CATEGORIES'), 'index.php?option=com_categories&view=categories&extension=com_users', 'class:category'), - $createUser - ); - - if ($createUser) - { - $this->addChild( - new JMenuNode( - JText::_('MOD_MENU_COM_CONTENT_NEW_CATEGORY'), 'index.php?option=com_categories&task=category.add&extension=com_users', - 'class:newarticle' - ) - ); - - $this->getParent(); - } - - if (JFactory::getApplication()->get('massmailoff') != 1) - { - $this->addSeparator(); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_MASS_MAIL_USERS'), 'index.php?option=com_users&view=mail', 'class:massmail')); - } - - $this->getParent(); -} - -/** - * Menus Submenu - */ -if ($user->authorise('core.manage', 'com_menus')) -{ - $this->addChild(new JMenuNode(JText::_('MOD_MENU_MENUS'), '#', $rootClass), true); - $createMenu = $shownew && $user->authorise('core.create', 'com_menus'); - - $this->addChild(new JMenuNode(JText::_('MOD_MENU_MENU_MANAGER'), 'index.php?option=com_menus&view=menus', 'class:menumgr'), $createMenu); - - if ($createMenu) - { - $this->addChild( - new JMenuNode(JText::_('MOD_MENU_MENU_MANAGER_NEW_MENU'), 'index.php?option=com_menus&view=menu&layout=edit', 'class:newarticle') - ); - $this->getParent(); - } - - $this->addSeparator(); - - $this->addChild(new JMenuNode(JText::_('MOD_MENU_MENUS_ALL_ITEMS'), 'index.php?option=com_menus&view=items&menutype=', 'class:allmenu')); - $this->addSeparator(JText::_('JSITE')); - - // Menu Types - $menuTypes = ModMenuHelper::getMenus(); - $menuTypes = ArrayHelper::sortObjects($menuTypes, isset($menuTypes[0]->client_id) ? array('client_id', 'title') : 'title', 1, false); - - foreach ($menuTypes as $mti => $menuType) - { - if (!$user->authorise('core.manage', 'com_menus.menu.' . (int) $menuType->id)) - { - continue; - } - - $alt = '*' . $menuType->sef . '*'; - - if ($menuType->home == 0) - { - $titleicon = ''; - } - elseif ($menuType->home == 1 && $menuType->language == '*') - { - $titleicon = ' '; - } - elseif ($menuType->home > 1) - { - $titleicon = ' ' - . JHtml::_('image', 'mod_languages/icon-16-language.png', $menuType->home, array('title' => JText::_('MOD_MENU_HOME_MULTIPLE')), true) - . ''; - } - elseif ($menuType->image && JHtml::_('image', 'mod_languages/' . $menuType->image . '.gif', null, null, true, true)) - { - $titleicon = ' ' . - JHtml::_('image', 'mod_languages/' . $menuType->image . '.gif', $alt, array('title' => $menuType->title_native), true) . ''; - } - else - { - $titleicon = ' ' . $menuType->sef . ''; - } - - if (isset($menuTypes[$mti - 1], $menuType->client_id) && $menuTypes[$mti - 1]->client_id != $menuType->client_id) - { - $this->addSeparator(JText::_('JADMINISTRATOR')); - } - - $this->addChild( - new JMenuNode( - $menuType->title, 'index.php?option=com_menus&view=items&menutype=' . $menuType->menutype, 'class:menu', null, null, $titleicon - ), - $user->authorise('core.create', 'com_menus.menu.' . (int) $menuType->id) - ); - - if ($user->authorise('core.create', 'com_menus.menu.' . (int) $menuType->id)) - { - $this->addChild( - new JMenuNode( - JText::_('MOD_MENU_MENU_MANAGER_NEW_MENU_ITEM'), - 'index.php?option=com_menus&view=item&layout=edit&menutype=' . $menuType->menutype, 'class:newarticle' - ) - ); - - $this->getParent(); - } - } - - $this->getParent(); -} - -/** - * Content Submenu - */ -if ($user->authorise('core.manage', 'com_content')) -{ - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_CONTENT'), '#', $rootClass), true); - $createContent = $shownew && $user->authorise('core.create', 'com_content'); - - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_CONTENT_ARTICLE_MANAGER'), 'index.php?option=com_content', 'class:article'), $createContent); - - if ($createContent) - { - $this->addChild( - new JMenuNode(JText::_('MOD_MENU_COM_CONTENT_NEW_ARTICLE'), 'index.php?option=com_content&task=article.add', 'class:newarticle') - ); - $this->getParent(); - } - - $this->addChild( - new JMenuNode( - JText::_('MOD_MENU_COM_CONTENT_CATEGORY_MANAGER'), 'index.php?option=com_categories&extension=com_content', 'class:category' - ), - $createContent - ); - - if ($createContent) - { - $this->addChild( - new JMenuNode( - JText::_('MOD_MENU_COM_CONTENT_NEW_CATEGORY'), - 'index.php?option=com_categories&task=category.add&extension=com_content', 'class:newarticle' - ) - ); - $this->getParent(); - } - - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COM_CONTENT_FEATURED'), 'index.php?option=com_content&view=featured', 'class:featured')); - - if ($comFieldsEnabled && JComponentHelper::getParams('com_content')->get('custom_fields_enable', '1')) - { - $this->addSeparator(); - $this->addChild( - new JMenuNode( - JText::_('MOD_MENU_FIELDS'), 'index.php?option=com_fields&context=com_content.article', 'class:fields') - ); - - $this->addChild( - new JMenuNode( - JText::_('MOD_MENU_FIELDS_GROUP'), 'index.php?option=com_fields&view=groups&context=com_content.article', 'class:category') - ); - } - - if ($user->authorise('core.manage', 'com_media')) - { - $this->addSeparator(); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_MEDIA_MANAGER'), 'index.php?option=com_media', 'class:media')); - } - - $this->getParent(); -} - -/** - * Components Submenu - */ - -// Get the authorised components and sub-menus. -$components = ModMenuHelper::getComponents(true); - -// Check if there are any components, otherwise, don't render the menu -if ($components) -{ - $this->addChild(new JMenuNode(JText::_('MOD_MENU_COMPONENTS'), '#', $rootClass), true); - - foreach ($components as &$component) - { - if (!empty($component->submenu)) - { - // This component has a db driven submenu. - $this->addChild(new JMenuNode($component->text, $component->link, $component->img), true); - - foreach ($component->submenu as $sub) - { - $this->addChild(new JMenuNode($sub->text, $sub->link, $sub->img)); - } - - $this->getParent(); - } - else - { - $this->addChild(new JMenuNode($component->text, $component->link, $component->img)); - } - } - - $this->getParent(); -} - -/** - * Extensions Submenu - */ -$im = $user->authorise('core.manage', 'com_installer'); -$mm = $user->authorise('core.manage', 'com_modules'); -$pm = $user->authorise('core.manage', 'com_plugins'); -$tm = $user->authorise('core.manage', 'com_templates'); -$lm = $user->authorise('core.manage', 'com_languages'); - -if ($im || $mm || $pm || $tm || $lm) -{ - $this->addChild(new JMenuNode(JText::_('MOD_MENU_EXTENSIONS_EXTENSIONS'), '#', $rootClass), true); - - if ($im) - { - $cls = 'class:install'; - - $this->addChild(new JMenuNode(JText::_('MOD_MENU_EXTENSIONS_EXTENSION_MANAGER'), 'index.php?option=com_installer', $cls), $im); - - $this->addChild(new JMenuNode(JText::_('MOD_MENU_INSTALLER_SUBMENU_INSTALL'), 'index.php?option=com_installer', $cls)); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_INSTALLER_SUBMENU_UPDATE'), 'index.php?option=com_installer&view=update', $cls)); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_INSTALLER_SUBMENU_MANAGE'), 'index.php?option=com_installer&view=manage', $cls)); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_INSTALLER_SUBMENU_DISCOVER'), 'index.php?option=com_installer&view=discover', $cls)); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_INSTALLER_SUBMENU_DATABASE'), 'index.php?option=com_installer&view=database', $cls)); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_INSTALLER_SUBMENU_WARNINGS'), 'index.php?option=com_installer&view=warnings', $cls)); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_INSTALLER_SUBMENU_LANGUAGES'), 'index.php?option=com_installer&view=languages', $cls)); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_INSTALLER_SUBMENU_UPDATESITES'), 'index.php?option=com_installer&view=updatesites', $cls)); - $this->getParent(); - } - - if ($im && ($mm || $pm || $tm || $lm)) - { - $this->addSeparator(); - } - - if ($mm) - { - $this->addChild(new JMenuNode(JText::_('MOD_MENU_EXTENSIONS_MODULE_MANAGER'), 'index.php?option=com_modules', 'class:module')); - } - - if ($pm) - { - $this->addChild(new JMenuNode(JText::_('MOD_MENU_EXTENSIONS_PLUGIN_MANAGER'), 'index.php?option=com_plugins', 'class:plugin')); - } - - if ($tm) - { - $this->addChild(new JMenuNode(JText::_('MOD_MENU_EXTENSIONS_TEMPLATE_MANAGER'), 'index.php?option=com_templates', 'class:themes'), $tm); - - $this->addChild( - new JMenuNode(JText::_('MOD_MENU_COM_TEMPLATES_SUBMENU_STYLES'), 'index.php?option=com_templates&view=styles', 'class:themes') - ); - $this->addChild( - new JMenuNode(JText::_('MOD_MENU_COM_TEMPLATES_SUBMENU_TEMPLATES'), 'index.php?option=com_templates&view=templates', 'class:themes') - ); - $this->getParent(); - } - - if ($lm) - { - $this->addChild(new JMenuNode(JText::_('MOD_MENU_EXTENSIONS_LANGUAGE_MANAGER'), 'index.php?option=com_languages', 'class:language'), $lm); - - $this->addChild( - new JMenuNode(JText::_('MOD_MENU_COM_LANGUAGES_SUBMENU_INSTALLED'), 'index.php?option=com_languages&view=installed', 'class:language') - ); - $this->addChild( - new JMenuNode(JText::_('MOD_MENU_COM_LANGUAGES_SUBMENU_CONTENT'), 'index.php?option=com_languages&view=languages', 'class:language') - ); - $this->addChild( - new JMenuNode(JText::_('MOD_MENU_COM_LANGUAGES_SUBMENU_OVERRIDES'), 'index.php?option=com_languages&view=overrides', 'class:language') - ); - $this->getParent(); - } - - $this->getParent(); -} - -/** - * Help Submenu - */ -if ($showhelp == 1) -{ - $this->addChild(new JMenuNode(JText::_('MOD_MENU_HELP'), '#', $rootClass), true); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_HELP_JOOMLA'), 'index.php?option=com_admin&view=help', 'class:help')); - $this->addSeparator(); - - $this->addChild(new JMenuNode(JText::_('MOD_MENU_HELP_SUPPORT_OFFICIAL_FORUM'), 'https://forum.joomla.org', 'class:help-forum', false, '_blank')); - - if ($forum_url = $params->get('forum_url')) - { - $this->addChild(new JMenuNode(JText::_('MOD_MENU_HELP_SUPPORT_CUSTOM_FORUM'), $forum_url, 'class:help-forum', false, '_blank')); - } - - $debug = $lang->setDebug(false); - - if ($lang->hasKey('MOD_MENU_HELP_SUPPORT_OFFICIAL_LANGUAGE_FORUM_VALUE') && JText::_('MOD_MENU_HELP_SUPPORT_OFFICIAL_LANGUAGE_FORUM_VALUE') != '') - { - $forum_url = 'https://forum.joomla.org/viewforum.php?f=' . (int) JText::_('MOD_MENU_HELP_SUPPORT_OFFICIAL_LANGUAGE_FORUM_VALUE'); - $lang->setDebug($debug); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_HELP_SUPPORT_OFFICIAL_LANGUAGE_FORUM'), $forum_url, 'class:help-forum', false, '_blank')); - } - - $lang->setDebug($debug); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_HELP_DOCUMENTATION'), 'https://docs.joomla.org', 'class:help-docs', false, '_blank')); - $this->addSeparator(); - - $this->addChild(new JMenuNode(JText::_('MOD_MENU_HELP_EXTENSIONS'), 'https://extensions.joomla.org', 'class:help-jed', false, '_blank')); - $this->addChild( - new JMenuNode(JText::_('MOD_MENU_HELP_TRANSLATIONS'), 'https://community.joomla.org/translations.html', 'class:help-trans', false, '_blank') - ); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_HELP_RESOURCES'), 'https://resources.joomla.org', 'class:help-jrd', false, '_blank')); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_HELP_COMMUNITY'), 'https://community.joomla.org', 'class:help-community', false, '_blank')); - $this->addChild( - new JMenuNode(JText::_('MOD_MENU_HELP_SECURITY'), 'https://developer.joomla.org/security-centre.html', 'class:help-security', false, '_blank') - ); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_HELP_DEVELOPER'), 'https://developer.joomla.org', 'class:help-dev', false, '_blank')); - $this->addChild(new JMenuNode(JText::_('MOD_MENU_HELP_XCHANGE'), 'https://joomla.stackexchange.com', 'class:help-dev', false, '_blank')); - $this->addChild( - new JMenuNode(JText::_('MOD_MENU_HELP_SHOP'), 'https://community.joomla.org/the-joomla-shop.html', 'class:help-shop', false, '_blank') - ); - $this->getParent(); -} diff --git a/administrator/modules/mod_menu/tmpl/default.php b/administrator/modules/mod_menu/tmpl/default.php index ff9edb4c9e2ef..76159f5b2c99a 100644 --- a/administrator/modules/mod_menu/tmpl/default.php +++ b/administrator/modules/mod_menu/tmpl/default.php @@ -9,6 +9,27 @@ defined('_JEXEC') or die; -$direction = JFactory::getDocument()->direction == 'rtl' ? 'pull-right' : ''; +$doc = JFactory::getDocument(); +$direction = $doc->direction == 'rtl' ? 'pull-right' : ''; +$class = $enabled ? 'nav ' . $direction : 'nav disabled ' . $direction; -$menu->renderMenu('menu', $enabled ? 'nav ' . $direction : 'nav disabled ' . $direction); +// Recurse through children of root node if they exist +$menuTree = $menu->getTree(); +$root = $menuTree->reset(); + +if ($root->hasChildren()) +{ + echo '\n"; + + echo ''; + + if ($css = $menuTree->getCss()) + { + $doc->addStyleDeclaration(implode("\n", $css)); + } +} diff --git a/administrator/modules/mod_menu/tmpl/default_submenu.php b/administrator/modules/mod_menu/tmpl/default_submenu.php new file mode 100644 index 0000000000000..4f13537b35f7d --- /dev/null +++ b/administrator/modules/mod_menu/tmpl/default_submenu.php @@ -0,0 +1,120 @@ +tree->getCurrent(); + +// Build the CSS class suffix +if (!$this->enabled) +{ + $class = ' class="disabled"'; +} +elseif ($current instanceOf Separator) +{ + $class = $current->get('title') ? ' class="menuitem-group"' : ' class="divider"'; +} +elseif ($current->hasChildren()) +{ + if ($current->getLevel() == 1) + { + $class = ' class="dropdown"'; + } + elseif ($current->get('class') == 'scrollable-menu') + { + $class = ' class="dropdown scrollable-menu"'; + } + else + { + $class = ' class="dropdown-submenu"'; + } +} +else +{ + $class = ''; +} + +// Print the item +echo ''; + +// Print a link if it exists +$linkClass = array(); +$dataToggle = ''; +$dropdownCaret = ''; + +if ($current->hasChildren()) +{ + $linkClass[] = 'dropdown-toggle'; + $dataToggle = ' data-toggle="dropdown"'; + + if ($current->getLevel() == 1) + { + $dropdownCaret = ' '; + } +} +else +{ + $linkClass[] = 'no-dropdown'; +} + +if (!($current instanceof Separator) && ($current->getLevel() > 1)) +{ + $iconClass = $this->tree->getIconClass(); + + if (trim($iconClass)) + { + $linkClass[] = $iconClass; + } +} + +// Implode out $linkClass for rendering +$linkClass = ' class="' . implode(' ', $linkClass) . '" '; + +// Links: component/url/heading/container +if ($link = $current->get('link')) +{ + $target = $current->get('target') ? 'target="' . $current->get('target') . '"' : ''; + + echo '' . + JText::_($current->get('title')) . ' ' . $current->get('icon') . $dropdownCaret . ''; +} +// Separator +else +{ + echo '' . JText::_($current->get('title')) . ''; +} + +// Recurse through children if they exist +if ($this->enabled && $current->hasChildren()) +{ + if ($current->getLevel() > 1) + { + $id = $current->get('id') ? ' id="menu-' . strtolower($current->get('id')) . '"' : ''; + + echo '' . "\n"; + } + else + { + echo '\n"; +} + +echo "\n"; diff --git a/libraries/src/Joomla/CMS/Menu/MenuHelper.php b/libraries/src/Joomla/CMS/Menu/MenuHelper.php new file mode 100644 index 0000000000000..72c7574f0d0ad --- /dev/null +++ b/libraries/src/Joomla/CMS/Menu/MenuHelper.php @@ -0,0 +1,368 @@ +name = $name; + $preset->title = $title; + $preset->path = $path; + + static::$presets[$name] = $preset; + } + } + + /** + * Get a list of available presets. + * + * @return \stdClass[] + * + * @since __DEPLOY_VERSION__ + */ + public static function getPresets() + { + if (static::$presets === null) + { + // Important: 'null' will cause infinite recursion. + static::$presets = array(); + + static::addPreset('joomla', 'JLIB_MENUS_PRESET_JOOMLA', JPATH_ADMINISTRATOR . '/components/com_menus/presets/joomla.xml'); + static::addPreset('modern', 'JLIB_MENUS_PRESET_MODERN', JPATH_ADMINISTRATOR . '/components/com_menus/presets/modern.xml'); + + // Load from template folder automatically + $app = \JFactory::getApplication(); + $tpl = JPATH_THEMES . '/' . $app->getTemplate() . '/html/com_menus/presets'; + + if (is_dir($tpl)) + { + jimport('joomla.filesystem.folder'); + + $files = \JFolder::files($tpl, '\.xml$'); + + foreach ($files as $file) + { + $name = substr($file, 0, -4); + $title = str_replace('-', ' ', $name); + + static::addPreset(strtolower($name), ucwords($title), $tpl . '/' . $file); + } + } + } + + return static::$presets; + } + + /** + * Load the menu items from a preset file into a hierarchical list of objects + * + * @param string $name The preset name + * @param bool $fallback Fallback to default (joomla) preset if the specified one could not be loaded? + * + * @return \stdClass[] + * + * @since __DEPLOY_VERSION__ + */ + public static function loadPreset($name, $fallback = true) + { + $items = array(); + $presets = static::getPresets(); + + if (isset($presets[$name]) && ($xml = simplexml_load_file($presets[$name]->path, null, LIBXML_NOCDATA)) && $xml instanceof \SimpleXMLElement) + { + static::loadXml($xml, $items); + } + elseif ($fallback && isset($presets['joomla'])) + { + if (($xml = simplexml_load_file($presets['joomla']->path, null, LIBXML_NOCDATA)) && $xml instanceof \SimpleXMLElement) + { + static::loadXml($xml, $items); + } + } + + return $items; + } + + /** + * Method to resolve the menu item alias type menu item + * + * @param \stdClass &$item The alias object + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public static function resolveAlias(&$item) + { + $obj = $item; + + while ($obj->type == 'alias') + { + $params = new Registry($obj->params); + $aliasTo = $params->get('aliasoptions'); + + $db = \JFactory::getDbo(); + $query = $db->getQuery(true); + $query->select('a.id, a.link, a.type, e.element') + ->from('#__menu a') + ->where('a.id = ' . (int) $aliasTo) + ->join('left', '#__extensions e ON e.id = a.component_id = e.id'); + + try + { + $obj = $db->setQuery($query)->loadObject(); + + if (!$obj) + { + $item->link = ''; + + return; + } + } + catch (\Exception $e) + { + $item->link = ''; + + return; + } + } + + $item->id = $obj->id; + $item->link = $obj->link; + $item->type = $obj->type; + $item->element = $obj->element; + } + + /** + * Parse the flat list of menu items and prepare the hierarchy of them using parent-child relationship. + * + * @param \stdClass[] $menuItems List of menu items loaded from database + * + * @return \stdClass[] + * + * @since __DEPLOY_VERSION__ + */ + public static function createLevels($menuItems) + { + $result = array(); + $result[1] = array(); + + foreach ($menuItems as $i => &$item) + { + // Resolve the alias item to get the original item + if ($item->type == 'alias') + { + static::resolveAlias($item); + } + + if ($item->link = in_array($item->type, array('separator', 'heading', 'container')) ? '#' : trim($item->link)) + { + $item->submenu = array(); + $item->class = isset($item->img) ? $item->img : ''; + $item->scope = isset($item->scope) ? $item->scope : null; + $item->browserNav = $item->browserNav ? '_blank' : ''; + + $result[$item->parent_id][$item->id] = $item; + } + } + + // Move each of the items under respective parent menu items. + if (count($result[1])) + { + foreach ($result as $parentId => &$mItems) + { + foreach ($mItems as &$mItem) + { + if (isset($result[$mItem->id])) + { + $mItem->submenu = &$result[$mItem->id]; + } + } + } + } + + // Return only top level items, subtree follows + return $result[1]; + } + + /** + * Load a menu tree from an XML file + * + * @param \SimpleXMLElement[] $elements The xml menuitem nodes + * @param \stdClass[] &$items The menu hierarchy list to be populated + * @param string[] $replace The substring replacements for iterator type items + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected static function loadXml($elements, &$items, $replace = array()) + { + foreach ($elements as $element) + { + if ($element->getName() != 'menuitem') + { + continue; + } + + $select = (string) $element['sql_select']; + $from = (string) $element['sql_from']; + + /** + * Following is a repeatable group based on simple database query. This requires sql_* attributes (sql_select and sql_from are required) + * The values can be used like - "{sql:columnName}" in any attribute of repeated elements. + * The repeated elements are place inside this xml node but they will be populated in the same level in the rendered menu + */ + if ($select && $from) + { + $hidden = $element['hidden'] == 'true'; + $where = (string) $element['sql_where']; + $order = (string) $element['sql_order']; + $group = (string) $element['sql_group']; + + $db = \JFactory::getDbo(); + $query = $db->getQuery(true); + $query->select($select)->from($from); + + if ($where) + { + $query->where($where); + } + + if ($order) + { + $query->order($order); + } + + if ($group) + { + $query->order($group); + } + + $results = $db->setQuery($query)->loadObjectList(); + + // Skip the entire group if no items to iterate over. + if ($results) + { + // Show the repeatable group heading node only if not set as hidden. + if (!$hidden) + { + $items[] = static::parseXmlNode($element, $replace); + } + + // Iterate over the matching records, items goes in the same level (not $item->submenu) as this node. + foreach ($results as $result) + { + static::loadXml($element->menuitem, $items, $result); + } + } + } + else + { + $item = static::parseXmlNode($element, $replace); + + // Process the child nodes + static::loadXml($element->menuitem, $item->submenu, $replace); + + $items[] = $item; + } + } + } + + /** + * Create a menu item node from an xml element + * + * @param \SimpleXMLElement $node A menuitem element from preset xml + * @param string[] $replace The values to substitute in the title, link and element texts + * + * @return \stdClass + * + * @since __DEPLOY_VERSION__ + */ + protected static function parseXmlNode($node, $replace = array()) + { + $item = new \stdClass; + + $item->id = null; + $item->type = (string) $node['type']; + $item->title = (string) $node['title']; + $item->link = (string) $node['link']; + $item->element = (string) $node['element']; + $item->class = (string) $node['class']; + $item->browserNav = (string) $node['target']; + $item->access = (int) $node['access']; + $item->params = new Registry(trim($node->params)); + $item->scope = (string) $node['scope'] ?: 'default'; + $item->submenu = array(); + + // Translate attributes for iterator values + foreach ($replace as $var => $val) + { + $item->title = str_replace("{sql:$var}", $val, $item->title); + $item->element = str_replace("{sql:$var}", $val, $item->element); + $item->link = str_replace("{sql:$var}", $val, $item->link); + } + + return $item; + } +} diff --git a/libraries/src/Joomla/CMS/Menu/Node.php b/libraries/src/Joomla/CMS/Menu/Node.php new file mode 100644 index 0000000000000..aa28b740159c4 --- /dev/null +++ b/libraries/src/Joomla/CMS/Menu/Node.php @@ -0,0 +1,272 @@ +params = new Registry; + } + + /** + * Add child to this node + * + * If the child already has a parent, the link is unset + * + * @param Node $child The child to be added + * + * @return Node The new added child + * + * @since __DEPLOY_VERSION__ + */ + public function addChild(Node $child) + { + $hash = spl_object_hash($child); + + if (isset($child->parent)) + { + $child->parent->removeChild($child); + } + + $child->parent = $this; + $this->children[$hash] = $child; + + return $child; + } + + /** + * Remove a child from this node + * + * If the child exists it is unset + * + * @param Node $child The child to be added + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function removeChild(Node $child) + { + $hash = spl_object_hash($child); + + if (isset($this->children[$hash])) + { + $child->parent = null; + + unset($this->children[$hash]); + } + } + + /** + * Test if this node has a parent + * + * @return bool True if there is a parent + * + * @since __DEPLOY_VERSION__ + */ + public function hasParent() + { + return isset($this->parent); + } + + /** + * Get the parent of this node + * + * @return Node The Node object's parent or null for no parent + * + * @since __DEPLOY_VERSION__ + */ + public function getParent() + { + return $this->parent; + } + + /** + * Test if this node has children + * + * @return bool + * + * @since __DEPLOY_VERSION__ + */ + public function hasChildren() + { + return count($this->children) > 0; + } + + /** + * Get the children of this node + * + * @return Node[] The children + * + * @since __DEPLOY_VERSION__ + */ + public function getChildren() + { + return $this->children; + } + + /** + * Find the current node depth in the tree hierarchy + * + * @return int The node level in the hierarchy, where ROOT == 0, First level menu item == 1, an so on. + * + * @since __DEPLOY_VERSION__ + */ + public function getLevel() + { + return $this->hasParent() ? $this->getParent()->getLevel() + 1 : 0; + } + + /** + * Check whether the object instance node is the root node + * + * @return bool + * + * @since __DEPLOY_VERSION__ + */ + public function isRoot() + { + return !$this->hasParent(); + } + + /** + * Set the active state on or off + * + * @param bool $active The new active state + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function setActive($active) + { + $this->active = (bool) $active; + } + + /** + * set the params array + * + * @param Registry $params The params attributes + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function setParams(Registry $params) + { + $this->params = $params; + } + + /** + * Get the param value from the node params + * + * @param string $key The param name + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function getParam($key) + { + return isset($this->params[$key]) ? $this->params[$key] : null; + } + + /** + * Get an attribute value + * + * @param string $name The attribute name + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function get($name) + { + switch ($name) + { + case 'id': + case 'class': + case 'active': + case 'params': + return $this->$name; + } + + return null; + } +} diff --git a/libraries/src/Joomla/CMS/Menu/Node/Component.php b/libraries/src/Joomla/CMS/Menu/Node/Component.php new file mode 100644 index 0000000000000..735288626c659 --- /dev/null +++ b/libraries/src/Joomla/CMS/Menu/Node/Component.php @@ -0,0 +1,117 @@ +title = $title; + $this->element = $element; + $this->link = $link ? \JFilterOutput::ampReplace($link) : 'index.php?option=' . $element; + $this->target = $target; + $this->class = $class; + $this->id = $id; + $this->icon = $icon; + + parent::__construct(); + } + + /** + * Get an attribute value + * + * @param string $name The attribute name + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function get($name) + { + switch ($name) + { + case 'title': + case 'element': + case 'link': + case 'target': + case 'icon': + return $this->$name; + } + + return parent::get($name); + } +} diff --git a/libraries/src/Joomla/CMS/Menu/Node/Container.php b/libraries/src/Joomla/CMS/Menu/Node/Container.php new file mode 100644 index 0000000000000..be7df8190bd45 --- /dev/null +++ b/libraries/src/Joomla/CMS/Menu/Node/Container.php @@ -0,0 +1,21 @@ +title = $title; + $this->class = $class; + $this->id = $id; + $this->icon = $icon; + + parent::__construct(); + } + + /** + * Get an attribute value + * + * @param string $name The attribute name + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function get($name) + { + switch ($name) + { + case 'title': + case 'link': + case 'icon': + return $this->$name; + } + + return parent::get($name); + } +} diff --git a/libraries/src/Joomla/CMS/Menu/Node/Separator.php b/libraries/src/Joomla/CMS/Menu/Node/Separator.php new file mode 100644 index 0000000000000..0a90f0594a7dd --- /dev/null +++ b/libraries/src/Joomla/CMS/Menu/Node/Separator.php @@ -0,0 +1,65 @@ +title = trim($title, '- ') ? $title : null; + + parent::__construct(); + } + + /** + * Get an attribute value + * + * @param string $name The attribute name + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function get($name) + { + switch ($name) + { + case 'title': + return $this->$name; + } + + return parent::get($name); + } +} diff --git a/libraries/src/Joomla/CMS/Menu/Node/Url.php b/libraries/src/Joomla/CMS/Menu/Node/Url.php new file mode 100644 index 0000000000000..0d45e51ecf220 --- /dev/null +++ b/libraries/src/Joomla/CMS/Menu/Node/Url.php @@ -0,0 +1,105 @@ +title = $title; + $this->link = \JFilterOutput::ampReplace($link); + $this->target = $target; + $this->class = $class; + $this->id = $id; + $this->icon = $icon; + + parent::__construct(); + } + + /** + * Get an attribute value + * + * @param string $name The attribute name + * + * @return mixed + * + * @since __DEPLOY_VERSION__ + */ + public function get($name) + { + switch ($name) + { + case 'title': + case 'link': + case 'target': + case 'icon': + return $this->$name; + } + + return parent::get($name); + } +} diff --git a/libraries/src/Joomla/CMS/Menu/Tree.php b/libraries/src/Joomla/CMS/Menu/Tree.php new file mode 100644 index 0000000000000..5708c9be770bf --- /dev/null +++ b/libraries/src/Joomla/CMS/Menu/Tree.php @@ -0,0 +1,219 @@ +root = new Node; + $this->current = $this->root; + } + + /** + * Get the root node + * + * @return Node + * + * @since __DEPLOY_VERSION__ + */ + public function getRoot() + { + return $this->root; + } + + /** + * Get the current node + * + * @return Node + * + * @since __DEPLOY_VERSION__ + */ + public function getCurrent() + { + return $this->current; + } + + /** + * Get the current node + * + * @param Node $node The node to be set as current + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function setCurrent($node) + { + if ($node) + { + $this->current = $node; + } + } + + /** + * Method to get the parent and set it as active optionally + * + * @param bool $setCurrent Set that parent as the current node for further working + * + * @return Node + * + * @since __DEPLOY_VERSION__ + */ + public function getParent($setCurrent = true) + { + $parent = $this->current->getParent(); + + if ($setCurrent) + { + $this->setCurrent($parent); + } + + return $parent; + } + + /** + * Method to reset the working pointer to the root node and optionally clear all menu nodes + * + * @param bool $clear Whether to clear the existing menu items or just reset the pointer to root element + * + * @return Node The root node + * + * @since __DEPLOY_VERSION__ + */ + public function reset($clear = false) + { + if ($clear) + { + $this->root = new Node; + $this->css = array(); + } + + $this->current = $this->root; + + return $this->current; + } + + /** + * Method to add a child + * + * @param Node $node The node to process + * @param bool $setCurrent Set this new child as the current node for further working + * + * @return Node The newly added node + * + * @since __DEPLOY_VERSION__ + */ + public function addChild(Node $node, $setCurrent = false) + { + $this->current->addChild($node); + + if ($setCurrent) + { + $this->setCurrent($node); + } + + return $node; + } + + /** + * Method to get the CSS class name for an icon identifier or create one if + * a custom image path is passed as the identifier + * + * @return string CSS class name + * + * @since __DEPLOY_VERSION__ + */ + public function getIconClass() + { + static $classes = array(); + + $identifier = $this->current->get('class'); + + // Top level is special + if (trim($identifier) == '' || !$this->current->hasParent()) + { + return null; + } + + if (!isset($classes[$identifier])) + { + // We were passed a class name + if (substr($identifier, 0, 6) == 'class:') + { + $class = substr($identifier, 6); + } + // We were passed background icon url. Build the CSS class for the icon + else + { + $class = preg_replace('#\.[^.]*$#', '', basename($identifier)); + $class = preg_replace('#\.\.[^A-Za-z0-9\.\_\- ]#', '', $class); + + if ($class) + { + $this->css[] = ".menu-$class {background: url($identifier) no-repeat;}"; + } + } + + $classes[$identifier] = "menu-$class"; + } + + return $classes[$identifier]; + } + + /** + * Get the CSS declarations for this tree + * + * @return string[] + * + * @since __DEPLOY_VERSION__ + */ + public function getCss() + { + return $this->css; + } +} diff --git a/modules/mod_articles_category/helper.php b/modules/mod_articles_category/helper.php index dbae6b2ec2fee..0033c725bc874 100644 --- a/modules/mod_articles_category/helper.php +++ b/modules/mod_articles_category/helper.php @@ -51,7 +51,7 @@ public static function getList(&$params) $articles->setState('filter.published', 1); // This module does not use tags data - $articles->setState('load_tags', false); + $articles->setState('load_tags', $params->get('filter_tag', '') !== '' ? true : false); // Access filter $access = !JComponentHelper::getParams('com_content')->get('show_noauth'); @@ -195,6 +195,10 @@ public static function getList(&$params) } // New Parameters + if ($params->get('filter_tag', '')) + { + $articles->setState('filter.tag', $params->get('filter_tag', '')); + } $articles->setState('filter.featured', $params->get('show_front', 'show')); $articles->setState('filter.author_id', $params->get('created_by', '')); $articles->setState('filter.author_id.include', $params->get('author_filtering_type', 1)); diff --git a/modules/mod_articles_category/mod_articles_category.xml b/modules/mod_articles_category/mod_articles_category.xml index e00420f2024af..ef955d661f03e 100644 --- a/modules/mod_articles_category/mod_articles_category.xml +++ b/modules/mod_articles_category/mod_articles_category.xml @@ -78,7 +78,7 @@ @@ -127,6 +127,21 @@ default="1" /> + + + + + +