added plugin screen (#671)
This commit is contained in:
@@ -27,8 +27,13 @@ Remember to execute the necessary timezone conversion script, if you haven't upd
|
|||||||
**BC BREAKS**
|
**BC BREAKS**
|
||||||
- in an ongoing effort to simplify future installation and upgrade processes the `.env` variable `DATABASE_PREFIX` was removed.
|
- in an ongoing effort to simplify future installation and upgrade processes the `.env` variable `DATABASE_PREFIX` was removed.
|
||||||
The table prefix is now hardcoded to `kimai2_`. If you used another prefix, you have to rename your tables manually
|
The table prefix is now hardcoded to `kimai2_`. If you used another prefix, you have to rename your tables manually
|
||||||
before starting the update process. You can also delete the row `DATABASE_PREFIX` from your `.env` file.
|
before starting the update process. And delete the row `DATABASE_PREFIX` from your `.env` file.
|
||||||
- API: DateTime objects will be returned including timezone identifier (previously 2019-03-02 14:23 - now 2019-03-02T14:23:00+00:00)
|
- API: DateTime objects will be returned including timezone identifier (previously 2019-03-02 14:23 - now 2019-03-02T14:23:00+00:00)
|
||||||
|
- Plugin mechanism changed: existing Plugins have to be deleted or updated
|
||||||
|
|
||||||
|
**Check if you want to apply changes to your `local.yaml`:**
|
||||||
|
|
||||||
|
- New permissions are available: `system_configuration`, `system_actions`, `plugins`
|
||||||
|
|
||||||
**Check if you want to apply changes to your `local.yaml`:**
|
**Check if you want to apply changes to your `local.yaml`:**
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
/*
|
/*!
|
||||||
* This file is part of the Kimai time-tracking app.
|
* This file is part of the Kimai time-tracking app.
|
||||||
*
|
*
|
||||||
|
* Main JS application file for Kimai 2. This file should be included in all pages.
|
||||||
|
*
|
||||||
* For the full copyright and license information, please view the LICENSE
|
* For the full copyright and license information, please view the LICENSE
|
||||||
* file that was distributed with this source code.
|
* file that was distributed with this source code.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"description": "Kimai Time-Tracking 2",
|
"description": "Kimai Time-Tracking 2",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Kevin Papst",
|
||||||
|
"homepage": "https://www.kevinpapst.de"
|
||||||
|
}
|
||||||
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"ext-gd": "*",
|
"ext-gd": "*",
|
||||||
@@ -56,7 +62,6 @@
|
|||||||
"require-dev": {
|
"require-dev": {
|
||||||
"friendsofphp/php-cs-fixer": "^2.10",
|
"friendsofphp/php-cs-fixer": "^2.10",
|
||||||
"phpunit/phpunit": "^7.0",
|
"phpunit/phpunit": "^7.0",
|
||||||
"squizlabs/php_codesniffer": "^3.2",
|
|
||||||
"symfony/browser-kit": "^4.0",
|
"symfony/browser-kit": "^4.0",
|
||||||
"symfony/css-selector": "^4.0",
|
"symfony/css-selector": "^4.0",
|
||||||
"symfony/phpunit-bridge": "^4.0",
|
"symfony/phpunit-bridge": "^4.0",
|
||||||
|
|||||||
459
composer.lock
generated
459
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -7,32 +7,30 @@ parameters:
|
|||||||
|
|
||||||
doctrine:
|
doctrine:
|
||||||
dbal:
|
dbal:
|
||||||
# configure these for your database server
|
default_connection: default
|
||||||
driver: 'pdo_mysql'
|
connections:
|
||||||
server_version: '5.7'
|
default:
|
||||||
charset: utf8mb4
|
url: '%env(resolve:DATABASE_URL)%'
|
||||||
default_table_options:
|
driver: 'pdo_mysql'
|
||||||
charset: utf8mb4
|
server_version: '5.7'
|
||||||
collate: utf8mb4_unicode_ci
|
charset: utf8mb4
|
||||||
|
default_table_options:
|
||||||
# With Symfony 3.3, remove the `resolve:` prefix
|
charset: utf8mb4
|
||||||
url: '%env(resolve:DATABASE_URL)%'
|
collate: utf8mb4_unicode_ci
|
||||||
|
|
||||||
types:
|
types:
|
||||||
datetime: App\Doctrine\UTCDateTimeType
|
datetime: App\Doctrine\UTCDateTimeType
|
||||||
orm:
|
orm:
|
||||||
auto_generate_proxy_classes: '%kernel.debug%'
|
auto_generate_proxy_classes: '%kernel.debug%'
|
||||||
naming_strategy: doctrine.orm.naming_strategy.underscore
|
default_entity_manager: default
|
||||||
auto_mapping: true
|
entity_managers:
|
||||||
mappings:
|
default:
|
||||||
App:
|
connection: default
|
||||||
is_bundle: false
|
naming_strategy: doctrine.orm.naming_strategy.underscore
|
||||||
type: annotation
|
auto_mapping: true
|
||||||
dir: '%kernel.project_dir%/src/Entity'
|
mappings:
|
||||||
prefix: 'App\Entity'
|
App:
|
||||||
alias: Kimai
|
type: annotation
|
||||||
loggable:
|
dir: '%kernel.project_dir%/src/Entity'
|
||||||
type: annotation
|
prefix: 'App\Entity'
|
||||||
alias: Gedmo
|
alias: Kimai
|
||||||
prefix: Gedmo\Loggable\Entity
|
|
||||||
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/lib/Gedmo/Loggable/Entity"
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ kimai:
|
|||||||
ROLE_USER: []
|
ROLE_USER: []
|
||||||
ROLE_TEAMLEAD: [view_invoice_template,create_invoice_template,edit_invoice_template,view_rate_own_timesheet,view_rate_other_timesheet,hourly-rate_own_profile]
|
ROLE_TEAMLEAD: [view_invoice_template,create_invoice_template,edit_invoice_template,view_rate_own_timesheet,view_rate_other_timesheet,hourly-rate_own_profile]
|
||||||
ROLE_ADMIN: [hourly-rate_own_profile]
|
ROLE_ADMIN: [hourly-rate_own_profile]
|
||||||
ROLE_SUPER_ADMIN: [hourly-rate_own_profile,hourly-rate_other_profile,delete_own_profile,roles_own_profile,system_information,system_configuration]
|
ROLE_SUPER_ADMIN: [hourly-rate_own_profile,hourly-rate_other_profile,delete_own_profile,roles_own_profile,system_information,system_actions,system_configuration,plugins]
|
||||||
|
|
||||||
# --------------------------------------------------------------------------------
|
# --------------------------------------------------------------------------------
|
||||||
# Language specific settings, like the date formats
|
# Language specific settings, like the date formats
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ security:
|
|||||||
path: fos_user_security_logout
|
path: fos_user_security_logout
|
||||||
target: homepage
|
target: homepage
|
||||||
|
|
||||||
|
access_decision_manager:
|
||||||
|
strategy: unanimous
|
||||||
|
allow_if_all_abstain: false
|
||||||
|
|
||||||
role_hierarchy:
|
role_hierarchy:
|
||||||
ROLE_USER: ROLE_CUSTOMER
|
ROLE_USER: ROLE_CUSTOMER
|
||||||
ROLE_TEAMLEAD: ROLE_USER
|
ROLE_TEAMLEAD: ROLE_USER
|
||||||
|
|||||||
@@ -64,16 +64,16 @@ services:
|
|||||||
App\Utils\MPdfConverter:
|
App\Utils\MPdfConverter:
|
||||||
arguments: ['%kernel.cache_dir%']
|
arguments: ['%kernel.cache_dir%']
|
||||||
|
|
||||||
|
App\Plugin\PluginManager:
|
||||||
|
arguments: [!tagged kimai.plugin]
|
||||||
|
|
||||||
|
App\Export\ServiceExport:
|
||||||
|
arguments: [!tagged export.renderer]
|
||||||
|
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
# DATABASE
|
# DATABASE
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
|
|
||||||
# service that prefixes every database table
|
|
||||||
App\Doctrine\TablePrefixSubscriber:
|
|
||||||
class: App\Doctrine\TablePrefixSubscriber
|
|
||||||
tags:
|
|
||||||
- { name: doctrine.event_subscriber }
|
|
||||||
|
|
||||||
# updates timesheet records and apply configured rate & rounding rules
|
# updates timesheet records and apply configured rate & rounding rules
|
||||||
App\Doctrine\TimesheetSubscriber:
|
App\Doctrine\TimesheetSubscriber:
|
||||||
class: App\Doctrine\TimesheetSubscriber
|
class: App\Doctrine\TimesheetSubscriber
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ namespace App\Controller;
|
|||||||
|
|
||||||
use App\Constants;
|
use App\Constants;
|
||||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||||
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
|
use Symfony\Component\Console\Output\BufferedOutput;
|
||||||
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,6 +44,11 @@ class AboutController extends AbstractController
|
|||||||
* @return \Symfony\Component\HttpFoundation\Response
|
* @return \Symfony\Component\HttpFoundation\Response
|
||||||
*/
|
*/
|
||||||
public function indexAction()
|
public function indexAction()
|
||||||
|
{
|
||||||
|
return $this->getAboutView();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getAboutView(array $additional = [])
|
||||||
{
|
{
|
||||||
$phpInfo = $this->getPhpInfo();
|
$phpInfo = $this->getPhpInfo();
|
||||||
unset($phpInfo[0]);
|
unset($phpInfo[0]);
|
||||||
@@ -71,7 +80,8 @@ class AboutController extends AbstractController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('about/system.html.twig', [
|
return $this->render('about/system.html.twig', array_merge(
|
||||||
|
[
|
||||||
'modules' => get_loaded_extensions(),
|
'modules' => get_loaded_extensions(),
|
||||||
'dotenv' => [
|
'dotenv' => [
|
||||||
'APP_ENV' => getenv('APP_ENV'),
|
'APP_ENV' => getenv('APP_ENV'),
|
||||||
@@ -81,7 +91,9 @@ class AboutController extends AbstractController
|
|||||||
'info' => $phpInfo,
|
'info' => $phpInfo,
|
||||||
'settings' => $settings,
|
'settings' => $settings,
|
||||||
'license' => $this->getLicense(),
|
'license' => $this->getLicense(),
|
||||||
]);
|
],
|
||||||
|
$additional
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -141,4 +153,26 @@ class AboutController extends AbstractController
|
|||||||
|
|
||||||
return $phpinfo['phpinfo'];
|
return $phpinfo['phpinfo'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route(path="/flush-cache", name="system_flush_cache", methods={"GET"})
|
||||||
|
*
|
||||||
|
* @Security("is_granted('system_actions')")
|
||||||
|
*/
|
||||||
|
public function rebuildContainer(KernelInterface $kernel)
|
||||||
|
{
|
||||||
|
$application = new Application($kernel);
|
||||||
|
$application->setAutoExit(false);
|
||||||
|
|
||||||
|
$input = new ArrayInput([
|
||||||
|
'command' => 'cache:clear',
|
||||||
|
'--env' => $kernel->getEnvironment(),
|
||||||
|
'-n',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$output = new BufferedOutput();
|
||||||
|
$application->run($input, $output);
|
||||||
|
|
||||||
|
return $this->getAboutView(['content_action' => $output->fetch()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
51
src/Controller/PluginController.php
Normal file
51
src/Controller/PluginController.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use App\Plugin\PluginManager;
|
||||||
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route(path="/admin/plugins")
|
||||||
|
* @Security("is_granted('plugins')")
|
||||||
|
*/
|
||||||
|
class PluginController extends AbstractController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var PluginManager
|
||||||
|
*/
|
||||||
|
protected $plugins;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PluginManager $manager
|
||||||
|
*/
|
||||||
|
public function __construct(PluginManager $manager)
|
||||||
|
{
|
||||||
|
$this->plugins = $manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route(path="/", name="plugins", methods={"GET"})
|
||||||
|
*
|
||||||
|
* @return \Symfony\Component\HttpFoundation\Response
|
||||||
|
*/
|
||||||
|
public function indexAction()
|
||||||
|
{
|
||||||
|
$plugins = $this->plugins->getPlugins();
|
||||||
|
foreach ($this->plugins->getPlugins() as $plugin) {
|
||||||
|
$this->plugins->loadMetadata($plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('plugin/index.html.twig', [
|
||||||
|
'plugins' => $plugins,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,11 +27,10 @@ class DoctrineCompilerPass implements CompilerPassInterface
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ContainerBuilder $container
|
|
||||||
* @return array|false|null|string
|
* @return array|false|null|string
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
protected function findEngine(ContainerBuilder $container)
|
protected function findEngine()
|
||||||
{
|
{
|
||||||
$engine = null;
|
$engine = null;
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@ class DoctrineCompilerPass implements CompilerPassInterface
|
|||||||
if (null === $engine) {
|
if (null === $engine) {
|
||||||
throw new \Exception(
|
throw new \Exception(
|
||||||
'Could not detect database engine. Please set the environment config DATABASE_ENGINE ' .
|
'Could not detect database engine. Please set the environment config DATABASE_ENGINE ' .
|
||||||
'to one of: "' . implode(', ', $this->allowedEngines) . '" in your .env file: DATABASE_ENGINE=sqlite'
|
'to one of: "' . implode(', ', $this->allowedEngines) . '" in your .env file, e.g. DATABASE_ENGINE=sqlite'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +67,7 @@ class DoctrineCompilerPass implements CompilerPassInterface
|
|||||||
*/
|
*/
|
||||||
protected function getConfigFile(ContainerBuilder $container)
|
protected function getConfigFile(ContainerBuilder $container)
|
||||||
{
|
{
|
||||||
$engine = $this->findEngine($container);
|
$engine = $this->findEngine();
|
||||||
|
|
||||||
$configDir = realpath(
|
$configDir = realpath(
|
||||||
$container->getParameter('kernel.project_dir') . '/vendor/beberlei/DoctrineExtensions/config/'
|
$container->getParameter('kernel.project_dir') . '/vendor/beberlei/DoctrineExtensions/config/'
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
<?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\DependencyInjection\Compiler;
|
|
||||||
|
|
||||||
use App\Export\ServiceExport;
|
|
||||||
use App\Kernel;
|
|
||||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dynamically adds all dependencies to the ExportService.
|
|
||||||
*/
|
|
||||||
class ExportServiceCompilerPass implements CompilerPassInterface
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param ContainerBuilder $container
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
public function process(ContainerBuilder $container)
|
|
||||||
{
|
|
||||||
// always first check if the primary service is defined
|
|
||||||
if (!$container->has(ServiceExport::class)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$definition = $container->findDefinition(ServiceExport::class);
|
|
||||||
|
|
||||||
$taggedRenderer = $container->findTaggedServiceIds(Kernel::TAG_EXPORT_RENDERER);
|
|
||||||
foreach ($taggedRenderer as $id => $tags) {
|
|
||||||
$definition->addMethodCall('addRenderer', [new Reference($id)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -45,10 +45,11 @@ abstract class AbstractMigration extends BaseAbstractMigration implements Contai
|
|||||||
/**
|
/**
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @return string
|
* @return string
|
||||||
|
* @deprecated since 0.9
|
||||||
*/
|
*/
|
||||||
protected function getTableName($name)
|
protected function getTableName($name)
|
||||||
{
|
{
|
||||||
return TablePrefixSubscriber::PREFIX . $name;
|
return 'kimai2_' . $name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
<?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\Doctrine;
|
|
||||||
|
|
||||||
use Doctrine\Common\EventSubscriber;
|
|
||||||
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
|
|
||||||
use Doctrine\ORM\Events;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a prefix to every doctrine entity AKA database table
|
|
||||||
*/
|
|
||||||
class TablePrefixSubscriber implements EventSubscriber
|
|
||||||
{
|
|
||||||
public const PREFIX = 'kimai2_';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array|string[]
|
|
||||||
*/
|
|
||||||
public function getSubscribedEvents()
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
Events::loadClassMetadata,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param LoadClassMetadataEventArgs $args
|
|
||||||
*/
|
|
||||||
public function loadClassMetadata(LoadClassMetadataEventArgs $args)
|
|
||||||
{
|
|
||||||
$classMetadata = $args->getClassMetadata();
|
|
||||||
if ($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
|
|
||||||
// if we are in an inheritance hierarchy, only apply this once
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$classMetadata->setPrimaryTable(['name' => self::PREFIX . $classMetadata->getTableName()]);
|
|
||||||
|
|
||||||
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
|
|
||||||
if (\Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY == $mapping['type']
|
|
||||||
// Check if "joinTable" exists:
|
|
||||||
// it can be null if this field is the reverse side of a ManyToMany relationship
|
|
||||||
&& array_key_exists('name', $classMetadata->associationMappings[$fieldName]['joinTable'])) {
|
|
||||||
$mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
|
|
||||||
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = self::PREFIX . $mappedTableName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,7 @@ use Doctrine\ORM\Mapping as ORM;
|
|||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Table(name="activities")
|
* @ORM\Table(name="kimai2_activities")
|
||||||
* @ORM\Entity(repositoryClass="App\Repository\ActivityRepository")
|
* @ORM\Entity(repositoryClass="App\Repository\ActivityRepository")
|
||||||
*/
|
*/
|
||||||
class Activity
|
class Activity
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Entity(repositoryClass="App\Repository\ConfigurationRepository")
|
* @ORM\Entity(repositoryClass="App\Repository\ConfigurationRepository")
|
||||||
* @ORM\Table(
|
* @ORM\Table(name="kimai2_configuration",
|
||||||
* name="configuration",
|
|
||||||
* uniqueConstraints={
|
* uniqueConstraints={
|
||||||
* @ORM\UniqueConstraint(columns={"name"})
|
* @ORM\UniqueConstraint(columns={"name"})
|
||||||
* }
|
* }
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use Doctrine\ORM\Mapping as ORM;
|
|||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Table(name="customers")
|
* @ORM\Table(name="kimai2_customers")
|
||||||
* @ORM\Entity(repositoryClass="App\Repository\CustomerRepository")
|
* @ORM\Entity(repositoryClass="App\Repository\CustomerRepository")
|
||||||
*/
|
*/
|
||||||
class Customer
|
class Customer
|
||||||
|
|||||||
@@ -13,11 +13,8 @@ use Doctrine\ORM\Mapping as ORM;
|
|||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InvoiceTemplate
|
|
||||||
*
|
|
||||||
* @ORM\Entity(repositoryClass="App\Repository\InvoiceTemplateRepository")
|
* @ORM\Entity(repositoryClass="App\Repository\InvoiceTemplateRepository")
|
||||||
* @ORM\Table(
|
* @ORM\Table(name="kimai2_invoice_templates",
|
||||||
* name="invoice_templates",
|
|
||||||
* uniqueConstraints={
|
* uniqueConstraints={
|
||||||
* @ORM\UniqueConstraint(columns={"name"})
|
* @ORM\UniqueConstraint(columns={"name"})
|
||||||
* }
|
* }
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use Doctrine\ORM\Mapping as ORM;
|
|||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Table(name="projects")
|
* @ORM\Table(name="kimai2_projects")
|
||||||
* @ORM\Entity(repositoryClass="App\Repository\ProjectRepository")
|
* @ORM\Entity(repositoryClass="App\Repository\ProjectRepository")
|
||||||
*/
|
*/
|
||||||
class Project
|
class Project
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ use Doctrine\ORM\Mapping as ORM;
|
|||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Table(
|
* @ORM\Table(name="kimai2_timesheet",
|
||||||
* name="timesheet",
|
|
||||||
* indexes={
|
* indexes={
|
||||||
* @ORM\Index(columns={"user"}),
|
* @ORM\Index(columns={"user"}),
|
||||||
* @ORM\Index(columns={"activity_id"})
|
* @ORM\Index(columns={"activity_id"})
|
||||||
|
|||||||
@@ -18,11 +18,8 @@ use Symfony\Component\Security\Core\User\UserInterface;
|
|||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application main User entity.
|
|
||||||
*
|
|
||||||
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
|
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
|
||||||
* @ORM\Table(
|
* @ORM\Table(name="kimai2_users",
|
||||||
* name="users",
|
|
||||||
* uniqueConstraints={
|
* uniqueConstraints={
|
||||||
* @ORM\UniqueConstraint(columns={"username"}),
|
* @ORM\UniqueConstraint(columns={"username"}),
|
||||||
* @ORM\UniqueConstraint(columns={"email"})
|
* @ORM\UniqueConstraint(columns={"email"})
|
||||||
|
|||||||
@@ -16,11 +16,8 @@ use Symfony\Component\Validator\Constraint;
|
|||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UserPreference
|
|
||||||
*
|
|
||||||
* @ORM\Entity()
|
* @ORM\Entity()
|
||||||
* @ORM\Table(
|
* @ORM\Table(name="kimai2_user_preferences",
|
||||||
* name="user_preferences",
|
|
||||||
* uniqueConstraints={
|
* uniqueConstraints={
|
||||||
* @ORM\UniqueConstraint(columns={"user_id", "name"})
|
* @ORM\UniqueConstraint(columns={"user_id", "name"})
|
||||||
* }
|
* }
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
namespace App\Event;
|
namespace App\Event;
|
||||||
|
|
||||||
use KevinPapst\AdminLTEBundle\Event\SidebarMenuEvent;
|
use KevinPapst\AdminLTEBundle\Event\SidebarMenuEvent;
|
||||||
|
use KevinPapst\AdminLTEBundle\Model\MenuItemModel;
|
||||||
use Symfony\Component\EventDispatcher\Event;
|
use Symfony\Component\EventDispatcher\Event;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
@@ -28,17 +29,27 @@ class ConfigureMainMenuEvent extends Event
|
|||||||
* @var SidebarMenuEvent
|
* @var SidebarMenuEvent
|
||||||
*/
|
*/
|
||||||
private $event;
|
private $event;
|
||||||
|
/**
|
||||||
|
* @var MenuItemModel
|
||||||
|
*/
|
||||||
|
private $admin;
|
||||||
|
/**
|
||||||
|
* @var MenuItemModel
|
||||||
|
*/
|
||||||
|
private $system;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @param SidebarMenuEvent $event
|
* @param SidebarMenuEvent $event
|
||||||
|
* @param MenuItemModel $admin
|
||||||
|
* @param MenuItemModel $system
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(Request $request, SidebarMenuEvent $event, MenuItemModel $admin, MenuItemModel $system)
|
||||||
Request $request,
|
{
|
||||||
SidebarMenuEvent $event
|
|
||||||
) {
|
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
$this->event = $event;
|
$this->event = $event;
|
||||||
|
$this->admin = $admin;
|
||||||
|
$this->system = $system;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,4 +67,20 @@ class ConfigureMainMenuEvent extends Event
|
|||||||
{
|
{
|
||||||
return $this->event;
|
return $this->event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return MenuItemModel
|
||||||
|
*/
|
||||||
|
public function getAdminMenu()
|
||||||
|
{
|
||||||
|
return $this->admin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return MenuItemModel
|
||||||
|
*/
|
||||||
|
public function getSystemMenu()
|
||||||
|
{
|
||||||
|
return $this->system;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ class ThemeEvent extends Event
|
|||||||
public const JAVASCRIPT = 'app.theme.javascript';
|
public const JAVASCRIPT = 'app.theme.javascript';
|
||||||
public const STYLESHEET = 'app.theme.css';
|
public const STYLESHEET = 'app.theme.css';
|
||||||
public const HTML_HEAD = 'app.theme.html_head';
|
public const HTML_HEAD = 'app.theme.html_head';
|
||||||
|
public const CONTENT_BEFORE = 'app.theme.content_before';
|
||||||
|
public const CONTENT_START = 'app.theme.content_start';
|
||||||
|
public const CONTENT_END = 'app.theme.content_end';
|
||||||
|
public const CONTENT_AFTER = 'app.theme.content_after';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var User
|
* @var User
|
||||||
|
|||||||
@@ -66,26 +66,36 @@ class MenuBuilderSubscriber implements EventSubscriberInterface
|
|||||||
new MenuItemModel('dashboard', 'menu.homepage', 'dashboard', [], 'fas fa-tachometer-alt')
|
new MenuItemModel('dashboard', 'menu.homepage', 'dashboard', [], 'fas fa-tachometer-alt')
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->eventDispatcher->dispatch(
|
$menuEvent = new ConfigureMainMenuEvent(
|
||||||
ConfigureMainMenuEvent::CONFIGURE,
|
$request,
|
||||||
new ConfigureMainMenuEvent(
|
$event,
|
||||||
$request,
|
new MenuItemModel('admin', 'menu.admin', ''),
|
||||||
$event
|
new MenuItemModel('system', 'menu.system', '')
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$admin = new MenuItemModel('admin', 'menu.admin', '', [], 'fas fa-wrench');
|
$this->eventDispatcher->dispatch(ConfigureMainMenuEvent::CONFIGURE, $menuEvent);
|
||||||
|
|
||||||
|
// @deprecated since 0.9, will be removed with 1.0
|
||||||
$this->eventDispatcher->dispatch(
|
$this->eventDispatcher->dispatch(
|
||||||
ConfigureAdminMenuEvent::CONFIGURE,
|
ConfigureAdminMenuEvent::CONFIGURE,
|
||||||
new ConfigureAdminMenuEvent(
|
new ConfigureAdminMenuEvent(
|
||||||
$request,
|
$request,
|
||||||
$admin
|
$menuEvent->getAdminMenu()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($admin->hasChildren()) {
|
if ($menuEvent->getAdminMenu()->hasChildren()) {
|
||||||
$event->addItem($admin);
|
$event->addItem(new MenuItemModel('admin', 'menu.admin', ''));
|
||||||
|
foreach ($menuEvent->getAdminMenu()->getChildren() as $child) {
|
||||||
|
$event->addItem($child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($menuEvent->getSystemMenu()->hasChildren()) {
|
||||||
|
$event->addItem(new MenuItemModel('system', 'menu.system', ''));
|
||||||
|
foreach ($menuEvent->getSystemMenu()->getChildren() as $child) {
|
||||||
|
$event->addItem($child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->activateByRoute(
|
$this->activateByRoute(
|
||||||
|
|||||||
@@ -9,15 +9,14 @@
|
|||||||
|
|
||||||
namespace App\EventSubscriber;
|
namespace App\EventSubscriber;
|
||||||
|
|
||||||
use App\Event\ConfigureAdminMenuEvent;
|
|
||||||
use App\Event\ConfigureMainMenuEvent;
|
use App\Event\ConfigureMainMenuEvent;
|
||||||
|
use KevinPapst\AdminLTEBundle\Event\SidebarMenuEvent;
|
||||||
use KevinPapst\AdminLTEBundle\Model\MenuItemModel;
|
use KevinPapst\AdminLTEBundle\Model\MenuItemModel;
|
||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Menu event subscriber for timesheet, customer, projects, activities.
|
* Menu event subscriber is creating the Kimai default menu structure.
|
||||||
* This is a sample implementation for developer who want to add new navigation entries in their bundles.
|
|
||||||
*/
|
*/
|
||||||
class MenuSubscriber implements EventSubscriberInterface
|
class MenuSubscriber implements EventSubscriberInterface
|
||||||
{
|
{
|
||||||
@@ -42,7 +41,6 @@ class MenuSubscriber implements EventSubscriberInterface
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
ConfigureMainMenuEvent::CONFIGURE => ['onMainMenuConfigure', 100],
|
ConfigureMainMenuEvent::CONFIGURE => ['onMainMenuConfigure', 100],
|
||||||
ConfigureAdminMenuEvent::CONFIGURE => ['onAdminMenuConfigure', 100],
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +55,17 @@ class MenuSubscriber implements EventSubscriberInterface
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$menu = $event->getMenu();
|
$this->configureMainMenu($event->getMenu());
|
||||||
|
$this->configureAdminMenu($event->getAdminMenu());
|
||||||
|
$this->configureSystemMenu($event->getSystemMenu());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param SidebarMenuEvent $menu
|
||||||
|
*/
|
||||||
|
protected function configureMainMenu(SidebarMenuEvent $menu)
|
||||||
|
{
|
||||||
|
$auth = $this->security;
|
||||||
|
|
||||||
if ($auth->isGranted('view_own_timesheet')) {
|
if ($auth->isGranted('view_own_timesheet')) {
|
||||||
$menu->addItem(
|
$menu->addItem(
|
||||||
@@ -67,7 +75,7 @@ class MenuSubscriber implements EventSubscriberInterface
|
|||||||
|
|
||||||
if ($auth->isGranted('view_invoice')) {
|
if ($auth->isGranted('view_invoice')) {
|
||||||
$menu->addItem(
|
$menu->addItem(
|
||||||
new MenuItemModel('invoice', 'menu.invoice', 'invoice', [], 'fas fa-file-invoice')
|
new MenuItemModel('invoice', 'menu.invoice', 'invoice', [], 'far fa-file-alt')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,33 +87,21 @@ class MenuSubscriber implements EventSubscriberInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \App\Event\ConfigureAdminMenuEvent $event
|
* @param MenuItemModel $menu
|
||||||
*/
|
*/
|
||||||
public function onAdminMenuConfigure(ConfigureAdminMenuEvent $event)
|
protected function configureAdminMenu(MenuItemModel $menu)
|
||||||
{
|
{
|
||||||
$auth = $this->security;
|
$auth = $this->security;
|
||||||
|
|
||||||
if (!$auth->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$menu = $event->getAdminMenu();
|
|
||||||
|
|
||||||
if ($auth->isGranted('view_other_timesheet')) {
|
if ($auth->isGranted('view_other_timesheet')) {
|
||||||
$menu->addChild(
|
$menu->addChild(
|
||||||
new MenuItemModel('timesheet_admin', 'menu.admin_timesheet', 'admin_timesheet', [], 'far fa-clock')
|
new MenuItemModel('timesheet_admin', 'menu.admin_timesheet', 'admin_timesheet', [], 'fas fa-user-clock')
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($auth->isGranted('view_user')) {
|
|
||||||
$menu->addChild(
|
|
||||||
new MenuItemModel('user_admin', 'menu.admin_user', 'admin_user', [], 'fas fa-user')
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($auth->isGranted('view_customer')) {
|
if ($auth->isGranted('view_customer')) {
|
||||||
$menu->addChild(
|
$menu->addChild(
|
||||||
new MenuItemModel('customer_admin', 'menu.admin_customer', 'admin_customer', [], 'fas fa-users')
|
new MenuItemModel('customer_admin', 'menu.admin_customer', 'admin_customer', [], 'fas fa-user-tie')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +116,26 @@ class MenuSubscriber implements EventSubscriberInterface
|
|||||||
new MenuItemModel('activity_admin', 'menu.admin_activity', 'admin_activity', [], 'fas fa-tasks')
|
new MenuItemModel('activity_admin', 'menu.admin_activity', 'admin_activity', [], 'fas fa-tasks')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param MenuItemModel $menu
|
||||||
|
*/
|
||||||
|
protected function configureSystemMenu(MenuItemModel $menu)
|
||||||
|
{
|
||||||
|
$auth = $this->security;
|
||||||
|
|
||||||
|
if ($auth->isGranted('view_user')) {
|
||||||
|
$menu->addChild(
|
||||||
|
new MenuItemModel('user_admin', 'menu.admin_user', 'admin_user', [], 'fas fa-users')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($auth->isGranted('plugins')) {
|
||||||
|
$menu->addChild(
|
||||||
|
new MenuItemModel('plugins', 'menu.plugin', 'plugins', [], 'fas fa-plug')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ($auth->isGranted('system_configuration')) {
|
if ($auth->isGranted('system_configuration')) {
|
||||||
$menu->addChild(
|
$menu->addChild(
|
||||||
|
|||||||
@@ -13,20 +13,21 @@ use App\Entity\Timesheet;
|
|||||||
use App\Export\RendererInterface;
|
use App\Export\RendererInterface;
|
||||||
use App\Repository\Query\TimesheetQuery;
|
use App\Repository\Query\TimesheetQuery;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
class HtmlRenderer implements RendererInterface
|
class HtmlRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
use RendererTrait;
|
use RendererTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Twig_Environment
|
* @var Environment
|
||||||
*/
|
*/
|
||||||
protected $twig;
|
protected $twig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Twig_Environment $twig
|
* @param Environment $twig
|
||||||
*/
|
*/
|
||||||
public function __construct(\Twig_Environment $twig)
|
public function __construct(Environment $twig)
|
||||||
{
|
{
|
||||||
$this->twig = $twig;
|
$this->twig = $twig;
|
||||||
}
|
}
|
||||||
@@ -35,9 +36,9 @@ class HtmlRenderer implements RendererInterface
|
|||||||
* @param Timesheet[] $timesheets
|
* @param Timesheet[] $timesheets
|
||||||
* @param TimesheetQuery $query
|
* @param TimesheetQuery $query
|
||||||
* @return Response
|
* @return Response
|
||||||
* @throws \Twig_Error_Loader
|
* @throws \Twig\Error\LoaderError
|
||||||
* @throws \Twig_Error_Runtime
|
* @throws \Twig\Error\RuntimeError
|
||||||
* @throws \Twig_Error_Syntax
|
* @throws \Twig\Error\SyntaxError
|
||||||
*/
|
*/
|
||||||
public function render(array $timesheets, TimesheetQuery $query): Response
|
public function render(array $timesheets, TimesheetQuery $query): Response
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ use App\Timesheet\UserDateTimeFactory;
|
|||||||
use App\Utils\HtmlToPdfConverter;
|
use App\Utils\HtmlToPdfConverter;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
class PDFRenderer implements RendererInterface
|
class PDFRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
use RendererTrait;
|
use RendererTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \Twig_Environment
|
* @var Environment
|
||||||
*/
|
*/
|
||||||
protected $twig;
|
protected $twig;
|
||||||
/**
|
/**
|
||||||
@@ -35,10 +36,11 @@ class PDFRenderer implements RendererInterface
|
|||||||
protected $converter;
|
protected $converter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Twig_Environment $twig
|
* @param Environment $twig
|
||||||
* @param UserDateTimeFactory $dateTime
|
* @param UserDateTimeFactory $dateTime
|
||||||
|
* @param HtmlToPdfConverter $converter
|
||||||
*/
|
*/
|
||||||
public function __construct(\Twig_Environment $twig, UserDateTimeFactory $dateTime, HtmlToPdfConverter $converter)
|
public function __construct(Environment $twig, UserDateTimeFactory $dateTime, HtmlToPdfConverter $converter)
|
||||||
{
|
{
|
||||||
$this->twig = $twig;
|
$this->twig = $twig;
|
||||||
$this->dateTime = $dateTime;
|
$this->dateTime = $dateTime;
|
||||||
@@ -49,10 +51,9 @@ class PDFRenderer implements RendererInterface
|
|||||||
* @param Timesheet[] $timesheets
|
* @param Timesheet[] $timesheets
|
||||||
* @param TimesheetQuery $query
|
* @param TimesheetQuery $query
|
||||||
* @return Response
|
* @return Response
|
||||||
* @throws \Mpdf\MpdfException
|
* @throws \Twig\Error\LoaderError
|
||||||
* @throws \Twig_Error_Loader
|
* @throws \Twig\Error\RuntimeError
|
||||||
* @throws \Twig_Error_Runtime
|
* @throws \Twig\Error\SyntaxError
|
||||||
* @throws \Twig_Error_Syntax
|
|
||||||
*/
|
*/
|
||||||
public function render(array $timesheets, TimesheetQuery $query): Response
|
public function render(array $timesheets, TimesheetQuery $query): Response
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,16 @@ class ServiceExport
|
|||||||
*/
|
*/
|
||||||
protected $renderer = [];
|
protected $renderer = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param RendererInterface[] $renderer
|
||||||
|
*/
|
||||||
|
public function __construct(iterable $renderer)
|
||||||
|
{
|
||||||
|
foreach ($renderer as $render) {
|
||||||
|
$this->addRenderer($render);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param RendererInterface $renderer
|
* @param RendererInterface $renderer
|
||||||
* @return $this
|
* @return $this
|
||||||
|
|||||||
@@ -13,18 +13,19 @@ use App\Entity\InvoiceDocument;
|
|||||||
use App\Invoice\RendererInterface;
|
use App\Invoice\RendererInterface;
|
||||||
use App\Model\InvoiceModel;
|
use App\Model\InvoiceModel;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
class TwigRenderer implements RendererInterface
|
class TwigRenderer implements RendererInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var \Twig_Environment
|
* @var Environment
|
||||||
*/
|
*/
|
||||||
protected $twig;
|
protected $twig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Twig_Environment $twig
|
* @param Environment $twig
|
||||||
*/
|
*/
|
||||||
public function __construct(\Twig_Environment $twig)
|
public function __construct(Environment $twig)
|
||||||
{
|
{
|
||||||
$this->twig = $twig;
|
$this->twig = $twig;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ namespace App;
|
|||||||
|
|
||||||
use App\DependencyInjection\AppExtension;
|
use App\DependencyInjection\AppExtension;
|
||||||
use App\DependencyInjection\Compiler\DoctrineCompilerPass;
|
use App\DependencyInjection\Compiler\DoctrineCompilerPass;
|
||||||
use App\DependencyInjection\Compiler\ExportServiceCompilerPass;
|
|
||||||
use App\DependencyInjection\Compiler\InvoiceServiceCompilerPass;
|
use App\DependencyInjection\Compiler\InvoiceServiceCompilerPass;
|
||||||
use App\DependencyInjection\Compiler\TwigContextCompilerPass;
|
use App\DependencyInjection\Compiler\TwigContextCompilerPass;
|
||||||
use App\Export\RendererInterface as ExportRendererInterface;
|
use App\Export\RendererInterface as ExportRendererInterface;
|
||||||
use App\Invoice\CalculatorInterface as InvoiceCalculator;
|
use App\Invoice\CalculatorInterface as InvoiceCalculator;
|
||||||
use App\Invoice\NumberGeneratorInterface;
|
use App\Invoice\NumberGeneratorInterface;
|
||||||
use App\Invoice\RendererInterface as InvoiceRendererInterface;
|
use App\Invoice\RendererInterface as InvoiceRendererInterface;
|
||||||
|
use App\Plugin\PluginInterface;
|
||||||
use App\Timesheet\CalculatorInterface as TimesheetCalculator;
|
use App\Timesheet\CalculatorInterface as TimesheetCalculator;
|
||||||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||||
@@ -34,6 +34,7 @@ class Kernel extends BaseKernel
|
|||||||
|
|
||||||
public const CONFIG_EXTS = '.{php,xml,yaml,yml}';
|
public const CONFIG_EXTS = '.{php,xml,yaml,yml}';
|
||||||
|
|
||||||
|
public const TAG_PLUGIN = 'kimai.plugin';
|
||||||
public const TAG_EXPORT_RENDERER = 'export.renderer';
|
public const TAG_EXPORT_RENDERER = 'export.renderer';
|
||||||
public const TAG_INVOICE_RENDERER = 'invoice.renderer';
|
public const TAG_INVOICE_RENDERER = 'invoice.renderer';
|
||||||
public const TAG_INVOICE_NUMBER_GENERATOR = 'invoice.number_generator';
|
public const TAG_INVOICE_NUMBER_GENERATOR = 'invoice.number_generator';
|
||||||
@@ -56,6 +57,7 @@ class Kernel extends BaseKernel
|
|||||||
$container->registerForAutoconfiguration(InvoiceRendererInterface::class)->addTag(self::TAG_INVOICE_RENDERER);
|
$container->registerForAutoconfiguration(InvoiceRendererInterface::class)->addTag(self::TAG_INVOICE_RENDERER);
|
||||||
$container->registerForAutoconfiguration(NumberGeneratorInterface::class)->addTag(self::TAG_INVOICE_NUMBER_GENERATOR);
|
$container->registerForAutoconfiguration(NumberGeneratorInterface::class)->addTag(self::TAG_INVOICE_NUMBER_GENERATOR);
|
||||||
$container->registerForAutoconfiguration(InvoiceCalculator::class)->addTag(self::TAG_INVOICE_CALCULATOR);
|
$container->registerForAutoconfiguration(InvoiceCalculator::class)->addTag(self::TAG_INVOICE_CALCULATOR);
|
||||||
|
$container->registerForAutoconfiguration(PluginInterface::class)->addTag(self::TAG_PLUGIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerBundles()
|
public function registerBundles()
|
||||||
@@ -67,6 +69,12 @@ class Kernel extends BaseKernel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// do not load Kimai plugin in test environment, they may alter the default behaviour and create
|
||||||
|
// false-negatives in integration/system tests
|
||||||
|
if ('test' === $this->environment) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$pluginsDir = $this->getProjectDir() . '/var/plugins';
|
$pluginsDir = $this->getProjectDir() . '/var/plugins';
|
||||||
if (!file_exists($pluginsDir)) {
|
if (!file_exists($pluginsDir)) {
|
||||||
return;
|
return;
|
||||||
@@ -110,7 +118,6 @@ class Kernel extends BaseKernel
|
|||||||
$container->addCompilerPass(new DoctrineCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1000);
|
$container->addCompilerPass(new DoctrineCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1000);
|
||||||
$container->addCompilerPass(new TwigContextCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1000);
|
$container->addCompilerPass(new TwigContextCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1000);
|
||||||
$container->addCompilerPass(new InvoiceServiceCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1000);
|
$container->addCompilerPass(new InvoiceServiceCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1000);
|
||||||
$container->addCompilerPass(new ExportServiceCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function configureRoutes(RouteCollectionBuilder $routes)
|
protected function configureRoutes(RouteCollectionBuilder $routes)
|
||||||
|
|||||||
83
src/Plugin/Plugin.php
Normal file
83
src/Plugin/Plugin.php
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<?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\Plugin;
|
||||||
|
|
||||||
|
class Plugin
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $name;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $path;
|
||||||
|
/**
|
||||||
|
* @var PluginMetadata
|
||||||
|
*/
|
||||||
|
private $metadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return PluginMetadata
|
||||||
|
*/
|
||||||
|
public function getMetadata(): ?PluginMetadata
|
||||||
|
{
|
||||||
|
return $this->metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PluginMetadata $metadata
|
||||||
|
* @return Plugin
|
||||||
|
*/
|
||||||
|
public function setMetadata(PluginMetadata $metadata)
|
||||||
|
{
|
||||||
|
$this->metadata = $metadata;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPath(): ?string
|
||||||
|
{
|
||||||
|
return $this->path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @return Plugin
|
||||||
|
*/
|
||||||
|
public function setPath(string $path)
|
||||||
|
{
|
||||||
|
$this->path = $path;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName(): ?string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name
|
||||||
|
* @return Plugin
|
||||||
|
*/
|
||||||
|
public function setName(string $name)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/Plugin/PluginInterface.php
Normal file
23
src/Plugin/PluginInterface.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?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\Plugin;
|
||||||
|
|
||||||
|
interface PluginInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPath();
|
||||||
|
}
|
||||||
111
src/Plugin/PluginManager.php
Normal file
111
src/Plugin/PluginManager.php
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<?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\Plugin;
|
||||||
|
|
||||||
|
use App\Constants;
|
||||||
|
|
||||||
|
class PluginManager
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Plugin[]
|
||||||
|
*/
|
||||||
|
private $plugins = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PluginInterface[] $plugins
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function __construct(iterable $plugins)
|
||||||
|
{
|
||||||
|
foreach ($plugins as $plugin) {
|
||||||
|
$this->addPlugin($plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PluginInterface $plugin
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function addPlugin(PluginInterface $plugin)
|
||||||
|
{
|
||||||
|
if (isset($this->plugins[$plugin->getName()])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->plugins[$plugin->getName()] = $this->createPlugin($plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Plugin[]
|
||||||
|
*/
|
||||||
|
public function getPlugins()
|
||||||
|
{
|
||||||
|
return $this->plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name
|
||||||
|
* @return Plugin|null
|
||||||
|
*/
|
||||||
|
public function getPlugin(string $name): ?Plugin
|
||||||
|
{
|
||||||
|
if (!isset($this->plugins[$name])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->plugins[$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PluginInterface $bundle
|
||||||
|
* @return Plugin
|
||||||
|
*/
|
||||||
|
protected function createPlugin(PluginInterface $bundle)
|
||||||
|
{
|
||||||
|
$plugin = new Plugin();
|
||||||
|
$plugin
|
||||||
|
->setName($bundle->getName())
|
||||||
|
->setPath($bundle->getPath())
|
||||||
|
->setMetadata(new PluginMetadata())
|
||||||
|
;
|
||||||
|
|
||||||
|
return $plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this method and pass a plugin, to set its metdata.
|
||||||
|
* This is not pre-filled by default, as it would mean to parse several composer.json on each request.
|
||||||
|
*
|
||||||
|
* @param Plugin $plugin
|
||||||
|
*/
|
||||||
|
public function loadMetadata(Plugin $plugin)
|
||||||
|
{
|
||||||
|
$composer = $plugin->getPath() . '/composer.json';
|
||||||
|
if (!file_exists($composer) || !is_readable($composer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_decode(file_get_contents($composer), true);
|
||||||
|
|
||||||
|
$reqVersion = $json['extra']['kimai']['require'] ?? 'unknown';
|
||||||
|
$version = $json['extra']['kimai']['version'] ?? 'unknown';
|
||||||
|
$description = $json['description'] ?? '';
|
||||||
|
|
||||||
|
$homepage = $json['homepage'] ?? Constants::HOMEPAGE . '/store/';
|
||||||
|
|
||||||
|
$plugin
|
||||||
|
->getMetadata()
|
||||||
|
->setHomepage($homepage)
|
||||||
|
->setKimaiVersion($reqVersion)
|
||||||
|
->setVersion($version)
|
||||||
|
->setDescription($description)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
src/Plugin/PluginMetadata.php
Normal file
106
src/Plugin/PluginMetadata.php
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<?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\Plugin;
|
||||||
|
|
||||||
|
class PluginMetadata
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $version;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $kimaiVersion;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $homepage;
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDescription(): ?string
|
||||||
|
{
|
||||||
|
return $this->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $description
|
||||||
|
* @return PluginMetadata
|
||||||
|
*/
|
||||||
|
public function setDescription(string $description)
|
||||||
|
{
|
||||||
|
$this->description = $description;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getVersion(): ?string
|
||||||
|
{
|
||||||
|
return $this->version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $version
|
||||||
|
* @return PluginMetadata
|
||||||
|
*/
|
||||||
|
public function setVersion(string $version)
|
||||||
|
{
|
||||||
|
$this->version = $version;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getKimaiVersion(): ?string
|
||||||
|
{
|
||||||
|
return $this->kimaiVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $kimaiVersion
|
||||||
|
* @return PluginMetadata
|
||||||
|
*/
|
||||||
|
public function setKimaiVersion(string $kimaiVersion)
|
||||||
|
{
|
||||||
|
$this->kimaiVersion = $kimaiVersion;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getHomepage(): ?string
|
||||||
|
{
|
||||||
|
return $this->homepage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $homepage
|
||||||
|
* @return PluginMetadata
|
||||||
|
*/
|
||||||
|
public function setHomepage(string $homepage)
|
||||||
|
{
|
||||||
|
$this->homepage = $homepage;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,6 +101,8 @@ class Extensions extends AbstractExtension
|
|||||||
'on' => 'fas fa-toggle-on',
|
'on' => 'fas fa-toggle-on',
|
||||||
'off' => 'fas fa-toggle-off',
|
'off' => 'fas fa-toggle-off',
|
||||||
'audit' => 'fas fa-history',
|
'audit' => 'fas fa-history',
|
||||||
|
'home' => 'fas fa-home',
|
||||||
|
'shop' => 'fas fa-shopping-cart',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class TimesheetVoter extends AbstractVoter
|
|||||||
|
|
||||||
switch ($attribute) {
|
switch ($attribute) {
|
||||||
case self::START:
|
case self::START:
|
||||||
if (!$this->canStart($subject, $user, $token)) {
|
if (!$this->canStart($subject)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$permission .= $attribute;
|
$permission .= $attribute;
|
||||||
@@ -113,11 +113,9 @@ class TimesheetVoter extends AbstractVoter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Timesheet $timesheet
|
* @param Timesheet $timesheet
|
||||||
* @param User $user
|
|
||||||
* @param TokenInterface $token
|
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
protected function canStart(Timesheet $timesheet, User $user, TokenInterface $token)
|
protected function canStart(Timesheet $timesheet)
|
||||||
{
|
{
|
||||||
// possible improvements for the future:
|
// possible improvements for the future:
|
||||||
// we could check the amount of active entries (maybe slow)
|
// we could check the amount of active entries (maybe slow)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class UserVoter extends AbstractVoter
|
|||||||
switch ($attribute) {
|
switch ($attribute) {
|
||||||
// special case for the UserController
|
// special case for the UserController
|
||||||
case self::DELETE:
|
case self::DELETE:
|
||||||
if (!$this->canDelete($subject, $user, $token)) {
|
if (!$this->canDelete($subject, $user)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ class UserVoter extends AbstractVoter
|
|||||||
* @param User $user
|
* @param User $user
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
protected function canDelete(User $profile, User $user, TokenInterface $token)
|
protected function canDelete(User $profile, User $user)
|
||||||
{
|
{
|
||||||
return $profile->getId() !== $user->getId();
|
return $profile->getId() !== $user->getId();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,9 +338,6 @@
|
|||||||
"setasign/fpdi": {
|
"setasign/fpdi": {
|
||||||
"version": "1.6.2"
|
"version": "1.6.2"
|
||||||
},
|
},
|
||||||
"squizlabs/php_codesniffer": {
|
|
||||||
"version": "3.2.2"
|
|
||||||
},
|
|
||||||
"swiftmailer/swiftmailer": {
|
"swiftmailer/swiftmailer": {
|
||||||
"version": "v6.0.2"
|
"version": "v6.0.2"
|
||||||
},
|
},
|
||||||
@@ -482,6 +479,9 @@
|
|||||||
"symfony/polyfill-intl-icu": {
|
"symfony/polyfill-intl-icu": {
|
||||||
"version": "v1.6.0"
|
"version": "v1.6.0"
|
||||||
},
|
},
|
||||||
|
"symfony/polyfill-intl-idn": {
|
||||||
|
"version": "v1.11.0"
|
||||||
|
},
|
||||||
"symfony/polyfill-mbstring": {
|
"symfony/polyfill-mbstring": {
|
||||||
"version": "v1.6.0"
|
"version": "v1.6.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,38 +1,57 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
|
|
||||||
{% block page_title %}{{ 'about.title'|trans({}, 'about') }}{% endblock %}
|
{% block page_title %}{{ 'about.title'|trans({}, 'about') }}{% endblock %}
|
||||||
{% block page_subtitle %}{{ 'about.subtitle'|trans({}, 'about') }}{% endblock %}
|
{% block page_subtitle %}{{ 'about.subtitle'|trans({}, 'about') }}{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
|
{{ widgets.callout('info', 'Thank you for using Kimai ' ~ constant('App\\Constants::VERSION') ~ ' ' ~ constant('App\\Constants::STATUS') ~ ' - ' ~ constant('App\\Constants::NAME'), 'Kimai ' ~ constant('App\\Constants::VERSION'), 'fas fa-thumbs-up') }}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
|
|
||||||
<div class="nav-tabs-custom">
|
<div class="nav-tabs-custom">
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
<li class="active"><a href="#kimai" data-toggle="tab" aria-expanded="true">Kimai</a></li>
|
<li role="presentation"class ="active"><a href="#systeminfo" aria-controls="systeminfo" role="tab" data-toggle="tab">{{ 'tab.system'|trans({}, 'about') }}</a></li>
|
||||||
<li><a href="#system" data-toggle="tab" aria-expanded="true">System</a></li>
|
<li role="presentation"><a href="#license" aria-controls="license" role="tab" data-toggle="tab">{{ 'tab.license'|trans({}, 'about') }}</a></li>
|
||||||
<li><a href="#license" data-toggle="tab" aria-expanded="true">License</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane active" id="kimai">
|
<div role="tabpanel" class="tab-pane active" id="systeminfo">
|
||||||
<dl>
|
|
||||||
<dt>Version</dt><dd>{{ constant('App\\Constants::VERSION') }}</dd>
|
|
||||||
<dt>Status</dt><dd>{{ constant('App\\Constants::STATUS') }}</dd>
|
|
||||||
<dt>Name</dt><dd>{{ constant('App\\Constants::NAME') }}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" id="system">
|
|
||||||
|
|
||||||
<div class="box-group" id="accordion">
|
<div class="box-group" id="accordion">
|
||||||
|
{% if is_granted('system_actions') %}
|
||||||
<div class="panel box">
|
<div class="panel box">
|
||||||
<div class="box-header with-border">
|
<div class="box-header with-border">
|
||||||
<h4 class="box-title">
|
<h4 class="box-title">
|
||||||
<a data-toggle="collapse" data-parent="#accordion" href="#collapseTwo" class="" aria-expanded="true">
|
<a data-toggle="collapse" data-parent="#accordion" href="#collapseSystemAction" aria-expanded="true">
|
||||||
|
{{ 'label.actions'|trans }}
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div id="collapseSystemAction" class="panel-collapse collapse in" aria-expanded="true">
|
||||||
|
<div class="box-body">
|
||||||
|
<p>
|
||||||
|
<a href="{{ path('system_flush_cache') }}" class="btn btn-primary" onclick="$(this).html('Clear cache <i class=\'fas fa-spinner fa-spin\'></i>')">Clear cache</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% if content_action is defined and content_action is not empty %}
|
||||||
|
<pre>
|
||||||
|
{{ content_action }}
|
||||||
|
</pre>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="panel box">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h4 class="box-title">
|
||||||
|
<a data-toggle="collapse" role="button" data-parent="#accordion" href="#collapseEnvironment" aria-expanded="false" aria-controls="collapseEnvironment">
|
||||||
Environment
|
Environment
|
||||||
</a>
|
</a>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="collapseTwo" class="panel-collapse collapse in" aria-expanded="true" style="">
|
<div id="collapseEnvironment" class="panel-collapse collapse" aria-expanded="false" role="tabpanel">
|
||||||
<div class="box-body">
|
<div class="box-body">
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
@@ -48,12 +67,12 @@
|
|||||||
<div class="panel box">
|
<div class="panel box">
|
||||||
<div class="box-header with-border">
|
<div class="box-header with-border">
|
||||||
<h4 class="box-title">
|
<h4 class="box-title">
|
||||||
<a data-toggle="collapse" data-parent="#accordion" href="#collapseThree" class="collapsed" aria-expanded="false">
|
<a data-toggle="collapse" data-parent="#accordion" href="#collapsePhp" class="collapsed" aria-expanded="false">
|
||||||
PHP
|
PHP
|
||||||
</a>
|
</a>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="collapseThree" class="panel-collapse collapse" aria-expanded="false" style="height: 0px;">
|
<div id="collapsePhp" class="panel-collapse collapse" aria-expanded="false">
|
||||||
<div class="box-body">
|
<div class="box-body">
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
@@ -77,12 +96,12 @@
|
|||||||
<div class="panel box">
|
<div class="panel box">
|
||||||
<div class="box-header with-border">
|
<div class="box-header with-border">
|
||||||
<h4 class="box-title">
|
<h4 class="box-title">
|
||||||
<a data-toggle="collapse" data-parent="#accordion" href="#collapseFour" class="collapsed" aria-expanded="false">
|
<a data-toggle="collapse" data-parent="#accordion" href="#collapseSystem" class="collapsed" aria-expanded="false">
|
||||||
Server
|
Server
|
||||||
</a>
|
</a>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="collapseFour" class="panel-collapse collapse" aria-expanded="false" style="height: 0px;">
|
<div id="collapseSystem" class="panel-collapse collapse" aria-expanded="false">
|
||||||
<div class="box-body">
|
<div class="box-body">
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
@@ -96,9 +115,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="license">
|
<div role="tabpanel" class="tab-pane" id="license">
|
||||||
<pre>{{ license }}</pre>
|
<pre>{{ license }}</pre>
|
||||||
<p>
|
<p>
|
||||||
<a href="https://choosealicense.com/licenses/mit/" target="_blank">choosealicense.com</a>,
|
<a href="https://choosealicense.com/licenses/mit/" target="_blank">choosealicense.com</a>,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block avanzu_page_content_before %}
|
{% block avanzu_page_content_before %}
|
||||||
|
{% set event = trigger(constant('App\\Event\\ThemeEvent::CONTENT_BEFORE')) %}
|
||||||
|
{{ event.content|raw }}
|
||||||
<div class="toolbar-pad no-print">
|
<div class="toolbar-pad no-print">
|
||||||
{% block main_before %}{% endblock %}
|
{% block main_before %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
@@ -17,10 +19,16 @@
|
|||||||
|
|
||||||
{% block avanzu_page_content_after %}
|
{% block avanzu_page_content_after %}
|
||||||
{% block main_after %}{% endblock %}
|
{% block main_after %}{% endblock %}
|
||||||
|
{% set event = trigger(constant('App\\Event\\ThemeEvent::CONTENT_AFTER')) %}
|
||||||
|
{{ event.content|raw }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block avanzu_page_content %}
|
{% block avanzu_page_content %}
|
||||||
|
{% set event = trigger(constant('App\\Event\\ThemeEvent::CONTENT_START')) %}
|
||||||
|
{{ event.content|raw }}
|
||||||
{% block main %}{% endblock %}
|
{% block main %}{% endblock %}
|
||||||
|
{% set event = trigger(constant('App\\Event\\ThemeEvent::CONTENT_END')) %}
|
||||||
|
{{ event.content|raw }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block avanzu_document_title %}
|
{% block avanzu_document_title %}
|
||||||
|
|||||||
@@ -222,3 +222,25 @@
|
|||||||
{{ widgets.page_actions(event.payload.actions) }}
|
{{ widgets.page_actions(event.payload.actions) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro plugins(view) %}
|
||||||
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
|
|
||||||
|
{% set actions = {'shop': {'url': constant('App\\Constants::HOMEPAGE') ~ '/store/', 'target': '_blank'}} %}
|
||||||
|
|
||||||
|
{% set event = trigger('actions.plugins', {'actions': actions, 'view': view}) %}
|
||||||
|
{{ widgets.page_actions(event.payload.actions) }}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro plugin(plugin, view) %}
|
||||||
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
|
|
||||||
|
{% set actions = {'home': {'url': plugin.metadata.homepage, 'target': '_blank'}} %}
|
||||||
|
|
||||||
|
{% set event = trigger('actions.plugin', {'actions': actions, 'view': view}) %}
|
||||||
|
{% if view == 'index' %}
|
||||||
|
{{ widgets.button_group(event.payload.actions) }}
|
||||||
|
{% else %}
|
||||||
|
{{ widgets.page_actions(event.payload.actions) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
|||||||
@@ -153,6 +153,7 @@
|
|||||||
{% set modal = null %}
|
{% set modal = null %}
|
||||||
{% set toggle = null %}
|
{% set toggle = null %}
|
||||||
{% set url = null %}
|
{% set url = null %}
|
||||||
|
{% set target = null %}
|
||||||
{% set class = "btn btn-default btn-" ~ icon ~ " " %}
|
{% set class = "btn btn-default btn-" ~ icon ~ " " %}
|
||||||
|
|
||||||
{% if not values is iterable %}
|
{% if not values is iterable %}
|
||||||
@@ -173,6 +174,7 @@
|
|||||||
{% set onclick = values.onclick ?? null %}
|
{% set onclick = values.onclick ?? null %}
|
||||||
{% set modal = values.modal ?? null %}
|
{% set modal = values.modal ?? null %}
|
||||||
{% set toggle = values.toggle ?? null %}
|
{% set toggle = values.toggle ?? null %}
|
||||||
|
{% set target = values.target ?? null %}
|
||||||
{% set id = values.id ?? null %}
|
{% set id = values.id ?? null %}
|
||||||
{% set class = class ~ ( values.class | default("")) %}
|
{% set class = class ~ ( values.class | default("")) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -190,6 +192,9 @@
|
|||||||
{%- if onclick is not empty -%}
|
{%- if onclick is not empty -%}
|
||||||
onclick="{{ onclick }}"
|
onclick="{{ onclick }}"
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
{%- if target is not empty -%}
|
||||||
|
target="{{ target }}"
|
||||||
|
{%- endif -%}
|
||||||
>{{ macro.icon(icon) }}</a>
|
>{{ macro.icon(icon) }}</a>
|
||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
58
templates/plugin/index.html.twig
Normal file
58
templates/plugin/index.html.twig
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
|
{% import "macros/actions.html.twig" as actions %}
|
||||||
|
|
||||||
|
{% block page_title %}{{ 'plugins.title'|trans({}, 'plugins') }}{% endblock %}
|
||||||
|
{% block page_subtitle %}{{ 'plugins.subtitle'|trans({}, 'plugins') }}{% endblock %}
|
||||||
|
{% block page_actions %}{{ actions.plugins('index') }}{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
{% if plugins|length == 0 %}
|
||||||
|
{{ widgets.callout('warning', 'plugin.none_installed'|trans({}, 'plugins')) }}
|
||||||
|
{% else %}
|
||||||
|
<div class="box data_table" id="datatable_plugins">
|
||||||
|
<div class="box-body no-padding">
|
||||||
|
<div class="dataTables_wrapper form-inline dt-bootstrap">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<table class="table table-striped table-hover dataTable" role="grid">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ 'label.name'|trans }}</th>
|
||||||
|
<th>{{ 'label.version'|trans({}, 'plugins') }}</th>
|
||||||
|
<th class="hidden-xs">{{ 'label.description'|trans }}</th>
|
||||||
|
<th class="hidden-xs hidden-sm">{{ 'label.required_version'|trans({}, 'plugins') }}</th>
|
||||||
|
<th>{{ 'label.actions'|trans }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for plugin in plugins %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ plugin.name }}</td>
|
||||||
|
<td>{{ widgets.label(plugin.metadata.version, 'primary') }}</td>
|
||||||
|
<td class="hidden-xs">{{ plugin.metadata.description }}</td>
|
||||||
|
<td class="hidden-xs hidden-sm">
|
||||||
|
{% if plugin.metadata.kimaiVersion > constant('App\\Constants::VERSION') %}
|
||||||
|
{{ widgets.label(plugin.metadata.kimaiVersion, 'danger') }}
|
||||||
|
{% elseif plugin.metadata.kimaiVersion < constant('App\\Constants::VERSION') %}
|
||||||
|
{{ widgets.label(plugin.metadata.kimaiVersion, 'warning') }}
|
||||||
|
{% else %}
|
||||||
|
{{ widgets.label(plugin.metadata.kimaiVersion, 'success') }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ actions.plugin(plugin, 'index') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -31,10 +31,10 @@ class ConfigurationControllerTest extends APIControllerBaseTest
|
|||||||
$this->assertIsArray($result);
|
$this->assertIsArray($result);
|
||||||
$this->assertNotEmpty($result);
|
$this->assertNotEmpty($result);
|
||||||
$this->assertEquals(7, count($result));
|
$this->assertEquals(7, count($result));
|
||||||
$this->assertStructure($result, false);
|
$this->assertStructure($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function assertStructure(array $result, $full = true)
|
protected function assertStructure(array $result)
|
||||||
{
|
{
|
||||||
$expectedKeys = ['date', 'date_time', 'duration', 'form_date', 'form_date_time', 'is24hours', 'time'];
|
$expectedKeys = ['date', 'date_time', 'duration', 'form_date', 'form_date_time', 'is24hours', 'time'];
|
||||||
$actual = array_keys($result);
|
$actual = array_keys($result);
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ class AboutControllerTest extends ControllerBaseTest
|
|||||||
$this->assertAccessIsGranted($client, '/admin/about');
|
$this->assertAccessIsGranted($client, '/admin/about');
|
||||||
|
|
||||||
$result = $client->getCrawler()->filter('div.nav-tabs-custom ul.nav.nav-tabs li');
|
$result = $client->getCrawler()->filter('div.nav-tabs-custom ul.nav.nav-tabs li');
|
||||||
$this->assertEquals(3, count($result));
|
$this->assertEquals(2, count($result));
|
||||||
|
|
||||||
$result = $client->getCrawler()->filter('div.nav-tabs-custom div.tab-content div.tab-pane');
|
$result = $client->getCrawler()->filter('div.nav-tabs-custom div.tab-content div.tab-pane');
|
||||||
$this->assertEquals(3, count($result));
|
$this->assertEquals(2, count($result));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class CalendarControllerTest extends ControllerBaseTest
|
|||||||
|
|
||||||
$crawler = $client->getCrawler();
|
$crawler = $client->getCrawler();
|
||||||
$calendar = $crawler->filter('div#calendar');
|
$calendar = $crawler->filter('div#calendar');
|
||||||
|
$this->assertEquals(1, $calendar->count());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCalendarEntriesAction()
|
public function testCalendarEntriesAction()
|
||||||
|
|||||||
@@ -202,6 +202,23 @@ abstract class ControllerBaseTest extends WebTestCase
|
|||||||
$this->assertEquals($count, $node->count());
|
$this->assertEquals($count, $node->count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Client $client
|
||||||
|
* @param array $buttons
|
||||||
|
*/
|
||||||
|
protected function assertPageActions(Client $client, array $buttons)
|
||||||
|
{
|
||||||
|
$node = $client->getCrawler()->filter('section.content-header div.breadcrumb div.box-tools div.btn-group a.btn');
|
||||||
|
$this->assertEquals(count($buttons), $node->count());
|
||||||
|
|
||||||
|
foreach ($node->getIterator() as $element) {
|
||||||
|
$expectedClass = str_replace('btn btn-default btn-', '', $element->getAttribute('class'));
|
||||||
|
$this->assertArrayHasKey($expectedClass, $buttons);
|
||||||
|
$expectedUrl = $buttons[$expectedClass];
|
||||||
|
$this->assertEquals($expectedUrl, $element->getAttribute('href'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $role the USER role to use for the request
|
* @param string $role the USER role to use for the request
|
||||||
* @param string $url the URL of the page displaying the initial form to submit
|
* @param string $url the URL of the page displaying the initial form to submit
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ class ExportControllerTest extends ControllerBaseTest
|
|||||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||||
|
|
||||||
$begin = new \DateTime('first day of this month');
|
$begin = new \DateTime('first day of this month');
|
||||||
$end = new \DateTime('last day of this month');
|
|
||||||
$fixture = new TimesheetFixtures();
|
$fixture = new TimesheetFixtures();
|
||||||
$fixture
|
$fixture
|
||||||
->setUser($this->getUserByRole($em, User::ROLE_USER))
|
->setUser($this->getUserByRole($em, User::ROLE_USER))
|
||||||
|
|||||||
48
tests/Controller/PluginControllerTest.php
Normal file
48
tests/Controller/PluginControllerTest.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?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\Controller;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Plugin\PluginManager;
|
||||||
|
use App\Tests\Plugin\Fixtures\TestPlugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @coversDefaultClass \App\Controller\PluginController
|
||||||
|
* @group integration
|
||||||
|
*/
|
||||||
|
class PluginControllerTest extends ControllerBaseTest
|
||||||
|
{
|
||||||
|
public function testIsSecure()
|
||||||
|
{
|
||||||
|
$this->assertUrlIsSecured('/admin/plugins/');
|
||||||
|
$this->assertUrlIsSecuredForRole(User::ROLE_ADMIN, '/admin/plugins/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIndexAction()
|
||||||
|
{
|
||||||
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||||
|
$this->assertAccessIsGranted($client, '/admin/plugins/');
|
||||||
|
$this->assertCalloutWidgetWithMessage($client, 'You have no plugin installed yet');
|
||||||
|
$this->assertPageActions($client, ['shop' => 'https://www.kimai.org/store/']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIndexActionWithInstalledPlugins()
|
||||||
|
{
|
||||||
|
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||||
|
|
||||||
|
/** @var PluginManager $manager */
|
||||||
|
$manager = self::$container->get(PluginManager::class);
|
||||||
|
$manager->addPlugin(new TestPlugin());
|
||||||
|
|
||||||
|
$this->assertAccessIsGranted($client, '/admin/plugins/');
|
||||||
|
$this->assertHasDataTable($client);
|
||||||
|
$this->assertDataTableRowCount($client, 'datatable_plugins', 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ namespace App\Tests\Export\Renderer;
|
|||||||
|
|
||||||
use App\Export\Renderer\HtmlRenderer;
|
use App\Export\Renderer\HtmlRenderer;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Twig\Loader\FilesystemLoader;
|
use Twig\Environment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \App\Export\Renderer\HtmlRenderer
|
* @covers \App\Export\Renderer\HtmlRenderer
|
||||||
@@ -22,7 +22,7 @@ class HtmlRendererTest extends AbstractRendererTest
|
|||||||
public function testConfiguration()
|
public function testConfiguration()
|
||||||
{
|
{
|
||||||
$sut = new HtmlRenderer(
|
$sut = new HtmlRenderer(
|
||||||
$this->getMockBuilder(\Twig_Environment::class)->disableOriginalConstructor()->getMock()
|
$this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock()
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals('html', $sut->getId());
|
$this->assertEquals('html', $sut->getId());
|
||||||
@@ -33,16 +33,13 @@ class HtmlRendererTest extends AbstractRendererTest
|
|||||||
public function testRender()
|
public function testRender()
|
||||||
{
|
{
|
||||||
$kernel = self::bootKernel();
|
$kernel = self::bootKernel();
|
||||||
/** @var \Twig_Environment $twig */
|
/** @var Environment $twig */
|
||||||
$twig = $kernel->getContainer()->get('twig');
|
$twig = $kernel->getContainer()->get('twig');
|
||||||
$stack = $kernel->getContainer()->get('request_stack');
|
$stack = $kernel->getContainer()->get('request_stack');
|
||||||
$request = new Request();
|
$request = new Request();
|
||||||
$request->setLocale('en');
|
$request->setLocale('en');
|
||||||
$stack->push($request);
|
$stack->push($request);
|
||||||
|
|
||||||
/** @var FilesystemLoader $loader */
|
|
||||||
$loader = $twig->getLoader();
|
|
||||||
|
|
||||||
$sut = new HtmlRenderer($twig);
|
$sut = new HtmlRenderer($twig);
|
||||||
|
|
||||||
$response = $this->render($sut);
|
$response = $this->render($sut);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use App\Utils\MPdfConverter;
|
|||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||||
use Twig\Loader\FilesystemLoader;
|
use Twig\Environment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \App\Export\Renderer\PDFRenderer
|
* @covers \App\Export\Renderer\PDFRenderer
|
||||||
@@ -45,7 +45,7 @@ class PdfRendererTest extends AbstractRendererTest
|
|||||||
public function testConfiguration()
|
public function testConfiguration()
|
||||||
{
|
{
|
||||||
$sut = new PDFRenderer(
|
$sut = new PDFRenderer(
|
||||||
$this->getMockBuilder(\Twig_Environment::class)->disableOriginalConstructor()->getMock(),
|
$this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock(),
|
||||||
$this->getDateTimeFactory(),
|
$this->getDateTimeFactory(),
|
||||||
$this->getMockBuilder(HtmlToPdfConverter::class)->getMock()
|
$this->getMockBuilder(HtmlToPdfConverter::class)->getMock()
|
||||||
);
|
);
|
||||||
@@ -58,7 +58,7 @@ class PdfRendererTest extends AbstractRendererTest
|
|||||||
public function testRender()
|
public function testRender()
|
||||||
{
|
{
|
||||||
$kernel = self::bootKernel();
|
$kernel = self::bootKernel();
|
||||||
/** @var \Twig_Environment $twig */
|
/** @var Environment $twig */
|
||||||
$twig = $kernel->getContainer()->get('twig');
|
$twig = $kernel->getContainer()->get('twig');
|
||||||
$stack = $kernel->getContainer()->get('request_stack');
|
$stack = $kernel->getContainer()->get('request_stack');
|
||||||
$cacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir');
|
$cacheDir = $kernel->getContainer()->getParameter('kernel.cache_dir');
|
||||||
@@ -67,9 +67,6 @@ class PdfRendererTest extends AbstractRendererTest
|
|||||||
$request->setLocale('en');
|
$request->setLocale('en');
|
||||||
$stack->push($request);
|
$stack->push($request);
|
||||||
|
|
||||||
/** @var FilesystemLoader $loader */
|
|
||||||
$loader = $twig->getLoader();
|
|
||||||
|
|
||||||
$sut = new PDFRenderer($twig, $this->getDateTimeFactory(), $converter);
|
$sut = new PDFRenderer($twig, $this->getDateTimeFactory(), $converter);
|
||||||
|
|
||||||
$response = $this->render($sut);
|
$response = $this->render($sut);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace App\Tests\Export;
|
|||||||
use App\Export\Renderer\HtmlRenderer;
|
use App\Export\Renderer\HtmlRenderer;
|
||||||
use App\Export\ServiceExport;
|
use App\Export\ServiceExport;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \App\Export\ServiceExport
|
* @covers \App\Export\ServiceExport
|
||||||
@@ -20,22 +21,22 @@ class ServiceExportTest extends TestCase
|
|||||||
{
|
{
|
||||||
public function testEmptyObject()
|
public function testEmptyObject()
|
||||||
{
|
{
|
||||||
$sut = new ServiceExport();
|
$sut = new ServiceExport([]);
|
||||||
$this->assertEmpty($sut->getRenderer());
|
$this->assertEmpty($sut->getRenderer());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUnknownRendererReturnsNull()
|
public function testUnknownRendererReturnsNull()
|
||||||
{
|
{
|
||||||
$sut = new ServiceExport();
|
$sut = new ServiceExport([]);
|
||||||
$this->assertNull($sut->getRendererById('default'));
|
$this->assertNull($sut->getRendererById('default'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAdd()
|
public function testAdd()
|
||||||
{
|
{
|
||||||
$sut = new ServiceExport();
|
$sut = new ServiceExport([]);
|
||||||
|
|
||||||
$sut->addRenderer(new HtmlRenderer(
|
$sut->addRenderer(new HtmlRenderer(
|
||||||
$this->getMockBuilder(\Twig_Environment::class)->disableOriginalConstructor()->getMock()
|
$this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock()
|
||||||
));
|
));
|
||||||
|
|
||||||
$this->assertEquals(1, count($sut->getRenderer()));
|
$this->assertEquals(1, count($sut->getRenderer()));
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace App\Tests\Invoice\Renderer;
|
|||||||
|
|
||||||
use App\Invoice\Renderer\TwigRenderer;
|
use App\Invoice\Renderer\TwigRenderer;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Twig\Environment;
|
||||||
use Twig\Loader\FilesystemLoader;
|
use Twig\Loader\FilesystemLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,7 +22,7 @@ class TwigRendererTest extends AbstractRendererTest
|
|||||||
public function testSupports()
|
public function testSupports()
|
||||||
{
|
{
|
||||||
$loader = new FilesystemLoader();
|
$loader = new FilesystemLoader();
|
||||||
$env = new \Twig_Environment($loader);
|
$env = new Environment($loader);
|
||||||
$sut = new TwigRenderer($env);
|
$sut = new TwigRenderer($env);
|
||||||
|
|
||||||
$this->assertTrue($sut->supports($this->getInvoiceDocument('default.html.twig')));
|
$this->assertTrue($sut->supports($this->getInvoiceDocument('default.html.twig')));
|
||||||
@@ -37,7 +38,7 @@ class TwigRendererTest extends AbstractRendererTest
|
|||||||
public function testRender()
|
public function testRender()
|
||||||
{
|
{
|
||||||
$kernel = self::bootKernel();
|
$kernel = self::bootKernel();
|
||||||
/** @var \Twig_Environment $twig */
|
/** @var Environment $twig */
|
||||||
$twig = $kernel->getContainer()->get('twig');
|
$twig = $kernel->getContainer()->get('twig');
|
||||||
$stack = $kernel->getContainer()->get('request_stack');
|
$stack = $kernel->getContainer()->get('request_stack');
|
||||||
$request = new Request();
|
$request = new Request();
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use App\Invoice\Renderer\TwigRenderer;
|
|||||||
use App\Invoice\ServiceInvoice;
|
use App\Invoice\ServiceInvoice;
|
||||||
use App\Repository\InvoiceDocumentRepository;
|
use App\Repository\InvoiceDocumentRepository;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \App\Invoice\ServiceInvoice
|
* @covers \App\Invoice\ServiceInvoice
|
||||||
@@ -65,7 +66,7 @@ class ServiceInvoiceTest extends TestCase
|
|||||||
$sut->addNumberGenerator(new DateNumberGenerator());
|
$sut->addNumberGenerator(new DateNumberGenerator());
|
||||||
$sut->addRenderer(
|
$sut->addRenderer(
|
||||||
new TwigRenderer(
|
new TwigRenderer(
|
||||||
$this->getMockBuilder(\Twig_Environment::class)->disableOriginalConstructor()->getMock()
|
$this->getMockBuilder(Environment::class)->disableOriginalConstructor()->getMock()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
31
tests/Plugin/Fixtures/TestPlugin.php
Normal file
31
tests/Plugin/Fixtures/TestPlugin.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?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\Plugin\Fixtures;
|
||||||
|
|
||||||
|
use App\Plugin\PluginInterface;
|
||||||
|
|
||||||
|
class TestPlugin implements PluginInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return 'TestPlugin';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPath()
|
||||||
|
{
|
||||||
|
return __DIR__;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
tests/Plugin/Fixtures/composer.json
Normal file
29
tests/Plugin/Fixtures/composer.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "kimai/test-plugin-composer",
|
||||||
|
"description": "Just a test fixture for the PluginManager",
|
||||||
|
"homepage": "https://github.com/kevinpapst/kimai2",
|
||||||
|
"type": "kimai-plugin",
|
||||||
|
"require": {
|
||||||
|
"kimai/kimai2-composer": "*",
|
||||||
|
"kevinpapst/kimai2": "*"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"kimai",
|
||||||
|
"kimai-plugin"
|
||||||
|
],
|
||||||
|
"license": "proprietary",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Kevin Papst",
|
||||||
|
"homepage": "https://www.kevinpapst.de"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extra": {
|
||||||
|
"kimai": {
|
||||||
|
"require": "0.9",
|
||||||
|
"version": "1.0",
|
||||||
|
"name": "TestPlugin from composer.json",
|
||||||
|
"license": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
78
tests/Plugin/PluginManagerTest.php
Normal file
78
tests/Plugin/PluginManagerTest.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?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\Plugin;
|
||||||
|
|
||||||
|
use App\Plugin\Plugin;
|
||||||
|
use App\Plugin\PluginInterface;
|
||||||
|
use App\Plugin\PluginManager;
|
||||||
|
use App\Plugin\PluginMetadata;
|
||||||
|
use App\Tests\Plugin\Fixtures\TestPlugin;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\Plugin\PluginManager
|
||||||
|
*/
|
||||||
|
class PluginManagerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testEmptyObject()
|
||||||
|
{
|
||||||
|
$sut = new PluginManager([]);
|
||||||
|
$this->assertEmpty($sut->getPlugins());
|
||||||
|
$this->assertNull($sut->getPlugin('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnknownRendererReturnsNull()
|
||||||
|
{
|
||||||
|
$sut = new PluginManager([]);
|
||||||
|
$this->assertNull($sut->getPlugin('foo'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAdd()
|
||||||
|
{
|
||||||
|
$sut = new PluginManager([]);
|
||||||
|
|
||||||
|
$plugin = $this->getMockBuilder(PluginInterface::class)
|
||||||
|
->setMethods(['getName', 'getPath'])
|
||||||
|
->getMock();
|
||||||
|
|
||||||
|
$plugin->method('getName')->willReturn('foo');
|
||||||
|
$plugin->method('getPath')->willReturn('bar');
|
||||||
|
|
||||||
|
$sut->addPlugin(new TestPlugin());
|
||||||
|
$sut->addPlugin($plugin);
|
||||||
|
$sut->addPlugin(new TestPlugin());
|
||||||
|
|
||||||
|
// make sure a plugin with the same name is not added twice, the first one wins!
|
||||||
|
$this->assertEquals(2, count($sut->getPlugins()));
|
||||||
|
|
||||||
|
$foo = $sut->getPlugin('foo');
|
||||||
|
$this->assertInstanceOf(Plugin::class, $foo);
|
||||||
|
$this->assertEquals('foo', $foo->getName());
|
||||||
|
$this->assertEquals('bar', $foo->getPath());
|
||||||
|
|
||||||
|
$test = $sut->getPlugin('TestPlugin');
|
||||||
|
$this->assertInstanceOf(Plugin::class, $test);
|
||||||
|
$this->assertEquals('TestPlugin', $test->getName());
|
||||||
|
$this->assertEquals(new PluginMetadata(), $test->getMetadata());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoadMetadata()
|
||||||
|
{
|
||||||
|
$sut = new PluginManager([new TestPlugin()]);
|
||||||
|
$plugin = $sut->getPlugin('TestPlugin');
|
||||||
|
$sut->loadMetadata($plugin);
|
||||||
|
|
||||||
|
$meta = $plugin->getMetadata();
|
||||||
|
$this->assertEquals('0.9', $meta->getKimaiVersion());
|
||||||
|
$this->assertEquals('1.0', $meta->getVersion());
|
||||||
|
$this->assertEquals('Just a test fixture for the PluginManager', $meta->getDescription());
|
||||||
|
$this->assertEquals('https://github.com/kevinpapst/kimai2', $meta->getHomepage());
|
||||||
|
}
|
||||||
|
}
|
||||||
42
tests/Plugin/PluginMetadataTest.php
Normal file
42
tests/Plugin/PluginMetadataTest.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?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\Plugin;
|
||||||
|
|
||||||
|
use App\Plugin\PluginMetadata;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\Plugin\PluginMetadata
|
||||||
|
*/
|
||||||
|
class PluginMetadataTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testEmptyObject()
|
||||||
|
{
|
||||||
|
$sut = new PluginMetadata();
|
||||||
|
$this->assertNull($sut->getDescription());
|
||||||
|
$this->assertNull($sut->getHomepage());
|
||||||
|
$this->assertNull($sut->getVersion());
|
||||||
|
$this->assertNull($sut->getKimaiVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetterAndSetter()
|
||||||
|
{
|
||||||
|
$sut = new PluginMetadata();
|
||||||
|
$this->assertInstanceOf(PluginMetadata::class, $sut->setVersion('13.7'));
|
||||||
|
$this->assertInstanceOf(PluginMetadata::class, $sut->setHomepage('http://www.example.com'));
|
||||||
|
$this->assertInstanceOf(PluginMetadata::class, $sut->setDescription('foo bar'));
|
||||||
|
$this->assertInstanceOf(PluginMetadata::class, $sut->setKimaiVersion('1.0'));
|
||||||
|
|
||||||
|
$this->assertEquals('13.7', $sut->getVersion());
|
||||||
|
$this->assertEquals('http://www.example.com', $sut->getHomepage());
|
||||||
|
$this->assertEquals('foo bar', $sut->getDescription());
|
||||||
|
$this->assertEquals('1.0', $sut->getKimaiVersion());
|
||||||
|
}
|
||||||
|
}
|
||||||
48
tests/Plugin/PluginTest.php
Normal file
48
tests/Plugin/PluginTest.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?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\Plugin;
|
||||||
|
|
||||||
|
use App\Plugin\Plugin;
|
||||||
|
use App\Plugin\PluginMetadata;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\Plugin\Plugin
|
||||||
|
*/
|
||||||
|
class PluginTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testEmptyObject()
|
||||||
|
{
|
||||||
|
$plugin = new Plugin();
|
||||||
|
$this->assertNull($plugin->getName());
|
||||||
|
$this->assertNull($plugin->getPath());
|
||||||
|
$this->assertNull($plugin->getMetadata());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetterAndSetter()
|
||||||
|
{
|
||||||
|
$metadata = new PluginMetadata();
|
||||||
|
$metadata
|
||||||
|
->setDescription('foo')
|
||||||
|
->setHomepage('http://www.example.com')
|
||||||
|
->setVersion('13.7')
|
||||||
|
->setKimaiVersion('1.1')
|
||||||
|
;
|
||||||
|
|
||||||
|
$plugin = new Plugin();
|
||||||
|
$this->assertInstanceOf(Plugin::class, $plugin->setName('foo'));
|
||||||
|
$this->assertInstanceOf(Plugin::class, $plugin->setPath('bar'));
|
||||||
|
$this->assertInstanceOf(Plugin::class, $plugin->setMetadata($metadata));
|
||||||
|
|
||||||
|
$this->assertEquals('foo', $plugin->getName());
|
||||||
|
$this->assertEquals('bar', $plugin->getPath());
|
||||||
|
$this->assertSame($metadata, $plugin->getMetadata());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ use Symfony\Component\HttpFoundation\Request;
|
|||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\Intl\Util\IntlTestHelper;
|
use Symfony\Component\Intl\Util\IntlTestHelper;
|
||||||
use Twig\TwigFilter;
|
use Twig\TwigFilter;
|
||||||
|
use Twig\TwigFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \App\Twig\Extensions
|
* @covers \App\Twig\Extensions
|
||||||
@@ -68,7 +69,7 @@ class ExtensionsTest extends TestCase
|
|||||||
$this->assertCount(count($functions), $twigFunctions);
|
$this->assertCount(count($functions), $twigFunctions);
|
||||||
$i = 0;
|
$i = 0;
|
||||||
foreach ($twigFunctions as $filter) {
|
foreach ($twigFunctions as $filter) {
|
||||||
$this->assertInstanceOf(\Twig_SimpleFunction::class, $filter);
|
$this->assertInstanceOf(TwigFunction::class, $filter);
|
||||||
$this->assertEquals($functions[$i++], $filter->getName());
|
$this->assertEquals($functions[$i++], $filter->getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,14 @@
|
|||||||
<source>about.subtitle</source>
|
<source>about.subtitle</source>
|
||||||
<target>System Informationen</target>
|
<target>System Informationen</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="tab.system">
|
||||||
|
<source>tab.system</source>
|
||||||
|
<target>System Info</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="tab.license">
|
||||||
|
<source>tab.license</source>
|
||||||
|
<target>Kimai Lizenz</target>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
@@ -10,6 +10,14 @@
|
|||||||
<source>about.subtitle</source>
|
<source>about.subtitle</source>
|
||||||
<target>System information</target>
|
<target>System information</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="tab.system">
|
||||||
|
<source>tab.system</source>
|
||||||
|
<target>System Info</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="tab.license">
|
||||||
|
<source>tab.license</source>
|
||||||
|
<target>Kimai License</target>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
@@ -85,6 +85,10 @@
|
|||||||
<source>menu.admin</source>
|
<source>menu.admin</source>
|
||||||
<target>Administration</target>
|
<target>Administration</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="menu.system">
|
||||||
|
<source>menu.system</source>
|
||||||
|
<target>System</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="menu.logout">
|
<trans-unit id="menu.logout">
|
||||||
<source>menu.logout</source>
|
<source>menu.logout</source>
|
||||||
<target>Abmelden</target>
|
<target>Abmelden</target>
|
||||||
@@ -121,6 +125,10 @@
|
|||||||
<source>menu.admin_user</source>
|
<source>menu.admin_user</source>
|
||||||
<target>Benutzer</target>
|
<target>Benutzer</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="menu.plugin">
|
||||||
|
<source>menu.plugin</source>
|
||||||
|
<target>Erweiterungen</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="menu.system_configuration">
|
<trans-unit id="menu.system_configuration">
|
||||||
<source>menu.system_configuration</source>
|
<source>menu.system_configuration</source>
|
||||||
<target>Settings</target>
|
<target>Settings</target>
|
||||||
|
|||||||
@@ -85,6 +85,10 @@
|
|||||||
<source>menu.admin</source>
|
<source>menu.admin</source>
|
||||||
<target>Administration</target>
|
<target>Administration</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="menu.system">
|
||||||
|
<source>menu.system</source>
|
||||||
|
<target>System</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="menu.logout">
|
<trans-unit id="menu.logout">
|
||||||
<source>menu.logout</source>
|
<source>menu.logout</source>
|
||||||
<target>Logout</target>
|
<target>Logout</target>
|
||||||
@@ -121,6 +125,10 @@
|
|||||||
<source>menu.admin_user</source>
|
<source>menu.admin_user</source>
|
||||||
<target>User</target>
|
<target>User</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="menu.plugin">
|
||||||
|
<source>menu.plugin</source>
|
||||||
|
<target>Plugins</target>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="menu.system_configuration">
|
<trans-unit id="menu.system_configuration">
|
||||||
<source>menu.system_configuration</source>
|
<source>menu.system_configuration</source>
|
||||||
<target>System configuration</target>
|
<target>System configuration</target>
|
||||||
|
|||||||
31
translations/plugins.de.xliff
Normal file
31
translations/plugins.de.xliff
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<file date="2018-10-29T10:00:00Z" source-language="en" target-language="de" datatype="plaintext" original="plugins.en.xliff">
|
||||||
|
<body>
|
||||||
|
<trans-unit id="plugins.title">
|
||||||
|
<source>plugins.title</source>
|
||||||
|
<target>Plugins</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="plugins.subtitle">
|
||||||
|
<source>plugins.subtitle</source>
|
||||||
|
<target>Mit Plugins können Sie den Funktionsumfang von Kimai erweitern</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="label.version">
|
||||||
|
<source>label.version</source>
|
||||||
|
<target>Version</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="label.required_version">
|
||||||
|
<source>label.required_version</source>
|
||||||
|
<target>Kompatibel mit</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="plugin.none_installed">
|
||||||
|
<source>plugin.none_installed</source>
|
||||||
|
<target>Sie haben noch kein Plugin installiert.</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="plugin.marketplace">
|
||||||
|
<source>plugin.marketplace</source>
|
||||||
|
<target>Kimai Marketplace</target>
|
||||||
|
</trans-unit>
|
||||||
|
</body>
|
||||||
|
</file>
|
||||||
|
</xliff>
|
||||||
31
translations/plugins.en.xliff
Normal file
31
translations/plugins.en.xliff
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<file date="2018-10-29T10:00:00Z" source-language="en" target-language="en" datatype="plaintext" original="none">
|
||||||
|
<body>
|
||||||
|
<trans-unit id="plugins.title">
|
||||||
|
<source>plugins.title</source>
|
||||||
|
<target>Plugins</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="plugins.subtitle">
|
||||||
|
<source>plugins.subtitle</source>
|
||||||
|
<target>You can enhance the functionality of Kimai with plugins</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="label.version">
|
||||||
|
<source>label.version</source>
|
||||||
|
<target>Version</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="label.required_version">
|
||||||
|
<source>label.required_version</source>
|
||||||
|
<target>Compatible with</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="plugin.none_installed">
|
||||||
|
<source>plugin.none_installed</source>
|
||||||
|
<target>You have no plugin installed yet.</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="plugin.marketplace">
|
||||||
|
<source>plugin.marketplace</source>
|
||||||
|
<target>Kimai marketplace</target>
|
||||||
|
</trans-unit>
|
||||||
|
</body>
|
||||||
|
</file>
|
||||||
|
</xliff>
|
||||||
@@ -23,15 +23,13 @@ Encore
|
|||||||
// generate only two files: app.js and app.css
|
// generate only two files: app.js and app.css
|
||||||
.addEntry('app', './assets/app.js')
|
.addEntry('app', './assets/app.js')
|
||||||
|
|
||||||
// enable sass/scss parser
|
|
||||||
.enableSassLoader()
|
|
||||||
|
|
||||||
// show OS notifications when builds finish/fail
|
// show OS notifications when builds finish/fail
|
||||||
.enableBuildNotifications()
|
.enableBuildNotifications()
|
||||||
|
|
||||||
// load jquery as Kimai and AdminLTE rely on it
|
// load jquery as Kimai and AdminLTE rely on it
|
||||||
.autoProvidejQuery()
|
.autoProvidejQuery()
|
||||||
|
|
||||||
|
// enable sass/scss parser
|
||||||
// see https://symfony.com/doc/current/frontend/encore/bootstrap.html
|
// see https://symfony.com/doc/current/frontend/encore/bootstrap.html
|
||||||
.enableSassLoader(function(sassOptions) {}, {
|
.enableSassLoader(function(sassOptions) {}, {
|
||||||
resolveUrlLoader: false
|
resolveUrlLoader: false
|
||||||
|
|||||||
Reference in New Issue
Block a user