Release 2.50 (#5835)

* replace p-0 class with fullsize embed option
* bump parsedown package
* remove support for file:// urls
* fix missing macro in export print template
* fix weekly hours with breaks
This commit is contained in:
Kevin Papst
2026-02-25 21:07:40 +01:00
committed by GitHub
parent 8094fcf5b5
commit 4154de6bd1
22 changed files with 116 additions and 166 deletions

46
composer.lock generated
View File

@@ -1869,24 +1869,24 @@
},
{
"name": "erusev/parsedown",
"version": "1.7.4",
"version": "1.8.0",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3"
"reference": "96baaad00f71ba04d76e45b4620f54d3beabd6f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/96baaad00f71ba04d76e45b4620f54d3beabd6f7",
"reference": "96baaad00f71ba04d76e45b4620f54d3beabd6f7",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.3.0"
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35"
"phpunit/phpunit": "^7.5|^8.5|^9.6"
},
"type": "library",
"autoload": {
@@ -1913,9 +1913,15 @@
],
"support": {
"issues": "https://github.com/erusev/parsedown/issues",
"source": "https://github.com/erusev/parsedown/tree/1.7.x"
"source": "https://github.com/erusev/parsedown/tree/1.8.0"
},
"time": "2019-12-30T22:54:17+00:00"
"funding": [
{
"url": "https://github.com/erusev",
"type": "github"
}
],
"time": "2026-02-16T11:41:01+00:00"
},
{
"name": "friendsofsymfony/rest-bundle",
@@ -11437,16 +11443,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.94.0",
"version": "v3.94.1",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "883b20fb38c7866de9844ab6d0a205c423bde2d4"
"reference": "d1a3634e29916367b885250e1fc4dfd5ffe3b091"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/883b20fb38c7866de9844ab6d0a205c423bde2d4",
"reference": "883b20fb38c7866de9844ab6d0a205c423bde2d4",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/d1a3634e29916367b885250e1fc4dfd5ffe3b091",
"reference": "d1a3634e29916367b885250e1fc4dfd5ffe3b091",
"shasum": ""
},
"require": {
@@ -11529,7 +11535,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.94.0"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.94.1"
},
"funding": [
{
@@ -11537,7 +11543,7 @@
"type": "github"
}
],
"time": "2026-02-11T16:44:33+00:00"
"time": "2026-02-18T12:24:42+00:00"
},
{
"name": "masterminds/html5",
@@ -11829,16 +11835,16 @@
},
{
"name": "phpstan/phpstan-doctrine",
"version": "2.0.16",
"version": "2.0.17",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-doctrine.git",
"reference": "f4ff6084a26d91174b3f0b047589af293a893104"
"reference": "734ef36c2709b51943f04aacadddb76f239562d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/f4ff6084a26d91174b3f0b047589af293a893104",
"reference": "f4ff6084a26d91174b3f0b047589af293a893104",
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/734ef36c2709b51943f04aacadddb76f239562d3",
"reference": "734ef36c2709b51943f04aacadddb76f239562d3",
"shasum": ""
},
"require": {
@@ -11899,9 +11905,9 @@
],
"support": {
"issues": "https://github.com/phpstan/phpstan-doctrine/issues",
"source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.16"
"source": "https://github.com/phpstan/phpstan-doctrine/tree/2.0.17"
},
"time": "2026-02-11T08:54:45+00:00"
"time": "2026-02-18T10:21:23+00:00"
},
{
"name": "phpstan/phpstan-phpunit",

View File

@@ -3800,11 +3800,6 @@ parameters:
count: 1
path: src/Utils/ParsedownExtension.php
-
message: "#^Property App\\\\Utils\\\\ParsedownExtension\\:\\:\\$safeLinksWhitelist has no type specified\\.$#"
count: 1
path: src/Utils/ParsedownExtension.php
-
message: "#^Parameter \\#1 \\$profile of method App\\\\Utils\\\\ProfileManager\\:\\:getProfile\\(\\) expects string, mixed given\\.$#"
count: 1

View File

@@ -17,11 +17,11 @@ final class Constants
/**
* The current release version
*/
public const VERSION = '2.49.0';
public const VERSION = '2.50.0';
/**
* The current release: major * 10000 + minor * 100 + patch
*/
public const VERSION_ID = 24900;
public const VERSION_ID = 25000;
/**
* The software name
*/

View File

@@ -95,6 +95,7 @@ final class QuickEntryTimesheetType extends AbstractType
$duration = $data->getDuration(false);
try {
if (null !== $duration) {
$duration += $data->getBreak();
$end = clone $data->getBegin();
$end->modify('+ ' . abs($duration) . ' seconds');
$data->setEnd($end);

View File

@@ -21,15 +21,17 @@ class Parsedown extends \Parsedown
{
$block = parent::blockHeader($Line);
$text = $block['element']['text'];
if (isset($block['element']['handler']['argument'])) {
$text = $block['element']['handler']['argument'];
if (\is_string($text) && $text !== '') {
$id = $this->getIDfromText($text);
if (\is_string($text) && $text !== '') {
$id = $this->getIDfromText($text);
// add id-attribute
$block['element']['attributes'] = [
'id' => $id
];
// add id-attribute
$block['element']['attributes'] = [
'id' => $id
];
}
}
return $block;

View File

@@ -44,55 +44,17 @@ final class ParsedownExtension extends Parsedown
];
/**
* Overwritten to add support for file:///
*/
protected $safeLinksWhitelist = [
'file:///',
'http://',
'https://',
'ftp://',
'ftps://',
'mailto:',
'data:image/png;base64,',
'data:image/gif;base64,',
'data:image/jpeg;base64,',
'irc:',
'ircs:',
'git:',
'ssh:',
'news:',
'steam:',
];
/**
* Overwritten:
* - added support for file:///
* - open links in new windows
* Overwritten to open links in new windows
*/
protected function inlineUrl($Excerpt): ?array
{
if ($this->urlsLinked !== true or !isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') {
return null;
$block = parent::inlineUrl($Excerpt);
if (isset($block['element']['attributes']) && \is_array($block['element']['attributes'])) {
$block['element']['attributes']['target'] = '_blank';
}
if (preg_match('/\b(https?:[\/]{2}|file:[\/]{3})[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) {
$url = $matches[0][0];
return [
'extent' => \strlen($matches[0][0]),
'position' => $matches[0][1],
'element' => [
'name' => 'a',
'text' => $url,
'attributes' => [
'href' => $url,
'target' => '_blank'
],
],
];
}
return null;
return $block;
}
protected function blockTable($Line, ?array $Block = null) // @phpstan-ignore missingType.return,missingType.iterableValue,missingType.parameter

View File

@@ -8,7 +8,7 @@
{% set currency = activity.project.customer.currency %}
{% endif %}
{% embed '@theme/embeds/card.html.twig' %}
{% embed '@theme/embeds/card.html.twig' with {fullsize: true} %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "customer/actions.html.twig" as customerActions %}
{% import "project/actions.html.twig" as projectActions %}
@@ -21,7 +21,6 @@
{{ widgets.card_tool_button('edit', {'class': 'modal-ajax-form open-edit', 'title': 'edit', 'url': path('admin_activity_edit', {'id': activity.id})}) }}
{% endif %}
{% endblock %}
{% block box_body_class %}p-0{% endblock %}
{% block box_body %}
{% if activity.comment is not empty %}
<div class="comment p-3">

View File

@@ -55,8 +55,7 @@
{% endif %}
<div class="col-xs-12 {% if hasTwoColumns %}col-md-8 col-lg-9 col-print-12{% endif %}">
{% embed '@theme/embeds/card.html.twig' %}
{% block box_body_class %}p-0{% endblock %}
{% embed '@theme/embeds/card.html.twig' with {fullsize: true} %}
{% block box_body %}
<div id="timesheet_calendar"></div>
{% endblock %}

View File

@@ -4,7 +4,7 @@
{% block main %}
{% set can_edit = is_granted('edit', customer) %}
{% embed '@theme/embeds/card.html.twig' %}
{% embed '@theme/embeds/card.html.twig' with {fullsize: true} %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_attributes %}id="customer_details_box"{% endblock %}
{% block box_title %}
@@ -16,7 +16,6 @@
{{ widgets.card_tool_button('edit', {'class': 'modal-ajax-form open-edit', 'title': 'edit', 'url': path('admin_customer_edit', {'id': customer.id})}) }}
{% endif %}
{% endblock %}
{% block box_body_class %}p-0{% endblock %}
{% block box_body %}
{% if customer.comment is not empty %}
<div class="comment p-3">

View File

@@ -1,13 +1,12 @@
{% set timeBudgetVisible = is_granted('time', entity) %}
{% set durationTrans = entity.isMonthlyBudget() ? 'stats.durationMonth' : 'stats.workingTime' %}
{% if timeBudgetVisible %}
{% embed '@theme/embeds/card.html.twig' %}
{% embed '@theme/embeds/card.html.twig' with {fullsize: true} %}
{% import "macros/progressbar.html.twig" as progress %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_title %}
{{ 'timeBudget'|trans }} {% if entity.isMonthlyBudget() %}({{ 'budgetType_month'|trans }}){% endif %}
{% endblock %}
{% block box_body_class %}p-0{% endblock %}
{% block box_attributes %}id="time_budget_box"{% endblock %}
{% block box_body %}
<div class="row">
@@ -59,13 +58,12 @@
{% endembed %}
{% endif %}
{% if is_granted('budget', entity) %}
{% embed '@theme/embeds/card.html.twig' %}
{% embed '@theme/embeds/card.html.twig' with {fullsize: true} %}
{% import "macros/progressbar.html.twig" as progress %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_title %}
{{ 'budget'|trans }} {% if entity.isMonthlyBudget() %}({{ 'budgetType_month'|trans }}){% endif %}
{% endblock %}
{% block box_body_class %}p-0{% endblock %}
{% block box_attributes %}id="budget_box"{% endblock %}
{% block box_body %}
<div class="row">

View File

@@ -104,13 +104,12 @@
{% if entries is empty %}
{{ widgets.nothing_found() }}
{% else %}
{% embed '@theme/embeds/card.html.twig' %}
{% embed '@theme/embeds/card.html.twig' with {fullsize: true} %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_title %}
{{ 'preview'|trans }}
{% endblock %}
{% block box_attributes %}id="preview_export"{% endblock %}
{% block box_body_class %}p-0{% endblock %}
{% block box_footer %}
{% if showMarkAsExportedButton %}
<div class="d-flex">

View File

@@ -1,6 +1,6 @@
<!DOCTYPE html>
<html lang="{{ app.locale }}">
{% from "macros/meta_fields.html.twig" import meta_field_value %}
{% from "macros/meta_fields.html.twig" import meta_field_value, form_type_value %}
{% set storageItemName = 'kimai_html_export' %}
{% set decimal = decimal|default(false) %}
{% set columnTitles = {} %}
@@ -507,7 +507,7 @@
<td class="column-{{ 'u_' ~ field.name }} text-nowrap" {% if not columns['u_' ~ field.name] %}style="display: none"{% endif %}>
{% set metaField = entry.user.preference(field.name) %}
{% if not metaField is null and metaField.value is not null and metaField.value is not empty %}
{{ _self.form_type_value(field.type, metaField.value, entry.user) }}
{{ form_type_value(field.type, metaField.value, entry.user) }}
{% endif %}
</td>
{% endfor %}

View File

@@ -35,14 +35,13 @@
{% endif %}
{% if documents|length > 0 %}
{% embed '@theme/embeds/card.html.twig' with {'documents': documents} %}
{% embed '@theme/embeds/card.html.twig' with {'documents': documents, fullsize: true} %}
{% import "invoice/actions.html.twig" as actions %}
{% import "macros/widgets.html.twig" as widgets %}
{% block box_title %}{{ 'invoice_renderer'|trans({}, 'invoice-renderer') }}{% endblock %}
{% block box_attributes %}
id="invoice_document_list"
{% endblock %}
{% block box_body_class %}p-0{% endblock %}
{% block box_body %}
<table class="table table-hover dataTable">
<thead>

View File

@@ -90,14 +90,13 @@
{% endif %}
{% if models|length > 0 %}
{% embed '@theme/embeds/card.html.twig' %}
{% embed '@theme/embeds/card.html.twig' with {fullsize: true} %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "macros/datatables.html.twig" as tables %}
{% block box_title %}
{{ 'preview'|trans }}
{% endblock %}
{% block box_attributes %}id="invoice_customer_forms"{% endblock %}
{% block box_body_class %}p-0{% endblock %}
{% block box_body %}
<table class="table dataTable">
<thead>

View File

@@ -3,40 +3,42 @@
{% set metaField = entity.metaField(field.name) %}
{% if not metaField is null %}
{% set metaField = metaField.merge(field) %}
{% set type = metaField.type %}
{% set value = metaField.value %}
{% if 'DurationType' in type %}
{{ value|duration }}
{% elseif 'YesNoType' in type or 'CheckboxType' in type %}
{{ value ? ('yes'|trans) : ('no'|trans) }}
{% elseif 'DatePickerType' in type %}
{{ value|date_short }}
{% elseif 'DateTimePickerType' in type %}
{{ value|date_time }}
{% elseif 'CountryType' in type %}
{{ value|country_name }}
{% elseif 'CurrencyType' in type %}
{{ value|currency_name }}
{% elseif 'LanguageType' in type %}
{{ value|locale_name }}
{% elseif 'MoneyType' in type %}
{% set classname = class_name(entity) %}
{% if classname == 'App\\Entity\\Timesheet' %}
{{ value|money(entity.project.customer.currency) }}
{% elseif classname == 'App\\Entity\\Customer' %}
{{ value|money(entity.currency) }}
{% elseif classname == 'App\\Entity\\Project' %}
{{ value|money(entity.customer.currency) }}
{% elseif classname == 'App\\Entity\\Activity' and entity.project is not null %}
{{ value|money(entity.project.customer.currency) }}
{% else %}
{{ value }}
{% endif %}
{% elseif 'TextareaType' in type %}
{{ value|nl2br }}
{% else %}
{# EmailType, UrlType, TagsType, ColorPickerType, ColorChoiceType #}
{{ value }}
{% endif %}
{{ _self.form_type_value(metaField.type, metaField.value, entity) }}
{% endif %}
{% endmacro %}
{% macro form_type_value(type, value, entity) %}
{% if 'DurationType' in type %}
{{ value|duration }}
{% elseif 'YesNoType' in type or 'CheckboxType' in type %}
{{ value ? ('yes'|trans) : ('no'|trans) }}
{% elseif 'DatePickerType' in type %}
{{ value|date_short }}
{% elseif 'DateTimePickerType' in type %}
{{ value|date_time }}
{% elseif 'CountryType' in type %}
{{ value|country_name }}
{% elseif 'CurrencyType' in type %}
{{ value|currency_name }}
{% elseif 'LanguageType' in type %}
{{ value|locale_name }}
{% elseif 'MoneyType' in type %}
{% set classname = class_name(entity) %}
{% if classname == 'App\\Entity\\Timesheet' %}
{{ value|money(entity.project.customer.currency) }}
{% elseif classname == 'App\\Entity\\Customer' %}
{{ value|money(entity.currency) }}
{% elseif classname == 'App\\Entity\\Project' %}
{{ value|money(entity.customer.currency) }}
{% elseif classname == 'App\\Entity\\Activity' and entity.project is not null %}
{{ value|money(entity.project.customer.currency) }}
{% else %}
{{ value }}
{% endif %}
{% elseif 'TextareaType' in type %}
{{ value|nl2br }}
{% else %}
{# EmailType, UrlType, TagsType, ColorPickerType, ColorChoiceType #}
{{ value }}
{% endif %}
{% endmacro %}

View File

@@ -7,9 +7,8 @@
{% if plugins|length == 0 %}
{{ widgets.callout('warning', 'plugin.none_installed'|trans({}, 'plugins')) }}
{% else %}
{% embed '@theme/embeds/card.html.twig' %}
{% embed '@theme/embeds/card.html.twig' with {fullsize: true} %}
{% block box_title %}{{ 'plugin.installed'|trans }}{% endblock %}
{% block box_body_class %}p-0{% endblock %}
{% block box_body %}
{% set columns = {
'name': {'class': 'alwaysVisible w-25'},
@@ -46,10 +45,9 @@
{% endif %}
{% if extensions|length > 0 %}
{% embed '@theme/embeds/card.html.twig' %}
{% embed '@theme/embeds/card.html.twig' with {fullsize: true} %}
{% from "macros/widgets.html.twig" import card_tool_button %}
{% block box_title %}{{ 'plugin.marketplace'|trans({}, 'plugins') }}{% endblock %}
{% block box_body_class %}p-0{% endblock %}
{% block box_tools %}
{{ card_tool_button('shop', {'title': 'shop', 'translation_domain': 'plugins', 'target': '_blank', 'url': constant('App\\Constants::HOMEPAGE') ~ '/store/', 'icon': false, 'class': 'btn-primary'}) }}
{% endblock %}

View File

@@ -4,7 +4,7 @@
{% block main %}
{% set can_edit = is_granted('edit', project) %}
{% embed '@theme/embeds/card.html.twig' %}
{% embed '@theme/embeds/card.html.twig' with {fullsize: true} %}
{% import "macros/widgets.html.twig" as widgets %}
{% import "customer/actions.html.twig" as customerActions %}
{% block box_attributes %}id="project_details_box"{% endblock %}
@@ -16,7 +16,6 @@
{% block box_title %}
{{ widgets.label_dot(project.name, project.color) }}
{% endblock %}
{% block box_body_class %}p-0{% endblock %}
{% block box_body %}
{% if project.comment is not empty %}
<div class="comment p-3">

View File

@@ -15,13 +15,17 @@ use App\Form\Type\QuickEntryTimesheetType;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Form\FormExtensionInterface;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
#[CoversClass(QuickEntryTimesheetType::class)]
class QuickEntryTimesheetTypeTest extends TypeTestCase
{
protected function getExtensions()
/**
* @return FormExtensionInterface[]
*/
protected function getExtensions(): array
{
$auth = $this->createMock(Security::class);
$auth->method('getUser')->willReturn(new User());
@@ -36,18 +40,19 @@ class QuickEntryTimesheetTypeTest extends TypeTestCase
public static function getTestData()
{
yield [4.5, 16200];
yield ['4,5', 16200];
yield ['4:30', 16200];
yield ['4h30m', 16200];
yield [4.5, 0, 16200];
yield ['4,5', 0, 16200];
yield ['4:30', 0, 16200];
yield ['4h30m', 0, 16200];
yield ['4h30m', 1800, 16200]; // it is important, that the duration does not change with breaks
}
#[DataProvider('getTestData')]
public function testSubmitValidData($value, $expectedDuration): void
public function testSubmitValidData(string|float $value, int $break, int $expectedDuration): void
{
$data = ['duration' => $value];
$model = $this->createDefaultModel();
$model = $this->createDefaultModel($expectedDuration, $break);
$form = $this->factory->create(QuickEntryTimesheetType::class, $model);
@@ -56,16 +61,19 @@ class QuickEntryTimesheetTypeTest extends TypeTestCase
self::assertTrue($form->isSynchronized());
self::assertEquals($expectedDuration, $model->getDuration());
self::assertEquals($expectedDuration, $model->getDuration(true));
self::assertEquals($break, $model->getBreak());
}
private function createDefaultModel(): Timesheet
private function createDefaultModel(int $duration = 0, int $break = 0): Timesheet
{
$begin = new \DateTime('2020-02-15 12:30:00');
$end = new \DateTime('2020-02-15 14:00:00');
$model = new Timesheet();
$model->setBegin($begin);
//$model->setDuration($duration);
$model->setEnd($end);
$model->setBreak($break);
return $model;
}

View File

@@ -33,7 +33,7 @@ class MarkdownTest extends TestCase
asdfasdfa</p>
<pre><code>ssdfsdf</code></pre>
<p><a href="http://example.com/foo-bar.html" target="_blank">http://example.com/foo-bar.html</a><br />
<a href="file:///home/kimai/images/beautiful-flower.png" target="_blank">file:///home/kimai/images/beautiful-flower.png</a></p>
file:///home/kimai/images/beautiful-flower.png</p>
<p>sdfsdf <a href="#test-1">asdfasdf</a> asdfasdf</p>
<p># test<br />
aasdfasdf<br />

View File

@@ -21,7 +21,7 @@ class ParsedownExtensionTest extends TestCase
public function testTableContainsCssClasses(): void
{
$sut = new ParsedownExtension();
$html = $sut->parse('
$html = $sut->text('
| Item | Price |
|---|---|
| Something | $ 472,78 |
@@ -34,7 +34,7 @@ class ParsedownExtensionTest extends TestCase
public function testHeaderIsNotConverted(): void
{
$sut = new ParsedownExtension();
$html = $sut->parse('
$html = $sut->text('
# Foo
');
self::assertEquals('<p># Foo</p>', $html);

View File

@@ -19,7 +19,7 @@ class ParsedownTest extends TestCase
public function testTableContainsCssClasses(): void
{
$sut = new Parsedown();
$html = $sut->parse('
$html = $sut->text('
| Item | Price |
|---|---|
| Something | $ 472,78 |
@@ -32,7 +32,7 @@ class ParsedownTest extends TestCase
public function testHeaderContainsId(): void
{
$sut = new Parsedown();
$html = $sut->parse('
$html = $sut->text('
# Foo
');
self::assertEquals('<h1 id="foo">Foo</h1>', $html);
@@ -41,7 +41,7 @@ class ParsedownTest extends TestCase
public function testHeaderContainsIdDoesNotDuplicate(): void
{
$sut = new Parsedown();
$html = $sut->parse('
$html = $sut->text('
# Foo
# Foo
# Foo

View File

@@ -1346,26 +1346,11 @@ parameters:
count: 1
path: Form/Type/DurationTypeTest.php
-
message: "#^Method App\\\\Tests\\\\Form\\\\Type\\\\QuickEntryTimesheetTypeTest\\:\\:getExtensions\\(\\) has no return type specified\\.$#"
count: 1
path: Form/Type/QuickEntryTimesheetTypeTest.php
-
message: "#^Method App\\\\Tests\\\\Form\\\\Type\\\\QuickEntryTimesheetTypeTest\\:\\:getTestData\\(\\) has no return type specified\\.$#"
count: 1
path: Form/Type/QuickEntryTimesheetTypeTest.php
-
message: "#^Method App\\\\Tests\\\\Form\\\\Type\\\\QuickEntryTimesheetTypeTest\\:\\:testSubmitValidData\\(\\) has parameter \\$expectedDuration with no type specified\\.$#"
count: 1
path: Form/Type/QuickEntryTimesheetTypeTest.php
-
message: "#^Method App\\\\Tests\\\\Form\\\\Type\\\\QuickEntryTimesheetTypeTest\\:\\:testSubmitValidData\\(\\) has parameter \\$value with no type specified\\.$#"
count: 1
path: Form/Type/QuickEntryTimesheetTypeTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Calculator\\\\AbstractCalculatorTestCase\\:\\:assertDescription\\(\\) has parameter \\$addActivity with no type specified\\.$#"
count: 1