prepare release 1.14 (#2495)
* removed un-maintained docker file * update all packages * fix deprecations * only show one line descriptions for customer, projects and activities * allow to show export column in timesheet listing
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
unreleased=true
|
unreleased=true
|
||||||
future-release=1.13
|
future-release=1.14
|
||||||
exclude-labels=duplicate,support,question,invalid,wontfix,release,waiting for feedback
|
exclude-labels=duplicate,support,question,invalid,wontfix,release,waiting for feedback
|
||||||
enhancement_labels=>enhancement,Enhancement,feature request,translation i18n,technical debt,documentation
|
enhancement_labels=>enhancement,Enhancement,feature request,translation i18n,technical debt,documentation
|
||||||
issues-wo-labels=false
|
issues-wo-labels=false
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.14](https://github.com/kevinpapst/kimai2/tree/1.14)
|
||||||
|
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/1.13...1.14)
|
||||||
|
|
||||||
## [1.13](https://github.com/kevinpapst/kimai2/tree/1.13)
|
## [1.13](https://github.com/kevinpapst/kimai2/tree/1.13)
|
||||||
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/1.12...1.13)
|
[Full Changelog](https://github.com/kevinpapst/kimai2/compare/1.12...1.13)
|
||||||
|
|
||||||
|
|||||||
55
Dockerfile
55
Dockerfile
@@ -1,55 +0,0 @@
|
|||||||
# 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.
|
|
||||||
FROM php:7.2.9-apache-stretch AS tmp_kimai2_base
|
|
||||||
|
|
||||||
RUN apt update && \
|
|
||||||
apt install -y --allow-unauthenticated \
|
|
||||||
git \
|
|
||||||
haveged \
|
|
||||||
libicu-dev \
|
|
||||||
libjpeg-dev \
|
|
||||||
libldap2-dev \
|
|
||||||
libldb-dev \
|
|
||||||
libpng-dev \
|
|
||||||
mysql-client \
|
|
||||||
unzip \
|
|
||||||
wget \
|
|
||||||
zip \
|
|
||||||
&& \
|
|
||||||
EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)" && \
|
|
||||||
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
|
|
||||||
ACTUAL_SIGNATURE="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" && \
|
|
||||||
if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]; then >&2 echo 'ERROR: Invalid installer signature'; rm composer-setup.php; exit 1; fi && \
|
|
||||||
php composer-setup.php --quiet && \
|
|
||||||
rm composer-setup.php && \
|
|
||||||
mv /var/www/html/composer.phar /usr/bin/composer && \
|
|
||||||
docker-php-ext-install \
|
|
||||||
gd \
|
|
||||||
intl \
|
|
||||||
ldap \
|
|
||||||
pdo_mysql \
|
|
||||||
zip && \
|
|
||||||
apt remove -y wget && \
|
|
||||||
apt -y autoremove && \
|
|
||||||
apt clean && \
|
|
||||||
cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
|
|
||||||
|
|
||||||
FROM tmp_kimai2_base
|
|
||||||
|
|
||||||
RUN git clone https://github.com/kevinpapst/kimai2.git /opt/kimai && \
|
|
||||||
sed "s/prod/dev/g" /opt/kimai/.env.dist > /opt/kimai/.env && \
|
|
||||||
composer install --working-dir=/opt/kimai --dev --optimize-autoloader && \
|
|
||||||
/opt/kimai/bin/console doctrine:database:create && \
|
|
||||||
/opt/kimai/bin/console doctrine:schema:create && \
|
|
||||||
/opt/kimai/bin/console doctrine:migrations:version --add --all && \
|
|
||||||
/opt/kimai/bin/console cache:warmup && \
|
|
||||||
chown -R www-data:www-data /opt/kimai/var && \
|
|
||||||
chown www-data:www-data /opt/kimai/vendor/mpdf/mpdf/tmp
|
|
||||||
|
|
||||||
WORKDIR /opt/kimai
|
|
||||||
|
|
||||||
EXPOSE 8001
|
|
||||||
USER www-data
|
|
||||||
CMD /opt/kimai/bin/console server:run 0.0.0.0:8001
|
|
||||||
21
README.md
21
README.md
@@ -1,5 +1,3 @@
|
|||||||
<h1 align="center">Kimai 2 - online time-tracker</h1>
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://raw.githubusercontent.com/kimai/images/master/repository-header.png" alt="Kimai logo">
|
<img src="https://raw.githubusercontent.com/kimai/images/master/repository-header.png" alt="Kimai logo">
|
||||||
</p>
|
</p>
|
||||||
@@ -13,29 +11,30 @@
|
|||||||
<a href="https://www.bountysource.com/teams/kimai2"><img alt="Bountysource" src="https://img.shields.io/bountysource/team/kimai2/activity"></a>
|
<a href="https://www.bountysource.com/teams/kimai2"><img alt="Bountysource" src="https://img.shields.io/bountysource/team/kimai2/activity"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Kimai is a free, open source and online time-tracking software designed for small businesses and freelancers.
|
<h1 align="center">Kimai 2 - Time Tracker</h1>
|
||||||
|
|
||||||
|
Kimai is a free, open source and online time tracking software designed for small businesses and freelancers.
|
||||||
It is built with modern technologies such as Symfony, Bootstrap, RESTful API, Doctrine, AdminLTE, Webpack, ES6 etc.
|
It is built with modern technologies such as Symfony, Bootstrap, RESTful API, Doctrine, AdminLTE, Webpack, ES6 etc.
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
- [Home](https://www.kimai.org) - The house of Kimai
|
- [Home](https://www.kimai.org) - Kimai project homepage
|
||||||
- [Blog](https://www.kimai.org/blog/) - Get the latest news
|
- [Blog](https://www.kimai.org/blog/) - Get the latest news
|
||||||
- [Documentation](https://www.kimai.org/documentation/) - Learn how to use
|
- [Documentation](https://www.kimai.org/documentation/) - Learn how to use Kimai
|
||||||
- [Translations](https://www.kimai.org/documentation/translations.html) - Kimai in your language
|
- [Translations](https://hosted.weblate.org/projects/kimai/#languages) - Kimai in your language
|
||||||
- [Migration](https://www.kimai.org/documentation/migration-v1.html) - Import data from v1
|
- [Migration](https://www.kimai.org/documentation/migration-v1.html) - Import data from v1
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- PHP 7.2.9 or higher
|
- PHP 7.2.9 or higher ([PHP 8 not yet](https://github.com/kevinpapst/kimai2/pull/2158))
|
||||||
- MariaDB or MySQL
|
- MariaDB or MySQL
|
||||||
- Webserver (nginx, Apache)
|
- Webserver (nginx, Apache)
|
||||||
- A modern browser
|
|
||||||
- Some PHP extensions, see [composer.json](composer.json) or [here](https://www.kimai.org/download/)
|
- Some PHP extensions, see [composer.json](composer.json) or [here](https://www.kimai.org/download/)
|
||||||
|
|
||||||
### About
|
### About
|
||||||
|
|
||||||
This is the new version of the open source timetracker Kimai. It is stable and production ready, ships
|
This is the new version of the open source time tracker Kimai. It is stable and production ready, ships
|
||||||
with most advanced features from Kimai 1 and many new ones, including but not limited to:
|
with a lot of advanced features, including but not limited to:
|
||||||
|
|
||||||
JSON API, invoicing, data exports, multi-timer and punch-in punch-out mode, tagging, multi-user and multi-timezones,
|
JSON API, invoicing, data exports, multi-timer and punch-in punch-out mode, tagging, multi-user and multi-timezones,
|
||||||
authentication via SAML/LDAP/Database, customizable role permissions, responsive and ready for your mobile device,
|
authentication via SAML/LDAP/Database, customizable role permissions, responsive and ready for your mobile device,
|
||||||
@@ -50,7 +49,7 @@ user specific rates, advanced search & filtering, money and time budgets with re
|
|||||||
|
|
||||||
### Updating Kimai
|
### Updating Kimai
|
||||||
|
|
||||||
- [Update Kimai](https://www.kimai.org/documentation/updates.html) - the documentation
|
- [Update Kimai](https://www.kimai.org/documentation/updates.html) - get the latest version
|
||||||
- [UPGRADING guide](UPGRADING.md) - version specific steps
|
- [UPGRADING guide](UPGRADING.md) - version specific steps
|
||||||
|
|
||||||
### Plugins
|
### Plugins
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ As announced in the [README](README.md) I only support the latest available rele
|
|||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| master | :white_check_mark: |
|
| master | :white_check_mark: |
|
||||||
| 1.13 | :white_check_mark: |
|
| 1.14 | :white_check_mark: |
|
||||||
| < 1.13 | :x: |
|
| < 1.14 | :x: |
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ Please report any security related vulnerability in the [advisories section at G
|
|||||||
I will work as fast as I can to fix the problem and publish a bugfix release / security update.
|
I will work as fast as I can to fix the problem and publish a bugfix release / security update.
|
||||||
Depending on the size of the required fixes, this might take a couple of hours or a couple of days.
|
Depending on the size of the required fixes, this might take a couple of hours or a couple of days.
|
||||||
|
|
||||||
You can expect that your message will be answered ASAP, but please take into account that I am living in the timezone Europe/Berlin.
|
You can expect that your message will be answered ASAP, but please take into account that I am living in the timezone CET.
|
||||||
|
|
||||||
If your issue is valid and after I verified and fixed it, you will be mentioned in the release notes.
|
If your issue is valid and after I verified and fixed it, you will be mentioned in the release notes.
|
||||||
I am grateful for any (discrete) disclosure of vulnerabilities!
|
I am grateful for any (discrete) disclosure of vulnerabilities!
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "kevinpapst/kimai2",
|
"name": "kevinpapst/kimai2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"description": "Kimai Time-Tracking 2",
|
"description": "Kimai - Time Tracking",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Kevin Papst",
|
"name": "Kevin Papst",
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.2",
|
"php": "^7.2.9",
|
||||||
"ext-gd": "*",
|
"ext-gd": "*",
|
||||||
"ext-intl": "*",
|
"ext-intl": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
|
|||||||
1224
composer.lock
generated
1224
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ doctrine:
|
|||||||
default_connection: default
|
default_connection: default
|
||||||
connections:
|
connections:
|
||||||
default:
|
default:
|
||||||
|
override_url: true
|
||||||
# existing migrations will fail if the schema filter is activated
|
# existing migrations will fail if the schema filter is activated
|
||||||
#schema_filter: ~^(?!(bundle_migration_|kimai2_sessions))~
|
#schema_filter: ~^(?!(bundle_migration_|kimai2_sessions))~
|
||||||
url: '%env(DATABASE_URL)%'
|
url: '%env(DATABASE_URL)%'
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ fos_rest:
|
|||||||
'Symfony\Component\Routing\Exception\ResourceNotFoundException': 404
|
'Symfony\Component\Routing\Exception\ResourceNotFoundException': 404
|
||||||
'Symfony\Component\HttpKernel\Exception\NotFoundHttpException': 404
|
'Symfony\Component\HttpKernel\Exception\NotFoundHttpException': 404
|
||||||
'App\API\NotFoundException': 404
|
'App\API\NotFoundException': 404
|
||||||
'Pagerfanta\Exception\OutOfRangeCurrentPageException': 404
|
|
||||||
body_listener:
|
body_listener:
|
||||||
enabled: true
|
enabled: true
|
||||||
decoders:
|
decoders:
|
||||||
@@ -45,7 +44,7 @@ handcraftedinthealps_rest_routing:
|
|||||||
routing_loader:
|
routing_loader:
|
||||||
default_format: 'json'
|
default_format: 'json'
|
||||||
prefix_methods: true
|
prefix_methods: true
|
||||||
include_format: true
|
include_format: false
|
||||||
formats:
|
formats:
|
||||||
json: true
|
json: true
|
||||||
xml: false
|
xml: false
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ nelmio_api_doc:
|
|||||||
- ^/api(?!/doc)
|
- ^/api(?!/doc)
|
||||||
documentation:
|
documentation:
|
||||||
info:
|
info:
|
||||||
title: Kimai 2 - API Docs
|
title: Kimai - API Docs
|
||||||
description: |
|
description: |
|
||||||
JSON API for the Kimai 2 time-tracking software: [API documentation](https://www.kimai.org/documentation/rest-api.html), [Swagger definition file](doc.json)
|
JSON API for the Kimai time-tracking software: [API documentation](https://www.kimai.org/documentation/rest-api.html), [Swagger definition file](doc.json)
|
||||||
version: '0.6'
|
version: '0.6'
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
apiUser:
|
apiUser:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|||||||
|
|
||||||
class NotFoundException extends NotFoundHttpException
|
class NotFoundException extends NotFoundHttpException
|
||||||
{
|
{
|
||||||
public function __construct(string $message = 'Not found', \Exception $previous = null, int $code = 0, array $headers = [])
|
public function __construct(string $message = 'Not found', \Exception $previous = null, int $code = 404, array $headers = [])
|
||||||
{
|
{
|
||||||
parent::__construct($message, $previous, $code, $headers);
|
parent::__construct($message, $previous, $code, $headers);
|
||||||
}
|
}
|
||||||
|
|||||||
45
src/EventSubscriber/PagerfantaExceptionSubscriber.php
Normal file
45
src/EventSubscriber/PagerfantaExceptionSubscriber.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?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\EventSubscriber;
|
||||||
|
|
||||||
|
use Pagerfanta\Exception\NotValidMaxPerPageException;
|
||||||
|
use Pagerfanta\Exception\OutOfRangeCurrentPageException;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catches Pagerfanta Exceptions and converts them to "normal" Http Exceptions with status code 404.
|
||||||
|
* This was mainly done to convert the 500 to 404 HTTP response code.
|
||||||
|
* This prevents also the need to register them in fos_rest.yaml for the API.
|
||||||
|
*/
|
||||||
|
final class PagerfantaExceptionSubscriber implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
public static function getSubscribedEvents(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
KernelEvents::EXCEPTION => ['onCoreException', 1]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ExceptionEvent $event
|
||||||
|
*/
|
||||||
|
public function onCoreException(ExceptionEvent $event)
|
||||||
|
{
|
||||||
|
$throwable = $event->getThrowable();
|
||||||
|
|
||||||
|
if ($throwable instanceof NotValidMaxPerPageException || $throwable instanceof OutOfRangeCurrentPageException) {
|
||||||
|
$notFoundHttpException = new NotFoundHttpException($throwable->getMessage(), $throwable, $throwable->getCode());
|
||||||
|
$event->setThrowable($notFoundHttpException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,27 +28,25 @@ use Symfony\Component\Form\FormEvent;
|
|||||||
use Symfony\Component\Form\FormEvents;
|
use Symfony\Component\Form\FormEvents;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the form used to manipulate Timesheet entries.
|
* Helper functions to manage dependent customer-project-activity fields.
|
||||||
* @internal
|
|
||||||
*/
|
*/
|
||||||
trait FormTrait
|
trait FormTrait
|
||||||
{
|
{
|
||||||
protected function addCustomer(FormBuilderInterface $builder, ?Customer $customer = null)
|
protected function addCustomer(FormBuilderInterface $builder, ?Customer $customer = null)
|
||||||
{
|
{
|
||||||
$builder
|
$builder->add('customer', CustomerType::class, [
|
||||||
->add('customer', CustomerType::class, [
|
'query_builder' => function (CustomerRepository $repo) use ($builder, $customer) {
|
||||||
'query_builder' => function (CustomerRepository $repo) use ($builder, $customer) {
|
$query = new CustomerFormTypeQuery($customer);
|
||||||
$query = new CustomerFormTypeQuery($customer);
|
$query->setUser($builder->getOption('user'));
|
||||||
$query->setUser($builder->getOption('user'));
|
|
||||||
|
|
||||||
return $repo->getQueryBuilderForFormType($query);
|
return $repo->getQueryBuilderForFormType($query);
|
||||||
},
|
},
|
||||||
'data' => $customer ? $customer : '',
|
'data' => $customer ? $customer : '',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'placeholder' => '',
|
'placeholder' => '',
|
||||||
'mapped' => false,
|
'mapped' => false,
|
||||||
'project_enabled' => true,
|
'project_enabled' => true,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function addProject(FormBuilderInterface $builder, bool $isNew, ?Project $project = null, ?Customer $customer = null)
|
protected function addProject(FormBuilderInterface $builder, bool $isNew, ?Project $project = null, ?Customer $customer = null)
|
||||||
@@ -102,17 +100,15 @@ trait FormTrait
|
|||||||
|
|
||||||
protected function addActivity(FormBuilderInterface $builder, ?Activity $activity = null, ?Project $project = null)
|
protected function addActivity(FormBuilderInterface $builder, ?Activity $activity = null, ?Project $project = null)
|
||||||
{
|
{
|
||||||
$builder
|
$builder->add('activity', ActivityType::class, [
|
||||||
->add('activity', ActivityType::class, [
|
'placeholder' => '',
|
||||||
'placeholder' => '',
|
'query_builder' => function (ActivityRepository $repo) use ($builder, $activity, $project) {
|
||||||
'query_builder' => function (ActivityRepository $repo) use ($builder, $activity, $project) {
|
$query = new ActivityFormTypeQuery($activity, $project);
|
||||||
$query = new ActivityFormTypeQuery($activity, $project);
|
$query->setUser($builder->getOption('user'));
|
||||||
$query->setUser($builder->getOption('user'));
|
|
||||||
|
|
||||||
return $repo->getQueryBuilderForFormType($query);
|
return $repo->getQueryBuilderForFormType($query);
|
||||||
},
|
},
|
||||||
])
|
]);
|
||||||
;
|
|
||||||
|
|
||||||
// replaces the activity select after submission, to make sure only activities for the selected project are displayed
|
// replaces the activity select after submission, to make sure only activities for the selected project are displayed
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
@@ -143,20 +139,23 @@ trait FormTrait
|
|||||||
{
|
{
|
||||||
@trigger_error('FormTrait::addDescription() is deprecated and will be removed with 2.0, use DescriptionType instead', E_USER_DEPRECATED);
|
@trigger_error('FormTrait::addDescription() is deprecated and will be removed with 2.0, use DescriptionType instead', E_USER_DEPRECATED);
|
||||||
|
|
||||||
$builder
|
$builder->add('description', DescriptionType::class, [
|
||||||
->add('description', DescriptionType::class, [
|
'required' => false,
|
||||||
'required' => false,
|
'attr' => [
|
||||||
'attr' => [
|
'autofocus' => 'autofocus'
|
||||||
'autofocus' => 'autofocus'
|
]
|
||||||
]
|
]);
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated since 1.14
|
||||||
|
*/
|
||||||
protected function addTags(FormBuilderInterface $builder)
|
protected function addTags(FormBuilderInterface $builder)
|
||||||
{
|
{
|
||||||
$builder
|
@trigger_error('FormTrait::addTags() is deprecated and will be removed with 2.0, use TagsType instead', E_USER_DEPRECATED);
|
||||||
->add('tags', TagsType::class, [
|
|
||||||
'required' => false,
|
$builder->add('tags', TagsType::class, [
|
||||||
]);
|
'required' => false,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use App\Form\Type\DurationType;
|
|||||||
use App\Form\Type\FixedRateType;
|
use App\Form\Type\FixedRateType;
|
||||||
use App\Form\Type\HourlyRateType;
|
use App\Form\Type\HourlyRateType;
|
||||||
use App\Form\Type\MetaFieldsCollectionType;
|
use App\Form\Type\MetaFieldsCollectionType;
|
||||||
|
use App\Form\Type\TagsType;
|
||||||
use App\Form\Type\UserType;
|
use App\Form\Type\UserType;
|
||||||
use App\Form\Type\YesNoType;
|
use App\Form\Type\YesNoType;
|
||||||
use App\Repository\CustomerRepository;
|
use App\Repository\CustomerRepository;
|
||||||
@@ -121,8 +122,7 @@ class TimesheetEditForm extends AbstractType
|
|||||||
$descriptionOptions['attr'] = ['autofocus' => 'autofocus'];
|
$descriptionOptions['attr'] = ['autofocus' => 'autofocus'];
|
||||||
}
|
}
|
||||||
$builder->add('description', DescriptionType::class, $descriptionOptions);
|
$builder->add('description', DescriptionType::class, $descriptionOptions);
|
||||||
|
$builder->add('tags', TagsType::class, ['required' => false]);
|
||||||
$this->addTags($builder);
|
|
||||||
$this->addRates($builder, $currency, $options);
|
$this->addRates($builder, $currency, $options);
|
||||||
$this->addUser($builder, $options);
|
$this->addUser($builder, $options);
|
||||||
$builder->add('metaFields', MetaFieldsCollectionType::class);
|
$builder->add('metaFields', MetaFieldsCollectionType::class);
|
||||||
|
|||||||
@@ -69,6 +69,36 @@ final class MarkdownExtension implements RuntimeExtensionInterface
|
|||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the entities comment (customer, project, activity ...) into a one-liner.
|
||||||
|
*
|
||||||
|
* @param string|null $content
|
||||||
|
* @param bool $fullLength
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function commentOneLiner(?string $content, bool $fullLength = false): string
|
||||||
|
{
|
||||||
|
if (empty($content)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$addHellip = false;
|
||||||
|
|
||||||
|
if (!$fullLength && \strlen($content) > 52) {
|
||||||
|
$content = trim(substr($content, 0, 50));
|
||||||
|
$addHellip = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = explode(PHP_EOL, $content);
|
||||||
|
$result = $content[0];
|
||||||
|
|
||||||
|
if (\count($content) > 1 || $addHellip) {
|
||||||
|
$result .= ' …';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms the timesheet description content into HTML.
|
* Transforms the timesheet description content into HTML.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class RuntimeExtensions extends AbstractExtension
|
|||||||
new TwigFilter('md2html', [MarkdownExtension::class, 'markdownToHtml'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
|
new TwigFilter('md2html', [MarkdownExtension::class, 'markdownToHtml'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
|
||||||
new TwigFilter('desc2html', [MarkdownExtension::class, 'timesheetContent'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
|
new TwigFilter('desc2html', [MarkdownExtension::class, 'timesheetContent'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
|
||||||
new TwigFilter('comment2html', [MarkdownExtension::class, 'commentContent'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
|
new TwigFilter('comment2html', [MarkdownExtension::class, 'commentContent'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
|
||||||
|
new TwigFilter('comment1line', [MarkdownExtension::class, 'commentOneLiner'], ['pre_escape' => 'html', 'is_safe' => ['html']]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,9 @@
|
|||||||
"doctrine/dbal": {
|
"doctrine/dbal": {
|
||||||
"version": "v2.6.3"
|
"version": "v2.6.3"
|
||||||
},
|
},
|
||||||
|
"doctrine/deprecations": {
|
||||||
|
"version": "v0.5.3"
|
||||||
|
},
|
||||||
"doctrine/doctrine-bundle": {
|
"doctrine/doctrine-bundle": {
|
||||||
"version": "1.6",
|
"version": "1.6",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment2html }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment1line }}</td>
|
||||||
{% for field in metaColumns %}
|
{% for field in metaColumns %}
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'mf_' ~ field.name) }}">
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'mf_' ~ field.name) }}">
|
||||||
{{ tables.datatable_meta_column(entry, field) }}
|
{{ tables.datatable_meta_column(entry, field) }}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
{% for project in projects %}
|
{% for project in projects %}
|
||||||
<tr class="{{ widgets.class_project_row(project, now) }}">
|
<tr class="{{ widgets.class_project_row(project, now) }}">
|
||||||
<td>{{ widgets.label_project(project) }}</td>
|
<td>{{ widgets.label_project(project) }}</td>
|
||||||
<td class="hidden-xs">{{ project.comment|comment2html }}</td>
|
<td class="hidden-xs">{{ project.comment|comment1line(false) }}</td>
|
||||||
<td class="w-min text-center">{% if project.end is not null %}{{ project.end|date_short }}{% endif %}</td>
|
<td class="w-min text-center">{% if project.end is not null %}{{ project.end|date_short }}{% endif %}</td>
|
||||||
<td class="w-min text-center">{{ widgets.label_visible(project.visible) }}</td>
|
<td class="w-min text-center">{{ widgets.label_visible(project.visible) }}</td>
|
||||||
<td class="w-min text-center">{{ widgets.badge_team_access(project.teams) }}</td>
|
<td class="w-min text-center">{{ widgets.badge_team_access(project.teams) }}</td>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<tr class="{{ class }}" data-href="{{ dataHref }}">
|
<tr class="{{ class }}" data-href="{{ dataHref }}">
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_color_dot('customer', true, entry.name, null, entry.color) }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_color_dot('customer', true, entry.name, null, entry.color) }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment2html }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment1line }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'number') }}">{{ entry.number }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'number') }}">{{ entry.number }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'company') }}">{{ entry.company }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'company') }}">{{ entry.company }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'vat_id') }}">{{ entry.vatId }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'vat_id') }}">{{ entry.vatId }}</td>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
{% for activity in activities %}
|
{% for activity in activities %}
|
||||||
<tr class="{{ widgets.class_activity_row(activity, now) }}">
|
<tr class="{{ widgets.class_activity_row(activity, now) }}">
|
||||||
<td>{{ widgets.label_activity(activity) }}</td>
|
<td>{{ widgets.label_activity(activity) }}</td>
|
||||||
<td class="hidden-xs">{{ activity.comment|comment2html }}</td>
|
<td class="hidden-xs">{{ activity.comment|comment1line(false) }}</td>
|
||||||
<td class="w-min text-center">{{ widgets.label_visible(activity.visible) }}</td>
|
<td class="w-min text-center">{{ widgets.label_visible(activity.visible) }}</td>
|
||||||
<td class="w-min text-center">{{ widgets.badge_team_access(activity.teams) }}</td>
|
<td class="w-min text-center">{{ widgets.badge_team_access(activity.teams) }}</td>
|
||||||
<td class="actions">{{ actions.activity(activity, 'custom') }}</td>
|
<td class="actions">{{ actions.activity(activity, 'custom') }}</td>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
<tr class="{{ class }}" data-href="{{ dataHref }}">
|
<tr class="{{ class }}" data-href="{{ dataHref }}">
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_color_dot('project', true, entry.name, null, entry.color) }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'name') }}">{{ widgets.label_color_dot('project', true, entry.name, null, entry.color) }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">{{ widgets.label_customer(entry.customer) }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">{{ widgets.label_customer(entry.customer) }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment2html }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'comment') }}">{{ entry.comment|comment1line }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderNumber') }}">{{ entry.orderNumber }}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderNumber') }}">{{ entry.orderNumber }}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderDate') }}">{% if entry.orderDate is not null %}{{ entry.orderDate|date_full }}{% endif %}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'orderDate') }}">{% if entry.orderDate is not null %}{{ entry.orderDate|date_full }}{% endif %}</td>
|
||||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'project_start') }}">{% if entry.start is not null %}{{ entry.start|date_full }}{% endif %}</td>
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'project_start') }}">{% if entry.start is not null %}{{ entry.start|date_full }}{% endif %}</td>
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
}) %}
|
}) %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% set columns = columns|merge({
|
{% set columns = columns|merge({
|
||||||
|
'exported': {'class': 'text-center hidden w-min', 'orderBy': false},
|
||||||
'actions': {'class': 'actions alwaysVisible'},
|
'actions': {'class': 'actions alwaysVisible'},
|
||||||
}) %}
|
}) %}
|
||||||
|
|
||||||
@@ -162,6 +163,9 @@
|
|||||||
{{ tables.datatable_meta_column(entry, field) }}
|
{{ tables.datatable_meta_column(entry, field) }}
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<td class="{{ tables.data_table_column_class(tableName, columns, 'exported') }}">
|
||||||
|
{{ widgets.label_boolean(entry.exported) }}
|
||||||
|
</td>
|
||||||
<td class="actions">
|
<td class="actions">
|
||||||
{{ block('row_action') }}
|
{{ block('row_action') }}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -60,21 +60,8 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
|
|||||||
return $client;
|
return $client;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function createUrl(string $url): string
|
||||||
* @param string $url
|
|
||||||
* @param bool $json
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function createUrl($url, $json = true)
|
|
||||||
{
|
{
|
||||||
if ($json) {
|
|
||||||
if (stripos($url, '?') !== false) {
|
|
||||||
$url = str_replace('?', '.json?', $url);
|
|
||||||
} else {
|
|
||||||
$url .= '.json';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return '/' . ltrim($url, '/');
|
return '/' . ltrim($url, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class ApiDocControllerTest extends ControllerBaseTest
|
|||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->assertAccessIsGranted($client, '/api/doc');
|
$this->assertAccessIsGranted($client, '/api/doc');
|
||||||
$this->assertStringContainsString('<title>Kimai 2 - API Docs</title>', $client->getResponse()->getContent());
|
$this->assertStringContainsString('<title>Kimai - API Docs</title>', $client->getResponse()->getContent());
|
||||||
$result = $client->getCrawler()->filter('script#swagger-data');
|
$result = $client->getCrawler()->filter('script#swagger-data');
|
||||||
$swaggerJson = json_decode($result->text(), true);
|
$swaggerJson = json_decode($result->text(), true);
|
||||||
$tags = [];
|
$tags = [];
|
||||||
@@ -51,17 +51,13 @@ class ApiDocControllerTest extends ControllerBaseTest
|
|||||||
{
|
{
|
||||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
|
||||||
$this->assertAccessIsGranted($client, '/api/doc.json');
|
$this->assertAccessIsGranted($client, '/api/doc.json');
|
||||||
$this->assertStringContainsString('"title":"Kimai 2 - API Docs"', $client->getResponse()->getContent());
|
$this->assertStringContainsString('"title":"Kimai - API Docs"', $client->getResponse()->getContent());
|
||||||
$result = json_decode($client->getResponse()->getContent(), true);
|
$result = json_decode($client->getResponse()->getContent(), true);
|
||||||
$this->assertIsArray($result);
|
$this->assertIsArray($result);
|
||||||
$this->assertNotEmpty($result);
|
$this->assertNotEmpty($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function createUrl(string $url): string
|
||||||
* @param string $url
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function createUrl($url)
|
|
||||||
{
|
{
|
||||||
return '/' . ltrim($url, '/');
|
return '/' . ltrim($url, '/');
|
||||||
}
|
}
|
||||||
|
|||||||
26
tests/API/NotFoundExceptionTest.php
Normal file
26
tests/API/NotFoundExceptionTest.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?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\Tests\API;
|
||||||
|
|
||||||
|
use App\API\NotFoundException;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\API\NotFoundException
|
||||||
|
*/
|
||||||
|
class NotFoundExceptionTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testConstructor()
|
||||||
|
{
|
||||||
|
$sut = new NotFoundException();
|
||||||
|
self::assertEquals('Not found', $sut->getMessage());
|
||||||
|
self::assertEquals(404, $sut->getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,11 +79,7 @@ abstract class ControllerBaseTest extends WebTestCase
|
|||||||
return $client;
|
return $client;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function createUrl(string $url): string
|
||||||
* @param string $url
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function createUrl($url)
|
|
||||||
{
|
{
|
||||||
return '/' . self::DEFAULT_LANGUAGE . '/' . ltrim($url, '/');
|
return '/' . self::DEFAULT_LANGUAGE . '/' . ltrim($url, '/');
|
||||||
}
|
}
|
||||||
|
|||||||
62
tests/EventSubscriber/PagerfantaExceptionSubscriberTest.php
Normal file
62
tests/EventSubscriber/PagerfantaExceptionSubscriberTest.php
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<?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\Tests\EventSubscriber;
|
||||||
|
|
||||||
|
use App\EventSubscriber\PagerfantaExceptionSubscriber;
|
||||||
|
use Pagerfanta\Exception\NotValidMaxPerPageException;
|
||||||
|
use Pagerfanta\Exception\OutOfRangeCurrentPageException;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\EventSubscriber\PagerfantaExceptionSubscriber
|
||||||
|
*/
|
||||||
|
class PagerfantaExceptionSubscriberTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGetSubscribedEvents()
|
||||||
|
{
|
||||||
|
$events = PagerfantaExceptionSubscriber::getSubscribedEvents();
|
||||||
|
$this->assertArrayHasKey(KernelEvents::EXCEPTION, $events);
|
||||||
|
$methodName = $events[KernelEvents::EXCEPTION][0];
|
||||||
|
$this->assertTrue(method_exists(PagerfantaExceptionSubscriber::class, $methodName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithExceptions()
|
||||||
|
{
|
||||||
|
$sut = new PagerfantaExceptionSubscriber();
|
||||||
|
|
||||||
|
$kernel = $this->createMock(HttpKernelInterface::class);
|
||||||
|
$request = $this->createMock(Request::class);
|
||||||
|
$exception = new \Exception();
|
||||||
|
$requestType = HttpKernelInterface::MASTER_REQUEST;
|
||||||
|
|
||||||
|
$event = new ExceptionEvent($kernel, $request, $requestType, $exception);
|
||||||
|
$sut->onCoreException($event);
|
||||||
|
self::assertSame($exception, $event->getThrowable());
|
||||||
|
|
||||||
|
$event = new ExceptionEvent($kernel, $request, $requestType, new NotValidMaxPerPageException('Foo baaaaar!', 999));
|
||||||
|
$sut->onCoreException($event);
|
||||||
|
self::assertInstanceOf(NotFoundHttpException::class, $event->getThrowable());
|
||||||
|
self::assertEquals('Foo baaaaar!', $event->getThrowable()->getMessage());
|
||||||
|
self::assertEquals(999, $event->getThrowable()->getCode());
|
||||||
|
self::assertEquals(404, $event->getThrowable()->getStatusCode());
|
||||||
|
|
||||||
|
$event = new ExceptionEvent($kernel, $request, $requestType, new OutOfRangeCurrentPageException('Trölölölölölö', 123));
|
||||||
|
$sut->onCoreException($event);
|
||||||
|
self::assertInstanceOf(NotFoundHttpException::class, $event->getThrowable());
|
||||||
|
self::assertEquals('Trölölölölölö', $event->getThrowable()->getMessage());
|
||||||
|
self::assertEquals(123, $event->getThrowable()->getCode());
|
||||||
|
self::assertEquals(404, $event->getThrowable()->getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,4 +77,42 @@ class MarkdownExtensionTest extends TestCase
|
|||||||
$sut->commentContent("- test\n- foo\n\nfoo __bar__")
|
$sut->commentContent("- test\n- foo\n\nfoo __bar__")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCommentOneLiner()
|
||||||
|
{
|
||||||
|
$loader = $this->createMock(ConfigLoaderInterface::class);
|
||||||
|
$config = new SystemConfiguration($loader, []);
|
||||||
|
$sut = new MarkdownExtension(new Markdown(), $config);
|
||||||
|
|
||||||
|
$loremIpsum = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.';
|
||||||
|
|
||||||
|
$this->assertEquals('', $sut->commentOneLiner(null));
|
||||||
|
$this->assertEquals('', $sut->commentOneLiner(''));
|
||||||
|
$this->assertEquals('', $sut->commentOneLiner(null, false));
|
||||||
|
$this->assertEquals('', $sut->commentOneLiner('', true));
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'Lorem ipsum dolor sit amet, consetetur sadipscing …',
|
||||||
|
$sut->commentOneLiner($loremIpsum, false)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. …',
|
||||||
|
$sut->commentOneLiner(implode(PHP_EOL, [$loremIpsum, $loremIpsum, $loremIpsum]), true)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'Lorem ipsum dolor sit amet, consetetur sadipscing …',
|
||||||
|
$sut->commentOneLiner(implode(PHP_EOL, [$loremIpsum, $loremIpsum, $loremIpsum]), false)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt',
|
||||||
|
$sut->commentOneLiner(implode(PHP_EOL, ['Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt']), true)
|
||||||
|
);
|
||||||
|
$this->assertEquals(
|
||||||
|
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt …',
|
||||||
|
$sut->commentOneLiner(implode(PHP_EOL, ['Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt', 'ssdf']), true)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class RuntimeExtensionsTest extends TestCase
|
|||||||
{
|
{
|
||||||
public function testGetFilters()
|
public function testGetFilters()
|
||||||
{
|
{
|
||||||
$expected = ['md2html', 'desc2html', 'comment2html'];
|
$expected = ['md2html', 'desc2html', 'comment2html', 'comment1line'];
|
||||||
$i = 0;
|
$i = 0;
|
||||||
|
|
||||||
$sut = new RuntimeExtensions();
|
$sut = new RuntimeExtensions();
|
||||||
@@ -67,6 +67,7 @@ class RuntimeExtensionsTest extends TestCase
|
|||||||
$found_md2html = false;
|
$found_md2html = false;
|
||||||
$found_desc2html = false;
|
$found_desc2html = false;
|
||||||
$found_comment2html = false;
|
$found_comment2html = false;
|
||||||
|
$found_comment1line = false;
|
||||||
|
|
||||||
foreach ($filters as $filter) {
|
foreach ($filters as $filter) {
|
||||||
switch ($filter->getName()) {
|
switch ($filter->getName()) {
|
||||||
@@ -85,11 +86,17 @@ class RuntimeExtensionsTest extends TestCase
|
|||||||
self::assertEquals(['html'], $filters[2]->getSafe(new Node()));
|
self::assertEquals(['html'], $filters[2]->getSafe(new Node()));
|
||||||
$found_comment2html = true;
|
$found_comment2html = true;
|
||||||
break;
|
break;
|
||||||
|
case 'comment1line':
|
||||||
|
self::assertEquals('html', $filters[3]->getPreEscape());
|
||||||
|
self::assertEquals(['html'], $filters[3]->getSafe(new Node()));
|
||||||
|
$found_comment1line = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self::assertTrue($found_md2html, 'Missing filter: md2html');
|
self::assertTrue($found_md2html, 'Missing filter: md2html');
|
||||||
self::assertTrue($found_desc2html, 'Missing filter: desc2html');
|
self::assertTrue($found_desc2html, 'Missing filter: desc2html');
|
||||||
self::assertTrue($found_comment2html, 'Missing filter: comment2html');
|
self::assertTrue($found_comment2html, 'Missing filter: comment2html');
|
||||||
|
self::assertTrue($found_comment1line, 'Missing filter: comment1line');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user