prepare release 1.7 (#1388)
This commit is contained in:
@@ -21,7 +21,7 @@ class Constants
|
||||
/**
|
||||
* The current release status, either "stable" or "dev"
|
||||
*/
|
||||
public const STATUS = 'dev';
|
||||
public const STATUS = 'stable';
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -55,6 +55,29 @@ class DoctorController extends AbstractController
|
||||
$this->projectDirectory = $projectDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/flush-log", name="doctor_flush_log", methods={"GET"})
|
||||
* @Security("is_granted('system_configuration')")
|
||||
*/
|
||||
public function deleteLogfileAction(): Response
|
||||
{
|
||||
$logfile = $this->getLogFilename();
|
||||
|
||||
if (file_exists($logfile)) {
|
||||
if (!is_writable($logfile)) {
|
||||
$this->flashError('action.delete.error', ['%reason%' => 'Logfile cannot be written']);
|
||||
} else {
|
||||
if (false === file_put_contents($logfile, '')) {
|
||||
$this->flashError('action.delete.error', ['%reason%' => 'Failed writing to logfile']);
|
||||
} else {
|
||||
$this->flashSuccess('action.delete.success');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('doctor');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="", name="doctor", methods={"GET"})
|
||||
*/
|
||||
@@ -62,6 +85,8 @@ class DoctorController extends AbstractController
|
||||
{
|
||||
$logLines = 100;
|
||||
|
||||
$canDeleteLogfile = $this->isGranted('system_configuration') && is_writable($this->getLogFilename());
|
||||
|
||||
return $this->render('doctor/index.html.twig', array_merge(
|
||||
[
|
||||
'modules' => get_loaded_extensions(),
|
||||
@@ -70,6 +95,7 @@ class DoctorController extends AbstractController
|
||||
'settings' => $this->getIniSettings(),
|
||||
'extensions' => $this->getLoadedExtensions(),
|
||||
'directories' => $this->getFilePermissions(),
|
||||
'log_delete' => $canDeleteLogfile,
|
||||
'logs' => $this->getLog(),
|
||||
'logLines' => $logLines,
|
||||
'logSize' => $this->getLogSize(),
|
||||
@@ -106,20 +132,31 @@ class DoctorController extends AbstractController
|
||||
return filesize($logfile);
|
||||
}
|
||||
|
||||
private function getLog(int $lines = 100)
|
||||
private function getLogFilename(): string
|
||||
{
|
||||
// why is this check here ???
|
||||
if (!in_array(getenv('APP_ENV'), ['test', 'dev', 'prod'])) {
|
||||
return [
|
||||
'Unsupported log environment'
|
||||
];
|
||||
throw new \RuntimeException('Unsupported log environment');
|
||||
}
|
||||
|
||||
$logfileName = 'var/log/' . getenv('APP_ENV') . '.log';
|
||||
$logfile = $this->projectDirectory . '/' . $logfileName;
|
||||
|
||||
return $this->projectDirectory . '/' . $logfileName;
|
||||
}
|
||||
|
||||
private function getLog(int $lines = 100)
|
||||
{
|
||||
try {
|
||||
$logfile = $this->getLogFilename();
|
||||
} catch (\Exception $ex) {
|
||||
return [
|
||||
$ex->getMessage()
|
||||
];
|
||||
}
|
||||
|
||||
if (!file_exists($logfile)) {
|
||||
return [
|
||||
'Could not find logfile: ' . $logfileName
|
||||
'Empty or missing logfile'
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ class ThemeOptionsSubscriber implements EventSubscriberInterface
|
||||
/** @var User $user */
|
||||
$user = $this->storage->getToken()->getUser();
|
||||
|
||||
/** @var UserPreference $ref */
|
||||
foreach ($user->getPreferences() as $ref) {
|
||||
$name = $ref->getName();
|
||||
switch ($name) {
|
||||
@@ -73,6 +74,16 @@ class ThemeOptionsSubscriber implements EventSubscriberInterface
|
||||
}
|
||||
break;
|
||||
|
||||
case 'theme.layout':
|
||||
if ($ref->getValue() === 'boxed') {
|
||||
$this->helper->setOption('boxed_layout', true);
|
||||
$this->helper->setOption('fixed_layout', false);
|
||||
} elseif ($ref->getValue() === 'fixed') {
|
||||
$this->helper->setOption('boxed_layout', false);
|
||||
$this->helper->setOption('fixed_layout', true);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'theme.collapsed_sidebar':
|
||||
$this->helper->setOption('collapsed_sidebar', $ref->getValue());
|
||||
break;
|
||||
|
||||
@@ -18,6 +18,7 @@ use App\Form\Type\CalendarViewType;
|
||||
use App\Form\Type\InitialViewType;
|
||||
use App\Form\Type\LanguageType;
|
||||
use App\Form\Type\SkinType;
|
||||
use App\Form\Type\ThemeLayoutType;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
@@ -128,6 +129,12 @@ class UserPreferenceSubscriber implements EventSubscriberInterface
|
||||
->setOrder(400)
|
||||
->setType(SkinType::class),
|
||||
|
||||
(new UserPreference())
|
||||
->setName('theme.layout')
|
||||
->setValue('fixed')
|
||||
->setOrder(450)
|
||||
->setType(ThemeLayoutType::class),
|
||||
|
||||
(new UserPreference())
|
||||
->setName('theme.collapsed_sidebar')
|
||||
->setValue(false)
|
||||
|
||||
42
src/Form/Type/ThemeLayoutType.php
Normal file
42
src/Form/Type/ThemeLayoutType.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?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\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Custom form field type to select the theme layout.
|
||||
*/
|
||||
class ThemeLayoutType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'required' => true,
|
||||
'choices' => [
|
||||
'fixed' => 'fixed',
|
||||
'boxed' => 'boxed',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return ChoiceType::class;
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,8 @@ class UserCreateType extends UserEditType
|
||||
->add('plainPassword', RepeatedType::class, [
|
||||
'required' => true,
|
||||
'type' => PasswordType::class,
|
||||
'first_options' => ['label' => 'label.password'],
|
||||
'second_options' => ['label' => 'label.password_repeat'],
|
||||
'first_options' => ['label' => 'label.password', 'attr' => ['autocomplete' => 'new-password']],
|
||||
'second_options' => ['label' => 'label.password_repeat', 'attr' => ['autocomplete' => 'new-password']],
|
||||
]);
|
||||
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
@@ -29,8 +29,8 @@ class UserPasswordType extends AbstractType
|
||||
$builder
|
||||
->add('plainPassword', RepeatedType::class, [
|
||||
'type' => PasswordType::class,
|
||||
'first_options' => ['label' => 'label.password'],
|
||||
'second_options' => ['label' => 'label.password_repeat'],
|
||||
'first_options' => ['label' => 'label.password', 'attr' => ['autocomplete' => 'new-password']],
|
||||
'second_options' => ['label' => 'label.password_repeat', 'attr' => ['autocomplete' => 'new-password']],
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
@@ -304,6 +304,8 @@ final class InvoiceModel
|
||||
'project.name' => $project->getName(),
|
||||
'project.comment' => $project->getComment(),
|
||||
'project.order_number' => $project->getOrderNumber(),
|
||||
'project.start_date' => null !== $project->getStart() ? $formatter->getFormattedDateTime($project->getStart()) : '',
|
||||
'project.end_date' => null !== $project->getEnd() ? $formatter->getFormattedDateTime($project->getEnd()) : '',
|
||||
'project.order_date' => null !== $project->getOrderDate() ? $formatter->getFormattedDateTime($project->getOrderDate()) : '',
|
||||
'project.fixed_rate' => $formatter->getFormattedMoney($project->getFixedRate(), $currency),
|
||||
'project.fixed_rate_nc' => $formatter->getFormattedMoney($project->getFixedRate(), null),
|
||||
@@ -408,14 +410,47 @@ final class InvoiceModel
|
||||
'entry.user_name' => $user->getUsername(),
|
||||
'entry.user_title' => $user->getTitle(),
|
||||
'entry.user_alias' => $user->getAlias(),
|
||||
'entry.activity' => $activity->getName(),
|
||||
'entry.activity_id' => $activity->getId(),
|
||||
'entry.project' => $project->getName(),
|
||||
'entry.project_id' => $project->getId(),
|
||||
'entry.customer' => $customer->getName(),
|
||||
'entry.customer_id' => $customer->getId(),
|
||||
];
|
||||
|
||||
if (null !== $activity) {
|
||||
$values = array_merge($values, [
|
||||
'entry.activity' => $activity->getName(),
|
||||
'entry.activity_id' => $activity->getId(),
|
||||
]);
|
||||
|
||||
foreach ($activity->getVisibleMetaFields() as $metaField) {
|
||||
$values = array_merge($values, [
|
||||
'entry.activity.meta.' . $metaField->getName() => $metaField->getValue(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $project) {
|
||||
$values = array_merge($values, [
|
||||
'entry.project' => $project->getName(),
|
||||
'entry.project_id' => $project->getId(),
|
||||
]);
|
||||
|
||||
foreach ($project->getVisibleMetaFields() as $metaField) {
|
||||
$values = array_merge($values, [
|
||||
'entry.project.meta.' . $metaField->getName() => $metaField->getValue(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $customer) {
|
||||
$values = array_merge($values, [
|
||||
'entry.customer' => $customer->getName(),
|
||||
'entry.customer_id' => $customer->getId(),
|
||||
]);
|
||||
|
||||
foreach ($customer->getVisibleMetaFields() as $metaField) {
|
||||
$values = array_merge($values, [
|
||||
'entry.customer.meta.' . $metaField->getName() => $metaField->getValue(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($invoiceItem->getAdditionalFields() as $name => $value) {
|
||||
$values = array_merge($values, [
|
||||
'entry.meta.' . $name => $value,
|
||||
|
||||
Reference in New Issue
Block a user