Spaces:
No application file
No application file
namespace Mautic\CoreBundle\Menu; | |
use Mautic\CoreBundle\Helper\CoreParametersHelper; | |
use Mautic\CoreBundle\Security\Permissions\CorePermissions; | |
use Mautic\PluginBundle\Helper\IntegrationHelper; | |
use Symfony\Component\HttpFoundation\RequestStack; | |
class MenuHelper | |
{ | |
/** | |
* Stores items that are assigned to another parent outside it's bundle. | |
*/ | |
private array $orphans = []; | |
public function __construct( | |
protected CorePermissions $security, | |
protected RequestStack $requestStack, | |
private CoreParametersHelper $coreParametersHelper, | |
protected IntegrationHelper $integrationHelper | |
) { | |
} | |
/** | |
* Converts menu config into something KNP menus expects. | |
* | |
* @param int $depth | |
* @param int $defaultPriority | |
* @param string $type | |
*/ | |
public function createMenuStructure(&$items, $depth = 0, $defaultPriority = 9999, $type = 'main'): void | |
{ | |
foreach ($items as $k => &$i) { | |
if (!is_array($i) || empty($i)) { | |
continue; | |
} | |
// Remove the item if the checks fail | |
if (false === $this->handleChecks($i)) { | |
unset($items[$k]); | |
continue; | |
} | |
// Set ID to route name | |
if (!isset($i['id'])) { | |
if (!empty($i['route'])) { | |
$i['id'] = $i['route']; | |
} else { | |
$i['id'] = 'menu-item-'.uniqid(); | |
} | |
} | |
// Set link attributes | |
if (!isset($i['linkAttributes'])) { | |
$i['linkAttributes'] = [ | |
'data-menu-link' => $i['id'], | |
'id' => $i['id'], | |
]; | |
} elseif (!isset($i['linkAttributes']['id'])) { | |
$i['linkAttributes']['id'] = $i['id']; | |
$i['linkAttributes']['data-menu-link'] = $i['id']; | |
} elseif (!isset($i['linkAttributes']['data-menu-link'])) { | |
$i['linkAttributes']['data-menu-link'] = $i['id']; | |
} | |
$i['extras'] = []; | |
$i['extras']['depth'] = $depth; | |
// Note a divider | |
if (!empty($i['divider'])) { | |
$i['extras']['divider'] = true; | |
} | |
// Note a header | |
if (!empty($i['header'])) { | |
$i['extras']['header'] = $i['header']; | |
} | |
// Set the icon class for the menu item | |
if (!empty($i['iconClass'])) { | |
$i['extras']['iconClass'] = $i['iconClass']; | |
} | |
// Set the actual route name so that it's available to the menu template | |
if (isset($i['route'])) { | |
$i['extras']['routeName'] = $i['route']; | |
} | |
// Repeat for sub items | |
if (isset($i['children'])) { | |
$this->createMenuStructure($i['children'], $depth + 1, $defaultPriority); | |
} | |
// Determine if this item needs to be listed in a bundle outside it's own | |
if (isset($i['parent'])) { | |
if (!isset($this->orphans[$type])) { | |
$this->orphans[$type] = []; | |
} | |
if (!isset($this->orphans[$type][$i['parent']])) { | |
$this->orphans[$type][$i['parent']] = []; | |
} | |
$this->orphans[$type][$i['parent']][$k] = $i; | |
unset($items[$k]); | |
// Don't set a default priority here as it'll assume that of it's parent | |
} elseif (!isset($i['priority'])) { | |
// Ensure a priority for non-orphans | |
$i['priority'] = $defaultPriority; | |
} | |
} | |
} | |
/** | |
* Get and reset orphaned menu items. | |
* | |
* @param string $type | |
* | |
* @return mixed | |
*/ | |
public function resetOrphans($type = 'main') | |
{ | |
$orphans = $this->orphans[$type] ?? []; | |
$this->orphans[$type] = []; | |
return $orphans; | |
} | |
/** | |
* Give orphaned menu items a home. | |
* | |
* @param bool $appendOrphans | |
* @param int $depth | |
*/ | |
public function placeOrphans(array &$menuItems, $appendOrphans = false, $depth = 1, $type = 'main'): void | |
{ | |
foreach ($menuItems as $key => &$items) { | |
if (isset($this->orphans[$type]) && isset($this->orphans[$type][$key])) { | |
$priority = $items['priority'] ?? 9999; | |
foreach ($this->orphans[$type][$key] as &$orphan) { | |
if (!isset($orphan['extras'])) { | |
$orphan['extras'] = []; | |
} | |
$orphan['extras']['depth'] = $depth; | |
if (!isset($orphan['priority'])) { | |
$orphan['priority'] = $priority; | |
} | |
} | |
$items['children'] = | |
(!isset($items['children'])) | |
? | |
$this->orphans[$type][$key] | |
: | |
array_merge($items['children'], $this->orphans[$type][$key]); | |
unset($this->orphans[$type][$key]); | |
} elseif (isset($items['children'])) { | |
foreach ($items['children'] as $subItems) { | |
$this->placeOrphans($subItems, false, $depth + 1, $type); | |
} | |
} | |
} | |
// Append orphans that couldn't find a home | |
if ($appendOrphans && !empty($this->orphans[$type])) { | |
$menuItems = array_merge($menuItems, $this->orphans[$type]); | |
$this->orphans[$type] = []; | |
} | |
} | |
/** | |
* Sort menu items by priority. | |
*/ | |
public function sortByPriority(&$menuItems, $defaultPriority = 9999): void | |
{ | |
foreach ($menuItems as &$items) { | |
$parentPriority = $items['priority'] ?? $defaultPriority; | |
if (isset($items['children'])) { | |
$this->sortByPriority($items['children'], $parentPriority); | |
} | |
} | |
uasort( | |
$menuItems, | |
function ($a, $b) use ($defaultPriority): int { | |
$ap = (isset($a['priority']) ? (int) $a['priority'] : $defaultPriority); | |
$bp = (isset($b['priority']) ? (int) $b['priority'] : $defaultPriority); | |
return $bp <=> $ap; | |
} | |
); | |
} | |
/** | |
* @return mixed | |
*/ | |
protected function getParameter($name) | |
{ | |
return $this->coreParametersHelper->get($name, false); | |
} | |
/** | |
* @param string $integrationName | |
*/ | |
protected function handleIntegrationChecks($integrationName, array $config): bool | |
{ | |
$integration = $this->integrationHelper->getIntegrationObject($integrationName); | |
if (!$integration) { | |
return false; | |
} | |
$settings = $integration->getIntegrationSettings(); | |
$passChecks = true; | |
foreach ($config as $key => $value) { | |
switch ($key) { | |
case 'enabled': | |
$passChecks = $settings->getIsPublished() == $value; | |
break; | |
case 'features': | |
$supportedFeatures = $settings->getSupportedFeatures(); | |
foreach ($value as $featureName) { | |
if (!in_array($featureName, $supportedFeatures)) { | |
$passChecks = false; | |
break; | |
} | |
} | |
break; | |
} | |
} | |
return $passChecks; | |
} | |
/** | |
* @param string $name | |
* @param mixed $value | |
*/ | |
protected function handleParametersChecks($name, $value): bool | |
{ | |
return $this->getParameter($name) == $value; | |
} | |
/** | |
* @param string $name | |
* @param mixed $value | |
*/ | |
protected function handleRequestChecks($name, $value): bool | |
{ | |
return $this->requestStack->getCurrentRequest()->get($name) == $value; | |
} | |
/** | |
* @return bool | |
*/ | |
protected function handleAccessCheck($accessLevel) | |
{ | |
return match ($accessLevel) { | |
'admin' => $this->security->isAdmin(), | |
default => $this->security->isGranted($accessLevel, 'MATCH_ONE'), | |
}; | |
} | |
/** | |
* Handle access check and other checks for menu items. | |
* | |
* @return bool Returns false if the item fails the access check or any other checks | |
*/ | |
protected function handleChecks(array $menuItem): bool | |
{ | |
if (isset($menuItem['access']) && false === $this->handleAccessCheck($menuItem['access'])) { | |
return false; | |
} | |
if (isset($menuItem['checks']) && is_array($menuItem['checks'])) { | |
foreach ($menuItem['checks'] as $checkGroup => $checkConfig) { | |
$checkMethod = 'handle'.ucfirst($checkGroup).'Checks'; | |
if (!method_exists($this, $checkMethod)) { | |
continue; | |
} | |
foreach ($checkConfig as $name => $value) { | |
if (false === $this->$checkMethod($name, $value)) { | |
return false; | |
} | |
} | |
} | |
} | |
return true; | |
} | |
} | |