diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 56bd9ae4..22829bd2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,8 +6,9 @@ - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) -## Checklist: +## Checklist - [ ] My code follows the code style -- [ ] All methods have a docheader with type declarations -- [ ] I have updated the documentation accordingly. -- [ ] I have added tests to cover my changes. +- [ ] All files have a license header +- [ ] All methods have a doc header with type declarations +- [ ] I have updated the documentation accordingly +- [ ] I have added tests to cover my changes diff --git a/.gitignore b/.gitignore index 8148e472..42e80fe6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ /bin/* !bin/console -/var/data/*.sqlite +/var/data/kimai.sqlite /var/cache/* !var/cache/.gitkeep diff --git a/composer.json b/composer.json index bdf733f9..d8338c6b 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "avanzu/admin-theme-bundle": "dev-kevinpapst", "beberlei/DoctrineExtensions": "^1.0", "dama/doctrine-test-bundle": "^4.0", + "erusev/parsedown": "^1.6", "sensio/framework-extra-bundle": "^5.1", "symfony/asset": "^4.0", "symfony/console": "^4.0", @@ -38,6 +39,7 @@ "phpunit/phpunit": "^6.5", "squizlabs/php_codesniffer": "^3.2", "symfony/browser-kit": "^4.0", + "symfony/css-selector": "^4.0", "symfony/debug-pack": "^1.0", "symfony/dotenv": "^4.0", "symfony/maker-bundle": "^1.0", diff --git a/composer.lock b/composer.lock index 6626f3ae..1d250b45 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "53194682429edad5d07d635bbb0cb5e0", + "content-hash": "e61ba50d155aed4f2355ddcfa87b6448", "packages": [ { "name": "almasaeed2010/adminlte", @@ -1207,6 +1207,51 @@ ], "time": "2017-11-15T23:40:40+00:00" }, + { + "name": "erusev/parsedown", + "version": "1.6.4", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/fbe3fe878f4fe69048bb8a52783a09802004f548", + "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "time": "2017-11-14T20:44:03+00:00" + }, { "name": "jdorn/sql-formatter", "version": "v1.2.17", @@ -6681,6 +6726,59 @@ "homepage": "https://symfony.com", "time": "2018-01-03T07:38:00+00:00" }, + { + "name": "symfony/css-selector", + "version": "v4.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "f97600434e3141ef3cbb9ea42cf500fba88022b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/f97600434e3141ef3cbb9ea42cf500fba88022b7", + "reference": "f97600434e3141ef3cbb9ea42cf500fba88022b7", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2018-01-03T07:38:00+00:00" + }, { "name": "symfony/debug-bundle", "version": "v4.0.3", diff --git a/config/packages/test/security.yaml b/config/packages/test/security.yaml new file mode 100644 index 00000000..5c7438f3 --- /dev/null +++ b/config/packages/test/security.yaml @@ -0,0 +1,11 @@ +# this configuration simplifies testing URLs protected by the security mechanism +# See https://symfony.com/doc/current/cookbook/testing/http_authentication.html +security: + encoders: + # to make tests much faster, BCrypt cost is changed to its minimum allowed value (4) + # See https://symfony.com/doc/current/reference/configuration/security.html#using-the-bcrypt-password-encoder + App\Entity\User: { algorithm: bcrypt, cost: 4 } + + firewalls: + secured_area: + http_basic: ~ diff --git a/src/Constants.php b/src/Constants.php new file mode 100644 index 00000000..a4f604f1 --- /dev/null +++ b/src/Constants.php @@ -0,0 +1,25 @@ +projectDirectory = $projectDirectory; + } + + /** + * @Route("/", defaults={"chapter": "README"}, name="help") + * @Route("/{chapter}", requirements={"chapter": "[a-zA-Z]*"}, name="help_chapter") + * @Method("GET") + * + * @param string $chapter + * @return \Symfony\Component\HttpFoundation\Response + */ + public function indexAction(?string $chapter) + { + $breadcrumb = [self::README]; + if ($chapter !== self::README) { + $breadcrumb[] = $chapter; + } + + $chapterFile = $this->getFilenameForChapter($chapter); + + if (!file_exists($chapterFile)) { + throw $this->createNotFoundException('Documentation chapter not found: ' . $chapter); + } + + $content = file_get_contents($chapterFile); + + return $this->render('help/index.html.twig', [ + 'breadcrumb' => $breadcrumb, + 'chapter' => $chapter, + 'documentation' => $content, + 'github' => Constants::GITHUB + ]); + } + + /** + * @param string $chapter + * @return string + */ + protected function getFilenameForChapter(string $chapter) + { + return $this->projectDirectory . DIRECTORY_SEPARATOR . self::DOCS_DIR . $chapter . '.md'; + } +} diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index e7f03678..e4b7f2da 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -27,6 +27,10 @@ class AppFixtures extends Fixture use FixturesTrait; const DEFAULT_PASSWORD = 'kitten'; + const USERNAME_USER = 'john_user'; + const USERNAME_TEAMLEAD = 'tony_teamlead'; + const USERNAME_ADMIN = 'anna_admin'; + const USERNAME_SUPER_ADMIN = 'susan_super'; /** * @var UserPasswordEncoderInterface @@ -93,7 +97,7 @@ class AppFixtures extends Fixture 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=monsterid&f=y', true ], [ - 'John Doe', 'Developer', 'john_user', 'john_user@example.com', 'ROLE_USER', + 'John Doe', 'Developer', self::USERNAME_USER, 'john_user@example.com', 'ROLE_USER', 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', true ], // inactive user to test login @@ -102,16 +106,16 @@ class AppFixtures extends Fixture 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', false ], [ - 'Tony Maier', 'Head of Development', 'tony_teamlead', 'tony_teamlead@example.com', 'ROLE_TEAMLEAD', + 'Tony Maier', 'Head of Development', self::USERNAME_TEAMLEAD, 'tony_teamlead@example.com', 'ROLE_TEAMLEAD', 'https://en.gravatar.com/userimage/3533186/bf2163b1dd23f3107a028af0195624e9.jpeg', true ], // no avatar to test default image macro [ - 'Anna Smith', 'Administrator', 'anna_admin', 'anna_admin@example.com', 'ROLE_ADMIN', null, true + 'Anna Smith', 'Administrator', self::USERNAME_ADMIN, 'anna_admin@example.com', 'ROLE_ADMIN', null, true ], // no alias to test twig username macro [ - null, 'Super Administrator', 'susan_super', 'susan_super@example.com', 'ROLE_SUPER_ADMIN', + null, 'Super Administrator', self::USERNAME_SUPER_ADMIN, 'susan_super@example.com', 'ROLE_SUPER_ADMIN', '/bundles/avanzuadmintheme/img/avatar.png', true ] ]; diff --git a/src/Twig/Extensions.php b/src/Twig/Extensions.php index 41d12c78..3724a0e2 100644 --- a/src/Twig/Extensions.php +++ b/src/Twig/Extensions.php @@ -11,6 +11,7 @@ namespace App\Twig; use Symfony\Component\Intl\Intl; use App\Entity\Timesheet; +use Twig\TwigFilter; /** * Multiple Twig extensions: filters and functions @@ -37,11 +38,11 @@ class Extensions extends \Twig_Extension public function getFilters() { return [ - new \Twig_SimpleFilter('duration', array($this, 'duration')), - new \Twig_SimpleFilter('durationForEntry', array($this, 'durationForEntry')), - new \Twig_SimpleFilter('money', array($this, 'money')), - new \Twig_SimpleFilter('currency', array($this, 'currency')), - new \Twig_SimpleFilter('country', array($this, 'country')), + new TwigFilter('duration', [$this, 'duration']), + new TwigFilter('durationForEntry', [$this, 'durationForEntry']), + new TwigFilter('money', [$this, 'money']), + new TwigFilter('currency', [$this, 'currency']), + new TwigFilter('country', [$this, 'country']), ]; } @@ -140,12 +141,4 @@ class Extensions extends \Twig_Extension return $locales; } - - /** - * {@inheritdoc} - */ - public function getName() - { - return 'kimai.extension'; - } } diff --git a/src/Twig/MarkdownExtension.php b/src/Twig/MarkdownExtension.php new file mode 100644 index 00000000..ae7377b1 --- /dev/null +++ b/src/Twig/MarkdownExtension.php @@ -0,0 +1,54 @@ +markdown = $parser; + } + + /** + * @return TwigFilter[] + */ + public function getFilters() + { + return [ + new TwigFilter('md2html', [$this, 'markdownToHtml'], ['is_safe' => ['html']]), + ]; + } + + /** + * Transforms the given Markdown content into HTML + * + * @param string $content + * @return string + */ + public function markdownToHtml(string $content): string + { + return $this->markdown->toHtml($content); + } +} diff --git a/src/Utils/Markdown.php b/src/Utils/Markdown.php new file mode 100644 index 00000000..f63b66e6 --- /dev/null +++ b/src/Utils/Markdown.php @@ -0,0 +1,41 @@ +parser = new MarkdownParser(); + } + + /** + * @param string $text + * @return string + */ + public function toHtml(string $text): string + { + return $this->parser->text($text); + } +} diff --git a/symfony.lock b/symfony.lock index 3f25db83..b379c717 100644 --- a/symfony.lock +++ b/symfony.lock @@ -101,6 +101,9 @@ "egulias/email-validator": { "version": "2.1.3" }, + "erusev/parsedown": { + "version": "1.6.4" + }, "friendsofphp/php-cs-fixer": { "version": "2.2", "recipe": { @@ -260,6 +263,9 @@ "ref": "9f94d3ea453cd8a3b95db7f82592d7344fe3a76a" } }, + "symfony/css-selector": { + "version": "v4.0.4" + }, "symfony/debug": { "version": "v4.0.3" }, diff --git a/templates/base.html.twig b/templates/base.html.twig index 4e0c2aaf..94e6b952 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -41,13 +41,6 @@ {% block avanzu_footer %} {% block footer %} - {% endblock %} {% endblock %} diff --git a/templates/help/index.html.twig b/templates/help/index.html.twig new file mode 100644 index 00000000..a8b9f6d1 --- /dev/null +++ b/templates/help/index.html.twig @@ -0,0 +1,26 @@ +{% extends 'base.html.twig' %} + +{% block page_title %}{{ 'help.title'|trans }}{% endblock %} +{% block page_subtitle %}{{ 'help.subtitle'|trans }}{% endblock %} + +{% block main %} + + {% set replacer = { + '.md"': '"', + '': '
', + 'href="../../': 'target="_blank" href="'~github~'blob/master/', + ('href="' ~ github): 'target="_blank" href="'~github + } %} + +
+ {% if chapter != 'README' %} +
+ {{ 'action.back'|trans }} +
+ {% endif %} +
+ {{ documentation|md2html|replace(replacer)|raw }} +
+
+ +{% endblock %} diff --git a/templates/sidebar/home.html.twig b/templates/sidebar/home.html.twig index ae5c4a10..4545be17 100644 --- a/templates/sidebar/home.html.twig +++ b/templates/sidebar/home.html.twig @@ -1,11 +1,24 @@

{{ 'home.title'|trans({}, 'sidebar') }}

{{ 'home.betainfo'|trans({}, 'sidebar')|raw|nl2br }} - +
+ +

{{ 'help.title'|trans({}, 'sidebar') }}

+
+ {{ 'help.link'|trans({}, 'sidebar') }} +
+ +

{{ 'about.title'|trans({}, 'sidebar') }}

+
+

+ Kimai {{ constant('App\\Constants::VERSION') }} +

+

+ {{ 'made.by.license'|trans({'%kevin%': 'Kevin Papst', '%simon%': 'Simon Schaufelberger'}, 'sidebar')|raw }} +

+

+ {{ 'github.link'|trans({'%url%': constant('App\\Constants::GITHUB')}, 'sidebar')|raw }} +

{#