support theme options via user preferences #89 (#91)

This commit is contained in:
Kevin Papst
2018-01-15 22:58:07 +01:00
committed by GitHub
parent 827b1ae9ad
commit 3f221fcdb8
14 changed files with 289 additions and 113 deletions

View File

@@ -6,7 +6,7 @@
"require": {
"php": "^7.1.3",
"ext-pdo_sqlite": "*",
"avanzu/admin-theme-bundle": "dev-symfony4",
"avanzu/admin-theme-bundle": "dev-kevinpapst",
"beberlei/DoctrineExtensions": "^1.0",
"dama/doctrine-test-bundle": "^4.0",
"sensio/framework-extra-bundle": "^5.1",
@@ -23,6 +23,7 @@
"symfony/security-bundle": "^4.0",
"symfony/security-csrf": "^4.0",
"symfony/swiftmailer-bundle": "^3.1",
"symfony/thanks": "^1.0",
"symfony/translation": "^4.0",
"symfony/twig-bundle": "^4.0",
"symfony/validator": "^4.0",

140
composer.lock generated
View File

@@ -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": "c8bbb84ec7e932d13852eab55f0306e2",
"content-hash": "fcf570204034bd83c84fd7f92fc3fab4",
"packages": [
{
"name": "almasaeed2010/adminlte",
@@ -48,16 +48,16 @@
},
{
"name": "avanzu/admin-theme-bundle",
"version": "dev-symfony4",
"version": "dev-kevinpapst",
"source": {
"type": "git",
"url": "https://github.com/kevinpapst/AdminThemeBundle.git",
"reference": "f007e946f56295f447d0f1b75e5a7cf55145cf7c"
"reference": "1aa9b1637b2f827fe79ca1edff26092840e30716"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kevinpapst/AdminThemeBundle/zipball/f007e946f56295f447d0f1b75e5a7cf55145cf7c",
"reference": "f007e946f56295f447d0f1b75e5a7cf55145cf7c",
"url": "https://api.github.com/repos/kevinpapst/AdminThemeBundle/zipball/1aa9b1637b2f827fe79ca1edff26092840e30716",
"reference": "1aa9b1637b2f827fe79ca1edff26092840e30716",
"shasum": ""
},
"require": {
@@ -109,9 +109,9 @@
],
"description": "Admin Theme based on the AdminLTE Template for easy integration into symfony",
"support": {
"source": "https://github.com/kevinpapst/AdminThemeBundle/tree/symfony4"
"source": "https://github.com/kevinpapst/AdminThemeBundle/tree/kevinpapst"
},
"time": "2018-01-12T20:01:56+00:00"
"time": "2018-01-15T19:14:46+00:00"
},
{
"name": "beberlei/DoctrineExtensions",
@@ -177,16 +177,16 @@
},
{
"name": "dama/doctrine-test-bundle",
"version": "v4.0.1",
"version": "v4.0.2",
"source": {
"type": "git",
"url": "https://github.com/dmaicher/doctrine-test-bundle.git",
"reference": "8567a6ebfc73822f1d105ce852edec1b06748d8f"
"reference": "438346b3380cc7675e37fbcdca912fdc33471d32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/8567a6ebfc73822f1d105ce852edec1b06748d8f",
"reference": "8567a6ebfc73822f1d105ce852edec1b06748d8f",
"url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/438346b3380cc7675e37fbcdca912fdc33471d32",
"reference": "438346b3380cc7675e37fbcdca912fdc33471d32",
"shasum": ""
},
"require": {
@@ -230,7 +230,7 @@
"symfony 2",
"tests"
],
"time": "2017-10-19T11:52:38+00:00"
"time": "2018-01-13T13:14:27+00:00"
},
{
"name": "doctrine/annotations",
@@ -1714,16 +1714,16 @@
},
{
"name": "sensio/framework-extra-bundle",
"version": "v5.1.3",
"version": "v5.1.4",
"source": {
"type": "git",
"url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git",
"reference": "0696496cb3e2d23add645d424699e5c723238aad"
"reference": "072c00c52b947e88a1e619e9ff426cee6c8c482b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/0696496cb3e2d23add645d424699e5c723238aad",
"reference": "0696496cb3e2d23add645d424699e5c723238aad",
"url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/072c00c52b947e88a1e619e9ff426cee6c8c482b",
"reference": "072c00c52b947e88a1e619e9ff426cee6c8c482b",
"shasum": ""
},
"require": {
@@ -1779,7 +1779,7 @@
"annotations",
"controllers"
],
"time": "2017-12-04T18:33:55+00:00"
"time": "2018-01-12T13:08:16+00:00"
},
{
"name": "swiftmailer/swiftmailer",
@@ -2508,16 +2508,16 @@
},
{
"name": "symfony/flex",
"version": "v1.0.60",
"version": "v1.0.61",
"source": {
"type": "git",
"url": "https://github.com/symfony/flex.git",
"reference": "066fe955b1a134841cdcc068a9f843594fdb1adc"
"reference": "f872f8faccf5ff95678e1e428f460a333ba1e5d0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/flex/zipball/066fe955b1a134841cdcc068a9f843594fdb1adc",
"reference": "066fe955b1a134841cdcc068a9f843594fdb1adc",
"url": "https://api.github.com/repos/symfony/flex/zipball/f872f8faccf5ff95678e1e428f460a333ba1e5d0",
"reference": "f872f8faccf5ff95678e1e428f460a333ba1e5d0",
"shasum": ""
},
"require": {
@@ -2550,7 +2550,7 @@
"email": "fabien.potencier@gmail.com"
}
],
"time": "2018-01-10T07:00:13+00:00"
"time": "2018-01-12T13:09:44+00:00"
},
{
"name": "symfony/form",
@@ -3982,6 +3982,49 @@
"homepage": "http://symfony.com",
"time": "2017-10-23T15:15:11+00:00"
},
{
"name": "symfony/thanks",
"version": "v1.0.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/thanks.git",
"reference": "b637913a92664571afd1067fc645ecb30c9cf5b2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/thanks/zipball/b637913a92664571afd1067fc645ecb30c9cf5b2",
"reference": "b637913a92664571afd1067fc645ecb30c9cf5b2",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.1",
"php": "^5.5.9|^7.0.8"
},
"type": "composer-plugin",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
},
"class": "Symfony\\Thanks\\Thanks"
},
"autoload": {
"psr-4": {
"Symfony\\Thanks\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
}
],
"description": "Give thanks (in the form of a GitHub ⭐) to your fellow PHP package maintainers (not limited to Symfony components)!",
"time": "2018-01-03T09:30:52+00:00"
},
{
"name": "symfony/translation",
"version": "v4.0.3",
@@ -5990,16 +6033,16 @@
},
{
"name": "sebastian/comparator",
"version": "2.1.1",
"version": "2.1.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "b11c729f95109b56a0fe9650c6a63a0fcd8c439f"
"reference": "11c07feade1d65453e06df3b3b90171d6d982087"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/b11c729f95109b56a0fe9650c6a63a0fcd8c439f",
"reference": "b11c729f95109b56a0fe9650c6a63a0fcd8c439f",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/11c07feade1d65453e06df3b3b90171d6d982087",
"reference": "11c07feade1d65453e06df3b3b90171d6d982087",
"shasum": ""
},
"require": {
@@ -6050,7 +6093,7 @@
"compare",
"equality"
],
"time": "2017-12-22T14:50:35+00:00"
"time": "2018-01-12T06:34:42+00:00"
},
{
"name": "sebastian/diff",
@@ -6942,49 +6985,6 @@
"homepage": "https://symfony.com",
"time": "2018-01-04T17:20:09+00:00"
},
{
"name": "symfony/thanks",
"version": "v1.0.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/thanks.git",
"reference": "b637913a92664571afd1067fc645ecb30c9cf5b2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/thanks/zipball/b637913a92664571afd1067fc645ecb30c9cf5b2",
"reference": "b637913a92664571afd1067fc645ecb30c9cf5b2",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.1",
"php": "^5.5.9|^7.0.8"
},
"type": "composer-plugin",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
},
"class": "Symfony\\Thanks\\Thanks"
},
"autoload": {
"psr-4": {
"Symfony\\Thanks\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
}
],
"description": "Give thanks (in the form of a GitHub ⭐) to your fellow PHP package maintainers (not limited to Symfony components)!",
"time": "2018-01-03T09:30:52+00:00"
},
{
"name": "symfony/web-server-bundle",
"version": "v4.0.3",

View File

@@ -4,11 +4,6 @@ avanzu_admin_theme:
bower_bin: "/usr/local/bin/bower"
options:
default_avatar: images/default_avatar.png
skin: skin-green-light
fixed_layout: false
boxed_layout: false
collapsed_sidebar: true
mini_sidebar: true
control_sidebar: true
knp_menu:
enable: false

View File

@@ -24,3 +24,8 @@ framework:
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu
#available with Symfony 4.1
#validation:
# possible values: 'loose', 'strict', 'html5'
#email_validation_mode: 'html5'

View File

@@ -1,5 +1,7 @@
$(document).ready(function() {
// $.AdminLTE.pushMenu.expandOnHover();
(function( $ ) {
var pubSub = $({});

View File

@@ -379,7 +379,7 @@ class User implements UserInterface, AdvancedUserInterface
*/
public function isAccountNonExpired()
{
return $this->isEnabled();
return true;
}
/**
@@ -387,7 +387,7 @@ class User implements UserInterface, AdvancedUserInterface
*/
public function isAccountNonLocked()
{
return $this->isEnabled();
return true;
}
/**
@@ -395,7 +395,7 @@ class User implements UserInterface, AdvancedUserInterface
*/
public function isCredentialsNonExpired()
{
return $this->isEnabled();
return true;
}
/**

View File

@@ -3,7 +3,8 @@
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints as Assert;
@@ -59,7 +60,7 @@ class UserPreference
/**
* @var string
*/
protected $type = TextType::class;
protected $type;
/**
* @var Constraint[]
@@ -121,18 +122,25 @@ class UserPreference
}
/**
* @return string
* @return string|int|bool
*/
public function getValue()
{
switch ($this->type) {
case CheckboxType::class:
return (bool) $this->value;
case IntegerType::class:
return (int) $this->value;
}
return $this->value;
}
/**
* @param string $value
* @param string|int|bool $value
* @return UserPreference
*/
public function setValue(string $value): UserPreference
public function setValue($value): UserPreference
{
$this->value = $value;
return $this;

View File

@@ -0,0 +1,111 @@
<?php
/*
* This file is part of the Kimai package.
*
* (c) Kevin Papst <kevin@kevinpapst.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\EventSubscriber;
use App\Entity\User;
use App\Entity\UserPreference;
use Avanzu\AdminThemeBundle\Helper\ContextHelper;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
/**
* @author Kevin Papst <kevin@kevinpapst.de>
*/
class ThemeOptionsSubscriber implements EventSubscriberInterface
{
/**
* @var TokenStorageInterface
*/
protected $storage;
/**
* @var ContextHelper
*/
protected $helper;
/**
* ThemeOptionsSubscriber constructor.
* @param TokenStorageInterface $storage
* @param ContextHelper $helper
*/
public function __construct(TokenStorageInterface $storage, ContextHelper $helper)
{
$this->storage = $storage;
$this->helper = $helper;
}
/**
* @return array
*/
public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER => ['setThemeOptions', 100]
];
}
/**
* @param KernelEvent $event
*/
public function setThemeOptions(KernelEvent $event)
{
if (!$this->canHandleEvent($event)) {
return;
}
/** @var User $user */
$user = $this->storage->getToken()->getUser();
$skin = '';
foreach ($user->getPreferences() as $ref) {
$name = $ref->getName();
if ($name === UserPreference::SKIN) {
$skin = 'skin-' . $ref->getValue();
}
if (strpos($name, 'theme.') !== false) {
$this->helper->setOption(str_replace('theme.', '', $name), $ref->getValue());
}
}
if (empty($skin)) {
$skin = 'skin-green';
}
$this->helper->setOption('skin', $skin);
}
/**
* @param KernelEvent $event
* @return bool
*/
protected function canHandleEvent(KernelEvent $event): bool
{
// Ignore sub-requests
if (!$event->isMasterRequest()) {
return false;
}
// ignore events like the toolbar where we do not have a token
if ($this->storage->getToken() === null) {
return false;
}
/** @var User $user */
$user = $this->storage->getToken()->getUser();
return ($user instanceof User);
}
}

View File

@@ -14,8 +14,10 @@ namespace App\EventSubscriber;
use App\Entity\User;
use App\Entity\UserPreference;
use App\Event\UserPreferenceEvent;
use App\Form\Type\SkinType;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\KernelEvents;
@@ -39,7 +41,7 @@ class UserPreferenceSubscriber implements EventSubscriberInterface
protected $storage;
/**
* PreferenceService constructor.
* UserPreferenceSubscriber constructor.
* @param EventDispatcherInterface $dispatcher
* @param TokenStorageInterface $storage
*/
@@ -55,7 +57,7 @@ class UserPreferenceSubscriber implements EventSubscriberInterface
public static function getSubscribedEvents(): array
{
return [
KernelEvents::CONTROLLER => ['onKernelEvent', -1],
KernelEvents::CONTROLLER => ['loadUserPreferences', 200]
];
}
@@ -70,11 +72,33 @@ class UserPreferenceSubscriber implements EventSubscriberInterface
->setValue(0)
->setType(IntegerType::class)
->addConstraint(new Range(['min' => 0])),
/*
(new UserPreference())
->setName(UserPreference::SKIN)
->setValue('blue')
->setValue('green')
->setType(SkinType::class),
(new UserPreference())
->setName('theme.fixed_layout')
->setValue(true)
->setType(CheckboxType::class),
(new UserPreference())
->setName('theme.boxed_layout')
->setValue(false)
->setType(CheckboxType::class),
(new UserPreference())
->setName('theme.collapsed_sidebar')
->setValue(false)
->setType(CheckboxType::class),
(new UserPreference())
->setName('theme.mini_sidebar')
->setValue(true)
->setType(CheckboxType::class),
/*
(new UserPreference())
->setName('language')
->setValue('de')
@@ -84,11 +108,17 @@ class UserPreferenceSubscriber implements EventSubscriberInterface
}
/**
* @param User $user
* @return User
* @param KernelEvent $event
*/
public function setupUserPreferences(User $user)
public function loadUserPreferences(KernelEvent $event)
{
if (!$this->canHandleEvent($event)) {
return;
}
/** @var User $user */
$user = $this->storage->getToken()->getUser();
$prefs = [];
foreach ($user->getPreferences() as $preference) {
$prefs[$preference->getName()] = $preference;
@@ -110,32 +140,27 @@ class UserPreferenceSubscriber implements EventSubscriberInterface
}
$user->setPreferences(array_values($prefs));
return $user;
}
/**
* @param KernelEvent $event
* @return bool
*/
public function onKernelEvent(KernelEvent $event): void
protected function canHandleEvent(KernelEvent $event): bool
{
// Ignore sub-requests
if (!$event->isMasterRequest()) {
return;
return false;
}
// ignore events like the toolbar where we do not have a token
if ($this->storage->getToken() === null) {
return;
return false;
}
/** @var User $user */
$user = $this->storage->getToken()->getUser();
if (!($user instanceof User)) {
return;
}
$this->setupUserPreferences($user);
return ($user instanceof User);
}
}

View File

@@ -13,6 +13,7 @@ namespace App\Form\Type;
use App\Entity\UserPreference;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
@@ -45,9 +46,15 @@ class UserPreferenceType extends AbstractType
return;
}
$required = true;
if ($preference->getType() == CheckboxType::class) {
$required = false;
}
$event->getForm()->add('value', $preference->getType(), [
'label' => 'label.' . $preference->getName(),
'constraints' => $preference->getConstraints()
'constraints' => $preference->getConstraints(),
'required' => $required,
]);
}
}

View File

@@ -40,17 +40,6 @@ class UserRepository extends AbstractRepository
return $stats;
}
/**
* Fetch a user by his username.
*
* @param $username
* @return null|User
*/
public function findByUsername($username)
{
return $this->findOneBy(['username' => $username]);
}
/**
* @param UserQuery $query
* @return \Pagerfanta\Pagerfanta

View File

@@ -75,6 +75,7 @@
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<script src="{{ asset('theme/bootstrap/js/bootstrap.min.js') }}"></script>
<script src="{{ asset('js/kimai.js') }}"></script>
<script src="{{ asset('theme/plugins/slimScroll/jquery.slimscroll.min.js') }}"></script>
<script src="{{ asset('js/Chart.min.js') }}"></script>
{% block javascript_imports %}{% endblock %}
{% endblock %}

View File

@@ -377,6 +377,22 @@
<source>profile.preferences</source>
<target>Einstellungen</target>
</trans-unit>
<trans-unit id="label.theme.fixed_layout">
<source>label.theme.fixed_layout</source>
<target>Aktiviert das "statische Layout" (linke Navigationsleiste und Header bleiben beim Scrollen an ihrer Stelle)</target>
</trans-unit>
<trans-unit id="label.theme.boxed_layout">
<source>label.theme.boxed_layout</source>
<target>Aktiviert das "Boxlayout" (gut für große Bildschirme, lässt sich aber nicht mit dem "statischen Layout" gemeinsam verwenden)</target>
</trans-unit>
<trans-unit id="label.theme.collapsed_sidebar">
<source>label.theme.collapsed_sidebar</source>
<target>Steuert den Status der linken Navigationsleiste (zugeklappt oder geöffnet)</target>
</trans-unit>
<trans-unit id="label.theme.mini_sidebar">
<source>label.theme.mini_sidebar</source>
<target>Aktiviert die minimierte Version der linken Navigationsleiste</target>
</trans-unit>
<!--
Admin: Timesheet

View File

@@ -379,6 +379,22 @@
<source>profile.preferences</source>
<target>Preferences</target>
</trans-unit>
<trans-unit id="label.theme.fixed_layout">
<source>label.theme.fixed_layout</source>
<target>Activate the fixed layout. You can't use fixed and boxed layouts together.</target>
</trans-unit>
<trans-unit id="label.theme.boxed_layout">
<source>label.theme.boxed_layout</source>
<target>Activate the boxed layout (nicer on large screens)</target>
</trans-unit>
<trans-unit id="label.theme.collapsed_sidebar">
<source>label.theme.collapsed_sidebar</source>
<target>Toggle the left sidebar's state on page load (open or collapse)</target>
</trans-unit>
<trans-unit id="label.theme.mini_sidebar">
<source>label.theme.mini_sidebar</source>
<target>Toggle the size of the left sidebar's header (full or collapse)</target>
</trans-unit>
<!--
Admin: Timesheet