mirror of https://github.com/joomla/joomla-cms.git
910 lines
32 KiB
PHP
910 lines
32 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @package Joomla.Administrator
|
|
* @subpackage com_menus
|
|
*
|
|
* @copyright (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
|
|
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
|
*/
|
|
|
|
namespace Joomla\Component\Menus\Administrator\Helper;
|
|
|
|
use Joomla\CMS\Application\ApplicationHelper;
|
|
use Joomla\CMS\Component\ComponentHelper;
|
|
use Joomla\CMS\Factory;
|
|
use Joomla\CMS\Filesystem\Folder;
|
|
use Joomla\CMS\Helper\ContentHelper;
|
|
use Joomla\CMS\Language\Associations;
|
|
use Joomla\CMS\Language\Multilanguage;
|
|
use Joomla\CMS\Language\Text;
|
|
use Joomla\CMS\Menu\AdministratorMenuItem;
|
|
use Joomla\CMS\Table\Table;
|
|
use Joomla\Database\DatabaseInterface;
|
|
use Joomla\Database\ParameterType;
|
|
use Joomla\Filesystem\File;
|
|
use Joomla\Registry\Registry;
|
|
|
|
// phpcs:disable PSR1.Files.SideEffects
|
|
\defined('_JEXEC') or die;
|
|
// phpcs:enable PSR1.Files.SideEffects
|
|
|
|
/**
|
|
* Menus component helper.
|
|
*
|
|
* @since 1.6
|
|
*/
|
|
class MenusHelper extends ContentHelper
|
|
{
|
|
/**
|
|
* Defines the valid request variables for the reverse lookup.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected static $_filter = ['option', 'view', 'layout'];
|
|
|
|
/**
|
|
* List of preset include paths
|
|
*
|
|
* @var array
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
protected static $presets = null;
|
|
|
|
/**
|
|
* Gets a standard form of a link for lookups.
|
|
*
|
|
* @param mixed $request A link string or array of request variables.
|
|
*
|
|
* @return mixed A link in standard option-view-layout form, or false if the supplied response is invalid.
|
|
*
|
|
* @since 1.6
|
|
*/
|
|
public static function getLinkKey($request)
|
|
{
|
|
if (empty($request)) {
|
|
return false;
|
|
}
|
|
|
|
// Check if the link is in the form of index.php?...
|
|
if (is_string($request)) {
|
|
$args = [];
|
|
|
|
if (strpos($request, 'index.php') === 0) {
|
|
parse_str(parse_url(htmlspecialchars_decode($request), PHP_URL_QUERY), $args);
|
|
} else {
|
|
parse_str($request, $args);
|
|
}
|
|
|
|
$request = $args;
|
|
}
|
|
|
|
// Only take the option, view and layout parts.
|
|
foreach ($request as $name => $value) {
|
|
if ((!in_array($name, self::$_filter)) && (!($name == 'task' && !array_key_exists('view', $request)))) {
|
|
// Remove the variables we want to ignore.
|
|
unset($request[$name]);
|
|
}
|
|
}
|
|
|
|
ksort($request);
|
|
|
|
return 'index.php?' . http_build_query($request, '', '&');
|
|
}
|
|
|
|
/**
|
|
* Get the menu list for create a menu module
|
|
*
|
|
* @param int $clientId Optional client id - viz 0 = site, 1 = administrator, can be NULL for all
|
|
*
|
|
* @return array The menu array list
|
|
*
|
|
* @since 1.6
|
|
*/
|
|
public static function getMenuTypes($clientId = 0)
|
|
{
|
|
$db = Factory::getDbo();
|
|
$query = $db->getQuery(true)
|
|
->select($db->quoteName('a.menutype'))
|
|
->from($db->quoteName('#__menu_types', 'a'));
|
|
|
|
if (isset($clientId)) {
|
|
$clientId = (int) $clientId;
|
|
$query->where($db->quoteName('a.client_id') . ' = :clientId')
|
|
->bind(':clientId', $clientId, ParameterType::INTEGER);
|
|
}
|
|
|
|
$db->setQuery($query);
|
|
|
|
return $db->loadColumn();
|
|
}
|
|
|
|
/**
|
|
* Get a list of menu links for one or all menus.
|
|
*
|
|
* @param string $menuType An option menu to filter the list on, otherwise all menu with given client id links
|
|
* are returned as a grouped array.
|
|
* @param integer $parentId An optional parent ID to pivot results around.
|
|
* @param integer $mode An optional mode. If parent ID is set and mode=2, the parent and children are excluded from the list.
|
|
* @param array $published An optional array of states
|
|
* @param array $languages Optional array of specify which languages we want to filter
|
|
* @param int $clientId Optional client id - viz 0 = site, 1 = administrator, can be NULL for all (used only if menutype not given)
|
|
*
|
|
* @return array|boolean
|
|
*
|
|
* @since 1.6
|
|
*/
|
|
public static function getMenuLinks($menuType = null, $parentId = 0, $mode = 0, $published = [], $languages = [], $clientId = 0)
|
|
{
|
|
$hasClientId = $clientId !== null;
|
|
$clientId = (int) $clientId;
|
|
|
|
$db = Factory::getDbo();
|
|
$query = $db->getQuery(true)
|
|
->select(
|
|
[
|
|
'DISTINCT ' . $db->quoteName('a.id', 'value'),
|
|
$db->quoteName('a.title', 'text'),
|
|
$db->quoteName('a.alias'),
|
|
$db->quoteName('a.level'),
|
|
$db->quoteName('a.menutype'),
|
|
$db->quoteName('a.client_id'),
|
|
$db->quoteName('a.type'),
|
|
$db->quoteName('a.published'),
|
|
$db->quoteName('a.template_style_id'),
|
|
$db->quoteName('a.checked_out'),
|
|
$db->quoteName('a.language'),
|
|
$db->quoteName('a.lft'),
|
|
$db->quoteName('e.name', 'componentname'),
|
|
$db->quoteName('e.element'),
|
|
]
|
|
)
|
|
->from($db->quoteName('#__menu', 'a'))
|
|
->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('a.component_id'));
|
|
|
|
if (Multilanguage::isEnabled()) {
|
|
$query->select(
|
|
[
|
|
$db->quoteName('l.title', 'language_title'),
|
|
$db->quoteName('l.image', 'language_image'),
|
|
$db->quoteName('l.sef', 'language_sef'),
|
|
]
|
|
)
|
|
->join('LEFT', $db->quoteName('#__languages', 'l'), $db->quoteName('l.lang_code') . ' = ' . $db->quoteName('a.language'));
|
|
}
|
|
|
|
// Filter by the type if given, this is more specific than client id
|
|
if ($menuType) {
|
|
$query->where('(' . $db->quoteName('a.menutype') . ' = :menuType OR ' . $db->quoteName('a.parent_id') . ' = 0)')
|
|
->bind(':menuType', $menuType);
|
|
} elseif ($hasClientId) {
|
|
$query->where($db->quoteName('a.client_id') . ' = :clientId')
|
|
->bind(':clientId', $clientId, ParameterType::INTEGER);
|
|
}
|
|
|
|
// Prevent the parent and children from showing if requested.
|
|
if ($parentId && $mode == 2) {
|
|
$query->join('LEFT', $db->quoteName('#__menu', 'p'), $db->quoteName('p.id') . ' = :parentId')
|
|
->where(
|
|
'(' . $db->quoteName('a.lft') . ' <= ' . $db->quoteName('p.lft')
|
|
. ' OR ' . $db->quoteName('a.rgt') . ' >= ' . $db->quoteName('p.rgt') . ')'
|
|
)
|
|
->bind(':parentId', $parentId, ParameterType::INTEGER);
|
|
}
|
|
|
|
if (!empty($languages)) {
|
|
$query->whereIn($db->quoteName('a.language'), (array) $languages, ParameterType::STRING);
|
|
}
|
|
|
|
if (!empty($published)) {
|
|
$query->whereIn($db->quoteName('a.published'), (array) $published);
|
|
}
|
|
|
|
$query->where($db->quoteName('a.published') . ' != -2');
|
|
$query->order($db->quoteName('a.lft') . ' ASC');
|
|
|
|
try {
|
|
// Get the options.
|
|
$db->setQuery($query);
|
|
$links = $db->loadObjectList();
|
|
} catch (\RuntimeException $e) {
|
|
Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
|
|
|
|
return false;
|
|
}
|
|
|
|
if (empty($menuType)) {
|
|
// If the menutype is empty, group the items by menutype.
|
|
$query = $db->getQuery(true)
|
|
->select('*')
|
|
->from($db->quoteName('#__menu_types'))
|
|
->where($db->quoteName('menutype') . ' <> ' . $db->quote(''))
|
|
->order(
|
|
[
|
|
$db->quoteName('title'),
|
|
$db->quoteName('menutype'),
|
|
]
|
|
);
|
|
|
|
if ($hasClientId) {
|
|
$query->where($db->quoteName('client_id') . ' = :clientId')
|
|
->bind(':clientId', $clientId, ParameterType::INTEGER);
|
|
}
|
|
|
|
try {
|
|
$db->setQuery($query);
|
|
$menuTypes = $db->loadObjectList();
|
|
} catch (\RuntimeException $e) {
|
|
Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
|
|
|
|
return false;
|
|
}
|
|
|
|
// Create a reverse lookup and aggregate the links.
|
|
$rlu = [];
|
|
|
|
foreach ($menuTypes as &$type) {
|
|
$rlu[$type->menutype] = & $type;
|
|
$type->links = [];
|
|
}
|
|
|
|
// Loop through the list of menu links.
|
|
foreach ($links as &$link) {
|
|
if (isset($rlu[$link->menutype])) {
|
|
$rlu[$link->menutype]->links[] = & $link;
|
|
|
|
// Cleanup garbage.
|
|
unset($link->menutype);
|
|
}
|
|
}
|
|
|
|
return $menuTypes;
|
|
} else {
|
|
return $links;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the associations
|
|
*
|
|
* @param integer $pk Menu item id
|
|
*
|
|
* @return array
|
|
*
|
|
* @since 3.0
|
|
*/
|
|
public static function getAssociations($pk)
|
|
{
|
|
$langAssociations = Associations::getAssociations('com_menus', '#__menu', 'com_menus.item', $pk, 'id', '', '');
|
|
$associations = [];
|
|
|
|
foreach ($langAssociations as $langAssociation) {
|
|
$associations[$langAssociation->language] = $langAssociation->id;
|
|
}
|
|
|
|
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 AdministratorMenuItem A root node with the menu items as children
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
public static function getMenuItems($menutype, $enabledOnly = false, $exclude = [])
|
|
{
|
|
$root = new AdministratorMenuItem();
|
|
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
|
$query = $db->getQuery(true);
|
|
|
|
// Prepare the query.
|
|
$query->select($db->quoteName('m') . '.*')
|
|
->from($db->quoteName('#__menu', 'm'))
|
|
->where(
|
|
[
|
|
$db->quoteName('m.menutype') . ' = :menutype',
|
|
$db->quoteName('m.client_id') . ' = 1',
|
|
$db->quoteName('m.id') . ' > 1',
|
|
]
|
|
)
|
|
->bind(':menutype', $menutype);
|
|
|
|
if ($enabledOnly) {
|
|
$query->where($db->quoteName('m.published') . ' = 1');
|
|
}
|
|
|
|
// Filter on the enabled states.
|
|
$query->select($db->quoteName('e.element'))
|
|
->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('m.component_id') . ' = ' . $db->quoteName('e.extension_id'))
|
|
->extendWhere(
|
|
'AND',
|
|
[
|
|
$db->quoteName('e.enabled') . ' = 1',
|
|
$db->quoteName('e.enabled') . ' IS NULL',
|
|
],
|
|
'OR'
|
|
);
|
|
|
|
if (count($exclude)) {
|
|
$exId = array_map('intval', array_filter($exclude, 'is_numeric'));
|
|
$exEl = array_filter($exclude, 'is_string');
|
|
|
|
if ($exId) {
|
|
$query->whereNotIn($db->quoteName('m.id'), $exId)
|
|
->whereNotIn($db->quoteName('m.parent_id'), $exId);
|
|
}
|
|
|
|
if ($exEl) {
|
|
$query->whereNotIn($db->quoteName('e.element'), $exEl, ParameterType::STRING);
|
|
}
|
|
}
|
|
|
|
// Order by lft.
|
|
$query->order($db->quoteName('m.lft'));
|
|
|
|
try {
|
|
$menuItems = [];
|
|
$iterator = $db->setQuery($query)->getIterator();
|
|
|
|
foreach ($iterator as $item) {
|
|
$menuItems[$item->id] = new AdministratorMenuItem((array) $item);
|
|
}
|
|
|
|
unset($iterator);
|
|
|
|
foreach ($menuItems as $menuitem) {
|
|
// Resolve the alias item to get the original item
|
|
if ($menuitem->type == 'alias') {
|
|
static::resolveAlias($menuitem);
|
|
}
|
|
|
|
if ($menuitem->link = in_array($menuitem->type, ['separator', 'heading', 'container']) ? '#' : trim($menuitem->link)) {
|
|
$menuitem->submenu = [];
|
|
$menuitem->class = $menuitem->img ?? '';
|
|
$menuitem->scope = $menuitem->scope ?? null;
|
|
$menuitem->target = $menuitem->browserNav ? '_blank' : '';
|
|
}
|
|
|
|
$menuitem->ajaxbadge = $menuitem->getParams()->get('ajax-badge');
|
|
$menuitem->dashboard = $menuitem->getParams()->get('dashboard');
|
|
|
|
if ($menuitem->parent_id > 1) {
|
|
if (isset($menuItems[$menuitem->parent_id])) {
|
|
$menuItems[$menuitem->parent_id]->addChild($menuitem);
|
|
}
|
|
} else {
|
|
$root->addChild($menuitem);
|
|
}
|
|
}
|
|
} catch (\RuntimeException $e) {
|
|
Factory::getApplication()->enqueueMessage(Text::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error');
|
|
}
|
|
|
|
return $root;
|
|
}
|
|
|
|
/**
|
|
* 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 4.0.0
|
|
*/
|
|
public static function installPreset($preset, $menutype)
|
|
{
|
|
$root = static::loadPreset($preset, false);
|
|
|
|
if (count($root->getChildren()) == 0) {
|
|
throw new \Exception(Text::_('COM_MENUS_PRESET_LOAD_FAILED'));
|
|
}
|
|
|
|
static::installPresetItems($root, $menutype);
|
|
}
|
|
|
|
/**
|
|
* Method to install a preset menu item into database and link it to the given menutype
|
|
*
|
|
* @param AdministratorMenuItem $node The parent node of the items to process
|
|
* @param string $menutype The target menutype
|
|
*
|
|
* @return void
|
|
*
|
|
* @throws \Exception
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
protected static function installPresetItems($node, $menutype)
|
|
{
|
|
$db = Factory::getDbo();
|
|
$query = $db->getQuery(true);
|
|
$items = $node->getChildren();
|
|
|
|
static $components = [];
|
|
|
|
if (!$components) {
|
|
$query->select(
|
|
[
|
|
$db->quoteName('extension_id'),
|
|
$db->quoteName('element'),
|
|
]
|
|
)
|
|
->from($db->quoteName('#__extensions'))
|
|
->where($db->quoteName('type') . ' = ' . $db->quote('component'));
|
|
$components = $db->setQuery($query)->loadObjectList();
|
|
$components = array_column((array) $components, 'element', 'extension_id');
|
|
}
|
|
|
|
Factory::getApplication()->triggerEvent('onPreprocessMenuItems', ['com_menus.administrator.import', &$items, null, true]);
|
|
|
|
foreach ($items as $item) {
|
|
/** @var \Joomla\CMS\Table\Menu $table */
|
|
$table = Table::getInstance('Menu');
|
|
|
|
$item->alias = $menutype . '-' . $item->title;
|
|
|
|
// Temporarily set unicodeslugs if a menu item has an unicode alias
|
|
$unicode = Factory::getApplication()->set('unicodeslugs', 1);
|
|
$item->alias = ApplicationHelper::stringURLSafe($item->alias);
|
|
Factory::getApplication()->set('unicodeslugs', $unicode);
|
|
|
|
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 = [
|
|
'menutype' => $menutype,
|
|
'type' => $item->type,
|
|
'title' => $item->title,
|
|
'parent_id' => (int) $item->getParent()->id,
|
|
'client_id' => 1,
|
|
];
|
|
$table->load($keys);
|
|
} elseif ($item->type == 'url' || $item->type == 'component') {
|
|
if (substr($item->link, 0, 8) === 'special:') {
|
|
$special = substr($item->link, 8);
|
|
|
|
if ($special === 'language-forum') {
|
|
$item->link = 'index.php?option=com_admin&view=help&layout=langforum';
|
|
} elseif ($special === 'custom-forum') {
|
|
$item->link = '';
|
|
}
|
|
}
|
|
|
|
// Try to match an existing record to have minimum collision for a link
|
|
$keys = [
|
|
'menutype' => $menutype,
|
|
'type' => $item->type,
|
|
'link' => $item->link,
|
|
'parent_id' => (int) $item->getParent()->id,
|
|
'client_id' => 1,
|
|
];
|
|
$table->load($keys);
|
|
}
|
|
|
|
// Translate "hideitems" param value from "element" into "menu-item-id"
|
|
if ($item->type == 'container' && count($hideitems = (array) $item->getParams()->get('hideitems'))) {
|
|
foreach ($hideitems as &$hel) {
|
|
if (!is_numeric($hel)) {
|
|
$hel = array_search($hel, $components);
|
|
}
|
|
}
|
|
|
|
$query = $db->getQuery(true)
|
|
->select($db->quoteName('id'))
|
|
->from($db->quoteName('#__menu'))
|
|
->whereIn($db->quoteName('component_id'), $hideitems);
|
|
$hideitems = $db->setQuery($query)->loadColumn();
|
|
|
|
$item->getParams()->set('hideitems', $hideitems);
|
|
}
|
|
|
|
$record = [
|
|
'menutype' => $menutype,
|
|
'title' => $item->title,
|
|
'alias' => $item->alias,
|
|
'type' => $item->type,
|
|
'link' => $item->link,
|
|
'browserNav' => $item->browserNav,
|
|
'img' => $item->class,
|
|
'access' => $item->access,
|
|
'component_id' => array_search($item->element, $components) ?: 0,
|
|
'parent_id' => (int) $item->getParent()->id,
|
|
'client_id' => 1,
|
|
'published' => 1,
|
|
'language' => '*',
|
|
'home' => 0,
|
|
'params' => (string) $item->getParams(),
|
|
];
|
|
|
|
if (!$table->bind($record)) {
|
|
throw new \Exception($table->getError());
|
|
}
|
|
|
|
$table->setLocation($item->getParent()->id, 'last-child');
|
|
|
|
if (!$table->check()) {
|
|
throw new \Exception($table->getError());
|
|
}
|
|
|
|
if (!$table->store()) {
|
|
throw new \Exception($table->getError());
|
|
}
|
|
|
|
$item->id = $table->get('id');
|
|
|
|
if ($item->hasChildren()) {
|
|
static::installPresetItems($item, $menutype);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a custom preset externally via plugin or any other means.
|
|
* WARNING: Presets with same name will replace previously added preset *except* Joomla's default preset (joomla)
|
|
*
|
|
* @param string $name The unique identifier for the preset.
|
|
* @param string $title The display label for the preset.
|
|
* @param string $path The path to the preset file.
|
|
* @param bool $replace Whether to replace the preset with the same name if any (except 'joomla').
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
public static function addPreset($name, $title, $path, $replace = true)
|
|
{
|
|
if (static::$presets === null) {
|
|
static::getPresets();
|
|
}
|
|
|
|
if ($name == 'joomla') {
|
|
$replace = false;
|
|
}
|
|
|
|
if (($replace || !array_key_exists($name, static::$presets)) && is_file($path)) {
|
|
$preset = new \stdClass();
|
|
|
|
$preset->name = $name;
|
|
$preset->title = $title;
|
|
$preset->path = $path;
|
|
|
|
static::$presets[$name] = $preset;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a list of available presets.
|
|
*
|
|
* @return \stdClass[]
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
public static function getPresets()
|
|
{
|
|
if (static::$presets === null) {
|
|
// Important: 'null' will cause infinite recursion.
|
|
static::$presets = [];
|
|
|
|
$components = ComponentHelper::getComponents();
|
|
$lang = Factory::getApplication()->getLanguage();
|
|
|
|
foreach ($components as $component) {
|
|
if (!$component->enabled) {
|
|
continue;
|
|
}
|
|
|
|
$folder = JPATH_ADMINISTRATOR . '/components/' . $component->option . '/presets/';
|
|
|
|
if (!Folder::exists($folder)) {
|
|
continue;
|
|
}
|
|
|
|
$lang->load($component->option . '.sys', JPATH_ADMINISTRATOR)
|
|
|| $lang->load($component->option . '.sys', JPATH_ADMINISTRATOR . '/components/' . $component->option);
|
|
|
|
$presets = Folder::files($folder, '.xml');
|
|
|
|
foreach ($presets as $preset) {
|
|
$name = File::stripExt($preset);
|
|
$title = strtoupper($component->option . '_MENUS_PRESET_' . $name);
|
|
static::addPreset($name, $title, $folder . $preset);
|
|
}
|
|
}
|
|
|
|
// Load from template folder automatically
|
|
$app = Factory::getApplication();
|
|
$tpl = JPATH_THEMES . '/' . $app->getTemplate() . '/html/com_menus/presets';
|
|
|
|
if (is_dir($tpl)) {
|
|
$files = Folder::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?
|
|
* @param AdministratorMenuItem $parent Root node of the menu
|
|
*
|
|
* @return AdministratorMenuItem
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
public static function loadPreset($name, $fallback = true, $parent = null)
|
|
{
|
|
$presets = static::getPresets();
|
|
|
|
if (!$parent) {
|
|
$parent = new AdministratorMenuItem();
|
|
}
|
|
|
|
if (isset($presets[$name]) && ($xml = simplexml_load_file($presets[$name]->path, null, LIBXML_NOCDATA)) && $xml instanceof \SimpleXMLElement) {
|
|
static::loadXml($xml, $parent);
|
|
} elseif ($fallback && isset($presets['default'])) {
|
|
if (($xml = simplexml_load_file($presets['default']->path, null, LIBXML_NOCDATA)) && $xml instanceof \SimpleXMLElement) {
|
|
static::loadXml($xml, $parent);
|
|
}
|
|
}
|
|
|
|
return $parent;
|
|
}
|
|
|
|
/**
|
|
* Method to resolve the menu item alias type menu item
|
|
*
|
|
* @param AdministratorMenuItem &$item The alias object
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
public static function resolveAlias(&$item)
|
|
{
|
|
$obj = $item;
|
|
|
|
while ($obj->type == 'alias') {
|
|
$aliasTo = (int) $obj->getParams()->get('aliasoptions');
|
|
|
|
$db = Factory::getDbo();
|
|
$query = $db->getQuery(true);
|
|
$query->select(
|
|
[
|
|
$db->quoteName('a.id'),
|
|
$db->quoteName('a.link'),
|
|
$db->quoteName('a.type'),
|
|
$db->quoteName('e.element'),
|
|
]
|
|
)
|
|
->from($db->quoteName('#__menu', 'a'))
|
|
->join('LEFT', $db->quoteName('#__extensions', 'e'), $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('a.component_id'))
|
|
->where($db->quoteName('a.id') . ' = :aliasTo')
|
|
->bind(':aliasTo', $aliasTo, ParameterType::INTEGER);
|
|
|
|
try {
|
|
$obj = new AdministratorMenuItem($db->setQuery($query)->loadAssoc());
|
|
|
|
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 AdministratorMenuItem $item Menu item to preprocess
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
public static function preprocess($item)
|
|
{
|
|
// Resolve the alias item to get the original item
|
|
if ($item->type == 'alias') {
|
|
static::resolveAlias($item);
|
|
}
|
|
|
|
if ($item->link = in_array($item->type, ['separator', 'heading', 'container']) ? '#' : trim($item->link)) {
|
|
$item->class = $item->img ?? '';
|
|
$item->scope = $item->scope ?? null;
|
|
$item->target = $item->browserNav ? '_blank' : '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load a menu tree from an XML file
|
|
*
|
|
* @param \SimpleXMLElement[] $elements The xml menuitem nodes
|
|
* @param AdministratorMenuItem $parent The menu hierarchy list to be populated
|
|
* @param string[] $replace The substring replacements for iterator type items
|
|
*
|
|
* @return void
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
protected static function loadXml($elements, $parent, $replace = [])
|
|
{
|
|
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'];
|
|
$lJoin = (string) $element['sql_leftjoin'];
|
|
$iJoin = (string) $element['sql_innerjoin'];
|
|
|
|
$db = Factory::getDbo();
|
|
$query = $db->getQuery(true);
|
|
$query->select($select)->from($from);
|
|
|
|
if ($where) {
|
|
$query->where($where);
|
|
}
|
|
|
|
if ($order) {
|
|
$query->order($order);
|
|
}
|
|
|
|
if ($group) {
|
|
$query->group($group);
|
|
}
|
|
|
|
if ($lJoin) {
|
|
$query->join('LEFT', $lJoin);
|
|
}
|
|
|
|
if ($iJoin) {
|
|
$query->join('INNER', $iJoin);
|
|
}
|
|
|
|
$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) {
|
|
$child = static::parseXmlNode($element, $replace);
|
|
$parent->addChild($child);
|
|
}
|
|
|
|
// Iterate over the matching records, items goes in the same level (not $item->submenu) as this node.
|
|
if ('self' == (string) $element['sql_target']) {
|
|
foreach ($results as $result) {
|
|
static::loadXml($element->menuitem, $child, $result);
|
|
}
|
|
} else {
|
|
foreach ($results as $result) {
|
|
static::loadXml($element->menuitem, $parent, $result);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$item = static::parseXmlNode($element, $replace);
|
|
|
|
// Process the child nodes
|
|
static::loadXml($element->menuitem, $item, $replace);
|
|
|
|
$parent->addChild($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 4.0.0
|
|
*/
|
|
protected static function parseXmlNode($node, $replace = [])
|
|
{
|
|
$item = new AdministratorMenuItem();
|
|
|
|
$item->id = null;
|
|
$item->type = (string) $node['type'];
|
|
$item->title = (string) $node['title'];
|
|
$item->alias = (string) $node['alias'];
|
|
$item->link = (string) $node['link'];
|
|
$item->target = (string) $node['target'];
|
|
$item->element = (string) $node['element'];
|
|
$item->class = (string) $node['class'];
|
|
$item->icon = (string) $node['icon'];
|
|
$item->access = (int) $node['access'];
|
|
$item->scope = (string) $node['scope'] ?: 'default';
|
|
$item->ajaxbadge = (string) $node['ajax-badge'];
|
|
$item->dashboard = (string) $node['dashboard'];
|
|
|
|
$params = new Registry(trim($node->params));
|
|
$params->set('menu-permission', (string) $node['permission']);
|
|
|
|
if ($item->type == 'separator' && trim($item->title, '- ')) {
|
|
$params->set('text_separator', 1);
|
|
}
|
|
|
|
if ($item->type == 'heading' || $item->type == 'container') {
|
|
$item->link = '#';
|
|
}
|
|
|
|
if ((string) $node['quicktask']) {
|
|
$params->set('menu-quicktask', (string) $node['quicktask']);
|
|
$params->set('menu-quicktask-title', (string) $node['quicktask-title']);
|
|
$params->set('menu-quicktask-icon', (string) $node['quicktask-icon']);
|
|
$params->set('menu-quicktask-permission', (string) $node['quicktask-permission']);
|
|
}
|
|
|
|
if ($item->ajaxbadge) {
|
|
$params->set('ajax-badge', $item->ajaxbadge);
|
|
}
|
|
|
|
if ($item->dashboard) {
|
|
$params->set('dashboard', $item->dashboard);
|
|
}
|
|
|
|
// 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);
|
|
$item->class = str_replace("{sql:$var}", $val, $item->class);
|
|
$item->icon = str_replace("{sql:$var}", $val, $item->icon);
|
|
$params->set('menu-quicktask', str_replace("{sql:$var}", $val, $params->get('menu-quicktask', '')));
|
|
}
|
|
|
|
$item->setParams($params);
|
|
|
|
return $item;
|
|
}
|
|
}
|