mautic / app /bundles /CoreBundle /Controller /CommonController.php
chrisbryan17's picture
Upload folder using huggingface_hub
d2897cd verified
raw
history blame contribute delete
23.4 kB
<?php
namespace Mautic\CoreBundle\Controller;
use Doctrine\Persistence\ManagerRegistry;
use Mautic\CoreBundle\CoreEvents;
use Mautic\CoreBundle\Event\CustomTemplateEvent;
use Mautic\CoreBundle\Factory\MauticFactory;
use Mautic\CoreBundle\Factory\ModelFactory;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Helper\DataExporterHelper;
use Mautic\CoreBundle\Helper\ExportHelper;
use Mautic\CoreBundle\Helper\InputHelper;
use Mautic\CoreBundle\Helper\TrailingSlashHelper;
use Mautic\CoreBundle\Helper\UserHelper;
use Mautic\CoreBundle\Model\AbstractCommonModel;
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
use Mautic\CoreBundle\Service\FlashBag;
use Mautic\CoreBundle\Translation\Translator;
use Mautic\PageBundle\Model\PageModel;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class CommonController extends AbstractController implements MauticController
{
use FormThemeTrait;
protected ?\Mautic\UserBundle\Entity\User $user;
/**
* @param ModelFactory<object> $modelFactory
*/
public function __construct(
protected ManagerRegistry $doctrine,
protected MauticFactory $factory,
protected ModelFactory $modelFactory,
UserHelper $userHelper,
protected CoreParametersHelper $coreParametersHelper,
protected EventDispatcherInterface $dispatcher,
protected Translator $translator,
private FlashBag $flashBag,
private ?RequestStack $requestStack,
protected ?CorePermissions $security
) {
$this->user = $userHelper->getUser();
}
protected function getCurrentRequest(): Request
{
$request = null !== $this->requestStack ? $this->requestStack->getCurrentRequest() : null;
if (null === $request) {
throw new \RuntimeException('Request is not set.');
}
return $request;
}
/**
* Check if a security level is granted.
*/
protected function accessGranted($level): bool
{
return in_array($level, $this->getPermissions());
}
/**
* Override this method in your controller
* for easy access to the permissions.
*/
protected function getPermissions(): array
{
return [];
}
/**
* Get a model instance from the service container.
*
* @param string $modelNameKey
*
* @return AbstractCommonModel<object>
*/
protected function getModel($modelNameKey)
{
return $this->modelFactory->getModel($modelNameKey);
}
/**
* Forwards the request to another controller and include the POST.
*
* @param string $controller The controller name (a string like BlogBundle:Post:index)
* @param array $request An array of request parameters
* @param array $path An array of path parameters
* @param array $query An array of query parameters
*
* @return Response A Response instance
*/
public function forwardWithPost($controller, array $request = [], array $path = [], array $query = [])
{
$path['_controller'] = $controller;
$subRequest = $this->requestStack->getCurrentRequest()->duplicate($query, $request, $path);
return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
/**
* Determines if ajax content should be returned or direct content (page refresh).
*
* @param array $args
*/
public function delegateView($args): Response
{
$request = $this->getCurrentRequest();
$bundle = $request->query->get('bundle');
$bundle = $bundle ? strtolower(InputHelper::alphanum($bundle)) : '';
// Used for error handling
defined('MAUTIC_DELEGATE_VIEW') || define('MAUTIC_DELEGATE_VIEW', 1);
if (!is_array($args)) {
$args = [
'contentTemplate' => $args,
'passthroughVars' => [
'mauticContent' => $bundle,
],
];
}
if (!isset($args['viewParameters']['currentRoute']) && isset($args['passthroughVars']['route'])) {
$args['viewParameters']['currentRoute'] = $args['passthroughVars']['route'];
}
if (!isset($args['passthroughVars']['inBuilder']) && $inBuilder = $request->get('inBuilder')) {
$args['passthroughVars']['inBuilder'] = (bool) $inBuilder;
}
if (!isset($args['viewParameters']['mauticContent'])) {
if (isset($args['passthroughVars']['mauticContent'])) {
$mauticContent = $args['passthroughVars']['mauticContent'];
} else {
$mauticContent = $bundle;
}
$args['viewParameters']['mauticContent'] = $mauticContent;
}
if ($request->isXmlHttpRequest() && !$request->get('ignoreAjax', false)) {
return $this->ajaxAction($request, $args);
}
$parameters = $args['viewParameters'] ?? [];
$template = $args['contentTemplate'];
$code = $args['responseCode'] ?? 200;
$response = new Response('', $code);
if ($this->dispatcher->hasListeners(CoreEvents::VIEW_INJECT_CUSTOM_TEMPLATE)) {
$event = $this->dispatcher->dispatch(
new CustomTemplateEvent($request, $template, $parameters),
CoreEvents::VIEW_INJECT_CUSTOM_TEMPLATE
);
$template = $event->getTemplate();
$parameters = $event->getVars();
}
$parameters['mauticTemplate'] = $template;
$parameters['mauticTemplateVars'] = $parameters;
return $this->render($template, $parameters, $response);
}
/**
* Determines if a redirect response should be returned or a Json response directing the ajax call to force a page
* refresh.
*
* @return JsonResponse|RedirectResponse
*/
public function delegateRedirect($url)
{
$request = $this->getCurrentRequest();
if ($request->isXmlHttpRequest()) {
return new JsonResponse(['redirect' => $url]);
} else {
return $this->redirect($url);
}
}
/**
* Redirects URLs with trailing slashes in order to prevent 404s.
*
* @return RedirectResponse
*/
public function removeTrailingSlashAction(Request $request, TrailingSlashHelper $trailingSlashHelper)
{
return $this->redirect($trailingSlashHelper->getSafeRedirectUrl($request), 301);
}
/**
* Redirects /s and /s/ to /s/dashboard.
*/
public function redirectSecureRootAction()
{
return $this->redirectToRoute('mautic_dashboard_index', [], 301);
}
/**
* Redirects controller if not ajax or retrieves html output for ajax request.
*
* @param array $args [returnUrl, viewParameters, contentTemplate, passthroughVars, flashes, forwardController]
*
* @return Response
*/
public function postActionRedirect(array $args = [])
{
$request = $this->getCurrentRequest();
$returnUrl = array_key_exists('returnUrl', $args) ? $args['returnUrl'] : $this->generateUrl('mautic_dashboard_index');
$flashes = array_key_exists('flashes', $args) ? $args['flashes'] : [];
// forward the controller by default
$args['forwardController'] = (array_key_exists('forwardController', $args)) ? $args['forwardController'] : true;
if (!empty($flashes)) {
foreach ($flashes as $flash) {
$this->addFlashMessage(
$flash['msg'],
!empty($flash['msgVars']) ? $flash['msgVars'] : [],
!empty($flash['type']) ? $flash['type'] : 'notice',
!empty($flash['domain']) ? $flash['domain'] : 'flashes'
);
}
}
if (isset($args['passthroughVars']['closeModal'])) {
$args['passthroughVars']['updateMainContent'] = true;
}
if (!$request->isXmlHttpRequest() || !empty($args['ignoreAjax'])) {
$code = $args['responseCode'] ?? 302;
return $this->redirect($returnUrl, $code);
}
// load by ajax
return $this->ajaxAction($request, $args);
}
/**
* Generates html for ajax request.
*
* @param array $args [parameters, contentTemplate, passthroughVars, forwardController]
*/
public function ajaxAction(Request $request, $args = []): Response
{
defined('MAUTIC_AJAX_VIEW') || define('MAUTIC_AJAX_VIEW', 1);
$parameters = array_key_exists('viewParameters', $args) ? $args['viewParameters'] : [];
$contentTemplate = array_key_exists('contentTemplate', $args) ? $args['contentTemplate'] : '';
$passthrough = array_key_exists('passthroughVars', $args) ? $args['passthroughVars'] : [];
$forward = array_key_exists('forwardController', $args) ? $args['forwardController'] : false;
$code = array_key_exists('responseCode', $args) ? $args['responseCode'] : 200;
/*
* Return json response if this is a modal
*/
if (!empty($passthrough['closeModal']) && empty($passthrough['updateModalContent']) && empty($passthrough['updateMainContent'])) {
return new JsonResponse($passthrough);
}
// set the route to the returnUrl
if (empty($passthrough['route']) && !empty($args['returnUrl'])) {
$passthrough['route'] = $args['returnUrl'];
}
if (!empty($passthrough['route'])) {
// Add the ajax route to the request so that the desired route is fed to plugins rather than the current request
$baseUrl = $request->getBaseUrl();
$routePath = str_replace($baseUrl, '', $passthrough['route']);
$ajaxRouteName = false;
try {
$routeParams = $this->get('router')->match($routePath);
$ajaxRouteName = $routeParams['_route'];
$request->attributes->set('ajaxRoute',
[
'_route' => $ajaxRouteName,
'_route_params' => $routeParams,
]
);
} catch (\Exception) {
// do nothing
}
// breadcrumbs may fail as it will retrieve the crumb path for currently loaded URI so we must override
$request->query->set('overrideRouteUri', $passthrough['route']);
if ($ajaxRouteName) {
if (isset($routeParams['objectAction'])) {
// action urls share same route name so tack on the action to differentiate
$ajaxRouteName .= "|{$routeParams['objectAction']}";
}
$request->query->set('overrideRouteName', $ajaxRouteName);
}
}
// Ajax call so respond with json
$newContent = '';
if ($contentTemplate) {
if ($forward) {
// the content is from another controller action so we must retrieve the response from it instead of
// directly parsing the template
$query = ['ignoreAjax' => true, 'request' => $request, 'subrequest' => true];
$newContentResponse = $this->forward($contentTemplate, $parameters, $query);
if ($newContentResponse instanceof RedirectResponse) {
$passthrough['redirect'] = $newContentResponse->getTargetUrl();
$passthrough['route'] = false;
} else {
$newContent = $newContentResponse->getContent();
}
} else {
$GLOBALS['MAUTIC_AJAX_DIRECT_RENDER'] = 1; // for error handling
$newContent = $this->renderView($contentTemplate, $parameters);
unset($GLOBALS['MAUTIC_AJAX_DIRECT_RENDER']);
}
}
// there was a redirect within the controller leading to a double call of this function so just return the content
// to prevent newContent from being json
if ($request->get('ignoreAjax', false)) {
return new Response($newContent, $code);
}
// render flashes
$passthrough['flashes'] = $this->getFlashContent();
if (!defined('MAUTIC_INSTALLER')) {
// Prevent error in case installer is loaded via dev environment
$passthrough['notifications'] = $this->getNotificationContent();
}
$tmpl = $parameters['tmpl'] ?? $request->get('tmpl', 'index');
if ('index' == $tmpl) {
$updatedContent = [];
if (!empty($newContent)) {
$updatedContent['newContent'] = $newContent;
}
$dataArray = array_merge(
$passthrough,
$updatedContent
);
} else {
// just retrieve the content
$dataArray = array_merge(
$passthrough,
['newContent' => $newContent]
);
}
if ($newContent instanceof Response) {
$response = $newContent;
} else {
$response = new JsonResponse($dataArray, $code);
}
return $response;
}
/**
* Get's the content of error page.
*
* @return Response
*/
public function renderException(\Exception $e)
{
$request = $this->getCurrentRequest();
$parameters = ['exception' => $e];
$query = ['ignoreAjax' => true, 'subrequest' => true];
return $this->forwardWithPost(
'Mautic\CoreBundle\Controller\ExceptionController::showAction',
$request->request->all(),
$parameters,
array_merge($query, $request->query->all())
);
}
/**
* Executes an action defined in route.
*
* @param string $objectAction
* @param int $objectId
* @param int $objectSubId
* @param string $objectModel
*
* @return Response
*/
public function executeAction(Request $request, $objectAction, $objectId = 0, $objectSubId = 0, $objectModel = '')
{
if (method_exists($this, $objectAction.'Action')) {
return $this->forward(
static::class.'::'.$objectAction.'Action',
array_merge(
[
'objectId' => $objectId,
'objectModel' => $objectModel,
],
$request->attributes->all(),
),
$request->query->all()
);
}
return $this->notFound();
}
/**
* Generates access denied message.
*
* @param bool $batch Flag if a batch action is being performed
* @param string $msg Message that is logged
*
* @return JsonResponse|RedirectResponse|array
*
* @throws AccessDeniedHttpException
*/
public function accessDenied($batch = false, $msg = 'mautic.core.url.error.401')
{
$request = $this->getCurrentRequest();
$anonymous = $this->security->isAnonymous();
if ($anonymous || !$batch) {
throw new AccessDeniedHttpException($this->translator->trans($msg, ['%url%' => $request->getRequestUri()]));
}
if ($batch) {
return [
'type' => 'error',
'msg' => $this->translator->trans('mautic.core.error.accessdenied', [], 'flashes'),
];
}
}
/**
* Generate 404 not found message.
*
* @param string $msg
*
* @return Response
*/
public function notFound($msg = 'mautic.core.url.error.404')
{
$request = $this->getCurrentRequest();
$page_404 = $this->coreParametersHelper->get('404_page');
if (!empty($page_404)) {
$pageModel = $this->getModel('page');
\assert($pageModel instanceof PageModel);
$page = $pageModel->getEntity($page_404);
if (!empty($page) && $page->getIsPublished() && !empty($page->getCustomHtml())) {
$slug = $pageModel->generateSlug($page);
return $this->redirectToRoute('mautic_page_public', ['slug' => $slug]);
}
}
return $this->renderException(
new NotFoundHttpException(
$this->translator->trans($msg,
[
'%url%' => $request->getRequestUri(),
]
)
)
);
}
/**
* Returns a json encoded access denied error for modal windows.
*
* @param string $msg
*/
public function modalAccessDenied($msg = 'mautic.core.error.accessdenied'): JsonResponse
{
return new JsonResponse([
'error' => $this->translator->trans($msg, [], 'flashes'),
]);
}
/**
* Updates list filters, order, limit.
*
* @param string|null $name
*/
protected function setListFilters($name = null)
{
$request = $this->getCurrentRequest();
$session = $request->getSession();
if (empty($name)) {
$name = InputHelper::clean($request->query->get('name'));
}
$name = 'mautic.'.$name;
if (false === $request->query->has('orderby') && false === $session->has("$name.orderbydir")) {
$session->set("$name.orderbydir", $this->getDefaultOrderDirection());
}
if ($request->query->has('orderby')) {
$orderBy = InputHelper::clean($request->query->get('orderby'), true);
$dir = $session->get("$name.orderbydir", 'ASC');
$dir = $orderBy === $session->get("$name.orderby") || false == $session->has("$name.orderby") ? (('ASC' == $dir) ? 'DESC' : 'ASC') : $dir;
$session->set("$name.orderby", $orderBy);
$session->set("$name.orderbydir", $dir);
}
if ($request->query->has('limit')) {
$limit = (int) $request->query->get('limit');
$session->set("$name.limit", $limit);
}
if ($request->query->has('filterby')) {
$filter = InputHelper::clean($request->query->get('filterby'), true);
$value = InputHelper::clean($request->query->get('value'), true);
$filters = $session->get("$name.filters", []);
if ('' == $value) {
if (isset($filters[$filter])) {
unset($filters[$filter]);
}
} else {
$filters[$filter] = [
'column' => $filter,
'expr' => 'like',
'value' => $value,
'strict' => false,
];
}
$session->set("$name.filters", $filters);
}
}
/**
* Renders flashes' HTML.
*
* @return string
*/
protected function getFlashContent()
{
return $this->renderView('@MauticCore/Notification/flash_messages.html.twig');
}
/**
* Renders notification info for ajax.
*/
protected function getNotificationContent(Request $request = null): array
{
if (null === $request) {
$request = $this->getCurrentRequest();
}
$afterId = $request->get('mauticLastNotificationId', null);
/** @var \Mautic\CoreBundle\Model\NotificationModel $model */
$model = $this->getModel('core.notification');
[$notifications, $showNewIndicator, $updateMessage] = $model->getNotificationContent($afterId, false, 200);
$lastNotification = reset($notifications);
return [
'content' => ($notifications || $updateMessage) ? $this->renderView('@MauticCore/Notification/notification_messages.html.twig', [
'notifications' => $notifications,
'updateMessage' => $updateMessage,
]) : '',
'lastId' => (!empty($lastNotification)) ? $lastNotification['id'] : $afterId,
'hasNewNotifications' => $showNewIndicator,
'updateAvailable' => (!empty($updateMessage)),
];
}
/**
* @param bool|true $isRead
*
* @deprecated Will be removed in Mautic 3.0 as unused.
*/
public function addNotification($message, $type = null, $isRead = true, $header = null, $iconClass = null, \DateTime $datetime = null): void
{
/** @var \Mautic\CoreBundle\Model\NotificationModel $notificationModel */
$notificationModel = $this->getModel('core.notification');
$notificationModel->addNotification($message, $type, $isRead, $header, $iconClass, $datetime);
}
/**
* @param string $message
* @param array<mixed> $messageVars
* @param string|null $level
* @param string|null $domain
* @param bool|null $addNotification
*/
public function addFlashMessage($message, $messageVars = [], $level = FlashBag::LEVEL_NOTICE, $domain = 'flashes', $addNotification = false): void
{
$this->flashBag->add($message, $messageVars, $level, $domain, $addNotification);
}
/**
* @param array|\Iterator $toExport
*
* @return StreamedResponse
*/
public function exportResultsAs($toExport, $type, $filename, ExportHelper $exportHelper)
{
if (!in_array($type, $exportHelper->getSupportedExportTypes())) {
throw new BadRequestHttpException($this->translator->trans('mautic.error.invalid.export.type', ['%type%' => $type]));
}
$dateFormat = $this->coreParametersHelper->get('date_format_dateonly');
$dateFormat = str_replace('--', '-', preg_replace('/[^a-zA-Z]/', '-', $dateFormat));
$filename = strtolower($filename.'_'.(new \DateTime())->format($dateFormat).'.'.$type);
return $exportHelper->exportDataAs($toExport, $type, $filename);
}
/**
* Standard function to generate an array of data via any model's "getEntities" method.
*
* Overwrite in your controller if required.
*
* @param AbstractCommonModel<object> $model
*
* @return array
*/
protected function getDataForExport(AbstractCommonModel $model, array $args, callable $resultsCallback = null, ?int $start = 0)
{
$data = new DataExporterHelper();
return $data->getDataForExport($start, $model, $args, $resultsCallback);
}
/**
* @return string
*/
protected function getDefaultOrderDirection()
{
return 'ASC';
}
}