user->isAdmin()) { return $this->accessDenied(); } $event = new ConfigBuilderEvent($bundleHelper); $dispatcher = $this->dispatcher; $dispatcher->dispatch($event, ConfigEvents::CONFIG_ON_GENERATE); $fileFields = $event->getFileFields(); $formThemes = $event->getFormThemes(); $formConfigs = $configMapper->bindFormConfigsWithRealValues($event->getForms()); $this->mergeParamsWithLocal($formConfigs, $pathsHelper); // Create the form $action = $this->generateUrl('mautic_config_action', ['objectAction' => 'edit']); $form = $this->formFactory->create( ConfigType::class, $formConfigs, [ 'action' => $action, 'fileFields' => $fileFields, ] ); $originalNormData = $form->getNormData(); $isWritable = $configurator->isFileWritable(); $openTab = null; // Check for a submitted form and process it if ('POST' == $request->getMethod()) { if (!$cancelled = $this->isFormCancelled($form)) { $isValid = false; if ($isWritable && $isValid = $this->isFormValid($form)) { // Bind request to the form $post = $request->request; /** @var mixed[] $formData */ $formData = $form->getData(); // Dispatch pre-save event. Bundles may need to modify some field values like passwords before save $configEvent = new ConfigEvent($formData, $post); $configEvent ->setOriginalNormData($originalNormData) ->setNormData($form->getNormData()); $dispatcher->dispatch($configEvent, ConfigEvents::CONFIG_PRE_SAVE); $formValues = $configEvent->getConfig(); $errors = $configEvent->getErrors(); $fieldErrors = $configEvent->getFieldErrors(); if ($errors || $fieldErrors) { foreach ($errors as $message => $messageVars) { $form->addError( new FormError($this->translator->trans($message, $messageVars, 'validators')) ); } foreach ($fieldErrors as $key => $fields) { foreach ($fields as $field => $fieldError) { $form[$key][$field]->addError( new FormError($this->translator->trans($fieldError[0], $fieldError[1], 'validators')) ); } } $isValid = false; } else { // Prevent these from getting overwritten with empty values $unsetIfEmpty = $configEvent->getPreservedFields(); $unsetIfEmpty = array_merge($unsetIfEmpty, $fileFields); // Merge each bundle's updated configuration into the local configuration foreach ($formValues as $object) { $checkThese = array_intersect(array_keys($object), $unsetIfEmpty); foreach ($checkThese as $checkMe) { if (empty($object[$checkMe])) { unset($object[$checkMe]); } } $configurator->mergeParameters($object); } try { // Ensure the config has a secret key $params = $configurator->getParameters(); if (empty($params['secret_key'])) { $configurator->mergeParameters(['secret_key' => EncryptionHelper::generateKey()]); } $configurator->write(); $dispatcher->dispatch($configEvent, ConfigEvents::CONFIG_POST_SAVE); $this->addFlashMessage('mautic.config.config.notice.updated'); $cacheHelper->refreshConfig(); if ($isValid && !empty($formData['coreconfig']['last_shown_tab'])) { $openTab = $formData['coreconfig']['last_shown_tab']; } } catch (\RuntimeException $exception) { $this->addFlashMessage('mautic.config.config.error.not.updated', ['%exception%' => $exception->getMessage()], 'error'); } $this->setLocale($request, $tokenStorage, $params); } } elseif (!$isWritable) { $form->addError( new FormError( $this->translator->trans('mautic.config.notwritable') ) ); } } // If the form is saved or cancelled, redirect back to the dashboard if ($cancelled || $isValid) { if (!$cancelled && $this->isFormApplied($form)) { $redirectParameters = ['objectAction' => 'edit']; if ($openTab) { $redirectParameters['tab'] = $openTab; } return $this->delegateRedirect($this->generateUrl('mautic_config_action', $redirectParameters)); } else { return $this->delegateRedirect($this->generateUrl('mautic_dashboard_index')); } } } $tmpl = $request->isXmlHttpRequest() ? $request->get('tmpl', 'index') : 'index'; return $this->delegateView( [ 'viewParameters' => [ 'tmpl' => $tmpl, 'security' => $this->security, 'form' => $form->createView(), 'formThemes' => $formThemes, 'formConfigs' => $formConfigs, 'isWritable' => $isWritable, ], 'contentTemplate' => '@MauticConfig/Config/form.html.twig', 'passthroughVars' => [ 'activeLink' => '#mautic_config_index', 'mauticContent' => 'config', 'route' => $this->generateUrl('mautic_config_action', ['objectAction' => 'edit']), ], ] ); } /** * @return array|JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse|Response */ public function downloadAction(Request $request, BundleHelper $bundleHelper, $objectId) { // admin only allowed if (!$this->user->isAdmin()) { return $this->accessDenied(); } $event = new ConfigBuilderEvent($bundleHelper); $dispatcher = $this->dispatcher; $dispatcher->dispatch($event, ConfigEvents::CONFIG_ON_GENERATE); // Extract and base64 encode file contents $fileFields = $event->getFileFields(); if (!in_array($objectId, $fileFields)) { return $this->accessDenied(); } $content = $this->coreParametersHelper->get($objectId); $filename = $request->get('filename', $objectId); if ($decoded = base64_decode($content)) { $response = new Response($decoded); $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; } return $this->notFound(); } /** * @return array|JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse|Response */ public function removeAction(BundleHelper $bundleHelper, Configurator $configurator, CacheHelper $cacheHelper, $objectId) { // admin only allowed if (!$this->user->isAdmin()) { return $this->accessDenied(); } $success = 0; $event = new ConfigBuilderEvent($bundleHelper); $dispatcher = $this->dispatcher; $dispatcher->dispatch($event, ConfigEvents::CONFIG_ON_GENERATE); // Extract and base64 encode file contents $fileFields = $event->getFileFields(); if (in_array($objectId, $fileFields)) { $configurator->mergeParameters([$objectId => null]); try { $configurator->write(); $cacheHelper->refreshConfig(); $success = 1; } catch (\Exception) { } } return new JsonResponse(['success' => $success]); } /** * Merges default parameters from each subscribed bundle with the local (real) params. */ private function mergeParamsWithLocal(array &$forms, PathsHelper $pathsHelper): void { $doNotChange = $this->coreParametersHelper->get('mautic.security.restrictedConfigFields'); $localConfigFile = $pathsHelper->getLocalConfigurationFile(); // Import the current local configuration, $parameters is defined in this file $parameters = []; include $localConfigFile; /** @var mixed[] $parameters */ $localParams = $parameters; foreach ($forms as &$form) { // Merge the bundle params with the local params foreach ($form['parameters'] as $key => $value) { if (in_array($key, $doNotChange)) { unset($form['parameters'][$key]); } elseif (array_key_exists($key, $localParams)) { $paramValue = $localParams[$key]; $form['parameters'][$key] = $paramValue; } } } } /** * @param array $params */ private function setLocale(Request $request, TokenStorageInterface $tokenStorage, array $params): void { $me = $tokenStorage->getToken()->getUser(); assert($me instanceof User); $locale = $me->getLocale(); if (empty($locale)) { $locale = $params['locale'] ?? $this->coreParametersHelper->get('locale'); } $request->getSession()->set('_locale', $locale); } }