added start and stop entries
enhanced administration of activities/projects/customers added navbar for active records and recent activities added edit timesheet entry form added translation packages for errors and flashmessages upgraded composer packages
This commit is contained in:
113
src/AppBundle/Controller/AbstractController.php
Normal file
113
src/AppBundle/Controller/AbstractController.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?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 AppBundle\Controller;
|
||||
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
|
||||
/**
|
||||
* The abstract base controller.
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
abstract class AbstractController extends Controller
|
||||
{
|
||||
const FLASH_SUCCESS = 'success';
|
||||
const FLASH_WARNING = 'warning';
|
||||
const FLASH_ERROR = 'error';
|
||||
|
||||
const DOMAIN_FLASH = 'flashmessages';
|
||||
const DOMAIN_ERROR = 'exceptions';
|
||||
|
||||
const ROLE_ADMIN = 'ROLE_ADMIN';
|
||||
|
||||
/**
|
||||
* @return object|\Symfony\Component\Translation\DataCollectorTranslator|\Symfony\Component\Translation\IdentityTranslator
|
||||
*/
|
||||
protected function getTranslator()
|
||||
{
|
||||
return $this->container->get('translator');
|
||||
}
|
||||
|
||||
/**
|
||||
* A translated helper for denyAccessUnlessGranted()
|
||||
*
|
||||
* @param $attributes
|
||||
* @param null $subject
|
||||
* @param string $translationKey
|
||||
* @param array $parameter
|
||||
* @throws AccessDeniedException
|
||||
*/
|
||||
protected function denyUnlessGranted($attributes, $subject = null, $translationKey = 'access.denied', $parameter = [])
|
||||
{
|
||||
$error = $this->getTranslator()->trans($translationKey, $parameter, self::DOMAIN_ERROR);
|
||||
// TODO try & catch and add to audit log?
|
||||
$this->denyAccessUnlessGranted($attributes, $subject, $error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a "successful" flash message to the stack.
|
||||
*
|
||||
* @param string $translationKey
|
||||
* @param array $parameter
|
||||
*/
|
||||
protected function flashSuccess($translationKey, $parameter = [])
|
||||
{
|
||||
if (!empty($parameter)) {
|
||||
$translationKey = $this->getTranslator()->trans(
|
||||
$translationKey,
|
||||
$parameter,
|
||||
self::DOMAIN_FLASH
|
||||
);
|
||||
}
|
||||
|
||||
$this->addFlash(self::FLASH_SUCCESS, $translationKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a "warning" flash message to the stack.
|
||||
*
|
||||
* @param $translationKey
|
||||
* @param array $parameter
|
||||
*/
|
||||
protected function flashWarning($translationKey, $parameter = [])
|
||||
{
|
||||
if (!empty($parameter)) {
|
||||
$translationKey = $this->getTranslator()->trans(
|
||||
$translationKey,
|
||||
$parameter,
|
||||
self::DOMAIN_FLASH
|
||||
);
|
||||
}
|
||||
|
||||
$this->addFlash(self::FLASH_WARNING, $translationKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a "error" flash message to the stack.
|
||||
*
|
||||
* @param $translationKey
|
||||
* @param array $parameter
|
||||
*/
|
||||
protected function flashError($translationKey, $parameter = [])
|
||||
{
|
||||
if (!empty($parameter)) {
|
||||
$translationKey = $this->getTranslator()->trans(
|
||||
$translationKey,
|
||||
$parameter,
|
||||
self::DOMAIN_FLASH
|
||||
);
|
||||
}
|
||||
|
||||
$this->addFlash(self::FLASH_ERROR, $translationKey);
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
*
|
||||
* @author Kevin Papst <kevin@kevinpapst.de>
|
||||
*/
|
||||
class ProfileController extends Controller
|
||||
class ProfileController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @Route("/{username}", name="user_profile")
|
||||
@@ -48,10 +48,11 @@ class ProfileController extends Controller
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @param Form $editForm
|
||||
* @param Form $pwdForm
|
||||
* @param Form|null $editForm
|
||||
* @param Form|null $pwdForm
|
||||
* @param string $tab
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* @throws \Doctrine\ORM\NonUniqueResultException
|
||||
*/
|
||||
protected function getProfileView(User $user, Form $editForm = null, Form $pwdForm = null, $tab = 'charts')
|
||||
{
|
||||
@@ -105,7 +106,7 @@ class ProfileController extends Controller
|
||||
$entityManager->persist($user);
|
||||
$entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'action.updated_successfully');
|
||||
$this->flashSuccess('action.updated_successfully');
|
||||
|
||||
return $this->redirectToRoute(
|
||||
'user_profile', ['username' => $user->getUsername()]
|
||||
@@ -135,7 +136,7 @@ class ProfileController extends Controller
|
||||
$entityManager->persist($user);
|
||||
$entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'action.updated_successfully');
|
||||
$this->flashSuccess('action.updated_successfully');
|
||||
|
||||
return $this->redirectToRoute(
|
||||
'user_profile', ['username' => $user->getUsername()]
|
||||
@@ -146,7 +147,7 @@ class ProfileController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME
|
||||
* FIXME implement profile deletion
|
||||
* @Route("/{username}/delete", name="user_profile_delete")
|
||||
* @Method({"GET", "POST"})
|
||||
*/
|
||||
@@ -174,7 +175,7 @@ class ProfileController extends Controller
|
||||
|
||||
// only administrator can bypass that part if the requested user is not the current user
|
||||
if ($username !== $user->getUsername()) {
|
||||
$this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page'); // TODO translation
|
||||
$this->denyUnlessGranted('ROLE_ADMIN');
|
||||
}
|
||||
|
||||
// if the user is not the current use, load the requested one
|
||||
@@ -192,7 +193,7 @@ class ProfileController extends Controller
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @return \Symfony\Component\Form\Form
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
*/
|
||||
private function createEditForm(User $user)
|
||||
{
|
||||
@@ -208,7 +209,7 @@ class ProfileController extends Controller
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @return \Symfony\Component\Form\Form
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
*/
|
||||
private function createPasswordForm(User $user)
|
||||
{
|
||||
@@ -225,8 +226,7 @@ class ProfileController extends Controller
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*
|
||||
* @return \Symfony\Component\Form\Form The form
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
*/
|
||||
private function createDeleteForm(User $user)
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace AppBundle\Tests\Controller;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
/**
|
||||
* FIXME CAN BE REMOVED
|
||||
* TODO adjust to actual app
|
||||
*
|
||||
* Functional test that implements a "smoke test" of all the public and secure
|
||||
* URLs of the application.
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Tests\Utils;
|
||||
|
||||
use AppBundle\Utils\Slugger;
|
||||
|
||||
/**
|
||||
* FIXME CAN BE REMOVED
|
||||
*
|
||||
* Unit test for the application utils.
|
||||
* See http://symfony.com/doc/current/book/testing.html#unit-tests
|
||||
*
|
||||
* Execute the application tests using this command (requires PHPUnit to be installed):
|
||||
*
|
||||
* $ cd your-symfony-project/
|
||||
* $ phpunit -c app
|
||||
*
|
||||
*/
|
||||
class SluggerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider getSlugs
|
||||
*/
|
||||
public function testSlugify($string, $slug)
|
||||
{
|
||||
$slugger = new Slugger();
|
||||
$result = $slugger->slugify($string);
|
||||
|
||||
$this->assertEquals($slug, $result);
|
||||
}
|
||||
|
||||
public function getSlugs()
|
||||
{
|
||||
yield ['Lorem Ipsum' , 'lorem-ipsum'];
|
||||
yield [' Lorem Ipsum ' , 'lorem-ipsum'];
|
||||
yield [' lOrEm iPsUm ' , 'lorem-ipsum'];
|
||||
yield ['!Lorem Ipsum!' , 'lorem-ipsum'];
|
||||
yield ['lorem-ipsum' , 'lorem-ipsum'];
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ use AppBundle\Utils\Markdown;
|
||||
use Symfony\Component\Intl\Intl;
|
||||
use DateTime;
|
||||
use DateInterval;
|
||||
use TimesheetBundle\Entity\Customer;
|
||||
use TimesheetBundle\Entity\Timesheet;
|
||||
|
||||
/**
|
||||
* Multiple Twig extensions: filters and functions
|
||||
@@ -52,7 +54,10 @@ class Extensions extends \Twig_Extension
|
||||
return [
|
||||
new \Twig_SimpleFilter('md2html', [$this, 'markdownToHtml'], ['is_safe' => ['html']]),
|
||||
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')),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -66,6 +71,18 @@ class Extensions extends \Twig_Extension
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted duration for a Timesheet entry.
|
||||
*
|
||||
* @param Timesheet $entry
|
||||
* @param bool $includeSeconds
|
||||
* @return string
|
||||
*/
|
||||
public function durationForEntry(Timesheet $entry, $includeSeconds = false)
|
||||
{
|
||||
return $this->duration($entry->getDuration(), $includeSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms seconds into a duration string.
|
||||
*
|
||||
@@ -91,13 +108,32 @@ class Extensions extends \Twig_Extension
|
||||
return $hour . ':' . $minute . ':' . $second . ' h';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $currency
|
||||
* @return string
|
||||
*/
|
||||
public function currency($currency)
|
||||
{
|
||||
return Intl::getCurrencyBundle()->getCurrencySymbol($currency);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $country
|
||||
* @return string
|
||||
*/
|
||||
public function country($country)
|
||||
{
|
||||
return Intl::getRegionBundle()->getCountryName($country);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $amount
|
||||
* @param string $currency
|
||||
* @return string
|
||||
*/
|
||||
public function money($amount, $currency = 'EUR')
|
||||
public function money($amount, $currency = null)
|
||||
{
|
||||
$currency = $currency ?: Customer::DEFAULT_CURRENCY;
|
||||
return round($amount) . ' ' . Intl::getCurrencyBundle()->getCurrencySymbol($currency);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace AppBundle\Utils;
|
||||
|
||||
/**
|
||||
* FIXME CAN BE REMOVED
|
||||
*
|
||||
* This class is used to provide an example of integrating simple classes as
|
||||
* services into a Symfony application.
|
||||
*
|
||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
||||
*/
|
||||
class Slugger
|
||||
{
|
||||
/**
|
||||
* @param string $string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function slugify($string)
|
||||
{
|
||||
return trim(preg_replace('/[^a-z0-9]+/', '-', strtolower(strip_tags($string))), '-');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user