0
0
Fork 0
mirror of https://github.com/kevinpapst/kimai2.git synced 2025-03-17 06:22:38 +00:00
* improve console version output
* fix empty string issue in csv export (DDE protection) - fixes 
* fix twig sort deprecation in php 8
* sort user by displayName in users report
* fix missing custom translations in edit modal
* fix font-family in invoice.css
* invoice template "freelancer pdf" - added customer number, moved some fields around
* invoice template "default" - relocate customer number and order number in
This commit is contained in:
Kevin Papst 2022-03-11 23:25:23 +01:00 committed by GitHub
parent 317ac59f47
commit 00b5a65859
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 363 additions and 300 deletions

View file

@ -34,6 +34,9 @@ p {
.text-center {
text-align: center;
}
.text-left {
text-align: left;
}
.text-right {
text-align: right;
}
@ -77,7 +80,7 @@ table.footer {
line-height: 14px;
}
table.footer td {
vertical-align: bottom;
vertical-align: top;
}
/* The invoice items list */
table.items {

View file

@ -19,280 +19,287 @@
@import "~bootstrap-sass/assets/stylesheets/bootstrap/utilities";
@import "~bootstrap-sass/assets/stylesheets/bootstrap/responsive-utilities";
body.invoice_print {
background-color: #eee;
body {
font-family: $font-family-sans-serif;
&.invoice_print {
background-color: #eee;
.table.no-border, .table.no-border td, .table.no-border th {
border: 0;
}
h1, h2, h3 {
font-family: 'Source Sans Pro', sans-serif;
}
.invoice {
margin: 105px auto 30px auto;
padding: 50px 65px;
min-height: 297mm;
position: relative;
max-width: 210mm;
box-shadow: 0 0 20px rgba(80,80,80,0.7);
background: #fff;
border: none;
}
.page-header {
margin: 10px 0 20px 0;
font-size: 22px;
>small {
color: #666;
display: block;
margin-top: 5px;
.table.no-border, .table.no-border td, .table.no-border th {
border: 0;
}
h3 {
border-color: #ddd;
h1, h2, h3 {
font-family: $font-family-sans-serif;
}
}
/*
.page-header {
margin: 10px 0 20px 0;
font-size: 22px;
}
*/
div.invoice-address {
margin-top: 30px;
margin-bottom: 30px;
}
table.invoice-meta th {
padding-right: 40px;
}
table.invoice-sum th {
width: 70%;
}
table.invoice-sum th, table.invoice-sum td {
text-align: right;
}
.invoice {
margin: 105px auto 30px auto;
padding: 50px 65px;
min-height: 297mm;
position: relative;
max-width: 210mm;
box-shadow: 0 0 20px rgba(80, 80, 80, 0.7);
background: #fff;
border: none;
}
.invoice-items {
margin-top: 2em;
margin-bottom: 3em;
.page-header {
margin: 10px 0 20px 0;
font-size: 22px;
.table {
thead th {
font-weight: bold;
border-bottom: 1px solid #ddd;
padding-bottom: 15px
> small {
color: #666;
display: block;
margin-top: 5px;
}
tfoot {
border-top: 1px solid #ddd;
tr:first-child th,
tr:first-child td {
padding-top: 15px
h3 {
border-color: #ddd;
}
}
div.invoice-address {
margin-top: 30px;
margin-bottom: 30px;
}
table.invoice-meta th {
padding-right: 40px;
}
table.invoice-sum th {
width: 70%;
}
table.invoice-sum th, table.invoice-sum td {
text-align: right;
}
.invoice-items {
margin-top: 2em;
margin-bottom: 3em;
.table {
thead th {
font-weight: bold;
border-bottom: 1px solid #ddd;
padding-bottom: 15px
}
tr {
th,td {
border:none;
padding: 4px 8px;
tfoot {
border-top: 1px solid #ddd;
tr:first-child th,
tr:first-child td {
padding-top: 15px
}
tr {
th, td {
border: none;
padding: 4px 8px;
}
}
}
}
}
}
.footer {
border-top: 1px solid #ccc;
margin-top: 50px;
font-size: 0.8em;
padding-top: 10px;
width: 100%;
}
/* ================================ FREELANCER INVOICE ================================ */
#freelancer-invoice {
box-sizing: border-box;
h1 {
font: bold 100% sans-serif;
letter-spacing: 0.5em;
text-align: center;
text-transform: uppercase;
}
h2 {
font: bold 1.5em sans-serif;
margin-bottom: 1em;
}
/* table */
table {
width: 100%;
}
table {
border-collapse: separate;
border-spacing: 0;
}
/* header */
header {
&:after {
clear: both;
content: "";
display: table;
font-size: 1em;
}
address {
float: left;
font-style: normal;
line-height: 1.25;
margin: 0 1em 1em 0;
font-size: 0.7em;
}
}
article address {
font-size: 1em;
float: left;
}
article.address {
margin:1em 0 1.5em 0;
}
/* article */
article:after {
clear: both;
content: "";
display: table;
}
article h1 {
clip: rect(0 0 0 0);
position: absolute;
}
article p {
margin-bottom: 20px;
}
/* table meta & balance */
table.meta:after, table.balance:after {
clear: both;
content: "";
display: table;
}
/* table meta */
table.meta {
float: right;
width: 50%;
font-size: 85%;
th {
text-align: right;
font-weight: normal;
}
td {
width: 120px;
text-align: right;
}
}
/* table balance */
table.balance {
float: right;
margin-top: 1em;
th {
text-align: right;
font-weight: normal;
}
td {
width: 140px;
text-align: right;
line-height: 22px;
}
.total {
font-weight: bold;
}
}
/* table items */
table.inventory {
clear: both;
width: 100%;
margin-top: 2em;
border-bottom: 1px solid #000;
}
table.inventory th,
table.inventory td {
padding: 5px;
}
table.inventory thead th {
font-weight: bold;
border-width: 0 0 1px 0;
border-bottom: 1px solid #000;
padding-bottom: 15px
}
table.inventory tbody tr:first-child td {
padding-top: 15px
}
table.inventory tbody tr:last-child td {
padding-bottom: 15px
}
.footer {
border-color: #000;
border-top: 1px solid #ccc;
margin-top: 50px;
font-size: 0.8em;
padding-top: 10px;
width: 100%;
}
}
/* ================================ PRINTING RULES ================================ */
/* ================================ FREELANCER INVOICE ================================ */
@media print {
* {
-webkit-print-color-adjust: exact;
}
body, .wrapper {
margin: 0;
padding: 0;
background-color: white;
}
#freelancer-invoice {
span:empty {
display: none;
box-sizing: border-box;
h1 {
font: bold 100% sans-serif;
letter-spacing: 0.5em;
text-align: center;
text-transform: uppercase;
}
.add, .cut {
display: none;
h2 {
font: bold 1.5em sans-serif;
margin-bottom: 1em;
}
/* table */
table {
width: 100%;
}
table {
border-collapse: separate;
border-spacing: 0;
}
/* header */
header {
&:after {
clear: both;
content: "";
display: table;
font-size: 1em;
}
address {
float: left;
font-style: normal;
line-height: 1.25;
margin: 0 1em 1em 0;
font-size: 0.7em;
}
}
article address {
font-size: 1em;
float: left;
}
article.address {
margin: 1em 0 1.5em 0;
}
/* article */
article:after {
clear: both;
content: "";
display: table;
}
article h1 {
clip: rect(0 0 0 0);
position: absolute;
}
article p {
margin-bottom: 20px;
}
/* table meta & balance */
table.meta:after, table.balance:after {
clear: both;
content: "";
display: table;
}
/* table meta */
table.meta {
float: right;
width: 50%;
font-size: 85%;
th {
text-align: right;
font-weight: normal;
}
td {
width: 120px;
text-align: right;
}
}
/* table balance */
table.balance {
float: right;
margin-top: 1em;
th {
text-align: right;
font-weight: normal;
}
td {
width: 140px;
text-align: right;
line-height: 22px;
}
.total {
font-weight: bold;
}
}
/* table items */
table.inventory {
clear: both;
width: 100%;
margin-top: 2em;
border-bottom: 1px solid #000;
}
table.inventory th,
table.inventory td {
padding: 5px;
}
table.inventory thead th {
font-weight: bold;
border-width: 0 0 1px 0;
border-bottom: 1px solid #000;
padding-bottom: 15px
}
table.inventory tbody tr:first-child td {
padding-top: 15px
}
table.inventory tbody tr:last-child td {
padding-bottom: 15px
}
.footer {
border-color: #000;
}
}
.invoice {
margin: 1em 2em;
font-size: 90%;
width: unset;
padding: 0;
min-height: unset;
position: unset;
max-width: unset;
box-shadow: unset;
/* ================================ PRINTING RULES ================================ */
@media print {
* {
-webkit-print-color-adjust: exact;
}
body, .wrapper {
margin: 0;
padding: 0;
background-color: white;
}
#freelancer-invoice {
span:empty {
display: none;
}
.add, .cut {
display: none;
}
}
.invoice {
margin: 1em 2em;
font-size: 90%;
width: unset;
padding: 0;
min-height: unset;
position: unset;
max-width: unset;
box-shadow: unset;
}
}
}
}
}

View file

@ -15,7 +15,7 @@
"build/invoice.19f36eca.js"
],
"css": [
"build/invoice.ff32661a.css"
"build/invoice.ccdecd42.css"
]
},
"invoice-pdf": {
@ -24,7 +24,7 @@
"build/invoice-pdf.0ce1f3ce.js"
],
"css": [
"build/invoice-pdf.9a7468ef.css"
"build/invoice-pdf.e73a6dda.css"
]
},
"chart": {

View file

@ -1 +0,0 @@
body{font-family:sans-serif;font-size:10pt}body,h1,h2,h3,h4,h5,p,table,td,th,tr{margin:0;padding:0}th{border:none;text-align:left}td{vertical-align:top}p{margin-bottom:12px}.text-small{font-size:7pt}.padding-left{padding-left:15px}.text-center{text-align:center}.text-right{text-align:right}.text-nowrap{white-space:nowrap}.date{color:#666;font-size:80%;text-align:right;padding-top:6px}.wrapper{margin:4px}table{width:100%;border-spacing:0}p,table.addresses{line-height:14pt}table.addresses{margin-bottom:30px}table.header{padding-bottom:4px;border-bottom:1px solid #999}table.header .title{font-weight:400;font-size:140%}table.footer{border-top:1px solid #999;font-size:80%;padding-top:8px;line-height:14px}table.footer td{vertical-align:bottom}table.items{border-collapse:collapse;margin-top:50px;margin-bottom:60px}table.items tbody td,table.items thead th{border-bottom:1px solid #ccc}table.items tbody td.first,table.items thead th.first{padding-left:0}table.items tbody td.last,table.items tfoot td.last,table.items thead th.last{padding-right:0}table.items td,table.items th{padding:8px 6px}table.items tfoot td,table.items tfoot th{padding:20px 0 0;line-height:1px}table.items tfoot td.last{padding-left:10px}table.items tr.odd{background-color:#f5f5f5}

View file

@ -0,0 +1 @@
body{font-family:sans-serif;font-size:10pt}body,h1,h2,h3,h4,h5,p,table,td,th,tr{margin:0;padding:0}th{border:none;text-align:left}td{vertical-align:top}p{margin-bottom:12px}.text-small{font-size:7pt}.padding-left{padding-left:15px}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.text-nowrap{white-space:nowrap}.date{color:#666;font-size:80%;text-align:right;padding-top:6px}.wrapper{margin:4px}table{width:100%;border-spacing:0}p,table.addresses{line-height:14pt}table.addresses{margin-bottom:30px}table.header{padding-bottom:4px;border-bottom:1px solid #999}table.header .title{font-weight:400;font-size:140%}table.footer{border-top:1px solid #999;font-size:80%;padding-top:8px;line-height:14px}table.footer td{vertical-align:top}table.items{border-collapse:collapse;margin-top:50px;margin-bottom:60px}table.items tbody td,table.items thead th{border-bottom:1px solid #ccc}table.items tbody td.first,table.items thead th.first{padding-left:0}table.items tbody td.last,table.items tfoot td.last,table.items thead th.last{padding-right:0}table.items td,table.items th{padding:8px 6px}table.items tfoot td,table.items tfoot th{padding:20px 0 0;line-height:1px}table.items tfoot td.last{padding-left:10px}table.items tr.odd{background-color:#f5f5f5}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,9 +1,9 @@
{
"build/app.css": "build/app.7efa8299.css",
"build/app.js": "build/app.7d469ea7.js",
"build/invoice.css": "build/invoice.ff32661a.css",
"build/invoice.css": "build/invoice.ccdecd42.css",
"build/invoice.js": "build/invoice.19f36eca.js",
"build/invoice-pdf.css": "build/invoice-pdf.9a7468ef.css",
"build/invoice-pdf.css": "build/invoice-pdf.e73a6dda.css",
"build/invoice-pdf.js": "build/invoice-pdf.0ce1f3ce.js",
"build/chart.js": "build/chart.e845e930.js",
"build/calendar.css": "build/calendar.25e7e872.css",

View file

@ -30,9 +30,10 @@ class VersionCommand extends Command
->setName('kimai:version')
->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('short', null, InputOption::VALUE_NONE, 'Display the version only')
->addOption('number', null, InputOption::VALUE_NONE, 'Display the version identifier only only')
// @deprecated since 1.14.1
->addOption('name', null, InputOption::VALUE_NONE, 'DEPRECATED: Display the major release name')
->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')
;
@ -46,6 +47,7 @@ class VersionCommand extends Command
$io = new SymfonyStyle($input, $output);
if ($input->getOption('semver')) {
@trigger_error('bin/console kimai:version --semver is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
$io->writeln(Constants::VERSION . '-' . Constants::STATUS);
return 0;
@ -58,18 +60,26 @@ class VersionCommand extends Command
}
if ($input->getOption('name')) {
@trigger_error('bin/console kimai:version --name is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
$io->writeln(Constants::NAME);
return 0;
}
if ($input->getOption('candidate')) {
@trigger_error('bin/console kimai:version --candidate is deprecated and will be removed with 2.0', E_USER_DEPRECATED);
$io->writeln(Constants::STATUS);
return 0;
}
$io->writeln(Constants::SOFTWARE . ' ' . Constants::VERSION . ' by Kevin Papst and contributors.');
if ($input->getOption('number')) {
$io->writeln((string) Constants::VERSION_ID);
return 0;
}
$io->writeln(sprintf('%s <info>%s</info> by Kevin Papst and contributors.', Constants::SOFTWARE, Constants::VERSION));
return 0;
}

View file

@ -22,4 +22,14 @@ class ConsoleApplication extends Application
{
return Constants::VERSION;
}
/**
* Overwritten to prevent unwanted SF core messages to show up here.
*
* @return string
*/
public function getLongVersion()
{
return sprintf('%s <info>%s</info> (env: <comment>%s</>, debug: <comment>%s</>)', $this->getName(), $this->getVersion(), $this->getKernel()->getEnvironment(), $this->getKernel()->isDebug() ? 'true' : 'false');
}
}

View file

@ -29,6 +29,11 @@ final class StringHelper
public static function sanitizeDDE(string $text): string
{
// see #3189
if (\strlen($text) === 0) {
return $text;
}
$sanitize = false;
if (\in_array($text[0], self::DDE_PAYLOADS)) {

View file

@ -25,7 +25,7 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
/**
* @var array
*/
private $localDomains = [];
private $localDomains;
public function __construct(BaseTranslator $translator, array $localDomains = [])
{
@ -51,8 +51,6 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
protected function hasLocalOverwrite($id, $domain, $locale = null): bool
{
$found = false;
$catalogue = $this->getCatalogue($locale);
while (false === ($found = $catalogue->defines($id, $domain))) {
if ($cat = $catalogue->getFallbackCatalogue()) {

View file

@ -1,6 +1,8 @@
<!DOCTYPE html>
<html lang="{{ app.request.locale }}">
<head></head>
<head>
<title>{% block page_title %}{{- get_title() -}}{% endblock %}</title>
</head>
<body class="ajax-forms">
<div id="ajax-messages">
{{ include('@AdminLTE/Partials/_flash_messages.html.twig') }}

View file

@ -47,7 +47,7 @@ mpdf-->
<div class="wrapper">
<table class="addresses">
<tr>
<td width="60%">
<td style="width:60%">
{{ 'invoice.to'|trans }}
<br>
<strong>{{ model.customer.company|default(model.customer.name) }}</strong>
@ -57,14 +57,6 @@ mpdf-->
<br>
{{ 'label.vat_id'|trans }}: {{ model.customer.vatId }}
{% endif %}
{% if model.customer.number is not empty %}
<br>
{{ 'label.number'|trans }}: {{ model.customer.number }}
{% endif %}
{% if model.query.project is not empty and model.query.project.orderNumber is not empty %}
<br>
{{ 'label.orderNumber'|trans }}: {{ model.query.project.orderNumber }}
{% endif %}
</td>
<td>
{{ 'invoice.from'|trans }}
@ -82,12 +74,24 @@ mpdf-->
</table>
<p>
<strong>{{ 'invoice.number'|trans }}:</strong>
{{ 'invoice.number'|trans }}:
{{ model.invoiceNumber }}
<br>
<strong>{{ 'invoice.due_days'|trans }}:</strong>
{{ 'invoice.due_days'|trans }}:
{{ model.dueDate|date_short }}
{% if model.customer.number is not empty %}
<br>
{{ 'label.number'|trans }}:
{{ model.customer.number }}
{% endif %}
{% if model.query.project is not empty and model.query.project.orderNumber is not empty %}
<br>
{{ 'label.orderNumber'|trans }}:
{{ model.query.project.orderNumber }}
{% endif %}
</p>
<table class="items">

View file

@ -18,6 +18,9 @@
<tr>
<td class="text-small">
{{ model.template.company }} &ndash; {{ model.template.address|nl2str(' &ndash; ') }}
{% if model.template.vatId is not empty %}
&ndash; {{ 'label.vat_id'|trans }}: {{ model.template.vatId }}
{% endif %}
</td>
<td class="text-small text-right">
{{ 'export.page_of'|trans({'%page%': '{PAGENO}', '%pages%': '{nb}'}) }}
@ -56,39 +59,43 @@ mpdf-->
<td>
<strong>{{ model.customer.company|default(model.customer.name) }}</strong><br>
{{ model.customer.address|nl2br }}
{% if model.customer.vatId is not empty %}
<br>
{{ 'label.vat_id'|trans }}: {{ model.customer.vatId }}
{% endif %}
</td>
<td class="text-right">
{% set classLeft = 'text-left' %}
{% set classRight = 'text-right text-nowrap padding-left' %}
<table style="width: 240px">
<tr>
<td>{{ 'label.date'|trans }}:</td>
<td class="text-nowrap padding-left">{{ model.invoiceDate|date_short }}</td>
<td class="{{ classLeft }}">{{ 'label.date'|trans }}</td>
<td class="{{ classRight }}">{{ model.invoiceDate|date_short }}</td>
</tr>
<tr>
<td>{{ 'invoice.service_date'|trans }}:</td>
<td class="text-nowrap padding-left">{{ model.query.end|month_name }} {{ model.query.end|date('Y') }}</td>
<td class="{{ classLeft }}">{{ 'invoice.service_date'|trans }}</td>
<td class="{{ classRight }}">{{ model.query.end|month_name }} {{ model.query.end|date('Y') }}</td>
</tr>
<tr>
<td>{{ 'invoice.number'|trans }}:</td>
<td class="text-nowrap padding-left">{{ model.invoiceNumber }}</td>
<td class="{{ classLeft }}">{{ 'invoice.number'|trans }}</td>
<td class="{{ classRight }}">{{ model.invoiceNumber }}</td>
</tr>
<tr>
<td>{{ 'invoice.due_days'|trans }}:</td>
<td class="text-nowrap padding-left">{{ model.dueDate|date_short }}</td>
<td class="{{ classLeft }}">{{ 'invoice.due_days'|trans }}</td>
<td class="{{ classRight }}">{{ model.dueDate|date_short }}</td>
</tr>
{% if model.query.project is not empty and model.query.project.orderNumber is not empty %}
{% if model.customer.number is not empty %}
<tr>
<td>{{ 'label.orderNumber'|trans }}</td>
<td class="text-nowrap padding-left">{{ model.query.project.orderNumber }}</td>
<td class="{{ classLeft }}">{{ 'label.number'|trans }}</td>
<td class="{{ classRight }}">{{ model.customer.number }}</td>
</tr>
{% endif %}
{% if model.template.vatId is not empty %}
{% if model.query.project is not empty and model.query.project.orderNumber is not empty %}
<tr>
<td>{{ 'label.vat_id'|trans }}:</td>
<td class="text-nowrap padding-left">{{ model.template.vatId }}</td>
<td class="{{ classLeft }}">{{ 'label.orderNumber'|trans }}</td>
<td class="{{ classRight }}">{{ model.query.project.orderNumber }}</td>
</tr>
{% endif %}
{% if model.customer.vatId is not empty %}
<tr>
<td class="{{ classLeft }}">{{ 'label.vat_id'|trans }}</td>
<td class="{{ classRight }}">{{ model.customer.vatId }}</td>
</tr>
{% endif %}
</table>

View file

@ -528,7 +528,7 @@
<td class="avatars">
{% set teamHiddenId = 'team_' ~ team.id ~ '_hiddenUser' ~ random() %}
{% set counter = 0 %}
{% for member in members|sort((a, b) => a.teamlead < b.teamlead) %}
{% for member in members|sort((a, b) => b.teamlead <=> a.teamlead) %}
{% set user = member.user %}
{% if member.teamlead %}
{{ _self.user_avatar(user, ('label.teamlead'|trans ~ ': ' ~ user.displayName), 'teamlead') }}

View file

@ -158,7 +158,7 @@
{% endfor %}
</tr>
{% set yearTotal = 0 %}
{% for userYearStat in userYearStats|sort((a, b) => a.duration <= b.duration) %}
{% for userYearStat in userYearStats|sort((a, b) => b.duration <=> a.duration) %}
{% set user = userYearStat.user %}
{% set userYear = userYearStat.year %}
{% set userTotal = userYearStat.duration %}
@ -216,7 +216,7 @@
<div class="row">
<div class="col-xs-12 col-sm-9 col-md-8 col-lg-6">
<table class="table table-hover dataTable">
{% for stat in activities|sort((a, b) => a.duration <= b.duration) %}
{% for stat in activities|sort((a, b) => b.duration <=> a.duration) %}
{% set dataset = dataset|merge([{'value': stat.duration, 'name': stat.name, 'color': stat.activity.color|colorize(stat.activity.name), 'duration': stat.duration|duration, 'rate': stat.rate|money(currency)}]) %}
{% set percentage = 0 %}
{% if totalDuration > 0 and stat.duration > 0 %}
@ -413,7 +413,7 @@
{% set totalDuration = 0 %}
{% set datasets = [] %}
{% set labels = [] %}
{% for userStat in project_details.userStats|sort((a, b) => a.duration <= b.duration) %}
{% for userStat in project_details.userStats|sort((a, b) => b.duration <=> a.duration) %}
{% set user = userStat.user %}
{% set color = user.color|colorize(user.displayName) %}
{% set rateTotal = rateTotal + userStat.rate %}

View file

@ -26,7 +26,7 @@
</tr>
</thead>
<tbody>
{% for userPeriod in stats|filter(row => row.user is not null) %}
{% for userPeriod in stats|filter(row => row.user is not null)|sort((a,b) => a.user.displayName|lower <=> b.user.displayName|lower) %}
{% set usersTotalDuration = 0 %}
{% set usersTotalInternalRate = 0 %}
{% set usersTotalRate = 0 %}

View file

@ -49,9 +49,10 @@ class VersionCommandTest extends KernelTestCase
{
return [
[[], 'Kimai ' . Constants::VERSION . ' by Kevin Papst and contributors.'],
[['--name' => true], Constants::NAME],
[['--short' => true], Constants::VERSION],
[['--number' => true], Constants::VERSION_ID],
// @deprecated since 1.14.1
[['--name' => true], Constants::NAME],
[['--candidate' => true], Constants::STATUS],
[['--semver' => true], Constants::VERSION . '-' . Constants::STATUS],
];

View file

@ -25,5 +25,6 @@ class ConsoleApplicationTest extends TestCase
$sut = new ConsoleApplication($kernel);
self::assertEquals(Constants::SOFTWARE, $sut->getName());
self::assertEquals(Constants::VERSION, $sut->getVersion());
self::assertEquals(sprintf('%s <info>%s</info> (env: <comment></>, debug: <comment>false</>)', Constants::SOFTWARE, Constants::VERSION), $sut->getLongVersion());
}
}

View file

@ -32,6 +32,7 @@ class StringHelperTest extends TestCase
public function getDdeAttackStrings()
{
yield ['DDE ("cmd";"/C calc";"!A0")A0'];
yield [' DDE ("cmd";"/C calc";"!A0")A0'];
yield ["@SUM(1+9)*cmd|' /C calc'!A0"];
yield ["-10+20+cmd|' /C calc'!A0"];
yield ["+10+20+cmd|' /C calc'!A0"];
@ -54,4 +55,18 @@ class StringHelperTest extends TestCase
{
self::assertEquals("' " . $input, StringHelper::sanitizeDDE($input));
}
public function getNonDdeAttackStrings()
{
yield [''];
yield [' '];
}
/**
* @dataProvider getNonDdeAttackStrings
*/
public function testSanitizeDdeWithCorrectStrings(string $input)
{
self::assertEquals($input, StringHelper::sanitizeDDE($input));
}
}