enhanced plugin support (#634)

- refactored admin controller and templates
- plugin support for entity actions
- changed column mail to email in customer table
- refactored theme events
This commit is contained in:
Kevin Papst
2019-03-11 14:46:30 +01:00
committed by GitHub
parent 519be2d5a2
commit 3ac72a5c89
104 changed files with 1878 additions and 976 deletions

View File

@@ -7,12 +7,10 @@
* file that was distributed with this source code.
*/
namespace App\Controller\Admin;
namespace App\Controller;
use App\Constants;
use App\Controller\AbstractController;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
/**
@@ -38,11 +36,10 @@ class AboutController extends AbstractController
/**
* @Route(path="", name="about", methods={"GET"})
* @param Request $request
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function indexAction(Request $request)
public function indexAction()
{
$phpInfo = $this->getPhpInfo();
unset($phpInfo[0]);
@@ -74,7 +71,7 @@ class AboutController extends AbstractController
}
}
return $this->render('admin/system.html.twig', [
return $this->render('about/system.html.twig', [
'modules' => get_loaded_extensions(),
'dotenv' => [
'APP_ENV' => getenv('APP_ENV'),

View File

@@ -10,19 +10,28 @@
namespace App\Controller;
use App\Entity\Activity;
use App\Form\ActivityEditForm;
use App\Form\Toolbar\ActivityToolbarForm;
use App\Form\Type\ActivityType;
use App\Repository\ActivityRepository;
use App\Repository\Query\ActivityQuery;
use Doctrine\ORM\ORMException;
use Pagerfanta\Pagerfanta;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
/**
* Controller used to manage activity contents in the public part of the site.
* Controller used to manage activities in the admin part of the site.
*
* @Security("is_granted('ROLE_USER')")
* @Route(path="/admin/activity")
* @Security("is_granted('view_activity')")
*/
class ActivityController extends AbstractController
{
/**
* @return ActivityRepository
* @return \App\Repository\ActivityRepository
*/
protected function getRepository()
{
@@ -30,18 +39,190 @@ class ActivityController extends AbstractController
}
/**
* The flyout to render recent activities and quick-start new recordings.
* @Route(path="/", defaults={"page": 1}, name="admin_activity", methods={"GET"})
* @Route(path="/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_activity_paginated", methods={"GET"})
* @Cache(smaxage="10")
* @Security("is_granted('view_activity')")
*
* @return Response
* @param int $page
* @param Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
public function recentActivitiesAction()
public function indexAction($page, Request $request)
{
$user = $this->getUser();
$entries = $this->getRepository()->getRecentActivities($user, new \DateTime('-1 year'));
$query = new ActivityQuery();
$query
->setOrderBy('name')
->setExclusiveVisibility(true)
->setPage($page)
;
$form = $this->getToolbarForm($query);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var ActivityQuery $query */
$query = $form->getData();
}
/* @var $entries Pagerfanta */
$entries = $this->getRepository()->findByQuery($query);
return $this->render('activity/index.html.twig', [
'entries' => $entries,
'query' => $query,
'showFilter' => $form->isSubmitted(),
'toolbarForm' => $form->createView(),
]);
}
/**
* @Route(path="/create", name="admin_activity_create", methods={"GET", "POST"})
* @Security("is_granted('create_activity')")
*
* @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function createAction(Request $request)
{
return $this->renderActivityForm(new Activity(), $request);
}
/**
* @Route(path="/{id}/edit", name="admin_activity_edit", methods={"GET", "POST"})
* @Security("is_granted('edit', activity)")
*
* @param Activity $activity
* @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function editAction(Activity $activity, Request $request)
{
return $this->renderActivityForm($activity, $request);
}
/**
* @Route(path="/{id}/delete", name="admin_activity_delete", methods={"GET", "POST"})
* @Security("is_granted('delete', activity)")
*
* @param Activity $activity
* @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function deleteAction(Activity $activity, Request $request)
{
$stats = $this->getRepository()->getActivityStatistics($activity);
$deleteForm = $this->createFormBuilder()
->add('activity', ActivityType::class, [
'label' => 'label.activity',
'query_builder' => function (ActivityRepository $repo) use ($activity) {
$query = new ActivityQuery();
$query
->setResultType(ActivityQuery::RESULT_TYPE_QUERYBUILDER)
->setProject($activity->getProject())
->setOrderGlobalsFirst(true)
->addIgnoredEntity($activity)
->setGlobalsOnly(null === $activity->getProject())
;
return $repo->findByQuery($query);
},
'required' => false,
])
->setAction($this->generateUrl('admin_activity_delete', ['id' => $activity->getId()]))
->setMethod('POST')
->getForm();
$deleteForm->handleRequest($request);
if (0 == $stats->getRecordAmount() || ($deleteForm->isSubmitted() && $deleteForm->isValid())) {
try {
$this->getRepository()->deleteActivity($activity, $deleteForm->get('activity')->getData());
$this->flashSuccess('action.delete.success');
} catch (ORMException $ex) {
$this->flashError('action.delete.error');
}
return $this->redirectToRoute('admin_activity');
}
return $this->render(
'navbar/recent-activities.html.twig',
['entries' => $entries]
'activity/delete.html.twig',
[
'activity' => $activity,
'stats' => $stats,
'form' => $deleteForm->createView(),
]
);
}
/**
* @param Activity $activity
* @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
protected function renderActivityForm(Activity $activity, Request $request)
{
$editForm = $this->createEditForm($activity);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($activity);
$entityManager->flush();
$this->flashSuccess('action.update.success');
if ($editForm->has('create_more') && $editForm->get('create_more')->getData() === true) {
$newActivity = new Activity();
$newActivity->setProject($activity->getProject());
$editForm = $this->createEditForm($newActivity);
$editForm->get('create_more')->setData(true);
$activity = $newActivity;
} else {
return $this->redirectToRoute('admin_activity');
}
}
return $this->render(
'activity/edit.html.twig',
[
'activity' => $activity,
'form' => $editForm->createView()
]
);
}
/**
* @param ActivityQuery $query
* @return \Symfony\Component\Form\FormInterface
*/
protected function getToolbarForm(ActivityQuery $query)
{
return $this->createForm(ActivityToolbarForm::class, $query, [
'action' => $this->generateUrl('admin_activity', [
'page' => $query->getPage(),
]),
'method' => 'GET',
]);
}
/**
* @param Activity $activity
* @return \Symfony\Component\Form\FormInterface
*/
private function createEditForm(Activity $activity)
{
if ($activity->getId() === null) {
$url = $this->generateUrl('admin_activity_create');
} else {
$url = $this->generateUrl('admin_activity_edit', ['id' => $activity->getId()]);
}
return $this->createForm(ActivityEditForm::class, $activity, [
'action' => $url,
'method' => 'POST'
]);
}
}

View File

@@ -1,229 +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\Controller\Admin;
use App\Controller\AbstractController;
use App\Entity\Activity;
use App\Form\ActivityEditForm;
use App\Form\Toolbar\ActivityToolbarForm;
use App\Form\Type\ActivityType;
use App\Repository\ActivityRepository;
use App\Repository\Query\ActivityQuery;
use Doctrine\ORM\ORMException;
use Pagerfanta\Pagerfanta;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
/**
* Controller used to manage activities in the admin part of the site.
*
* @Route(path="/admin/activity")
* @Security("is_granted('view_activity')")
*/
class ActivityController extends AbstractController
{
/**
* @return \App\Repository\ActivityRepository
*/
protected function getRepository()
{
return $this->getDoctrine()->getRepository(Activity::class);
}
/**
* @Route(path="/", defaults={"page": 1}, name="admin_activity", methods={"GET"})
* @Route(path="/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_activity_paginated", methods={"GET"})
* @Cache(smaxage="10")
* @Security("is_granted('view_activity')")
*
* @param int $page
* @param Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
public function indexAction($page, Request $request)
{
$query = new ActivityQuery();
$query
->setOrderBy('name')
->setExclusiveVisibility(true)
->setPage($page)
;
$form = $this->getToolbarForm($query);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var ActivityQuery $query */
$query = $form->getData();
}
/* @var $entries Pagerfanta */
$entries = $this->getRepository()->findByQuery($query);
return $this->render('admin/activity.html.twig', [
'entries' => $entries,
'query' => $query,
'showFilter' => $form->isSubmitted(),
'toolbarForm' => $form->createView(),
]);
}
/**
* @Route(path="/create", name="admin_activity_create", methods={"GET", "POST"})
* @Security("is_granted('create_activity')")
*
* @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function createAction(Request $request)
{
return $this->renderActivityForm(new Activity(), $request);
}
/**
* @Route(path="/{id}/edit", name="admin_activity_edit", methods={"GET", "POST"})
* @Security("is_granted('edit', activity)")
*
* @param Activity $activity
* @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function editAction(Activity $activity, Request $request)
{
return $this->renderActivityForm($activity, $request);
}
/**
* @Route(path="/{id}/delete", name="admin_activity_delete", methods={"GET", "POST"})
* @Security("is_granted('delete', activity)")
*
* @param Activity $activity
* @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function deleteAction(Activity $activity, Request $request)
{
$stats = $this->getRepository()->getActivityStatistics($activity);
$deleteForm = $this->createFormBuilder()
->add('activity', ActivityType::class, [
'label' => 'label.activity',
'query_builder' => function (ActivityRepository $repo) use ($activity) {
$query = new ActivityQuery();
$query
->setResultType(ActivityQuery::RESULT_TYPE_QUERYBUILDER)
->setProject($activity->getProject())
->setOrderGlobalsFirst(true)
->addIgnoredEntity($activity)
->setGlobalsOnly(null === $activity->getProject())
;
return $repo->findByQuery($query);
},
'required' => false,
])
->setAction($this->generateUrl('admin_activity_delete', ['id' => $activity->getId()]))
->setMethod('POST')
->getForm();
$deleteForm->handleRequest($request);
if (0 == $stats->getRecordAmount() || ($deleteForm->isSubmitted() && $deleteForm->isValid())) {
try {
$this->getRepository()->deleteActivity($activity, $deleteForm->get('activity')->getData());
$this->flashSuccess('action.delete.success');
} catch (ORMException $ex) {
$this->flashError('action.delete.error');
}
return $this->redirectToRoute('admin_activity');
}
return $this->render(
'admin/activity_delete.html.twig',
[
'activity' => $activity,
'stats' => $stats,
'form' => $deleteForm->createView(),
]
);
}
/**
* @param Activity $activity
* @param Request $request
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
protected function renderActivityForm(Activity $activity, Request $request)
{
$editForm = $this->createEditForm($activity);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($activity);
$entityManager->flush();
$this->flashSuccess('action.update.success');
if ($editForm->has('create_more') && $editForm->get('create_more')->getData() === true) {
$newActivity = new Activity();
$newActivity->setProject($activity->getProject());
$editForm = $this->createEditForm($newActivity);
$editForm->get('create_more')->setData(true);
$activity = $newActivity;
} else {
return $this->redirectToRoute('admin_activity');
}
}
return $this->render(
'admin/activity_edit.html.twig',
[
'activity' => $activity,
'form' => $editForm->createView()
]
);
}
/**
* @param ActivityQuery $query
* @return \Symfony\Component\Form\FormInterface
*/
protected function getToolbarForm(ActivityQuery $query)
{
return $this->createForm(ActivityToolbarForm::class, $query, [
'action' => $this->generateUrl('admin_activity', [
'page' => $query->getPage(),
]),
'method' => 'GET',
]);
}
/**
* @param Activity $activity
* @return \Symfony\Component\Form\FormInterface
*/
private function createEditForm(Activity $activity)
{
if ($activity->getId() === null) {
$url = $this->generateUrl('admin_activity_create');
} else {
$url = $this->generateUrl('admin_activity_edit', ['id' => $activity->getId()]);
}
return $this->createForm(ActivityEditForm::class, $activity, [
'action' => $url,
'method' => 'POST'
]);
}
}

View File

@@ -7,9 +7,8 @@
* file that was distributed with this source code.
*/
namespace App\Controller\Admin;
namespace App\Controller;
use App\Controller\AbstractController;
use App\Entity\Customer;
use App\Form\CustomerEditForm;
use App\Form\Toolbar\CustomerToolbarForm;
@@ -23,7 +22,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
/**
* Controller used to manage activities in the admin part of the site.
* Controller used to manage customer in the admin part of the site.
*
* @Route(path="/admin/customer")
* @Security("is_granted('view_customer')")
@@ -78,7 +77,7 @@ class CustomerController extends AbstractController
/* @var $entries Pagerfanta */
$entries = $this->getRepository()->findByQuery($query);
return $this->render('admin/customer.html.twig', [
return $this->render('customer/index.html.twig', [
'entries' => $entries,
'query' => $query,
'showFilter' => $form->isSubmitted(),
@@ -158,7 +157,7 @@ class CustomerController extends AbstractController
return $this->redirectToRoute('admin_customer');
}
return $this->render('admin/customer_delete.html.twig', [
return $this->render('customer/delete.html.twig', [
'customer' => $customer,
'stats' => $stats,
'form' => $deleteForm->createView(),
@@ -186,7 +185,7 @@ class CustomerController extends AbstractController
return $this->redirectToRoute('admin_customer');
}
return $this->render('admin/customer_edit.html.twig', [
return $this->render('customer/edit.html.twig', [
'customer' => $customer,
'form' => $editForm->createView()
]);

View File

@@ -1,67 +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\Controller;
use App\Event\ThemeEvent;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class EventController extends Controller
{
/**
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* @param EventDispatcherInterface $dispatcher
*/
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->eventDispatcher = $dispatcher;
}
/**
* @return EventDispatcherInterface
*/
protected function getDispatcher()
{
return $this->eventDispatcher;
}
/**
* @param string $eventName
*
* @return bool
*/
protected function hasListener($eventName)
{
return $this->getDispatcher()->hasListeners($eventName);
}
/**
* @param Request $request
* @param string $eventName
* @return ThemeEvent|Response
*/
public function trigger(Request $request, string $event)
{
if (!$this->hasListener($event)) {
return new Response();
}
$themeEvent = new ThemeEvent($request, $this->getUser());
$this->getDispatcher()->dispatch($event, $themeEvent);
return new Response($themeEvent->getContent());
}
}

View File

@@ -0,0 +1,60 @@
<?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\Repository\ActivityRepository;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Response;
/**
* Controller used to manage navigation-bar contents.
*
* @Security("is_granted('ROLE_USER')")
*/
class NavbarController extends AbstractController
{
/**
* @var ActivityRepository
*/
private $repository;
/**
* @param ActivityRepository $repository
*/
public function __construct(ActivityRepository $repository)
{
$this->repository = $repository;
}
/**
* @return ActivityRepository
*/
protected function getRepository()
{
return $this->repository;
}
/**
* The flyout to render recent activities and quick-start new recordings.
*
* @return Response
* @throws \Doctrine\ORM\Query\QueryException
*/
public function recentActivitiesAction()
{
$user = $this->getUser();
$entries = $this->getRepository()->getRecentActivities($user, new \DateTime('-1 year'));
return $this->render(
'navbar/recent-activities.html.twig',
['entries' => $entries]
);
}
}

View File

@@ -7,9 +7,8 @@
* file that was distributed with this source code.
*/
namespace App\Controller\Admin;
namespace App\Controller;
use App\Controller\AbstractController;
use App\Entity\Customer;
use App\Entity\Project;
use App\Form\ProjectEditForm;
@@ -69,7 +68,7 @@ class ProjectController extends AbstractController
/* @var $entries Pagerfanta */
$entries = $this->getDoctrine()->getRepository(Project::class)->findByQuery($query);
return $this->render('admin/project.html.twig', [
return $this->render('project/index.html.twig', [
'entries' => $entries,
'query' => $query,
'showFilter' => $form->isSubmitted(),
@@ -145,7 +144,7 @@ class ProjectController extends AbstractController
return $this->redirectToRoute('admin_project');
}
return $this->render('admin/project_delete.html.twig', [
return $this->render('project/delete.html.twig', [
'project' => $project,
'stats' => $stats,
'form' => $deleteForm->createView(),
@@ -181,7 +180,7 @@ class ProjectController extends AbstractController
}
}
return $this->render('admin/project_edit.html.twig', [
return $this->render('project/edit.html.twig', [
'project' => $project,
'form' => $editForm->createView()
]);

View File

@@ -238,7 +238,7 @@ class TimesheetController extends AbstractController
$this->flashError('action.delete.error', ['%reason%' => $ex->getMessage()]);
}
return $this->redirectToRoute('timesheet_paginated', ['page' => $request->get('page')]);
return $this->redirectToRoute('timesheet_paginated', ['page' => $request->get('page', 1)]);
}
/**

View File

@@ -103,11 +103,11 @@ trait TimesheetControllerTrait
$this->flashSuccess('action.update.success');
return $this->redirectToRoute($redirectRoute, ['page' => $request->get('page')]);
return $this->redirectToRoute($redirectRoute, ['page' => $request->get('page', 1)]);
}
return $this->render($renderTemplate, [
'entry' => $entry,
'timesheet' => $entry,
'form' => $editForm->createView(),
]);
}
@@ -180,7 +180,7 @@ trait TimesheetControllerTrait
}
return $this->render($renderTemplate, [
'entry' => $entry,
'timesheet' => $entry,
'form' => $createForm->createView(),
]);
}

View File

@@ -7,10 +7,8 @@
* file that was distributed with this source code.
*/
namespace App\Controller\Admin;
namespace App\Controller;
use App\Controller\AbstractController;
use App\Controller\TimesheetControllerTrait;
use App\Entity\Timesheet;
use App\Form\TimesheetEditForm;
use App\Form\Toolbar\TimesheetToolbarForm;
@@ -26,7 +24,7 @@ use Symfony\Component\Routing\Annotation\Route;
* @Route(path="/team/timesheet")
* @Security("is_granted('view_other_timesheet')")
*/
class TimesheetController extends AbstractController
class TimesheetTeamController extends AbstractController
{
use TimesheetControllerTrait;
@@ -60,7 +58,7 @@ class TimesheetController extends AbstractController
/* @var $entries Pagerfanta */
$entries = $this->getRepository()->findByQuery($query);
return $this->render('admin/timesheet.html.twig', [
return $this->render('timesheet-team/index.html.twig', [
'entries' => $entries,
'page' => $query->getPage(),
'query' => $query,
@@ -101,7 +99,7 @@ class TimesheetController extends AbstractController
/* @var $entries Pagerfanta */
$entries = $this->getRepository()->findByQuery($query);
return $this->render('admin/timesheet_export.html.twig', [
return $this->render('timesheet-team/export.html.twig', [
'entries' => $entries,
'query' => $query,
]);
@@ -129,7 +127,7 @@ class TimesheetController extends AbstractController
*/
public function editAction(Timesheet $entry, Request $request)
{
return $this->edit($entry, $request, 'admin_timesheet_paginated', 'admin/timesheet_edit.html.twig');
return $this->edit($entry, $request, 'admin_timesheet_paginated', 'timesheet-team/edit.html.twig');
}
/**
@@ -141,7 +139,7 @@ class TimesheetController extends AbstractController
*/
public function createAction(Request $request)
{
return $this->create($request, 'admin_timesheet', 'admin/timesheet_edit.html.twig');
return $this->create($request, 'admin_timesheet', 'timesheet-team/edit.html.twig');
}
/**
@@ -164,7 +162,7 @@ class TimesheetController extends AbstractController
$this->flashError('action.delete.error', ['%reason%' => $ex->getMessage()]);
}
return $this->redirectToRoute('admin_timesheet_paginated', ['page' => $request->get('page')]);
return $this->redirectToRoute('admin_timesheet_paginated', ['page' => $request->get('page', 1)]);
}
/**

View File

@@ -7,9 +7,8 @@
* file that was distributed with this source code.
*/
namespace App\Controller\Admin;
namespace App\Controller;
use App\Controller\AbstractController;
use App\Entity\Timesheet;
use App\Entity\User;
use App\Form\Toolbar\UserToolbarForm;
@@ -74,7 +73,7 @@ class UserController extends AbstractController
/* @var $entries Pagerfanta */
$entries = $this->getRepository()->findByQuery($query);
return $this->render('admin/user.html.twig', [
return $this->render('user/index.html.twig', [
'entries' => $entries,
'query' => $query,
'showFilter' => $form->isSubmitted(),
@@ -119,7 +118,7 @@ class UserController extends AbstractController
}
return $this->render(
'admin/user_edit.html.twig',
'user/edit.html.twig',
[
'user' => $user,
'form' => $editForm->createView()
@@ -159,7 +158,7 @@ class UserController extends AbstractController
}
return $this->render(
'admin/user_delete.html.twig',
'user/delete.html.twig',
[
'user' => $userToDelete,
'stats' => $stats,