Spaces:
No application file
No application file
namespace Mautic\CoreBundle\Twig\Helper; | |
use Mautic\CoreBundle\CoreEvents; | |
use Mautic\CoreBundle\Event\CustomButtonEvent; | |
use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Contracts\Translation\TranslatorInterface; | |
use Twig\Environment; | |
final class ButtonHelper | |
{ | |
/** | |
* List dropdown actions. | |
*/ | |
public const LOCATION_LIST_ACTIONS = 'list_actions'; | |
/** | |
* Toolbar actions. | |
*/ | |
public const LOCATION_TOOLBAR_ACTIONS = 'toolbar_actions'; | |
/** | |
* Page actions. | |
*/ | |
public const LOCATION_PAGE_ACTIONS = 'page_actions'; | |
/** | |
* Navbar actions. | |
*/ | |
public const LOCATION_NAVBAR = 'navbar_actions'; | |
/** | |
* Bulk actions. | |
*/ | |
public const LOCATION_BULK_ACTIONS = 'bulk_actions'; | |
/** | |
* Buttons are displayed in group and/or dropdown depending on button count. | |
*/ | |
public const TYPE_BUTTON_DROPDOWN = 'button-dropdown'; | |
/** | |
* Buttons are displayed in dropdown depending on button count. | |
*/ | |
public const TYPE_DROPDOWN = 'dropdown'; | |
/** | |
* Buttons are grouped together. | |
*/ | |
public const TYPE_GROUP = 'group'; | |
/** | |
* Location of the buttons. | |
* | |
* @var string | |
*/ | |
private $location; | |
/** | |
* @var string|null | |
*/ | |
private $wrapOpeningTag; | |
/** | |
* @var string|null | |
*/ | |
private $wrapClosingTag; | |
private string $groupType = self::TYPE_GROUP; | |
/** | |
* @var string|null | |
*/ | |
private $menuLink; | |
/** | |
* @var array<array<string,mixed>> | |
*/ | |
private $buttons = []; | |
/** | |
* @var int | |
*/ | |
private $buttonCount = 0; | |
private bool $buttonsFetched = false; | |
private ?Request $request = null; | |
/** | |
* @var mixed | |
*/ | |
private $item; | |
/** | |
* @var int | |
*/ | |
private $listMarker = 3; | |
public function __construct( | |
private Environment $twig, | |
private TranslatorInterface $translator, | |
private EventDispatcherInterface $dispatcher | |
) { | |
} | |
/** | |
* @param array<array<string,mixed>> $buttons | |
* | |
* @return $this | |
*/ | |
public function addButtons(array $buttons) | |
{ | |
$this->buttonCount += count($buttons); | |
$this->buttons = array_merge($this->buttons, $buttons); | |
return $this; | |
} | |
/** | |
* @param array<string,mixed> $button | |
* | |
* @return $this | |
*/ | |
public function addButton(array $button) | |
{ | |
$this->buttons[] = $button; | |
++$this->buttonCount; | |
return $this; | |
} | |
/** | |
* @param string|null $wrapOpeningTag | |
* @param string|null $wrapClosingTag | |
* | |
* @return $this | |
*/ | |
public function setWrappingTags($wrapOpeningTag, $wrapClosingTag) | |
{ | |
$this->wrapOpeningTag = $wrapOpeningTag; | |
$this->wrapClosingTag = $wrapClosingTag; | |
return $this; | |
} | |
/** | |
* @param string $groupType | |
* | |
* @return $this | |
*/ | |
public function setGroupType($groupType) | |
{ | |
$this->groupType = $groupType; | |
return $this; | |
} | |
/** | |
* @param string|null $menuLink | |
* | |
* @return $this | |
*/ | |
public function setMenuLink($menuLink) | |
{ | |
$this->menuLink = $menuLink; | |
return $this; | |
} | |
/** | |
* @return int | |
*/ | |
public function getButtonCount() | |
{ | |
return $this->buttonCount; | |
} | |
/** | |
* @param string $dropdownHtml | |
* @param string $closingDropdownHtml | |
*/ | |
public function renderButtons($dropdownHtml = '', $closingDropdownHtml = ''): string | |
{ | |
$this->fetchCustomButtons(); | |
$this->orderButtons(); | |
$content = ''; | |
$dropdownHtmlAppended = false; | |
if (!empty($this->buttons)) { | |
$buttonCount = 0; | |
foreach ($this->buttons as $button) { | |
++$buttonCount; | |
$content .= $this->buildButton($button, $buttonCount); | |
$nextButton = $buttonCount + 1; | |
if (self::TYPE_BUTTON_DROPDOWN == $this->groupType && $nextButton === $this->listMarker && $buttonCount !== $this->buttonCount) { | |
$content .= $dropdownHtml; | |
$dropdownHtmlAppended = true; | |
} | |
} | |
} | |
if ($dropdownHtmlAppended) { | |
$content .= $closingDropdownHtml; | |
} | |
return $content; | |
} | |
/** | |
* @return mixed | |
*/ | |
public function getLocation() | |
{ | |
return $this->location; | |
} | |
/** | |
* @param mixed $location | |
* | |
* @return ButtonHelper | |
*/ | |
public function setLocation($location) | |
{ | |
$this->location = $location; | |
return $this; | |
} | |
/** | |
* Reset the buttons. | |
* | |
* @param string $buttonCount | |
* @param string $groupType | |
* | |
* @return $this | |
*/ | |
public function reset(Request $request, $buttonCount, $groupType = self::TYPE_GROUP, $item = null) | |
{ | |
// @escopecz: I think there is a possible bug here | |
$this->location = $buttonCount; | |
$this->groupType = $groupType; | |
$this->buttonCount = 0; | |
$this->buttons = []; | |
$this->buttonsFetched = false; | |
$this->request = $request; | |
$this->item = $item; | |
$this->listMarker = 3; | |
$this->wrapOpeningTag = null; | |
$this->wrapClosingTag = null; | |
return $this; | |
} | |
public function getName(): string | |
{ | |
return 'buttons'; | |
} | |
/** | |
* @param array<string,mixed> $button | |
* @param int $buttonCount | |
*/ | |
private function buildButton($button, $buttonCount = 0): string | |
{ | |
$buttons = ''; | |
// Wrap links in a tag | |
if (self::TYPE_DROPDOWN == $this->groupType || (self::TYPE_BUTTON_DROPDOWN == $this->groupType && $buttonCount >= $this->listMarker)) { | |
$this->wrapOpeningTag = "<li>\n"; | |
$this->wrapClosingTag = "</li>\n"; | |
} | |
if (!isset($button['attr'])) { | |
$button['attr'] = []; | |
} | |
if (self::TYPE_GROUP == $this->groupType || (self::TYPE_BUTTON_DROPDOWN == $this->groupType && $buttonCount < $this->listMarker)) { | |
$this->addButtonClasses($button); | |
} elseif (in_array($this->groupType, [self::TYPE_BUTTON_DROPDOWN, self::TYPE_DROPDOWN])) { | |
$this->removeButtonClasses($button); | |
} | |
if (isset($button['confirm'])) { | |
$button['confirm']['btnTextAttr'] = $this->generateTextAttributes($button); | |
$buttons .= $this->wrapOpeningTag.$this->twig->render('@MauticCore/Helper/confirm.html.twig', $button['confirm']). | |
"{$this->wrapClosingTag}\n"; | |
} else { | |
$attr = $this->menuLink; | |
if (!isset($button['attr']['data-toggle'])) { | |
$button['attr']['data-toggle'] = 'ajax'; | |
} | |
$btnTextAttr = $this->generateTextAttributes($button); | |
$tooltip = $this->generateTooltipAttributes($button); | |
foreach ($button['attr'] as $k => $v) { | |
$attr .= " $k=".'"'.$v.'"'; | |
} | |
$buttonContent = (isset($button['iconClass'])) ? '<i class="'.$button['iconClass'].'"></i> ' : ''; | |
if (!empty($button['btnText'])) { | |
$buttonContent .= '<span'.$btnTextAttr.'>'.$this->translator->trans($button['btnText']).'</span>'; | |
} | |
$buttons .= "{$this->wrapOpeningTag}<a{$attr}><span{$tooltip}>{$buttonContent}</span></a>{$this->wrapClosingTag}\n"; | |
} | |
return $buttons; | |
} | |
/** | |
* @return $this | |
*/ | |
private function fetchCustomButtons() | |
{ | |
if (!$this->buttonsFetched && $this->dispatcher->hasListeners(CoreEvents::VIEW_INJECT_CUSTOM_BUTTONS)) { | |
$event = $this->dispatcher->dispatch( | |
new CustomButtonEvent($this->location, $this->request, $this->buttons, $this->item), | |
CoreEvents::VIEW_INJECT_CUSTOM_BUTTONS | |
); | |
$this->buttonsFetched = true; | |
$this->buttons = $event->getButtons(); | |
$this->buttonCount = count($this->buttons); | |
} | |
return $this; | |
} | |
/** | |
* Order buttons by priority. | |
*/ | |
private function orderButtons(): void | |
{ | |
foreach ($this->buttons as $key => $button) { | |
$this->validatePriority($this->buttons[$key]); | |
} | |
uasort( | |
$this->buttons, | |
function ($a, $b): int { | |
$ap = (isset($a['priority']) ? (int) $a['priority'] : 0); | |
$bp = (isset($b['priority']) ? (int) $b['priority'] : 0); | |
if ($ap == $bp) { | |
$aText = $bText = ''; | |
// Sort alphabetically | |
if (isset($a['confirm']) && isset($a['confirm']['btnText'])) { | |
$aText = $a['confirm']['btnText']; | |
} elseif (isset($a['btnText'])) { | |
$aText = $a['btnText']; | |
} | |
if (isset($b['confirm']) && isset($b['confirm']['btnText'])) { | |
$bText = $b['confirm']['btnText']; | |
} elseif (isset($b['btnText'])) { | |
$bText = $b['btnText']; | |
} | |
return strcasecmp($aText, $bText); | |
} | |
return ($ap > $bp) ? -1 : 1; | |
} | |
); | |
if (self::TYPE_BUTTON_DROPDOWN == $this->groupType) { | |
// Find the start of the non-primary buttons | |
$counter = 0; | |
foreach ($this->buttons as $button) { | |
++$counter; | |
if (empty($button['primary'])) { | |
$this->listMarker = $counter; | |
break; | |
} | |
} | |
if ($this->listMarker <= 1 && $this->buttonCount) { | |
// Show at least one button | |
$this->listMarker = 2; | |
} | |
} | |
} | |
/** | |
* @param array<string,mixed> $button | |
*/ | |
private function validatePriority(&$button): void | |
{ | |
if (!empty($button['primary'])) { | |
if (!isset($button['priority']) || $button['priority'] < 200) { | |
$button['priority'] = 215; | |
} | |
} elseif (!isset($button['priority'])) { | |
$button['priority'] = 0; | |
} | |
} | |
/** | |
* @param array<string,mixed> $button | |
*/ | |
private function generateTextAttributes(&$button): string | |
{ | |
$btnTextAttr = ''; | |
if (isset($button['btnTextAttr'])) { | |
foreach ($button['btnTextAttr'] as $k => $v) { | |
$btnTextAttr .= " $k=".'"'.$v.'"'; | |
} | |
} | |
if (isset($button['btnTextClass'])) { | |
$btnTextAttr .= ' class="'.$button['btnTextClass'].'"'; | |
unset($button['btnTextClass']); | |
} | |
return $btnTextAttr; | |
} | |
/** | |
* @param array<string,mixed> $button | |
*/ | |
private function generateTooltipAttributes($button): string | |
{ | |
$tooltip = ''; | |
if (isset($button['tooltip'])) { | |
$tooltip .= ' data-toggle="tooltip"'; | |
if (is_array($button['tooltip'])) { | |
foreach ($button['tooltip'] as $k => $v) { | |
if ('title' == $k) { | |
$v = $this->translator->trans($v); | |
} | |
$tooltip .= " $k=".'"'.$v.'"'; | |
} | |
} else { | |
$tooltip .= ' title="'.$this->translator->trans($button['tooltip']).'" data-placement="left"'; | |
} | |
} | |
return $tooltip; | |
} | |
/** | |
* @param array<string,mixed> $button | |
*/ | |
private function addMobileResponsiveClasses(&$button): void | |
{ | |
if (isset($button['confirm'])) { | |
$change = &$button['confirm']; | |
} else { | |
$change = &$button; | |
} | |
// Default all text to hidden for mobile | |
if (isset($change['btnTextClass'])) { | |
$change['btnTextClass'] .= ' hidden-xs hidden-sm'; | |
} else { | |
$change['btnTextClass'] = 'hidden-xs hidden-sm'; | |
} | |
} | |
/** | |
* @param array<string,mixed> $button | |
*/ | |
private function addButtonClasses(&$button): void | |
{ | |
if (isset($button['confirm'])) { | |
$addTo = &$button['confirm']; | |
} else { | |
$addTo = &$button; | |
} | |
if (!empty($addTo['btnClass'])) { | |
$addTo['attr']['class'] = $addTo['btnClass']; | |
} elseif (!isset($button['attr']['class'])) { | |
$addTo['attr']['class'] = 'btn btn-default'; | |
} elseif (!strstr($addTo['attr']['class'], 'btn-')) { | |
$addTo['attr']['class'] .= ' btn btn-default'; | |
} | |
if (self::LOCATION_PAGE_ACTIONS == $this->location) { | |
$this->addMobileResponsiveClasses($addTo); | |
} | |
} | |
/** | |
* @param array<string,mixed> $button | |
*/ | |
private function removeButtonClasses(&$button): void | |
{ | |
if (isset($button['confirm'])) { | |
$removeFrom = &$button['confirm']; | |
} else { | |
$removeFrom = &$button; | |
} | |
if (!empty($removeFrom['btnClass'])) { | |
$removeFrom['attr']['class'] = &$removeFrom['btnClass']; | |
} elseif (!isset($removeFrom['attr']['class'])) { | |
$removeFrom['attr']['class'] = ''; | |
$removeFrom['btnClass'] = false; | |
} | |
$search = [ | |
'btn-default', | |
'btn-primary', | |
'btn-success', | |
'btn-info', | |
'btn-warning', | |
'btn-danger', | |
'btn', | |
]; | |
$removeFrom['attr']['class'] = str_replace($search, '', $removeFrom['attr']['class']); | |
} | |
} | |