Spaces:
No application file
No application file
namespace Mautic\DashboardBundle\Controller; | |
use Mautic\CoreBundle\Controller\AbstractFormController; | |
use Mautic\CoreBundle\Form\Type\DateRangeType; | |
use Mautic\CoreBundle\Helper\InputHelper; | |
use Mautic\CoreBundle\Helper\PathsHelper; | |
use Mautic\CoreBundle\Helper\PhpVersionHelper; | |
use Mautic\CoreBundle\Release\ThisRelease; | |
use Mautic\DashboardBundle\Dashboard\Widget as WidgetService; | |
use Mautic\DashboardBundle\Entity\Widget; | |
use Mautic\DashboardBundle\Form\Type\UploadType; | |
use Mautic\DashboardBundle\Model\DashboardModel; | |
use Symfony\Component\Filesystem\Exception\IOException; | |
use Symfony\Component\Form\FormError; | |
use Symfony\Component\Form\FormFactoryInterface; | |
use Symfony\Component\HttpFoundation\JsonResponse; | |
use Symfony\Component\HttpFoundation\RedirectResponse; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpFoundation\Response; | |
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; | |
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | |
class DashboardController extends AbstractFormController | |
{ | |
/** | |
* Generates the default view. | |
*/ | |
public function indexAction(Request $request, WidgetService $widget, FormFactoryInterface $formFactory, PathsHelper $pathsHelper): Response | |
{ | |
$model = $this->getModel('dashboard'); | |
\assert($model instanceof DashboardModel); | |
$widgets = $model->getWidgets(); | |
// Apply the default dashboard if no widget exists | |
if (!count($widgets) && $this->user->getId()) { | |
return $this->applyDashboardFileAction($request, $pathsHelper, 'global.default'); | |
} | |
$action = $this->generateUrl('mautic_dashboard_index'); | |
$dateRangeFilter = $request->get('daterange', []); | |
// Set new date range to the session | |
if ($request->isMethod(Request::METHOD_POST)) { | |
if (!empty($dateRangeFilter['date_from'])) { | |
$from = new \DateTime($dateRangeFilter['date_from']); | |
$request->getSession()->set('mautic.daterange.form.from', $from->format(WidgetService::FORMAT_MYSQL)); | |
} | |
if (!empty($dateRangeFilter['date_to'])) { | |
$to = new \DateTime($dateRangeFilter['date_to']); | |
$request->getSession()->set('mautic.daterange.form.to', $to->format(WidgetService::FORMAT_MYSQL.' 23:59:59')); | |
} | |
$model->clearDashboardCache(); | |
} | |
// Set new date range to the session, if present in POST | |
$widget->setFilter($request); | |
// Load date range from session | |
$filter = $model->getDefaultFilter(); | |
// Set the final date range to the form | |
$dateRangeFilter['date_from'] = $filter['dateFrom']->format(WidgetService::FORMAT_HUMAN); | |
$dateRangeFilter['date_to'] = $filter['dateTo']->format(WidgetService::FORMAT_HUMAN); | |
$dateRangeForm = $formFactory->create(DateRangeType::class, $dateRangeFilter, ['action' => $action]); | |
$model->populateWidgetsContent($widgets, $filter); | |
$releaseMetadata = ThisRelease::getMetadata(); | |
$model->populateWidgetPreviews($widgets); | |
return $this->delegateView([ | |
'viewParameters' => [ | |
'security' => $this->security, | |
'widgets' => $widgets, | |
'dateRangeForm' => $dateRangeForm->createView(), | |
'phpVersion' => [ | |
'isOutdated' => version_compare(PHP_VERSION, $releaseMetadata->getShowPHPVersionWarningIfUnder(), 'lt'), | |
'version' => PhpVersionHelper::getCurrentSemver(), | |
], | |
], | |
'contentTemplate' => '@MauticDashboard/Dashboard/index.html.twig', | |
'passthroughVars' => [ | |
'activeLink' => '#mautic_dashboard_index', | |
'mauticContent' => 'dashboard', | |
'route' => $this->generateUrl('mautic_dashboard_index'), | |
], | |
]); | |
} | |
public function widgetAction(Request $request, WidgetService $widgetService, $widgetId): JsonResponse | |
{ | |
if (!$request->isXmlHttpRequest()) { | |
throw new NotFoundHttpException('Not found.'); | |
} | |
$widgetService->setFilter($request); | |
$widget = $widgetService->get((int) $widgetId); | |
if (!$widget) { | |
throw new NotFoundHttpException('Not found.'); | |
} | |
$content = $this->get('twig')->render( | |
'@MauticDashboard/Dashboard/widget.html.twig', | |
['widget' => $widget] | |
); | |
return new JsonResponse([ | |
'success' => 1, | |
'widgetId' => $widgetId, | |
'widgetHtml' => $content, | |
'widgetWidth' => $widget->getWidth(), | |
'widgetHeight' => $widget->getHeight(), | |
]); | |
} | |
/** | |
* Generate new dashboard widget and processes post data. | |
* | |
* @return JsonResponse|RedirectResponse|Response | |
*/ | |
public function newAction(Request $request, FormFactoryInterface $formFactory) | |
{ | |
// retrieve the entity | |
$widget = new Widget(); | |
$model = $this->getModel('dashboard'); | |
\assert($model instanceof DashboardModel); | |
$action = $this->generateUrl('mautic_dashboard_action', ['objectAction' => 'new']); | |
// get the user form factory | |
$form = $model->createForm($widget, $formFactory, $action); | |
$closeModal = false; | |
$valid = false; | |
// /Check for a submitted form and process it | |
if ($request->isMethod(Request::METHOD_POST)) { | |
if (!$cancelled = $this->isFormCancelled($form)) { | |
if ($valid = $this->isFormValid($form)) { | |
$closeModal = true; | |
// form is valid so process the data | |
$model->saveEntity($widget); | |
} | |
} else { | |
$closeModal = true; | |
} | |
} | |
if ($closeModal) { | |
// just close the modal | |
$passthroughVars = [ | |
'closeModal' => 1, | |
'mauticContent' => 'widget', | |
]; | |
$filter = $model->getDefaultFilter(); | |
$model->populateWidgetContent($widget, $filter); | |
if ($valid && !$cancelled) { | |
$passthroughVars['upWidgetCount'] = 1; | |
$passthroughVars['widgetHtml'] = $this->renderView('@MauticDashboard/Widget/detail.html.twig', [ | |
'widget' => $widget, | |
]); | |
$passthroughVars['widgetId'] = $widget->getId(); | |
$passthroughVars['widgetWidth'] = $widget->getWidth(); | |
$passthroughVars['widgetHeight'] = $widget->getHeight(); | |
} | |
return new JsonResponse($passthroughVars); | |
} else { | |
return $this->delegateView([ | |
'viewParameters' => [ | |
'form' => $form->createView(), | |
], | |
'contentTemplate' => '@MauticDashboard/Widget/form.html.twig', | |
]); | |
} | |
} | |
/** | |
* edit widget and processes post data. | |
* | |
* @return JsonResponse|RedirectResponse|Response | |
*/ | |
public function editAction(Request $request, FormFactoryInterface $formFactory, $objectId) | |
{ | |
$model = $this->getModel('dashboard'); | |
\assert($model instanceof DashboardModel); | |
$widget = $model->getEntity($objectId); | |
$action = $this->generateUrl('mautic_dashboard_action', ['objectAction' => 'edit', 'objectId' => $objectId]); | |
// get the user form factory | |
$form = $model->createForm($widget, $formFactory, $action); | |
$closeModal = false; | |
$valid = false; | |
// /Check for a submitted form and process it | |
if ($request->isMethod(Request::METHOD_POST)) { | |
if (!$cancelled = $this->isFormCancelled($form)) { | |
if ($valid = $this->isFormValid($form)) { | |
$closeModal = true; | |
// form is valid so process the data | |
$model->saveEntity($widget); | |
} | |
} else { | |
$closeModal = true; | |
} | |
} | |
if ($closeModal) { | |
// just close the modal | |
$passthroughVars = [ | |
'closeModal' => 1, | |
'mauticContent' => 'widget', | |
]; | |
$filter = $model->getDefaultFilter(); | |
$model->populateWidgetContent($widget, $filter); | |
if ($valid && !$cancelled) { | |
$passthroughVars['upWidgetCount'] = 1; | |
$passthroughVars['widgetHtml'] = $this->renderView('@MauticDashboard/Widget/detail.html.twig', [ | |
'widget' => $widget, | |
]); | |
$passthroughVars['widgetId'] = $widget->getId(); | |
$passthroughVars['widgetWidth'] = $widget->getWidth(); | |
$passthroughVars['widgetHeight'] = $widget->getHeight(); | |
} | |
return new JsonResponse($passthroughVars); | |
} else { | |
return $this->delegateView([ | |
'viewParameters' => [ | |
'form' => $form->createView(), | |
], | |
'contentTemplate' => '@MauticDashboard/Widget/form.html.twig', | |
]); | |
} | |
} | |
/** | |
* Deletes entity if exists. | |
* | |
* @param int $objectId | |
* | |
* @return Response | |
*/ | |
public function deleteAction(Request $request, $objectId) | |
{ | |
if (!$request->isXmlHttpRequest()) { | |
throw new BadRequestHttpException(); | |
} | |
$flashes = []; | |
$success = 0; | |
/** @var DashboardModel $model */ | |
$model = $this->getModel('dashboard'); | |
$entity = $model->getEntity($objectId); | |
if ($entity) { | |
$model->deleteEntity($entity); | |
$name = $entity->getName(); | |
$flashes[] = [ | |
'type' => 'notice', | |
'msg' => 'mautic.core.notice.deleted', | |
'msgVars' => [ | |
'%name%' => $name, | |
'%id%' => $objectId, | |
], | |
]; | |
$success = 1; | |
} else { | |
$flashes[] = [ | |
'type' => 'error', | |
'msg' => 'mautic.api.client.error.notfound', | |
'msgVars' => ['%id%' => $objectId], | |
]; | |
} | |
return $this->postActionRedirect( | |
[ | |
'success' => $success, | |
'flashes' => $flashes, | |
] | |
); | |
} | |
/** | |
* Saves the widgets of current user into a json and stores it for later as a file. | |
* | |
* @return Response | |
*/ | |
public function saveAction(Request $request) | |
{ | |
// Accept only AJAX POST requests because those are check for CSRF tokens | |
if (!$request->isMethod(Request::METHOD_POST) || !$request->isXmlHttpRequest()) { | |
return $this->accessDenied(); | |
} | |
$name = $this->getNameFromRequest($request); | |
/** @var DashboardModel $dashboardModel */ | |
$dashboardModel = $this->getModel('dashboard'); | |
try { | |
$dashboardModel->saveSnapshot($name); | |
$type = 'notice'; | |
$msg = $this->translator->trans('mautic.dashboard.notice.save', [ | |
'%name%' => $name, | |
'%viewUrl%' => $this->generateUrl( | |
'mautic_dashboard_action', | |
[ | |
'objectAction' => 'import', | |
] | |
), | |
], 'flashes'); | |
} catch (IOException $e) { | |
$type = 'error'; | |
$msg = $this->translator->trans('mautic.dashboard.error.save', [ | |
'%msg%' => $e->getMessage(), | |
], 'flashes'); | |
} | |
return $this->postActionRedirect( | |
[ | |
'flashes' => [ | |
[ | |
'type' => $type, | |
'msg' => $msg, | |
], | |
], | |
] | |
); | |
} | |
/** | |
* Exports the widgets of current user into a json file and downloads it. | |
*/ | |
public function exportAction(Request $request): JsonResponse | |
{ | |
$dashboardModel = $this->getModel('dashboard'); | |
\assert($dashboardModel instanceof DashboardModel); | |
$filename = InputHelper::filename($this->getNameFromRequest($request), 'json'); | |
$response = new JsonResponse($dashboardModel->toArray($filename)); | |
$response->setEncodingOptions($response->getEncodingOptions() | JSON_PRETTY_PRINT); | |
$response->headers->set('Content-Type', 'application/force-download'); | |
$response->headers->set('Content-Type', 'application/octet-stream'); | |
$response->headers->set('Content-Disposition', 'attachment; filename="'.$filename.'"'); | |
$response->headers->set('Expires', '0'); | |
$response->headers->set('Cache-Control', 'must-revalidate'); | |
$response->headers->set('Pragma', 'public'); | |
return $response; | |
} | |
/** | |
* Exports the widgets of current user into a json file. | |
*/ | |
public function deleteDashboardFileAction(Request $request, PathsHelper $pathsHelper): RedirectResponse | |
{ | |
$file = $request->get('file'); | |
$parts = explode('.', $file); | |
$type = array_shift($parts); | |
$name = implode('.', $parts); | |
$dir = $pathsHelper->getSystemPath("dashboard.$type"); | |
$path = $dir.'/'.$name.'.json'; | |
if (file_exists($path) && is_writable($path)) { | |
unlink($path); | |
} | |
return $this->redirectToRoute('mautic_dashboard_action', ['objectAction' => 'import']); | |
} | |
/** | |
* Applies dashboard layout. | |
* | |
* @param string|null $file | |
*/ | |
public function applyDashboardFileAction(Request $request, PathsHelper $pathsHelper, $file = null): RedirectResponse | |
{ | |
if (!$file) { | |
$file = $request->get('file'); | |
} | |
$parts = explode('.', $file); | |
$type = array_shift($parts); | |
$name = implode('.', $parts); | |
$dir = $pathsHelper->getSystemPath("dashboard.$type"); | |
$path = $dir.'/'.$name.'.json'; | |
if (!file_exists($path) || !is_readable($path)) { | |
$this->addFlashMessage('mautic.dashboard.upload.filenotfound', [], 'error', 'validators'); | |
return $this->redirectToRoute('mautic_dashboard_action', ['objectAction' => 'import']); | |
} | |
$widgets = json_decode(file_get_contents($path), true); | |
if (isset($widgets['widgets'])) { | |
$widgets = $widgets['widgets']; | |
} | |
if ($widgets) { | |
/** @var DashboardModel $model */ | |
$model = $this->getModel('dashboard'); | |
$model->clearDashboardCache(); | |
$currentWidgets = $model->getWidgets(); | |
if (count($currentWidgets)) { | |
foreach ($currentWidgets as $widget) { | |
$model->deleteEntity($widget); | |
} | |
} | |
$filter = $model->getDefaultFilter(); | |
foreach ($widgets as $widget) { | |
$widget = $model->populateWidgetEntity($widget); | |
$model->saveEntity($widget); | |
} | |
} | |
return $this->redirect($this->get('router')->generate('mautic_dashboard_index')); | |
} | |
public function importAction(Request $request, FormFactoryInterface $formFactory, PathsHelper $pathsHelper): Response | |
{ | |
$preview = $request->get('preview'); | |
/** @var DashboardModel $model */ | |
$model = $this->getModel('dashboard'); | |
$directories = [ | |
'user' => $pathsHelper->getSystemPath('dashboard.user'), | |
'global' => $pathsHelper->getSystemPath('dashboard.global'), | |
]; | |
$action = $this->generateUrl('mautic_dashboard_action', ['objectAction' => 'import']); | |
$form = $formFactory->create(UploadType::class, [], ['action' => $action]); | |
if ($request->isMethod(Request::METHOD_POST)) { | |
if (!$this->isFormCancelled($form)) { | |
if ($this->isFormValid($form)) { | |
$fileData = $form['file']->getData(); | |
if (!empty($fileData)) { | |
$extension = pathinfo($fileData->getClientOriginalName(), PATHINFO_EXTENSION); | |
if ('json' === $extension) { | |
$fileData->move($directories['user'], $fileData->getClientOriginalName()); | |
} else { | |
$form->addError( | |
new FormError( | |
$this->translator->trans('mautic.core.not.allowed.file.extension', ['%extension%' => $extension], 'validators') | |
) | |
); | |
} | |
} else { | |
$form->addError( | |
new FormError( | |
$this->translator->trans('mautic.dashboard.upload.filenotfound', [], 'validators') | |
) | |
); | |
} | |
} | |
} | |
} | |
$dashboardFiles = ['user' => [], 'gobal' => []]; | |
$dashboards = []; | |
if (is_readable($directories['user'])) { | |
// User specific layouts | |
chdir($directories['user']); | |
$dashboardFiles['user'] = glob('*.json'); | |
} | |
if (is_readable($directories['global'])) { | |
// Global dashboards | |
chdir($directories['global']); | |
$dashboardFiles['global'] = glob('*.json'); | |
} | |
foreach ($dashboardFiles as $type => $dirDashboardFiles) { | |
$tempDashboard = []; | |
foreach ($dirDashboardFiles as $dashId => $dashboard) { | |
$dashboard = str_replace('.json', '', $dashboard); | |
$config = json_decode( | |
file_get_contents($directories[$type].'/'.$dirDashboardFiles[$dashId]), | |
true | |
); | |
// Check for name, description, etc | |
$tempDashboard[$dashboard] = [ | |
'type' => $type, | |
'name' => $config['name'] ?? $dashboard, | |
'description' => $config['description'] ?? '', | |
'widgets' => $config['widgets'] ?? $config, | |
]; | |
} | |
// Sort by name | |
uasort($tempDashboard, | |
fn ($a, $b): int => strnatcasecmp($a['name'], $b['name']) | |
); | |
$dashboards = array_merge( | |
$dashboards, | |
$tempDashboard | |
); | |
} | |
if ($preview && isset($dashboards[$preview])) { | |
// @todo check is_writable | |
$widgets = $dashboards[$preview]['widgets']; | |
$filter = $model->getDefaultFilter(); | |
$model->populateWidgetsContent($widgets, $filter); | |
} else { | |
$widgets = []; | |
} | |
return $this->delegateView( | |
[ | |
'viewParameters' => [ | |
'form' => $form->createView(), | |
'dashboards' => $dashboards, | |
'widgets' => $widgets, | |
'preview' => $preview, | |
], | |
'contentTemplate' => '@MauticDashboard/Dashboard/import.html.twig', | |
'passthroughVars' => [ | |
'activeLink' => '#mautic_dashboard_index', | |
'mauticContent' => 'dashboardImport', | |
'route' => $this->generateUrl( | |
'mautic_dashboard_action', | |
[ | |
'objectAction' => 'import', | |
] | |
), | |
], | |
] | |
); | |
} | |
/** | |
* Gets name from request and defaults it to the timestamp if not provided. | |
* | |
* @return string | |
* | |
* @throws \Exception | |
*/ | |
private function getNameFromRequest(Request $request) | |
{ | |
return $request->get('name', (new \DateTime())->format('Y-m-dTH:i:s')); | |
} | |
} | |