mirror of
https://github.com/kevinpapst/kimai2.git
synced 2025-03-17 06:22:38 +00:00
1.18.2 (#3190)
* improve console version output * fix empty string issue in csv export (DDE protection) - fixes #3189 * 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:
parent
317ac59f47
commit
00b5a65859
21 changed files with 363 additions and 300 deletions
assets/sass
public/build
entrypoints.jsoninvoice-pdf.9a7468ef.cssinvoice-pdf.e73a6dda.cssinvoice.ccdecd42.cssinvoice.ff32661a.cssmanifest.json
src
templates
tests
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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": {
|
||||
|
|
|
@ -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}
|
1
public/build/invoice-pdf.e73a6dda.css
Normal file
1
public/build/invoice-pdf.e73a6dda.css
Normal 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}
|
1
public/build/invoice.ccdecd42.css
Normal file
1
public/build/invoice.ccdecd42.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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') }}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
<tr>
|
||||
<td class="text-small">
|
||||
{{ model.template.company }} – {{ model.template.address|nl2str(' – ') }}
|
||||
{% if model.template.vatId is not empty %}
|
||||
– {{ '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>
|
||||
|
|
|
@ -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') }}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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],
|
||||
];
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue