version 1.14.1 (#2532)
* no back links in modal pages * remove unused service links to bountysource and gitter * add validation for budget and time-budget fields * display time budget if set * remove console log * sanitize DDE payloads * do not show status and name in version string
This commit is contained in:
@@ -80,5 +80,5 @@ class Version
|
||||
* @Serializer\Groups({"Default"})
|
||||
* @Serializer\Type(name="string")
|
||||
*/
|
||||
protected $copyright = Constants::SOFTWARE . ' - ' . Constants::VERSION . ' ' . Constants::STATUS . ' (' . Constants::NAME . ') by Kevin Papst and contributors.';
|
||||
protected $copyright = Constants::SOFTWARE . ' ' . Constants::VERSION . ' by Kevin Papst and contributors.';
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ final class InstallCommand extends Command
|
||||
}
|
||||
|
||||
$io->success(
|
||||
sprintf('Congratulations! Successfully installed %s version %s (%s)', Constants::SOFTWARE, Constants::VERSION, Constants::STATUS)
|
||||
sprintf('Congratulations! Successfully installed %s version %s', Constants::SOFTWARE, Constants::VERSION)
|
||||
);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -121,7 +121,7 @@ final class UpdateCommand extends Command
|
||||
if ($cacheResult !== 0) {
|
||||
$io->warning(
|
||||
[
|
||||
sprintf('Updated %s to version %s (%s) but the cache could not be rebuilt.', Constants::SOFTWARE, Constants::VERSION, Constants::STATUS),
|
||||
sprintf('Updated %s to version %s but the cache could not be rebuilt.', Constants::SOFTWARE, Constants::VERSION),
|
||||
'Please run the cache commands manually:',
|
||||
'bin/console cache:clear --env=' . $environment . PHP_EOL .
|
||||
'bin/console cache:warmup --env=' . $environment
|
||||
@@ -129,7 +129,7 @@ final class UpdateCommand extends Command
|
||||
);
|
||||
} else {
|
||||
$io->success(
|
||||
sprintf('Congratulations! Successfully updated %s to version %s (%s)', Constants::SOFTWARE, Constants::VERSION, Constants::STATUS)
|
||||
sprintf('Congratulations! Successfully updated %s to version %s', Constants::SOFTWARE, Constants::VERSION)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,10 @@ class VersionCommand extends Command
|
||||
->setDescription('Receive version information')
|
||||
->setHelp('This command allows you to fetch various version information about Kimai.')
|
||||
->addOption('name', null, InputOption::VALUE_NONE, 'Display the major release name')
|
||||
->addOption('candidate', null, InputOption::VALUE_NONE, 'Display the current version candidate (e.g. "stable" or "dev")')
|
||||
->addOption('short', null, InputOption::VALUE_NONE, 'Display the version only')
|
||||
->addOption('semver', null, InputOption::VALUE_NONE, 'Semantical versioning (SEMVER) compatible version string')
|
||||
// @deprecated since 1.14.1
|
||||
->addOption('candidate', null, InputOption::VALUE_NONE, 'DEPRECATED: Display the current version candidate (e.g. "stable" or "dev")')
|
||||
->addOption('semver', null, InputOption::VALUE_NONE, 'DEPRECATED: Semantical versioning (SEMVER) compatible version string')
|
||||
;
|
||||
}
|
||||
|
||||
@@ -68,7 +69,7 @@ class VersionCommand extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
$io->writeln(Constants::SOFTWARE . ' - ' . Constants::VERSION . ' ' . Constants::STATUS . ' (' . Constants::NAME . ') by Kevin Papst and contributors.');
|
||||
$io->writeln(Constants::SOFTWARE . ' ' . Constants::VERSION . ' by Kevin Papst and contributors.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '1.14';
|
||||
public const VERSION = '1.14.1';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 11400;
|
||||
public const VERSION_ID = 11401;
|
||||
/**
|
||||
* The current release status, either "stable" or "dev"
|
||||
*/
|
||||
|
||||
@@ -135,11 +135,12 @@ class Activity implements EntityWithMetaFields
|
||||
* @ Exporter\Expose(label="label.budget")
|
||||
*
|
||||
* @ORM\Column(name="budget", type="float", nullable=false)
|
||||
* @Assert\Range(min=0.00, max=900000000000.00)
|
||||
* @Assert\NotNull()
|
||||
*/
|
||||
private $budget = 0.00;
|
||||
/**
|
||||
* The time budget in seconds, will be be zero if unconfigured.
|
||||
* The time budget in seconds, will be zero if unconfigured.
|
||||
*
|
||||
* @var int
|
||||
*
|
||||
@@ -149,6 +150,7 @@ class Activity implements EntityWithMetaFields
|
||||
* @ Exporter\Expose(label="label.timeBudget", type="duration")
|
||||
*
|
||||
* @ORM\Column(name="time_budget", type="integer", nullable=false)
|
||||
* @Assert\Range(min=0, max=2145600000)
|
||||
* @Assert\NotNull()
|
||||
*/
|
||||
private $timeBudget = 0;
|
||||
|
||||
@@ -266,11 +266,12 @@ class Customer implements EntityWithMetaFields
|
||||
* @ Exporter\Expose(label="label.budget")
|
||||
*
|
||||
* @ORM\Column(name="budget", type="float", nullable=false)
|
||||
* @Assert\Range(min=0.00, max=900000000000.00)
|
||||
* @Assert\NotNull()
|
||||
*/
|
||||
private $budget = 0.00;
|
||||
/**
|
||||
* The time budget in seconds, will be be zero if not configured.
|
||||
* The time budget in seconds, will be zero if not configured.
|
||||
*
|
||||
* @var int
|
||||
*
|
||||
@@ -280,6 +281,7 @@ class Customer implements EntityWithMetaFields
|
||||
* @ Exporter\Expose(label="label.timeBudget", type="duration")
|
||||
*
|
||||
* @ORM\Column(name="time_budget", type="integer", nullable=false)
|
||||
* @Assert\Range(min=0, max=2145600000)
|
||||
* @Assert\NotNull()
|
||||
*/
|
||||
private $timeBudget = 0;
|
||||
|
||||
@@ -207,11 +207,12 @@ class Project implements EntityWithMetaFields
|
||||
* @ Exporter\Expose(label="label.budget")
|
||||
*
|
||||
* @ORM\Column(name="budget", type="float", nullable=false)
|
||||
* @Assert\Range(min=0.00, max=900000000000.00)
|
||||
* @Assert\NotNull()
|
||||
*/
|
||||
private $budget = 0.00;
|
||||
/**
|
||||
* The time budget in seconds, will be be zero if not configured.
|
||||
* The time budget in seconds, will be zero if not configured.
|
||||
*
|
||||
* @var int
|
||||
*
|
||||
@@ -221,6 +222,7 @@ class Project implements EntityWithMetaFields
|
||||
* @ Exporter\Expose(label="label.timeBudget", type="duration")
|
||||
*
|
||||
* @ORM\Column(name="time_budget", type="integer", nullable=false)
|
||||
* @Assert\Range(min=0, max=2145600000)
|
||||
* @Assert\NotNull()
|
||||
*/
|
||||
private $timeBudget = 0;
|
||||
|
||||
@@ -20,10 +20,6 @@ abstract class AbstractTimesheetSubscriber extends AbstractActionsSubscriber
|
||||
|
||||
/** @var Timesheet $timesheet */
|
||||
$timesheet = $payload['timesheet'];
|
||||
if (!$event->isIndexView()) {
|
||||
$event->addBack($this->path($routeListing));
|
||||
}
|
||||
|
||||
if ($timesheet->getId() !== null) {
|
||||
if ($this->isGranted('edit', $timesheet)) {
|
||||
$class = $event->isView('edit') ? '' : 'modal-ajax-form';
|
||||
|
||||
@@ -20,11 +20,6 @@ class PermissionsSubscriber extends AbstractActionsSubscriber
|
||||
|
||||
public function onActions(PageActionsEvent $event): void
|
||||
{
|
||||
// the "create role" page
|
||||
if (!$event->isIndexView() && $this->isGranted('role_permissions')) {
|
||||
//$event->addBack($this->path('admin_user_permissions'));
|
||||
}
|
||||
|
||||
if ($this->isGranted('role_permissions')) {
|
||||
$event->addCreate($this->path('admin_user_roles'), !$event->isView('role'));
|
||||
}
|
||||
|
||||
@@ -37,10 +37,6 @@ class TagSubscriber extends AbstractActionsSubscriber
|
||||
$name = $tag->getName();
|
||||
}
|
||||
|
||||
if (!$event->isIndexView() && $this->isGranted('view_tag')) {
|
||||
//$event->addBack($this->path('tags'));
|
||||
}
|
||||
|
||||
if ($id === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,10 +26,6 @@ class TeamSubscriber extends AbstractActionsSubscriber
|
||||
/** @var Team $team */
|
||||
$team = $payload['team'];
|
||||
|
||||
if (!$event->isIndexView() && $this->isGranted('view_tag')) {
|
||||
//$event->addBack($this->path('admin_team'));
|
||||
}
|
||||
|
||||
if ($team->getId() === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use App\Export\ExportItemInterface;
|
||||
use App\Repository\Query\CustomerQuery;
|
||||
use App\Repository\Query\TimesheetQuery;
|
||||
use App\Twig\LocaleFormatExtensions;
|
||||
use App\Utils\StringHelper;
|
||||
use DateTime;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
@@ -80,6 +81,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
'description' => [
|
||||
'maxWidth' => 50,
|
||||
'wrapText' => false,
|
||||
'sanitizeDDE' => true,
|
||||
],
|
||||
'exported' => [],
|
||||
'billable' => [],
|
||||
@@ -337,15 +339,21 @@ abstract class AbstractSpreadsheetRenderer
|
||||
if (isset($columns['description']) && !isset($columns['description']['render'])) {
|
||||
$maxWidth = \array_key_exists('maxWidth', $columns['description']) ? \intval($columns['description']['maxWidth']) : null;
|
||||
$wrapText = \array_key_exists('wrapText', $columns['description']) ? (bool) $columns['description']['wrapText'] : false;
|
||||
$sanitizeText = \array_key_exists('sanitizeDDE', $columns['description']) ? (bool) $columns['description']['sanitizeDDE'] : true;
|
||||
|
||||
// This column has a column-only formatter to set the maximum width of a column.
|
||||
// It needs to be executed once, so we use this as a flag on when to skip it.
|
||||
$isColumnFormatted = false;
|
||||
|
||||
$columns['description']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) use (&$isColumnFormatted, $maxWidth, $wrapText) {
|
||||
$columns['description']['render'] = function (Worksheet $sheet, int $row, int $column, ExportItemInterface $entity) use (&$isColumnFormatted, $maxWidth, $wrapText, $sanitizeText) {
|
||||
$cell = $sheet->getCellByColumnAndRow($column, $row);
|
||||
$desc = $entity->getDescription();
|
||||
|
||||
$cell->setValueExplicit($entity->getDescription(), DataType::TYPE_STRING);
|
||||
if ($sanitizeText && null !== $desc) {
|
||||
$desc = StringHelper::sanitizeDDE($desc);
|
||||
}
|
||||
|
||||
$cell->setValueExplicit($desc, DataType::TYPE_STRING);
|
||||
|
||||
// Apply wrap text if configured
|
||||
if ($wrapText) {
|
||||
@@ -355,8 +363,7 @@ abstract class AbstractSpreadsheetRenderer
|
||||
// Apply max width, only needs to be once per column
|
||||
if (!$isColumnFormatted) {
|
||||
if (null !== $maxWidth) {
|
||||
$sheet->getColumnDimensionByColumn($column)
|
||||
->setWidth($maxWidth);
|
||||
$sheet->getColumnDimensionByColumn($column)->setWidth($maxWidth);
|
||||
}
|
||||
$isColumnFormatted = true;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace App\Utils;
|
||||
|
||||
final class StringHelper
|
||||
{
|
||||
// @see https://github.com/payloadbox/csv-injection-payloads
|
||||
private const DDE_PAYLOADS = ['=', '-', '@', '+', "\t", "\n", "\r", "\r\n"];
|
||||
|
||||
public static function ensureMaxLength(?string $string, int $length): ?string
|
||||
{
|
||||
if (null === $string) {
|
||||
@@ -23,4 +26,22 @@ final class StringHelper
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
public static function sanitizeDDE(string $text): string
|
||||
{
|
||||
$sanitize = false;
|
||||
|
||||
if (\in_array($text[0], self::DDE_PAYLOADS)) {
|
||||
$sanitize = true;
|
||||
} elseif (stripos($text, 'DDE') !== false) {
|
||||
$sanitize = true;
|
||||
}
|
||||
|
||||
if ($sanitize) {
|
||||
// trying to prevent fucking Microsoft "feature" DDE
|
||||
$text = "' " . $text;
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user