added database driven system configurations with admin screen (#647)
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Configuration\FormConfiguration;
|
||||
use App\Entity\Customer;
|
||||
use App\Form\CustomerEditForm;
|
||||
use App\Form\Toolbar\CustomerToolbarForm;
|
||||
@@ -30,16 +31,22 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
class CustomerController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
* @var CustomerRepository
|
||||
*/
|
||||
private $defaults;
|
||||
private $repository;
|
||||
/**
|
||||
* @var FormConfiguration
|
||||
*/
|
||||
private $configuration;
|
||||
|
||||
/**
|
||||
* @param array $defaults
|
||||
* @param CustomerRepository $repository
|
||||
* @param FormConfiguration $configuration
|
||||
*/
|
||||
public function __construct(array $defaults)
|
||||
public function __construct(CustomerRepository $repository, FormConfiguration $configuration)
|
||||
{
|
||||
$this->defaults = $defaults;
|
||||
$this->repository = $repository;
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +54,7 @@ class CustomerController extends AbstractController
|
||||
*/
|
||||
protected function getRepository()
|
||||
{
|
||||
return $this->getDoctrine()->getRepository(Customer::class);
|
||||
return $this->repository;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,9 +102,9 @@ class CustomerController extends AbstractController
|
||||
public function createAction(Request $request)
|
||||
{
|
||||
$customer = new Customer();
|
||||
$customer->setCountry($this->defaults['customer']['country']);
|
||||
$customer->setCurrency($this->defaults['customer']['currency']);
|
||||
$customer->setTimezone($this->defaults['customer']['timezone']);
|
||||
$customer->setCountry($this->configuration->getCustomerDefaultCountry());
|
||||
$customer->setCurrency($this->configuration->getCustomerDefaultCurrency());
|
||||
$customer->setTimezone($this->configuration->getCustomerDefaultTimezone());
|
||||
|
||||
return $this->renderCustomerForm($customer, $request);
|
||||
}
|
||||
|
||||
246
src/Controller/SystemConfigurationController.php
Normal file
246
src/Controller/SystemConfigurationController.php
Normal file
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Event\SystemConfigurationEvent;
|
||||
use App\Form\Model\Configuration;
|
||||
use App\Form\Model\SystemConfiguration as SystemConfigurationModel;
|
||||
use App\Form\SystemConfigurationForm;
|
||||
use App\Repository\ConfigurationRepository;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CountryType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CurrencyType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TimezoneType;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
|
||||
|
||||
/**
|
||||
* Controller used for executing system relevant tasks.
|
||||
*
|
||||
* @Route(path="/admin/system-config")
|
||||
* @Security("is_granted('system_configuration')")
|
||||
*/
|
||||
class SystemConfigurationController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
/**
|
||||
* @var SystemConfiguration
|
||||
*/
|
||||
protected $configurations;
|
||||
/**
|
||||
* @var ConfigurationRepository
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
/**
|
||||
* @param EventDispatcherInterface $dispatcher
|
||||
* @param ConfigurationRepository $repository
|
||||
* @param SystemConfiguration $config
|
||||
*/
|
||||
public function __construct(EventDispatcherInterface $dispatcher, ConfigurationRepository $repository, SystemConfiguration $config)
|
||||
{
|
||||
$this->eventDispatcher = $dispatcher;
|
||||
$this->repository = $repository;
|
||||
$this->configurations = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/", name="system_configuration", methods={"GET"})
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$configSettings = $this->getInitializedConfigurations();
|
||||
|
||||
$configurations = [];
|
||||
foreach ($configSettings as $configModel) {
|
||||
$configurations[] = [
|
||||
'model' => $configModel,
|
||||
'form' => $this->createConfigurationsForm($configModel)->createView(),
|
||||
];
|
||||
}
|
||||
|
||||
return $this->render('system-configuration/index.html.twig', [
|
||||
'sections' => $configurations,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/timesheet", name="system_configuration_timesheet", methods={"POST"})
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function timesheet(Request $request)
|
||||
{
|
||||
return $this->handleConfigUpdate($request, SystemConfigurationModel::SECTION_TIMESHEET);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/customer", name="system_configuration_form_customer", methods={"POST"})
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function formDefaults(Request $request)
|
||||
{
|
||||
return $this->handleConfigUpdate($request, SystemConfigurationModel::SECTION_FORM_CUSTOMER);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param string $section
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
protected function handleConfigUpdate(Request $request, string $section)
|
||||
{
|
||||
$configSettings = $this->getInitializedConfigurations();
|
||||
foreach ($configSettings as $configModel) {
|
||||
if ($configModel->getSection() === $section) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $configModel) {
|
||||
throw $this->createNotFoundException('Could not find config model: ' . $section);
|
||||
}
|
||||
|
||||
$form = $this->createConfigurationsForm($configModel);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
try {
|
||||
$this->repository->saveSystemConfiguration($form->getData());
|
||||
$this->flashSuccess('action.update.success');
|
||||
} catch (\Exception $ex) {
|
||||
$this->flashError('action.update.error', ['%reason%' => $ex->getMessage()]);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('system_configuration');
|
||||
}
|
||||
|
||||
$configSettings = $this->getInitializedConfigurations();
|
||||
|
||||
$configurations = [];
|
||||
foreach ($configSettings as $configModel) {
|
||||
if ($section !== $configModel->getSection()) {
|
||||
$form2 = $this->createConfigurationsForm($configModel);
|
||||
} else {
|
||||
$form2 = $form;
|
||||
}
|
||||
$configurations[] = [
|
||||
'model' => $configModel,
|
||||
'form' => $form2->createView(),
|
||||
];
|
||||
}
|
||||
|
||||
return $this->render('system-configuration/index.html.twig', [
|
||||
'sections' => $configurations,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SystemConfigurationModel $configuration
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
*/
|
||||
private function createConfigurationsForm(SystemConfigurationModel $configuration)
|
||||
{
|
||||
return $this->createForm(
|
||||
SystemConfigurationForm::class,
|
||||
$configuration,
|
||||
[
|
||||
'attr' => ['id' => 'system_configuration_form_' . $configuration->getSection()],
|
||||
'action' => $this->generateUrl('system_configuration_' . $configuration->getSection()),
|
||||
'method' => 'POST'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SystemConfigurationModel[]
|
||||
*/
|
||||
protected function getInitializedConfigurations()
|
||||
{
|
||||
$types = $this->getConfigurationTypes();
|
||||
|
||||
$event = new SystemConfigurationEvent($types);
|
||||
$this->eventDispatcher->dispatch(SystemConfigurationEvent::CONFIGURE, $event);
|
||||
|
||||
foreach ($event->getConfigurations() as $configs) {
|
||||
foreach ($configs->getConfiguration() as $config) {
|
||||
$config->setValue($this->configurations->find($config->getName()));
|
||||
}
|
||||
}
|
||||
|
||||
return $event->getConfigurations();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SystemConfigurationModel[]
|
||||
*/
|
||||
protected function getConfigurationTypes()
|
||||
{
|
||||
return [
|
||||
(new SystemConfigurationModel())
|
||||
->setSection(SystemConfigurationModel::SECTION_TIMESHEET)
|
||||
->setConfiguration([
|
||||
(new Configuration())
|
||||
->setName('timesheet.markdown_content')
|
||||
->setType(CheckboxType::class)
|
||||
->setTranslationDomain('system-configuration'),
|
||||
(new Configuration())
|
||||
->setName('timesheet.duration_only')
|
||||
->setType(CheckboxType::class)
|
||||
->setTranslationDomain('system-configuration'),
|
||||
(new Configuration())
|
||||
->setName('timesheet.rules.allow_future_times')
|
||||
->setType(CheckboxType::class)
|
||||
->setTranslationDomain('system-configuration'),
|
||||
(new Configuration())
|
||||
->setName('timesheet.active_entries.hard_limit')
|
||||
->setType(IntegerType::class)
|
||||
->setTranslationDomain('system-configuration')
|
||||
->setConstraints([
|
||||
new GreaterThanOrEqual(['value' => 1])
|
||||
]),
|
||||
(new Configuration())
|
||||
->setName('timesheet.active_entries.soft_limit')
|
||||
->setType(IntegerType::class)
|
||||
->setTranslationDomain('system-configuration')
|
||||
->setConstraints([
|
||||
new GreaterThanOrEqual(['value' => 1])
|
||||
]),
|
||||
]),
|
||||
(new SystemConfigurationModel())
|
||||
->setSection(SystemConfigurationModel::SECTION_FORM_CUSTOMER)
|
||||
->setConfiguration([
|
||||
(new Configuration())
|
||||
->setName('defaults.customer.timezone')
|
||||
->setLabel('timezone')
|
||||
->setType(TimezoneType::class),
|
||||
(new Configuration())
|
||||
->setName('defaults.customer.country')
|
||||
->setLabel('country')
|
||||
->setType(CountryType::class),
|
||||
(new Configuration())
|
||||
->setName('defaults.customer.currency')
|
||||
->setLabel('currency')
|
||||
->setType(CurrencyType::class),
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ class TimesheetController extends AbstractController
|
||||
'showFilter' => $form->isSubmitted(),
|
||||
'toolbarForm' => $form->createView(),
|
||||
'showSummary' => $this->getUser()->getPreferenceValue('timesheet.daily_stats', false),
|
||||
'duration_only' => $this->configuration->isDurationOnly(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -124,7 +125,10 @@ class TimesheetController extends AbstractController
|
||||
|
||||
return $this->render(
|
||||
'navbar/active-entries.html.twig',
|
||||
['entries' => $activeEntries]
|
||||
[
|
||||
'entries' => $activeEntries,
|
||||
'soft_limit' => $this->getSoftLimit(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Configuration\TimesheetConfiguration;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Repository\TimesheetRepository;
|
||||
@@ -23,32 +24,23 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
*/
|
||||
trait TimesheetControllerTrait
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $hardLimit = 1;
|
||||
|
||||
/**
|
||||
* @var UserDateTimeFactory
|
||||
*/
|
||||
protected $dateTime;
|
||||
/**
|
||||
* @var TimesheetConfiguration
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* @param UserDateTimeFactory $dateTime
|
||||
* @param int $hardLimit
|
||||
* @param TimesheetConfiguration $configuration
|
||||
*/
|
||||
public function __construct(UserDateTimeFactory $dateTime, int $hardLimit)
|
||||
public function __construct(UserDateTimeFactory $dateTime, TimesheetConfiguration $configuration)
|
||||
{
|
||||
$this->dateTime = $dateTime;
|
||||
$this->setHardLimit($hardLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $hardLimit
|
||||
*/
|
||||
protected function setHardLimit(int $hardLimit)
|
||||
{
|
||||
$this->hardLimit = $hardLimit;
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +48,15 @@ trait TimesheetControllerTrait
|
||||
*/
|
||||
protected function getHardLimit()
|
||||
{
|
||||
return $this->hardLimit;
|
||||
return $this->configuration->getActiveEntriesHardLimit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
protected function getSoftLimit()
|
||||
{
|
||||
return $this->configuration->getActiveEntriesSoftLimit();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,6 +65,7 @@ class TimesheetTeamController extends AbstractController
|
||||
'query' => $query,
|
||||
'showFilter' => $form->isSubmitted(),
|
||||
'toolbarForm' => $form->createView(),
|
||||
'duration_only' => $this->configuration->isDurationOnly(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user