at the end of the day, it was inevitable
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
</IfModule>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
namespace AdminBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
/**
|
||||
* Class AdminBundle
|
||||
* @package AdminBundle
|
||||
*/
|
||||
class AdminBundle extends Bundle
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Controller;
|
||||
|
||||
use AdminBundle\Form\ConfigParametersSectionType;
|
||||
use AppBundle\AppBundleServices;
|
||||
use AppBundle\Configuration\ConfigurationMutableInterface;
|
||||
use AppBundle\Configuration\ConfigurationParameterInterface;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
|
||||
/**
|
||||
* Class ConfigurationOptionsController
|
||||
* @package AdminBundle\Controller
|
||||
*
|
||||
* @Security("has_role('ROLE_SUPER_ADMIN')")
|
||||
* @Route("configuration")
|
||||
*/
|
||||
class ConfigurationOptionsController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Route("/", name="admin_configuration_index")
|
||||
* @Template
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return array|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function indexAction(Request $request)
|
||||
{
|
||||
/** @var ConfigurationMutableInterface $configuration */
|
||||
$configuration = $this->get(AppBundleServices::CONFIGURATION);
|
||||
|
||||
$oldParams = $configuration->getParameters();
|
||||
/** @var ConfigurationParameterInterface[] $newParams */
|
||||
$newParams = array_map(function ($object) {
|
||||
return clone $object;
|
||||
}, $oldParams);
|
||||
|
||||
$form = $this->createForm(ConfigParametersSectionType::class, $newParams);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$updated = [];
|
||||
foreach ($newParams as $param) {
|
||||
$name = $param->getName();
|
||||
|
||||
if ($oldParams[$name]->getValue() !== $param->getValue()) {
|
||||
$updated[$name] = $param->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
$configuration->setParameters($updated);
|
||||
$configuration->sync();
|
||||
|
||||
// We should re-render form.
|
||||
return $this->redirect($request->getRequestUri());
|
||||
}
|
||||
|
||||
return [ 'form' => $form->createView() ];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Controller;
|
||||
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
|
||||
/**
|
||||
* Class DashboardController
|
||||
* @package AdminBundle\Controller
|
||||
*/
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
/**
|
||||
* @Security("is_granted('ROLE_SUPER_ADMIN')")
|
||||
* @Route("/", name="admin_dashboard")
|
||||
* @Template
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Controller;
|
||||
|
||||
use AdminBundle\Form\OrganizationType;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use UserBundle\Entity\Organization;
|
||||
use UserBundle\Entity\Subscription\OrganizationSubscription;
|
||||
use UserBundle\Repository\OrganizationRepository;
|
||||
use UserBundle\Repository\SubscriptionRepository;
|
||||
|
||||
/**
|
||||
* Class OrganizationController
|
||||
* @package AdminBundle\Controller
|
||||
*
|
||||
* @Route("/organization")
|
||||
*/
|
||||
class OrganizationController extends Controller
|
||||
{
|
||||
|
||||
const LIMIT = 20;
|
||||
|
||||
/**
|
||||
* @Security("is_granted('ROLE_ADMIN')")
|
||||
* @Route("/")
|
||||
* @Template
|
||||
*
|
||||
* @param Request $request A HTTP Request instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function indexAction(Request $request)
|
||||
{
|
||||
/** @var OrganizationRepository $repository */
|
||||
$repository = $this->getDoctrine()->getRepository(Organization::class);
|
||||
|
||||
$paginator = $this->get('knp_paginator');
|
||||
$pagination = $paginator->paginate(
|
||||
$repository->getListQueryBuilder(),
|
||||
$request->query->getInt('page', 1),
|
||||
self::LIMIT
|
||||
);
|
||||
|
||||
return [
|
||||
'organizations' => $pagination,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @Security("is_granted('ROLE_ADMIN')")
|
||||
* @Route("/{id}", methods={ "GET", "POST" }, requirements={ "id": "\d+" })
|
||||
* @Template
|
||||
*
|
||||
* @param Request $request A HTTP Request instance.
|
||||
* @param integer $id A Organization entity id.
|
||||
*
|
||||
* @return array|Response
|
||||
*/
|
||||
public function editAction(Request $request, $id)
|
||||
{
|
||||
/** @var OrganizationRepository $repository */
|
||||
$repository = $this->getDoctrine()->getRepository(Organization::class);
|
||||
|
||||
$organization = $repository->find($id);
|
||||
if (! $organization instanceof Organization) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
/** @var SubscriptionRepository $repository */
|
||||
$repository = $this->getDoctrine()->getRepository(OrganizationSubscription::class);
|
||||
|
||||
$paginator = $this->get('knp_paginator');
|
||||
$pagination = $paginator->paginate(
|
||||
$repository->getForOrganization($organization->getId()),
|
||||
$request->query->getInt('page', 1),
|
||||
self::LIMIT
|
||||
);
|
||||
|
||||
$form = $this->createForm(OrganizationType::class, $organization);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
$em->persist($organization);
|
||||
$em->flush();
|
||||
|
||||
return $this->redirect($request->getUri());
|
||||
}
|
||||
|
||||
return [
|
||||
'form' => $form->createView(),
|
||||
'organization' => $organization,
|
||||
'subscriptions' => $pagination,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @Security("is_granted('ROLE_ADMIN')")
|
||||
* @Route("/{id}/delete", methods={ "GET" }, requirements={ "id": "\d+" })
|
||||
*
|
||||
* @param integer $id A Organization entity id.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
public function deleteAction($id)
|
||||
{
|
||||
/** @var OrganizationRepository $repository */
|
||||
$repository = $this->getDoctrine()->getRepository(Organization::class);
|
||||
|
||||
$organization = $repository->find($id);
|
||||
if (! $organization instanceof Organization) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
$em->remove($organization);
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('admin_organization_index');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Security("is_granted('ROLE_ADMIN')")
|
||||
* @Route(
|
||||
* "/{organizationId}/subscription/{subscriptionId}",
|
||||
* methods={ "GET" },
|
||||
* requirements={
|
||||
* "organizationId": "\d+",
|
||||
* "subscriptionId": "\d+"
|
||||
* }
|
||||
* )
|
||||
*
|
||||
* @param integer $organizationId A Organization entity id.
|
||||
* @param integer $subscriptionId A billing Subscription entity id.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
public function subscriptionDeleteAction($organizationId, $subscriptionId)
|
||||
{
|
||||
/** @var OrganizationRepository $repository */
|
||||
$repository = $this->getDoctrine()->getRepository(Organization::class);
|
||||
|
||||
$organization = $repository->find($organizationId);
|
||||
if (! $organization instanceof Organization) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
/** @var SubscriptionRepository $repository */
|
||||
$repository = $this->getDoctrine()->getRepository(OrganizationSubscription::class);
|
||||
|
||||
$subscription = $repository->find($subscriptionId);
|
||||
if ((! $subscription instanceof OrganizationSubscription) || ($subscription->getOrganization()->getId() === $organizationId)) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
$em->remove($subscription);
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('admin_organization_edit', [
|
||||
'id' => $organizationId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Controller;
|
||||
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use PaymentBundle\Entity\Payment;
|
||||
use PaymentBundle\Repository\PaymentRepository;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Class PaymentController
|
||||
* @package AdminBundle\Controller
|
||||
*
|
||||
* @Route("/payment")
|
||||
*/
|
||||
class PaymentController extends Controller
|
||||
{
|
||||
|
||||
const LIMIT = 10;
|
||||
|
||||
/**
|
||||
* @Security("is_granted('ROLE_SUPER_ADMIN')")
|
||||
* @Route("/", methods={ "GET" })
|
||||
* @Template
|
||||
*
|
||||
* @param Request $request A HTTP Request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function indexAction(Request $request)
|
||||
{
|
||||
/** @var PaginatorInterface $paginator */
|
||||
$paginator = $this->get('knp_paginator');
|
||||
/** @var PaymentRepository $repository */
|
||||
$repository = $this->getDoctrine()->getRepository(Payment::class);
|
||||
|
||||
$pagination = $paginator->paginate(
|
||||
$repository->getListQueryBuilder(),
|
||||
$request->query->getInt('page', 1),
|
||||
self::LIMIT
|
||||
);
|
||||
|
||||
return [
|
||||
'payments' => $pagination,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Controller;
|
||||
|
||||
use PaymentBundle\Enum\PaymentGatewayEnum;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use UserBundle\Entity\Plan;
|
||||
use UserBundle\Entity\Subscription\AbstractSubscription;
|
||||
use UserBundle\Form\PlanType;
|
||||
use UserBundle\Repository\PlanRepository;
|
||||
use UserBundle\Repository\SubscriptionRepository;
|
||||
|
||||
/**
|
||||
* Class PlanController
|
||||
* @package AdminBundle\Controller
|
||||
*
|
||||
* @Route("/plans")
|
||||
*/
|
||||
class PlanController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Security("is_granted('ROLE_SUPER_ADMIN')")
|
||||
* @Route("/", methods={ "GET" })
|
||||
* @Template
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
/** @var PlanRepository $repository */
|
||||
$repository = $this->getDoctrine()->getRepository(Plan::class);
|
||||
|
||||
$plans = $repository->findAll();
|
||||
|
||||
return [ 'plans' => $plans ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @Security("is_granted('ROLE_SUPER_ADMIN')")
|
||||
* @Route("/{id}", methods={ "GET", "POST" }, requirements={ "id": "\d+" })
|
||||
* @Template
|
||||
*
|
||||
* @param Request $request A HTTP Request instance.
|
||||
* @param integer $id A Plan entity id.
|
||||
*
|
||||
* @return array|RedirectResponse
|
||||
*/
|
||||
public function editAction(Request $request, $id)
|
||||
{
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
/** @var PlanRepository $repository */
|
||||
$repository = $em->getRepository(Plan::class);
|
||||
$plan = $repository->find($id);
|
||||
|
||||
if (! $plan instanceof Plan) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
$previousPrice = $plan->getPrice();
|
||||
|
||||
$form = $this->createForm(PlanType::class, $plan);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$em->persist($plan);
|
||||
|
||||
if (abs($plan->getPrice() - $previousPrice) >= 0.000001) {
|
||||
//
|
||||
// Plan price was changed.
|
||||
//
|
||||
// For now we unsubscribe and disable all subscribed to this plan
|
||||
// users, remove this plan for payment gateway and create new one
|
||||
//
|
||||
// todo rewrite this code when we find out that we should do with already subscribed users.
|
||||
//
|
||||
/** @var SubscriptionRepository $subscriptionRepository */
|
||||
$subscriptionRepository = $em->getRepository(AbstractSubscription::class);
|
||||
$subscriptions = $subscriptionRepository->getForPlan($plan->getId());
|
||||
|
||||
$paymentGateway = $this->get('payment.gateway_factory')
|
||||
->getGateway(PaymentGatewayEnum::paypal());
|
||||
|
||||
/** @var AbstractSubscription $subscription */
|
||||
foreach ($subscriptions as $subscription) {
|
||||
$paymentGateway->cancelSubscription($subscription, 'Billing plan is removed');
|
||||
$subscription
|
||||
->setPayed(false)
|
||||
->setPlan(null);
|
||||
|
||||
$em->persist($subscription);
|
||||
}
|
||||
|
||||
if ($plan->isFree()) {
|
||||
$paymentGateway->removePlan($plan);
|
||||
} else {
|
||||
$paymentGateway->updatePlan($plan);
|
||||
}
|
||||
}
|
||||
|
||||
$em->flush();
|
||||
|
||||
return $this->redirect($request->getUri());
|
||||
}
|
||||
|
||||
return [
|
||||
'form' => $form->createView(),
|
||||
'plan' => $plan,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
|
||||
|
||||
/**
|
||||
* Class SecurityController
|
||||
* @package AdminBundle\Controller
|
||||
*/
|
||||
class SecurityController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @Route("/login", name="admin_login")
|
||||
* @Template
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function loginAction(Request $request)
|
||||
{
|
||||
/** @var $session \Symfony\Component\HttpFoundation\Session\Session */
|
||||
$session = $request->getSession();
|
||||
|
||||
$authErrorKey = Security::AUTHENTICATION_ERROR;
|
||||
$lastUsernameKey = Security::LAST_USERNAME;
|
||||
|
||||
// get the error if any (works with forward and redirect -- see below)
|
||||
if ($request->attributes->has($authErrorKey)) {
|
||||
$error = $request->attributes->get($authErrorKey);
|
||||
} elseif (null !== $session && $session->has($authErrorKey)) {
|
||||
$error = $session->get($authErrorKey);
|
||||
$session->remove($authErrorKey);
|
||||
} else {
|
||||
$error = null;
|
||||
}
|
||||
|
||||
if (!$error instanceof AuthenticationException) {
|
||||
$error = null; // The value does not come from the security component.
|
||||
}
|
||||
|
||||
// last username entered by the user
|
||||
$lastUsername = (null === $session) ? '' : $session->get($lastUsernameKey);
|
||||
|
||||
$csrfToken = $this->has('security.csrf.token_manager')
|
||||
? $this->get('security.csrf.token_manager')->getToken('authenticate')->getValue()
|
||||
: null;
|
||||
|
||||
return [
|
||||
'last_username' => $lastUsername,
|
||||
'error' => $error,
|
||||
'csrf_token' => $csrfToken,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/login_check", name="admin_login_check")
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function loginCheckAction()
|
||||
{
|
||||
throw new \RuntimeException('You must activate the logout in your security firewall configuration.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/logout", name="admin_logging_out")
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logoutAction()
|
||||
{
|
||||
throw new \RuntimeException('You must activate the logout in your security firewall configuration.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Controller\User;
|
||||
|
||||
use AdminBundle\Form\Search;
|
||||
use AdminBundle\Form\Type\SearchType;
|
||||
use FOS\UserBundle\Model\UserManagerInterface;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use UserBundle\Entity\User;
|
||||
use UserBundle\Enum\UserRoleEnum;
|
||||
use UserBundle\Mailer\MailerInterface;
|
||||
use UserBundle\Repository\UserRepository;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use UserBundle\UserBundleServices;
|
||||
|
||||
/**
|
||||
* Class AbstractUserController
|
||||
* @package AdminBundle\Controller\User
|
||||
*/
|
||||
abstract class AbstractUserController extends Controller
|
||||
{
|
||||
|
||||
protected static $role;
|
||||
protected static $formClass;
|
||||
protected static $limit = 20;
|
||||
|
||||
/**
|
||||
* @Route("/")
|
||||
* @Method({"GET", "POST"})
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function indexAction(Request $request)
|
||||
{
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
/** @var UserRepository $userRepository */
|
||||
$userRepository = $em->getRepository(User::class);
|
||||
|
||||
$search = new Search();
|
||||
$searchForm = $this->createForm(SearchType::class, $search);
|
||||
$searchForm->handleRequest($request);
|
||||
|
||||
if ($searchForm->isSubmitted() && !$searchForm->isValid()) {
|
||||
$search = new Search();
|
||||
}
|
||||
|
||||
$query = $userRepository->getUserByRoleQB(new UserRoleEnum(static::$role), $search->getHandledQuery());
|
||||
|
||||
$paginator = $this->get('knp_paginator');
|
||||
$pagination = $paginator->paginate(
|
||||
$query,
|
||||
$request->query->getInt('page', 1),
|
||||
self::$limit
|
||||
);
|
||||
|
||||
return $this->render($this->getTemplate('index'), [
|
||||
'users' => $pagination,
|
||||
'search' => $searchForm->createView(),
|
||||
'newUrl' => $this->generateCRUDUrl('new'),
|
||||
'deleteRoute' => $this->getRoute('delete'),
|
||||
'changeEnabledRoute' => $this->getRoute('changeEnabled'),
|
||||
'resendPasswordRoute' => $this->getRoute('resendPassword'),
|
||||
'showRoute' => $this->getRoute('show'),
|
||||
'editRoute' => $this->getRoute('edit'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user entity.
|
||||
*
|
||||
* @Route("/new")
|
||||
* @Method({"GET", "POST"})
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function newAction(Request $request)
|
||||
{
|
||||
/** @var $userManager UserManagerInterface */
|
||||
$userManager = $this->get('fos_user.user_manager');
|
||||
|
||||
/** @var \UserBundle\Entity\User $user */
|
||||
$user = $userManager->createUser();
|
||||
$user->setEnabled(true);
|
||||
|
||||
$form = $this->createForm(static::$formClass, $user);
|
||||
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
if (!$form->has('plainPassword')) {
|
||||
$user->generatePassword();
|
||||
$password = $user->getPlainPassword();
|
||||
} else {
|
||||
$password = $form->get('plainPassword')->getData();
|
||||
}
|
||||
|
||||
$user
|
||||
->setVerified(true)
|
||||
->addRole(static::$role);
|
||||
|
||||
if (($response = $this->preCreate($user)) !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$userManager->updateUser($user);
|
||||
|
||||
$mailer = $this->get(UserBundleServices::MAILER);
|
||||
$mailer->sendPassword($user, $password);
|
||||
|
||||
if ($request->isXmlHttpRequest()) {
|
||||
return new JsonResponse(['success' => true]);
|
||||
}
|
||||
|
||||
return $this->redirect($this->generateCRUDUrl('show', $user->getId()));
|
||||
}
|
||||
|
||||
if ($request->isXmlHttpRequest()) {
|
||||
$resolveName = static function (FormInterface $form) use (&$resolveName) {
|
||||
$parent = $form->getParent();
|
||||
|
||||
if ($parent === null) {
|
||||
return $form->getName();
|
||||
}
|
||||
|
||||
return $resolveName($parent) .'_'. $form->getName();
|
||||
};
|
||||
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'errors' => array_map(function (FormError $error) use ($resolveName) {
|
||||
return [
|
||||
'field' => $resolveName($error->getOrigin()),
|
||||
'message' => $error->getMessage(),
|
||||
];
|
||||
}, iterator_to_array($form->getErrors(true, true))),
|
||||
], 400);
|
||||
}
|
||||
|
||||
return $this->render($this->getTemplate('new'), [
|
||||
'user' => $user,
|
||||
'form' => $form->createView(),
|
||||
'indexUrl' => $this->generateCRUDUrl('index'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/show"), requirements={ "id": "\d+" }
|
||||
* @Method({ "GET" })
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function showAction(User $user)
|
||||
{
|
||||
return $this->render($this->getTemplate('show'), [
|
||||
'user' => $user,
|
||||
'listUrl' => $this->generateCRUDUrl('index'),
|
||||
'editUrl' => $this->generateCRUDUrl('edit', $user->getId()),
|
||||
'deleteUrl' => $this->generateCRUDUrl('delete', $user->getId()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/edit", requirements={ "id": "\d+" })
|
||||
* @Method({"GET", "POST"})
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function editAction(Request $request, User $user)
|
||||
{
|
||||
$form = $this->createForm(static::$formClass, $user);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$manager = $this->get('user.user_manager');
|
||||
|
||||
if ((trim($user->getPlainPassword()) !== '') && $form->get('notifyAboutPassword')->getData()) {
|
||||
/** @var MailerInterface $mailer */
|
||||
$mailer = $this->get(UserBundleServices::MAILER);
|
||||
$mailer->sendPassword($user, $user->getPlainPassword());
|
||||
}
|
||||
|
||||
$manager->updateUser($user);
|
||||
|
||||
return $this->redirect($this->generateCRUDUrl('index'));
|
||||
}
|
||||
|
||||
return $this->render($this->getTemplate('edit'), [
|
||||
'user' => $user,
|
||||
'form' => $form->createView(),
|
||||
'indexUrl' => $this->generateCRUDUrl('index'),
|
||||
'deleteUrl' => $this->generateCRUDUrl('delete', $user->getId()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/delete", requirements={ "id": "\d+" })
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function deleteAction(User $user)
|
||||
{
|
||||
$manager = $this->get('user.user_manager');
|
||||
|
||||
if (($response = $this->preDelete($user)) !== null) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$manager->deleteUser($user);
|
||||
|
||||
return $this->redirectToRoute($this->getRoute('index'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $entity Deleted entity instance.
|
||||
*
|
||||
* @return null|Response
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function preDelete($entity)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name CRUD operation name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRoute($name)
|
||||
{
|
||||
$controllerName = strtolower(str_replace('Controller', '', \app\c\getShortName(static::class)));
|
||||
$name = strtolower($name);
|
||||
|
||||
return "admin_user_{$controllerName}_{$name}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name CRUD operation name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTemplate($name)
|
||||
{
|
||||
$controllerName = str_replace('Controller', '', \app\c\getShortName(static::class));
|
||||
|
||||
return "AdminBundle:User\\{$controllerName}:$name.html.twig";
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "/{id}/change-enabled",
|
||||
* requirements={ "id": "\d+" },
|
||||
* condition="request.isXmlHttpRequest()"
|
||||
* )
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function changeEnabledAction(User $user)
|
||||
{
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$user->setEnabled(! $user->isEnabled());
|
||||
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
return new JsonResponse([
|
||||
'enabled' => $user->isEnabled(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(
|
||||
* "/{id}/resend-password",
|
||||
* requirements={ "id": "\d+" },
|
||||
* condition="request.isXmlHttpRequest()"
|
||||
* )
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function resendPasswordAction(User $user)
|
||||
{
|
||||
$manager = $this->get('user.user_manager');
|
||||
|
||||
$user->generatePassword();
|
||||
$password = $user->getPlainPassword();
|
||||
$manager->updateUser($user);
|
||||
|
||||
/** @var MailerInterface $mailer */
|
||||
$mailer = $this->get(UserBundleServices::MAILER);
|
||||
$mailer->sendPassword($user, $password);
|
||||
|
||||
return new JsonResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Route name.
|
||||
* @param integer $user A User entity id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateCRUDUrl($name, $user = null)
|
||||
{
|
||||
$parameters = [];
|
||||
if ($user !== null) {
|
||||
$parameters = [ 'id' => $user ];
|
||||
}
|
||||
|
||||
return $this->generateUrl($this->getRoute($name), $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $entity Created entity instance.
|
||||
*
|
||||
* @return null|Response
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
protected function preCreate($entity)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Controller\User;
|
||||
|
||||
use AdminBundle\Form\User\AdminType;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use UserBundle\Enum\UserRoleEnum;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
|
||||
/**
|
||||
* Class AdminController
|
||||
* @package AdminBundle\Controller\User
|
||||
*
|
||||
* @Security("has_role('ROLE_SUPER_ADMIN')")
|
||||
* @Route("/users/admins")
|
||||
*/
|
||||
class AdminController extends AbstractUserController
|
||||
{
|
||||
|
||||
protected static $role = UserRoleEnum::ADMIN;
|
||||
protected static $formClass = AdminType::class;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Controller\User;
|
||||
|
||||
use AdminBundle\Form\User\MasterUserType;
|
||||
use AdminBundle\Form\User\SubscriberType;
|
||||
use PaymentBundle\Enum\PaymentGatewayEnum;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use UserBundle\Entity\Plan;
|
||||
use UserBundle\Entity\Subscription\PersonalSubscription;
|
||||
use UserBundle\Entity\User;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use UserBundle\Enum\UserRoleEnum;
|
||||
use \Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* User controller.
|
||||
*
|
||||
* @Security("has_role('ROLE_ADMIN')")
|
||||
* @Route("/users/masters")
|
||||
*/
|
||||
class MasterController extends AbstractUserController
|
||||
{
|
||||
|
||||
protected static $role = UserRoleEnum::MASTER_USER;
|
||||
protected static $formClass = MasterUserType::class;
|
||||
|
||||
/**
|
||||
* @Route("/{id}/show"), requirements={ "id": "\d+" }
|
||||
* @Method({ "GET" })
|
||||
*
|
||||
* Finds and displays a user entity.
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function showAction(User $user)
|
||||
{
|
||||
$subscriber = new User();
|
||||
$subscriber->setMasterUser($user);
|
||||
|
||||
$form = $this->createForm(
|
||||
SubscriberType::class,
|
||||
$subscriber,
|
||||
[
|
||||
'action' => $this->generateUrl('admin_user_subscriber_new'),
|
||||
'show_master_selector' => false,
|
||||
]
|
||||
);
|
||||
|
||||
return $this->render($this->getTemplate('show'), [
|
||||
'user' => $user,
|
||||
'subscriberForm' => $form->createView(),
|
||||
'listUrl' => $this->generateCRUDUrl('index'),
|
||||
'editUrl' => $this->generateCRUDUrl('edit', $user->getId()),
|
||||
'deleteUrl' => $this->generateCRUDUrl('delete', $user->getId()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object|User $entity Created entity instance.
|
||||
*
|
||||
* @return null|Response
|
||||
*/
|
||||
public function preCreate($entity)
|
||||
{
|
||||
if ($entity->hasRole(UserRoleEnum::MASTER_USER)) {
|
||||
//
|
||||
// Subscribe created masters to free plan.
|
||||
//
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
$freePlan = current(array_filter(
|
||||
$em->getRepository(Plan::class)->findAll(),
|
||||
function (Plan $plan) {
|
||||
return $plan->isFree();
|
||||
}
|
||||
));
|
||||
|
||||
if ($freePlan === null) {
|
||||
$this->addFlash('admin_error', 'Can\'t create master \'cause we don\'t have free plan');
|
||||
|
||||
return $this->redirectToRoute($this->getRoute('index'));
|
||||
}
|
||||
|
||||
$subscription = new PersonalSubscription();
|
||||
$subscription
|
||||
->setPlan($freePlan)
|
||||
->setGateway(PaymentGatewayEnum::paypal())
|
||||
->setPayed(true)
|
||||
->setOwner($entity);
|
||||
|
||||
$entity->setBillingSubscription($subscription);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Controller\User;
|
||||
|
||||
use AdminBundle\Form\User\SubscriberType;
|
||||
use AppBundle\Exception\LimitExceedException;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use UserBundle\Entity\User;
|
||||
use UserBundle\Enum\AppLimitEnum;
|
||||
use UserBundle\Enum\UserRoleEnum;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
|
||||
/**
|
||||
* Class SubscriberController
|
||||
* @package AdminBundle\Controller\User
|
||||
*
|
||||
* @Security("has_role('ROLE_ADMIN')")
|
||||
* @Route("/users/subscribers")
|
||||
*/
|
||||
class SubscriberController extends AbstractUserController
|
||||
{
|
||||
|
||||
protected static $role = UserRoleEnum::SUBSCRIBER;
|
||||
protected static $formClass = SubscriberType::class;
|
||||
|
||||
/**
|
||||
* @param object|User $entity Created entity instance.
|
||||
*
|
||||
* @return null|Response
|
||||
*/
|
||||
public function preCreate($entity)
|
||||
{
|
||||
if ($entity->hasRole(UserRoleEnum::SUBSCRIBER)) {
|
||||
$master = $entity->getMasterUser();
|
||||
|
||||
try {
|
||||
$entity->useLimit(AppLimitEnum::subscriberAccounts());
|
||||
} catch (LimitExceedException $exception) {
|
||||
$this->addFlash('admin_error', 'Allowed limit for creating subscribers for this master is exceeded');
|
||||
|
||||
return $this->redirectToRoute($this->getRoute('index'));
|
||||
}
|
||||
|
||||
$entity->setBillingSubscription($master->getBillingSubscription());
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($entity);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object|User $entity Deleted entity instance.
|
||||
*
|
||||
* @return null|Response
|
||||
*/
|
||||
public function preDelete($entity)
|
||||
{
|
||||
if ($entity->hasRole(UserRoleEnum::SUBSCRIBER)) {
|
||||
$master = $entity->getMasterUser();
|
||||
if ($master !== null) { // The impossible happens.
|
||||
$entity->releaseLimit(AppLimitEnum::subscriberAccounts());
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($entity);
|
||||
$em->flush();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Controller\User;
|
||||
|
||||
use AdminBundle\Form\User\AdminType;
|
||||
use FOS\UserBundle\Model\UserManagerInterface;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
|
||||
|
||||
/**
|
||||
* Class SuperAdminController
|
||||
* @package AdminBundle\Controller\User
|
||||
*
|
||||
* @Security("has_role('ROLE_SUPER_ADMIN')")
|
||||
* @Route("/users/super-admin")
|
||||
*/
|
||||
class SuperAdminController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Displays a form to edit an existing user entity.
|
||||
*
|
||||
* @Route("/edit")
|
||||
* @Method({"GET", "POST"})
|
||||
* @Template
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
*
|
||||
* @return RedirectResponse|array
|
||||
*/
|
||||
public function editAction(Request $request)
|
||||
{
|
||||
/** @var $userManager UserManagerInterface */
|
||||
$userManager = $this->get('fos_user.user_manager');
|
||||
|
||||
$user = $this->getUser();
|
||||
|
||||
$form = $this->createForm(AdminType::class, $user);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$userManager->updateUser($user);
|
||||
|
||||
return $this->redirect($request->getUri());
|
||||
}
|
||||
|
||||
return [
|
||||
'user' => $user,
|
||||
'form' => $form->createView(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Controller;
|
||||
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use PaymentBundle\Agreement\AgreementManagerInterface;
|
||||
use PaymentBundle\Gateway\Factory\PaymentGatewayFactoryInterface;
|
||||
use PaymentBundle\PaymentBundleServices;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use UserBundle\Entity\Subscription\OrganizationSubscription;
|
||||
use UserBundle\Entity\User;
|
||||
use UserBundle\Mailer\MailerInterface;
|
||||
use UserBundle\Manager\User\UserManagerInterface;
|
||||
use UserBundle\Repository\UserRepository;
|
||||
use UserBundle\UserBundleServices;
|
||||
|
||||
/**
|
||||
* Class UserVerificationController
|
||||
* @package AdminBundle\Controller
|
||||
*
|
||||
* @Route("/users/verifications")
|
||||
*/
|
||||
class UserVerificationController extends Controller
|
||||
{
|
||||
|
||||
const LIMIT = 20;
|
||||
|
||||
/**
|
||||
* @Security("is_granted('ROLE_ADMIN')")
|
||||
*
|
||||
* @Route("/", methods={ "GET" })
|
||||
* @Template
|
||||
*
|
||||
* @param Request $request A HTTP Request instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function indexAction(Request $request)
|
||||
{
|
||||
/** @var UserRepository $repository */
|
||||
$repository = $this->getDoctrine()->getRepository(User::class);
|
||||
|
||||
$notVerifiedUsers = $repository->getNotVerifiedQueryBuilder();
|
||||
|
||||
/** @var PaginatorInterface $paginator */
|
||||
$paginator = $this->get('knp_paginator');
|
||||
$pagination = $paginator->paginate(
|
||||
$notVerifiedUsers,
|
||||
$request->query->getInt('page', 1),
|
||||
self::LIMIT
|
||||
);
|
||||
|
||||
return [ 'users' => $pagination ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @Security("is_granted('ROLE_ADMIN')")
|
||||
*
|
||||
* @Route("/{id}", methods={ "GET", "POST" }, requirements={ "id": "\d+" })
|
||||
* @Template
|
||||
*
|
||||
* @param Request $request A HTTP Request instance.
|
||||
* @param integer $id A not verified user instance.
|
||||
*
|
||||
* @return array|RedirectResponse
|
||||
*/
|
||||
public function showAction(Request $request, $id)
|
||||
{
|
||||
/** @var UserRepository $repository */
|
||||
$repository = $this->getDoctrine()->getRepository(User::class);
|
||||
|
||||
$user = $repository->find($id);
|
||||
if (! $user instanceof User) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if ($user->isVerified() || ! $user->getBillingSubscription()->isPayed()) {
|
||||
return $this->redirectToRoute('admin_userverification_index');
|
||||
}
|
||||
|
||||
if ($request->isMethod('post')) {
|
||||
/** @var $userManager UserManagerInterface */
|
||||
$userManager = $this->get('fos_user.user_manager');
|
||||
/** @var MailerInterface $mail */
|
||||
$mailer = $this->get(UserBundleServices::MAILER);
|
||||
|
||||
if ($request->request->has('reject')) {
|
||||
//
|
||||
// Admin reject registration attempt.
|
||||
//
|
||||
/** @var PaymentGatewayFactoryInterface $factory */
|
||||
$factory = $this->get(PaymentBundleServices::PAYMENT_GATEWAY_FACTORY);
|
||||
/** @var AgreementManagerInterface $manager */
|
||||
$manager = $this->get(PaymentBundleServices::AGREEMENT_MANAGER);
|
||||
|
||||
$user->getBillingSubscription()->cancel($factory, 'Account registration rejected');
|
||||
$manager->removeAgreement($user->getBillingSubscription());
|
||||
$userManager->deleteUser($user);
|
||||
|
||||
$mailer->sendVerificationRejected($user);
|
||||
|
||||
$this->addFlash('admin_success', sprintf(
|
||||
'%s registration rejected and email sent to %s',
|
||||
$user->getFullName(),
|
||||
$user->getEmail()
|
||||
));
|
||||
} elseif ($request->request->has('verify')) {
|
||||
//
|
||||
// User verified so we should generate password and email it to
|
||||
// user.
|
||||
//
|
||||
$password = $userManager->confirmUser($user);
|
||||
$mailer->sendVerificationSuccess($user, $password);
|
||||
|
||||
$this->addFlash('admin_success', sprintf(
|
||||
'%s registration verified and email sent to %s',
|
||||
$user->getFullName(),
|
||||
$user->getEmail()
|
||||
));
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_userverification_index');
|
||||
}
|
||||
|
||||
$billingSubscription = $user->getBillingSubscription();
|
||||
$organization = null;
|
||||
if ($billingSubscription instanceof OrganizationSubscription) {
|
||||
$organization = $billingSubscription->getOrganization();
|
||||
}
|
||||
|
||||
return [
|
||||
'user' => $user,
|
||||
'plan' => $billingSubscription->getPlan(),
|
||||
'subscription' => $billingSubscription,
|
||||
'organization' => $organization,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Entity;
|
||||
|
||||
use AppBundle\Configuration\ConfigurationParameterInterface;
|
||||
use AppBundle\Configuration\ConfigurationParameterMutableInterface;
|
||||
use AppBundle\Entity\BaseEntityTrait;
|
||||
use AppBundle\Entity\EntityInterface;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* SiteSettings
|
||||
*
|
||||
* @ORM\Table(name="site_settings")
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class SiteSettings implements
|
||||
ConfigurationParameterInterface,
|
||||
ConfigurationParameterMutableInterface,
|
||||
EntityInterface
|
||||
{
|
||||
|
||||
use BaseEntityTrait;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column
|
||||
*/
|
||||
private $section;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @ORM\Column(type="text")
|
||||
*
|
||||
* @Assert\NotBlank
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Set section
|
||||
*
|
||||
* @param string $section Section name.
|
||||
*
|
||||
* @return SiteSettings
|
||||
*/
|
||||
public function setSection($section)
|
||||
{
|
||||
$this->section = $section;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get section
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSection()
|
||||
{
|
||||
return $this->section;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set title
|
||||
*
|
||||
* @param string $title Human readable parameter title.
|
||||
*
|
||||
* @return SiteSettings
|
||||
*/
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name
|
||||
*
|
||||
* @param string $name Parameter name.
|
||||
*
|
||||
* @return SiteSettings
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value
|
||||
*
|
||||
* @param mixed $value Parameter value.
|
||||
*
|
||||
* @return SiteSettings
|
||||
*/
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEntityType()
|
||||
{
|
||||
return 'config_parameter';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Form;
|
||||
|
||||
use AdminBundle\Entity\SiteSettings;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* Class ConfigParametersType
|
||||
* @package AdminBundle\Form
|
||||
*/
|
||||
class ConfigParametersSectionType extends AbstractType
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$parameters = $builder->getData();
|
||||
|
||||
$checker = \nspl\f\rpartial('\app\op\isInstanceOf', SiteSettings::class);
|
||||
if (! \nspl\a\all($parameters, $checker)) {
|
||||
throw new \LogicException(
|
||||
'Each \'parameters\' item should be instance of '
|
||||
. SiteSettings::class
|
||||
);
|
||||
}
|
||||
|
||||
$parameters = \app\a\group(\nspl\op\methodCaller('getSection'), $parameters);
|
||||
|
||||
foreach ($parameters as $section => $sectionParams) {
|
||||
$builder->add($section, ConfigParametersType::class, [
|
||||
'data' => $sectionParams,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Form;
|
||||
|
||||
use AdminBundle\Entity\SiteSettings;
|
||||
use AppBundle\Configuration\ConfigurationDefinitionMap;
|
||||
use AppBundle\Configuration\ConfigurationParameterInterface;
|
||||
use Ivory\CKEditorBundle\Form\Type\CKEditorType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Validator\Constraints\Type;
|
||||
|
||||
/**
|
||||
* Class ConfigParametersType
|
||||
* @package AdminBundle\Form
|
||||
*/
|
||||
class ConfigParametersType extends AbstractType implements DataMapperInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ConfigurationDefinitionMap
|
||||
*/
|
||||
private $definitionMap;
|
||||
|
||||
/**
|
||||
* ConfigParametersType constructor.
|
||||
*
|
||||
* @param ConfigurationDefinitionMap $definitionMap A ConfigurationDefinitionMap
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(ConfigurationDefinitionMap $definitionMap)
|
||||
{
|
||||
$this->definitionMap = $definitionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$parameters = $builder->getData();
|
||||
|
||||
/** @var SiteSettings $parameter */
|
||||
foreach ($parameters as $parameter) {
|
||||
$definition = $this->definitionMap->getDefinition($parameter->getName());
|
||||
$name = str_replace('.', ':', $parameter->getName());
|
||||
$type = $definition['type'];
|
||||
$type = $type === 'integer' ? 'numeric' : $type;
|
||||
|
||||
$options = [
|
||||
'label' => $parameter->getTitle(),
|
||||
'constraints' => new Type([ 'type' => $type ]),
|
||||
];
|
||||
|
||||
if ($definition['formType'] === CKEditorType::class) {
|
||||
$options['config_name'] = 'default';
|
||||
}
|
||||
|
||||
if ($definition['formType'] === ChoiceType::class) {
|
||||
$options['choices'] = $definition['choices'];
|
||||
}
|
||||
|
||||
$builder->add($name, $definition['formType'], $options);
|
||||
}
|
||||
|
||||
$builder->setDataMapper($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps properties of some data to a list of forms.
|
||||
*
|
||||
* @param ConfigurationParameterInterface[]|null $data Structured data.
|
||||
* @param FormInterface[]|\Iterator $forms A list of {@link FormInterface}
|
||||
* instances.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mapDataToForms($data, $forms)
|
||||
{
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
foreach ($data as $parameter) {
|
||||
$forms[str_replace('.', ':', $parameter->getName())]->setData($this->definitionMap->denormalize(
|
||||
$parameter->getName(),
|
||||
$parameter->getValue()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the data of a list of forms into the properties of some data.
|
||||
*
|
||||
* @param FormInterface[]|\Iterator $forms A list of {@link FormInterface}
|
||||
* instances.
|
||||
* @param ConfigurationParameterInterface[]|null $data Structured data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mapFormsToData($forms, &$data)
|
||||
{
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
/** @var FormInterface $form */
|
||||
foreach ($forms as $form) {
|
||||
$name = str_replace(':', '.', $form->getName());
|
||||
$value = $form->getData();
|
||||
settype($value, $this->definitionMap->getDefinition($name)['type']);
|
||||
|
||||
$data[$name]->setValue($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Form;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use UserBundle\Entity\Organization;
|
||||
|
||||
/**
|
||||
* Class OrganizationType
|
||||
* @package AdminBundle\Form
|
||||
*/
|
||||
class OrganizationType extends AbstractType
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Organization::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Form;
|
||||
|
||||
/**
|
||||
* Class Search
|
||||
* @package AdminBundle\Form
|
||||
*/
|
||||
class Search
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $query = '';
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getHandledQuery()
|
||||
{
|
||||
$handledQuery = explode(' ', $this->query);
|
||||
if (count($handledQuery) === 1) {
|
||||
if (!$handledQuery[0]) {
|
||||
$handledQuery = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $handledQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $query Search query.
|
||||
*
|
||||
* @return Search
|
||||
*/
|
||||
public function setQuery($query)
|
||||
{
|
||||
$this->query = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Form\Type;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
|
||||
/**
|
||||
* Class HiddenEntityType
|
||||
* @package AdminBundle\Form\Type
|
||||
*/
|
||||
class HiddenEntityType extends AbstractType
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* HiddenEntityType constructor.
|
||||
*
|
||||
* @param EntityManagerInterface $em A EntityManagerInterface instance.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$idParameter = $options['id_parameter'];
|
||||
$class = $options['class'];
|
||||
|
||||
$transformer = function ($entity) use ($class, $idParameter) {
|
||||
// Transform.
|
||||
if (! $entity instanceof $class) {
|
||||
throw new TransformationFailedException('Entity should be instance of '. $class);
|
||||
}
|
||||
return PropertyAccess::createPropertyAccessor()
|
||||
->getValue($entity, $idParameter);
|
||||
};
|
||||
|
||||
$reverseTransformer = function ($id) use ($class) {
|
||||
if (trim($id) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$entity = $this->em->find($class, $id);
|
||||
|
||||
if (null === $entity) {
|
||||
throw new TransformationFailedException(sprintf(
|
||||
'An entity with ID "%s" does not exist!',
|
||||
$id
|
||||
));
|
||||
}
|
||||
|
||||
return $entity;
|
||||
};
|
||||
|
||||
$builder->addModelTransformer(new CallbackTransformer($transformer, $reverseTransformer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setRequired('id_parameter')
|
||||
->setRequired('class')
|
||||
->setDefault('id_parameter', 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the parent type.
|
||||
*
|
||||
* @return string|null The name of the parent type if any, null otherwise
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return HiddenType::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Form\Type;
|
||||
|
||||
use AdminBundle\Form\Search;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class SearchType
|
||||
* @package AdminBundle\Form\SearchType
|
||||
*/
|
||||
class SearchType extends AbstractType
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('query', TextType::class, [
|
||||
'required' => false,
|
||||
'label' => false,
|
||||
'trim' => true,
|
||||
])
|
||||
->setMethod('GET');
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefault('data_class', Search::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Form\User;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class AbstractUserType
|
||||
* @package AdminBundle\Form\User
|
||||
*/
|
||||
abstract class AbstractUserType extends AbstractType
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('email', EmailType::class)
|
||||
->add('firstName')
|
||||
->add('lastName')
|
||||
->add('plainPassword', PasswordType::class, [ 'required' => false ])
|
||||
->add('notifyAboutPassword', CheckboxType::class, [
|
||||
'mapped' => false,
|
||||
'label' => 'Notify the user about password change',
|
||||
'required' => false,
|
||||
'data' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => User::class,
|
||||
'validation_groups' => [ 'admin_users_creation' ],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Form\User;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class AdminType
|
||||
* @package AdminBundle\Form\User
|
||||
*/
|
||||
class AdminType extends AbstractUserType
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
$builder
|
||||
->add('plainPassword', RepeatedType::class, [
|
||||
'type' => PasswordType::class,
|
||||
'options' => [ 'translation_domain' => 'FOSUserBundle' ],
|
||||
'first_options' => [ 'label' => 'form.password' ],
|
||||
'second_options' => [ 'label' => 'form.password_confirmation' ],
|
||||
'invalid_message' => 'fos_user.password.mismatch',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefault('validation_groups', [
|
||||
'admin_administrator_creation',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Form\User\DataMapper;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use UserBundle\Entity\Subscription\AbstractSubscription;
|
||||
use UserBundle\Entity\Subscription\OrganizationSubscription;
|
||||
use UserBundle\Entity\Subscription\PersonalSubscription;
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class MasterDataMapper
|
||||
*
|
||||
* @package AdminBundle\Form\User\DataMaper
|
||||
*/
|
||||
class MasterDataMapper extends PropertyPathMapper
|
||||
{
|
||||
|
||||
/**
|
||||
* Maps properties of some data to a list of forms.
|
||||
*
|
||||
* @param mixed|User $data Structured data.
|
||||
* @param FormInterface[]|\Traversable $forms A list of {@link FormInterface}
|
||||
* instances.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mapDataToForms($data, $forms)
|
||||
{
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
$billingSubscription = $data->getBillingSubscription();
|
||||
|
||||
$forms['email']->setData($data->getEmail());
|
||||
$forms['firstName']->setData($data->getFirstName());
|
||||
$forms['lastName']->setData($data->getLastName());
|
||||
$forms['enabled']->setData($data->isEnabled());
|
||||
if ($billingSubscription instanceof AbstractSubscription) {
|
||||
$forms['plan']->setData($billingSubscription->getPlan());
|
||||
$forms['privatePerson']->setData($billingSubscription instanceof PersonalSubscription);
|
||||
|
||||
if ($billingSubscription instanceof OrganizationSubscription) {
|
||||
$forms['organizationName']->setData($billingSubscription->getOrganization());
|
||||
$forms['organizationAddress']->setData($billingSubscription->getOrganizationAddress());
|
||||
$forms['organizationEmail']->setData($billingSubscription->getOrganizationEmail());
|
||||
$forms['organizationPhone']->setData($billingSubscription->getOrganizationPhone());
|
||||
}
|
||||
}
|
||||
$forms['expirationDay']->setData($data->getExpirationDay());
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the data of a list of forms into the properties of some data.
|
||||
*
|
||||
* @param FormInterface[]|\Traversable $forms A list of {@link FormInterface}
|
||||
* instances.
|
||||
* @param mixed|User $data Structured data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mapFormsToData($forms, &$data)
|
||||
{
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
if (isset($forms['plan'], $forms['privatePerson'])) {
|
||||
$personal = (boolean) $forms['privatePerson']->getData();
|
||||
|
||||
$oldBillingSubscription = $data->getBillingSubscription();
|
||||
$newBillingSubscription = $personal
|
||||
? new PersonalSubscription()
|
||||
: new OrganizationSubscription();
|
||||
|
||||
$allowedSearches = $oldBillingSubscription->getSearchesPerDay();
|
||||
$allowedSavedFeeds = $oldBillingSubscription->getSavedFeeds();
|
||||
$allowedMasters = $oldBillingSubscription->getMasterAccounts();
|
||||
$allowedSubscribers = $oldBillingSubscription->getSubscriberAccounts();
|
||||
$allowedAlerts = $oldBillingSubscription->getAlerts();
|
||||
$allowedNewsletter = $oldBillingSubscription->getNewsletters();
|
||||
|
||||
if (($personal && ($oldBillingSubscription instanceof PersonalSubscription))
|
||||
|| (! $personal && ($oldBillingSubscription instanceof OrganizationSubscription))
|
||||
) {
|
||||
$newBillingSubscription = $oldBillingSubscription;
|
||||
} else {
|
||||
$oldBillingSubscription->removeUser($data);
|
||||
}
|
||||
|
||||
$newBillingSubscription
|
||||
->setPlan($forms['plan']->getData())
|
||||
->setSearchesPerDay($allowedSearches)
|
||||
->setSavedFeeds($allowedSavedFeeds)
|
||||
->setMasterAccounts($allowedMasters)
|
||||
->setSubscriberAccounts($allowedSubscribers)
|
||||
->setAlerts($allowedAlerts)
|
||||
->setNewsletters($allowedNewsletter);
|
||||
|
||||
if (! $personal) {
|
||||
$newBillingSubscription
|
||||
->setOrganization($forms['organizationName']->getData())
|
||||
->setOrganizationAddress($forms['organizationAddress']->getData())
|
||||
->setOrganizationEmail($forms['organizationEmail']->getData())
|
||||
->setOrganizationPhone($forms['organizationPhone']->getData());
|
||||
|
||||
unset(
|
||||
$forms['organizationName'],
|
||||
$forms['organizationAddress'],
|
||||
$forms['organizationEmail'],
|
||||
$forms['organizationPhone']
|
||||
);
|
||||
}
|
||||
|
||||
$data->setBillingSubscription($newBillingSubscription);
|
||||
|
||||
unset($forms['plan'], $forms['privatePerson']);
|
||||
}
|
||||
|
||||
parent::mapFormsToData($forms, $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Form\User;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* Class MasterUserType
|
||||
* @package AdminBundle\Form\User
|
||||
*/
|
||||
class MasterUserType extends AbstractUserType
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
$builder
|
||||
->add('enabled')
|
||||
->add('expirationDay', DateType::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Form\User;
|
||||
|
||||
use AdminBundle\Form\Type\HiddenEntityType;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use UserBundle\Entity\User;
|
||||
use UserBundle\Enum\UserRoleEnum;
|
||||
use UserBundle\Repository\UserRepository;
|
||||
|
||||
/**
|
||||
* Class SubscriberType
|
||||
* @package AdminBundle\Form\User
|
||||
*/
|
||||
class SubscriberType extends AbstractUserType
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
$builder
|
||||
->add('position')
|
||||
->add('phoneNumber', TextType::class, [
|
||||
'attr' => [ 'data-type' => 'phone-number' ],
|
||||
]);
|
||||
|
||||
$type = EntityType::class;
|
||||
$parameters = [ 'class' => User::class ];
|
||||
if ((boolean) $options['show_master_selector']) {
|
||||
$parameters['choice_label'] = 'username';
|
||||
$parameters['query_builder'] = function (UserRepository $repository) {
|
||||
$qb = $repository->getUserByRoleQB(UserRoleEnum::masterUser(), []);
|
||||
$alias = $qb->getRootAliases()[0];
|
||||
|
||||
return $qb
|
||||
->orderBy($alias. '.username', 'DESC');
|
||||
};
|
||||
} else {
|
||||
$type = HiddenEntityType::class;
|
||||
}
|
||||
$builder->add('masterUser', $type, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
$resolver
|
||||
->setDefaults([
|
||||
'validation_groups' => [
|
||||
'admin_users_creation',
|
||||
'admin_subscribers_creation',
|
||||
],
|
||||
'show_master_selector' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
services:
|
||||
admin.forms.parameters:
|
||||
class: 'AdminBundle\Form\ConfigParametersType'
|
||||
arguments:
|
||||
- '@app.configuration_definitions'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
admin.forms.hidden_entity_type:
|
||||
class: 'AdminBundle\Form\Type\HiddenEntityType'
|
||||
arguments:
|
||||
- '@doctrine.orm.default_entity_manager'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
admin.twig_extension:
|
||||
class: 'AdminBundle\Twig\AdminTwigExtension'
|
||||
tags:
|
||||
- { name: twig.extension }
|
||||
@@ -0,0 +1,14 @@
|
||||
(function ($) {
|
||||
|
||||
$(document).on('click', '.btn-delete', function () {
|
||||
var href = this.dataset.href;
|
||||
|
||||
if (! href || ! confirm("Are you sure you want to delete?")) {
|
||||
return;
|
||||
}
|
||||
this.setAttribute('disabled', true);
|
||||
|
||||
window.location = href;
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,62 @@
|
||||
(function ($) {
|
||||
var modalSubscriberWindow = $("#subscriber-modal");
|
||||
var modalSubscriberForm = modalSubscriberWindow.find("form");
|
||||
|
||||
$("#subscriber-add").click(function () {
|
||||
modalSubscriberWindow.modal("show");
|
||||
});
|
||||
|
||||
modalSubscriberForm.submit(function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
clearErrors(modalSubscriberForm);
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: this.getAttribute("action"),
|
||||
processData: false,
|
||||
contentType: false,
|
||||
data: new FormData(this),
|
||||
})
|
||||
.done(function () {
|
||||
window.location.reload();
|
||||
})
|
||||
.fail(function (jqXHR) {
|
||||
var errors = jqXHR.responseJSON.errors;
|
||||
|
||||
for (var idx in errors) {
|
||||
if (errors.hasOwnProperty(idx)) {
|
||||
var error = errors[idx];
|
||||
|
||||
attachError(error.field, error.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function clearErrors(form) {
|
||||
if (typeof form === "string") {
|
||||
form = $(form);
|
||||
}
|
||||
|
||||
form.find(".help-block ul").html("");
|
||||
}
|
||||
|
||||
function attachError(field, message) {
|
||||
var view = undefined;
|
||||
|
||||
if (field === "subscriber_masterUser") {
|
||||
view = $("#subscriber").find("> .help-block ul");
|
||||
} else {
|
||||
view = $("#" + field)
|
||||
.parent()
|
||||
.find(".help-block ul");
|
||||
}
|
||||
view.append(
|
||||
'<li><span className="glyphicon glyphicon-exclamation-sign"></span>' +
|
||||
message +
|
||||
"</li>"
|
||||
);
|
||||
}
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,49 @@
|
||||
(function ($) {
|
||||
var $document = $(document);
|
||||
|
||||
$document.on('click', '.user-change-status', function () {
|
||||
var $button = $(this);
|
||||
$button.attr('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
type: 'post',
|
||||
url: $button.data('href')
|
||||
})
|
||||
.always(function () {
|
||||
$button.attr('disabled', false)
|
||||
})
|
||||
.done(function (data) {
|
||||
$button.removeClass('btn-success btn-default');
|
||||
|
||||
$button.find('span').hide();
|
||||
|
||||
if (data.enabled) {
|
||||
$button.find('.user-enabled').show();
|
||||
$button.addClass('btn-success');
|
||||
} else {
|
||||
$button.find('.user-disabled').show();
|
||||
$button.addClass('btn-default');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$document.on('click', '.user-password-resend', function () {
|
||||
if (! confirm("Are you sure you want to generate and resend user's password?")) {
|
||||
return;
|
||||
}
|
||||
var button = this;
|
||||
button.setAttribute('disabled', true);
|
||||
|
||||
$.ajax({
|
||||
type: 'post',
|
||||
url: button.dataset.href
|
||||
})
|
||||
.always(function () {
|
||||
button.removeAttribute('disabled');
|
||||
})
|
||||
.done(function () {
|
||||
alert('Email with new password was sent');
|
||||
});
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,3 @@
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
{{ form_end(form) }}
|
||||
@@ -0,0 +1,42 @@
|
||||
{% extends 'AdminBundle::layout-inner.html.twig' %}
|
||||
|
||||
{% block inner_content %}
|
||||
<h1>Configuration Settings</h1>
|
||||
{{- form_start(form) -}}
|
||||
|
||||
<div>
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
{%- for section in form -%}
|
||||
<li role="presentation" {% if loop.first -%}class="active"{%- endif -%}>
|
||||
<a href="#section_{{- loop.index0 -}}" aria-controls="home" role="tab" data-toggle="tab">
|
||||
{{- section.vars.name -}}
|
||||
</a>
|
||||
</li>
|
||||
{%- endfor -%}
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
{%- for section in form -%}
|
||||
<div role="tabpanel" class="tab-pane {% if loop.first -%}active{%- endif -%}" id="section_{{- loop.index0 -}}">
|
||||
<table class="table table-bordered">
|
||||
{%- for parameter in section -%}
|
||||
<tr>
|
||||
<td>
|
||||
{{- form_label(parameter) -}}
|
||||
{{- form_errors(parameter) -}}
|
||||
</td>
|
||||
<td>
|
||||
{{- form_widget(parameter) -}}
|
||||
</td>
|
||||
</tr>
|
||||
{%- endfor -%}
|
||||
</table>
|
||||
</div>
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success">Save</button>
|
||||
|
||||
{{- form_end(form) -}}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,14 @@
|
||||
{% extends "AdminBundle::layout-inner.html.twig" %}
|
||||
|
||||
{% block inner_content %}
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<h1>Dashboard</h1>
|
||||
</div>
|
||||
<div class="col-xs-3 col-xs-offset-6">
|
||||
<a href="{{ path('admin_logging_out') }}">
|
||||
logout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,59 @@
|
||||
{% extends "AdminBundle::layout-inner.html.twig" %}
|
||||
|
||||
{% block inner_content %}
|
||||
<h1>
|
||||
Organization {{ organization.name -}}
|
||||
</h1>
|
||||
|
||||
{{- form_start(form) -}}
|
||||
{{- form_widget(form) -}}
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(subscriptions, 'Address', 'Subscription.organizationAddress') }}
|
||||
</th>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(subscriptions, 'Owner', 'Owner.email') }}
|
||||
</th>
|
||||
<th>
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%- for subscription in subscriptions -%}
|
||||
<tr>
|
||||
<td>
|
||||
{{- subscription.organizationAddress -}}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ path('admin_user_master_show', { 'id': subscription.owner.id }) }}">
|
||||
{{- subscription.owner.email -}}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" data-href="{{- path('admin_organization_subscriptiondelete', {
|
||||
'organizationId': organization.id,
|
||||
'subscriptionId': subscription.id
|
||||
}) -}}" class="btn btn-danger btn-block btn-delete">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
{%- endfor -%}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="navigation">
|
||||
{{ knp_pagination_render(subscriptions) }}
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success">Edit</button>
|
||||
<a class="btn btn-default" href="{{- path('admin_organization_index') -}}">Back</a>
|
||||
{{- form_end(form) -}}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{- parent() -}}
|
||||
<script src="{{ asset('/bundles/admin/js/common_crud.js') }}"></script>
|
||||
{% endblock javascripts %}
|
||||
@@ -0,0 +1,58 @@
|
||||
{% extends "AdminBundle::layout-inner.html.twig" %}
|
||||
|
||||
{% block inner_content %}
|
||||
<h1>
|
||||
Organizations
|
||||
</h1>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(organizations, 'Name', 'Organization.name') }}
|
||||
</th>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(organizations, 'Departments count', 'subscriptionCount') }}
|
||||
</th>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(organizations, 'Total user count', 'usersCount') }}
|
||||
</th>
|
||||
<th>
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{%- for organization in organizations -%}
|
||||
{%- set path = path('admin_organization_edit', { 'id': organization.id }) -%}
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ path }}">{{- organization.name -}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{- organization.subscriptionCount -}}
|
||||
</td>
|
||||
<td>
|
||||
{{- organization.usersCount -}}
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-info btn-block" href="{{ path }}">Edit</a>
|
||||
<button type="button" class="btn btn-danger btn-block btn-delete" data-href="{{ path('admin_organization_delete', { 'id': organization.id }) }}">
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{%- endfor -%}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="navigation">
|
||||
{{ knp_pagination_render(organizations) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{- parent() -}}
|
||||
<script src="{{ asset('/bundles/admin/js/common_crud.js') }}"></script>
|
||||
{% endblock javascripts %}
|
||||
@@ -0,0 +1,155 @@
|
||||
{% extends "AdminBundle::layout-inner.html.twig" %}
|
||||
|
||||
{% block stylesheets %}
|
||||
{{- parent() -}}
|
||||
<style>
|
||||
.payment-details {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
{% endblock stylesheets %}
|
||||
|
||||
{% block inner_content %}
|
||||
<h1>
|
||||
Payments
|
||||
</h1>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 1%">#</th>
|
||||
<th>
|
||||
{{- knp_pagination_sortable(payments, 'Date', 'Payment.createdAt') -}}
|
||||
</th>
|
||||
<th>
|
||||
{{- knp_pagination_sortable(payments, 'User', 'Owner.email') -}}
|
||||
</th>
|
||||
<th>
|
||||
{{- knp_pagination_sortable(payments, 'Amount', 'Payment.amount.amoun') -}}
|
||||
</th>
|
||||
<th>
|
||||
{{- knp_pagination_sortable(payments, 'Currency', 'Payment.amount.currency') -}}
|
||||
</th>
|
||||
<th>
|
||||
{{- knp_pagination_sortable(payments, 'Status', 'Payment.status') -}}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for payment in payments %}
|
||||
<tr>
|
||||
<td>
|
||||
<a class="more" href="#" data-target="#payment_{{- payment.id -}}">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{- payment.createdAt|date('Y-m-d H:i:s') -}}
|
||||
</td>
|
||||
<td>
|
||||
{{- payment.subscription.owner.email -}}
|
||||
</td>
|
||||
<td>
|
||||
{{- payment.amount.amount|number_format(2) -}}
|
||||
</td>
|
||||
<td>
|
||||
{{- payment.amount.currency -}}
|
||||
</td>
|
||||
<td>
|
||||
{{- payment.status.value -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="payment_{{- payment.id -}}" class="payment-details">
|
||||
<td colspan="6">
|
||||
{%- set subscriptionType = payment.subscription.subscriptionType -%}
|
||||
<h3>Subscription</h3>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
Type
|
||||
</th>
|
||||
<td>
|
||||
{{- subscriptionType.value is constant('ORGANIZATION', subscriptionType) ? 'organization' : 'personal' -}}
|
||||
</td>
|
||||
</tr>
|
||||
{%- if subscriptionType.value is constant('ORGANIZATION', subscriptionType) -%}
|
||||
<tr>
|
||||
<th>
|
||||
Name
|
||||
</th>
|
||||
<td>
|
||||
{{- payment.subscription.getOrganization.name -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Department address
|
||||
</th>
|
||||
<td>
|
||||
{{- payment.subscription.organizationAddress -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Department email
|
||||
</th>
|
||||
<td>
|
||||
{{- payment.subscription.organizationEmail -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Department phone
|
||||
</th>
|
||||
<td>
|
||||
{{- payment.subscription.organizationPhone -}}
|
||||
</td>
|
||||
</tr>
|
||||
{%- endif -%}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">No data.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination">
|
||||
{{- knp_pagination_render(payments) -}}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{- parent() -}}
|
||||
<script>
|
||||
(function ($) {
|
||||
$(function() {
|
||||
$('.more').click(function () {
|
||||
var $this = $(this);
|
||||
var details = $($this.data('target'));
|
||||
|
||||
if (details.hasClass('open')) {
|
||||
details
|
||||
.removeClass('open')
|
||||
.hide();
|
||||
$this.find('i')
|
||||
.removeClass('fa-minus')
|
||||
.addClass('fa-plus');
|
||||
} else {
|
||||
details
|
||||
.addClass('open')
|
||||
.show();
|
||||
$this.find('i')
|
||||
.removeClass('fa-plus')
|
||||
.addClass('fa-minus');
|
||||
}
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
||||
</script>
|
||||
{% endblock javascripts %}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% extends "AdminBundle::layout-inner.html.twig" %}
|
||||
|
||||
{% block inner_content %}
|
||||
<h1>
|
||||
Edit {{ plan.title }} billing plan
|
||||
</h1>
|
||||
{{- form_start(form) -}}
|
||||
<h3>Payment information</h3>
|
||||
{{- form_widget(form.price) -}}
|
||||
|
||||
<hr>
|
||||
<h3>Restrictions</h3>
|
||||
{{- form_rest(form) -}}
|
||||
<div class="btn-group">
|
||||
<button type="submit" class="btn btn-success">Save</button>
|
||||
<a class="btn btn-default" href="{{- path('admin_plan_index') -}}">Back</a>
|
||||
</div>
|
||||
{{- form_end(form) -}}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,30 @@
|
||||
{% extends "AdminBundle::layout-inner.html.twig" %}
|
||||
|
||||
{% block inner_content %}
|
||||
<h1>
|
||||
Billing plans
|
||||
</h1>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Price</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for plan in plans %}
|
||||
<tr>
|
||||
<td>{{ plan.title }}</td>
|
||||
<td>
|
||||
$ {{ plan.price|number_format(2) -}}
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-info btn-block" href="{{ path('admin_plan_edit', { 'id': plan.id }) }}">Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,11 @@
|
||||
{% extends "AdminBundle::layout.html.twig" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ include("AdminBundle:Security:login_content.html.twig", {
|
||||
"error": error,
|
||||
"last_username": last_username,
|
||||
"csrf_token": csrf_token
|
||||
}) }}
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,37 @@
|
||||
{% trans_default_domain 'FOSUserBundle' %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3">
|
||||
<div style="padding: 20px;margin-top: 15%;background-color: #fce2d9;">
|
||||
<div style="padding: 10px 0;text-align: center;font-size: 20px;">
|
||||
Administrative panel
|
||||
</div>
|
||||
{% if error %}
|
||||
<div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
|
||||
{% endif %}
|
||||
|
||||
<form class="form" action="{{ path("admin_login_check") }}" method="post">
|
||||
{% if csrf_token %}
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token }}" />
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="username">{{ 'security.login.username'|trans }}</label>
|
||||
<input class="form-control" type="text" id="username" name="_username" value="{{ last_username }}" required="required" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">{{ 'security.login.password'|trans }}</label>
|
||||
<input class="form-control" type="password" id="password" name="_password" required="required" />
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<input type="checkbox" id="remember_me" name="_remember_me" value="on" />
|
||||
<label for="remember_me">{{ 'security.login.remember_me'|trans }}</label>
|
||||
</div>
|
||||
|
||||
<input type="submit" class="btn btn-default" id="_submit" name="_submit" value="{{ 'security.login.submit'|trans }}" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
{% extends 'AdminBundle:User/Layout:edit_layout.html.twig' %}
|
||||
|
||||
{% block edit_title %}
|
||||
Administrator edit
|
||||
{% endblock edit_title %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% extends 'AdminBundle:User/Layout:index_layout.html.twig' %}
|
||||
|
||||
{% block index_title %}
|
||||
Administrators list
|
||||
{% endblock index_title %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% extends 'AdminBundle:User/Layout:new_layout.html.twig' %}
|
||||
|
||||
{% block new_title %}
|
||||
Administrator creation
|
||||
{% endblock new_title %}
|
||||
@@ -0,0 +1,24 @@
|
||||
{% extends 'AdminBundle:User/Layout:show_layout.html.twig' %}
|
||||
|
||||
{% block show_title %}
|
||||
Administrator
|
||||
{% endblock show_title %}
|
||||
|
||||
{% block show_content %}
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<td>{{ user.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Firstname</th>
|
||||
<td>{{ user.firstName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Lastname</th>
|
||||
<td>{{ user.lastName }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock show_content %}
|
||||
@@ -0,0 +1,20 @@
|
||||
{% extends 'AdminBundle::layout-inner.html.twig' %}
|
||||
|
||||
{% block inner_content %}
|
||||
<h1>
|
||||
{%- block edit_title -%}
|
||||
{%- endblock edit_title -%}
|
||||
</h1>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
<div>
|
||||
<div style="display: inline-block">
|
||||
<input type="submit" class="btn btn-success" value="Save" />
|
||||
<a class="btn btn-default" href="{{ indexUrl }}">Back to the list</a>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,92 @@
|
||||
{% extends 'AdminBundle::layout-inner.html.twig' %}
|
||||
|
||||
{% block inner_content %}
|
||||
<h1>
|
||||
{%- block index_title -%}
|
||||
{%- endblock index_title -%}
|
||||
</h1>
|
||||
|
||||
<div class="row" style="margin-bottom: 20px;">
|
||||
<div class="col-xs-3 col-xs-offset-9">
|
||||
{{ form_start(search) }}
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
name="{{ search.query.vars.full_name }}"
|
||||
class="form-control"
|
||||
value="{{ search.query.vars.value }}"
|
||||
/>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="submit">Go!</button>
|
||||
</span>
|
||||
</div><!-- /input-group -->
|
||||
{{ form_end(search, {'render_rest': false}) }}
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ knp_pagination_sortable(users, 'Email', 'User.email') }}</th>
|
||||
<th>{{ knp_pagination_sortable(users, 'First Name', 'User.firstName') }}</th>
|
||||
<th>{{ knp_pagination_sortable(users, 'Last Name', 'User.lastName') }}</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>{{ user.firstName }}</td>
|
||||
<td>{{ user.lastName }}</td>
|
||||
<td>
|
||||
{%- block index_row_buttons -%}
|
||||
<a class="btn btn-info btn-block" href="{{ path(showRoute, { 'id': user.id }) }}">Show</a>
|
||||
<a class="btn btn-warning btn-block" href="{{ path(editRoute, { 'id': user.id }) }}">Edit</a>
|
||||
<button data-href="{{ path(deleteRoute, {'id': user.id}) }}" class="btn btn-danger btn-block btn-delete">
|
||||
Delete
|
||||
</button>
|
||||
<button data-href="{{ path(changeEnabledRoute, {'id': user.id}) }}" class="btn
|
||||
btn-block
|
||||
{% if user.isEnabled %}
|
||||
btn-success
|
||||
{% else %}
|
||||
btn-default
|
||||
{% endif %}
|
||||
user-change-status">
|
||||
<span class="user-enabled"
|
||||
{% if not user.isEnabled %}
|
||||
style="display: none"
|
||||
{% endif %}
|
||||
>Block</span>
|
||||
<span class="user-disabled"
|
||||
{% if user.isEnabled %}
|
||||
style="display: none"
|
||||
{% endif %}
|
||||
>Unblock</span>
|
||||
</button>
|
||||
{%- endblock index_row_buttons -%}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="navigation">
|
||||
{{ knp_pagination_render(users) }}
|
||||
</div>
|
||||
|
||||
{%- block inner_footer -%}
|
||||
<ul>
|
||||
<li>
|
||||
<a href="{{ newUrl }}">Create a new user</a>
|
||||
</li>
|
||||
</ul>
|
||||
{%- endblock inner_footer -%}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{- parent() -}}
|
||||
<script src="{{ asset('/bundles/admin/js/common_crud.js') }}"></script>
|
||||
<script src="{{ asset('/bundles/admin/js/user_crud.js') }}"></script>
|
||||
{% endblock javascripts %}
|
||||
@@ -0,0 +1,20 @@
|
||||
{% extends 'AdminBundle::layout-inner.html.twig' %}
|
||||
|
||||
{% block inner_content %}
|
||||
<h1>
|
||||
{%- block new_title -%}
|
||||
{%- endblock new_title -%}
|
||||
</h1>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
<div>
|
||||
<div style="display: inline-block">
|
||||
<input type="submit" class="btn btn-success" value="Save" />
|
||||
<a class="btn btn-default" href="{{ indexUrl }}">Back to the list</a>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,23 @@
|
||||
{% extends 'AdminBundle::layout-inner.html.twig' %}
|
||||
|
||||
{% block inner_content %}
|
||||
<h1>
|
||||
{%- block show_title -%}
|
||||
{%- endblock show_title -%}
|
||||
</h1>
|
||||
|
||||
{%- block show_content -%}
|
||||
{%- endblock show_content -%}
|
||||
|
||||
<div>
|
||||
{%- block show_buttons -%}
|
||||
<div style="display: inline-block">
|
||||
<a class="btn btn-default" href="{{ listUrl }}">Back to the list</a>
|
||||
</div>
|
||||
<div style="display: inline-block">
|
||||
<a class="btn btn-warning" href="{{ editUrl }}">Edit</a>
|
||||
<a class="btn btn-danger" href="{{ deleteUrl }}">Delete</a>
|
||||
</div>
|
||||
{%- endblock show_buttons -%}
|
||||
</div>
|
||||
{% endblock inner_content %}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% extends 'AdminBundle:User/Layout:edit_layout.html.twig' %}
|
||||
|
||||
{% block edit_title %}
|
||||
Master User edit
|
||||
{% endblock edit_title %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script src="{{ asset('/bundles/admin/js/master_crud.js') }}"></script>
|
||||
{% endblock javascripts %}
|
||||
@@ -0,0 +1,15 @@
|
||||
{% extends 'AdminBundle:User/Layout:index_layout.html.twig' %}
|
||||
|
||||
{% block index_title %}
|
||||
Master User list
|
||||
{% endblock index_title %}
|
||||
|
||||
{% block index_row_buttons %}
|
||||
{{- parent() -}}
|
||||
<button
|
||||
data-href="{{ path(resendPasswordRoute, { 'id': user.id }) }}"
|
||||
class="btn btn-default btn-block user-password-resend"
|
||||
>
|
||||
Resend password
|
||||
</button>
|
||||
{% endblock index_row_buttons %}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% extends 'AdminBundle:User/Layout:new_layout.html.twig' %}
|
||||
|
||||
{% block new_title %}
|
||||
Master User creation
|
||||
{% endblock new_title %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script src="{{ asset('/bundles/admin/js/master_crud.js') }}"></script>
|
||||
{% endblock javascripts %}
|
||||
@@ -0,0 +1,185 @@
|
||||
{% extends 'AdminBundle:User/Layout:show_layout.html.twig' %}
|
||||
|
||||
{% form_theme subscriberForm _self %}
|
||||
|
||||
{%- set billingType = user.billingSubscription.subscriptionType -%}
|
||||
|
||||
{% block show_title %}
|
||||
Master User
|
||||
{% endblock show_title %}
|
||||
|
||||
{% block show_content %}
|
||||
<h3>
|
||||
User information
|
||||
</h3>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>First name</th>
|
||||
<td>{{ user.firstName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Last name</th>
|
||||
<td>{{ user.lastName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Expiration Day
|
||||
</th>
|
||||
<td>{{ user.getExpirationDay|date("m/d/Y") }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Number of subscribers</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.getPlan.subscriberAccounts }} /
|
||||
{{ user.billingSubscription.subscriberAccounts -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Number of saved feeds allowed</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.getPlan.savedFeeds }} /
|
||||
{{ user.billingSubscription.savedFeeds -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Number of alerts allowed</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.getPlan.alerts }} /
|
||||
{{ user.billingSubscription.alerts -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Number of newsletters allowed</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.getPlan.newsletters }} /
|
||||
{{ user.billingSubscription.newsletters -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Number of ad-hoc searches per day allowed</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.getPlan.searchesPerDay }} /
|
||||
{{ user.billingSubscription.searchesPerDay -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Billing subscription type
|
||||
</th>
|
||||
<td>
|
||||
{{- billingType.value is constant('ORGANIZATION', billingType) ? 'organization' : 'personal' -}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{%- if billingType.value is constant('ORGANIZATION', billingType) -%}
|
||||
<h3>
|
||||
Organization information
|
||||
</h3>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
Name
|
||||
</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.getOrganization.name -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Department address
|
||||
</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.organizationAddress -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Department email
|
||||
</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.organizationEmail -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Department phone
|
||||
</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.organizationPhone -}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{%- endif -%}
|
||||
|
||||
<h3>
|
||||
Subscribers
|
||||
</h3>
|
||||
|
||||
<ul>
|
||||
{% for subscriber in user.getSubscribers %}
|
||||
<li>
|
||||
<a href="{{ path('admin_user_subscriber_show', {'id': subscriber.id}) }}">
|
||||
{{ subscriber.firstName }} {{ subscriber.lastName }} ({{ subscriber.email }})
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{# Add subscribers on MasterUser page Modal #}
|
||||
<div class="modal fade" id="subscriber-modal" tabindex="-1" role="dialog" aria-labelledby="add_subscriber">
|
||||
{{- form_start(subscriberForm) -}}
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="add_subscriber_header">
|
||||
Subscriber creation
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{- form_widget(subscriberForm) -}}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- form_end(subscriberForm) -}}
|
||||
</div>
|
||||
|
||||
{% endblock show_content %}
|
||||
|
||||
{% block show_buttons %}
|
||||
{{- parent() -}}
|
||||
<div style="display: inline-block">
|
||||
<button type="button" id="subscriber-add" class="btn btn-success">Add Subscriber</button>
|
||||
</div>
|
||||
{% endblock show_buttons %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
<script src="{{ asset('/bundles/admin/js/master_crud.js') }}"></script>
|
||||
{% endblock javascripts %}
|
||||
|
||||
{% block form_errors -%}
|
||||
<span class="help-block">
|
||||
<ul class="list-unstyled">
|
||||
{%- for error in errors -%}
|
||||
<li><span class="glyphicon glyphicon-exclamation-sign"></span> {{ error.message }}</li>
|
||||
{%- endfor -%}
|
||||
</ul>
|
||||
</span>
|
||||
{%- endblock form_errors %}
|
||||
|
||||
{%- block hidden_widget -%}
|
||||
<div>
|
||||
{%- set type = type|default('hidden') -%}
|
||||
{{ block('form_widget_simple') }}
|
||||
</div>
|
||||
{%- endblock hidden_widget -%}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% extends 'AdminBundle:User/Layout:edit_layout.html.twig' %}
|
||||
|
||||
{% block edit_title %}
|
||||
Subscriber edit
|
||||
{% endblock edit_title %}
|
||||
@@ -0,0 +1,15 @@
|
||||
{% extends 'AdminBundle:User/Layout:index_layout.html.twig' %}
|
||||
|
||||
{% block index_title %}
|
||||
Subscribers list
|
||||
{% endblock index_title %}
|
||||
|
||||
{% block index_row_buttons %}
|
||||
{{- parent() -}}
|
||||
<button
|
||||
data-href="{{ path(resendPasswordRoute, { 'id': user.id }) }}"
|
||||
class="btn btn-default btn-block user-password-resend"
|
||||
>
|
||||
Resend password
|
||||
</button>
|
||||
{% endblock index_row_buttons %}
|
||||
@@ -0,0 +1,5 @@
|
||||
{% extends 'AdminBundle:User/Layout:new_layout.html.twig' %}
|
||||
|
||||
{% block new_title %}
|
||||
Subscriber creation
|
||||
{% endblock new_title %}
|
||||
@@ -0,0 +1,129 @@
|
||||
{% extends 'AdminBundle:User/Layout:show_layout.html.twig' %}
|
||||
|
||||
{%- set billingType = user.billingSubscription.subscriptionType -%}
|
||||
|
||||
{% block show_title %}
|
||||
Subscriber
|
||||
{% endblock show_title %}
|
||||
|
||||
{% block show_content %}
|
||||
<h3>
|
||||
User information
|
||||
</h3>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Firstname</th>
|
||||
<td>{{ user.firstName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Lastname</th>
|
||||
<td>{{ user.lastName }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Position
|
||||
</th>
|
||||
<td>
|
||||
{{ user.position }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Phone Number
|
||||
</th>
|
||||
<td>
|
||||
{{ user.phoneNumber }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Number of saved feeds allowed</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.getPlan.savedFeeds }} /
|
||||
{{ user.billingSubscription.savedFeeds -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Number of alerts allowed</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.getPlan.alerts }} /
|
||||
{{ user.billingSubscription.alerts -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Number of newsletters allowed</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.getPlan.newsletters }} /
|
||||
{{ user.billingSubscription.newsletters -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Number of ad-hoc searches per day allowed</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.getPlan.searchesPerDay }} /
|
||||
{{ user.billingSubscription.searchesPerDay -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Billing subscription type
|
||||
</th>
|
||||
<td>
|
||||
{{- billingType.value is constant('ORGANIZATION', billingType) ? 'organization' : 'personal' -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Master
|
||||
</th>
|
||||
<td>
|
||||
<a href="{{ path('admin_user_master_show', { 'id': user.masterUser.id }) }}">
|
||||
{{- user.masterUser.email -}}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{%- if billingType.value is constant('ORGANIZATION', billingType) -%}
|
||||
<h3>
|
||||
Organization information
|
||||
</h3>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
Name
|
||||
</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.getOrganization.name -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Department address
|
||||
</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.organizationAddress -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Department email
|
||||
</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.organizationEmail -}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Department phone
|
||||
</th>
|
||||
<td>
|
||||
{{- user.billingSubscription.organizationPhone -}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{%- endif -%}
|
||||
{% endblock show_content %}
|
||||
@@ -0,0 +1,12 @@
|
||||
{% extends 'AdminBundle::layout-inner.html.twig' %}
|
||||
|
||||
{% block inner_content %}
|
||||
<h1>Administrator edit</h1>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
<input type="submit" class="btn btn-success" value="Save" />
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,40 @@
|
||||
{% extends 'AdminBundle::layout-inner.html.twig' %}
|
||||
|
||||
{% block inner_content %}
|
||||
<h1>
|
||||
Not verified users
|
||||
</h1>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ knp_pagination_sortable(users, 'Email', 'User.email') }}</th>
|
||||
<th>{{ knp_pagination_sortable(users, 'First Name', 'User.firstName') }}</th>
|
||||
<th>{{ knp_pagination_sortable(users, 'Last Name', 'User.lastName') }}</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>{{ user.firstName }}</td>
|
||||
<td>{{ user.lastName }}</td>
|
||||
<td>
|
||||
<a class="btn btn-info btn-block" href="{{-
|
||||
path('admin_userverification_show', { 'id': user.id })
|
||||
-}}">Show</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">No data.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="navigation">
|
||||
{{ knp_pagination_render(users) }}
|
||||
</div>
|
||||
{% endblock inner_content %}
|
||||
@@ -0,0 +1,46 @@
|
||||
{% extends 'AdminBundle::layout-inner.html.twig' %}
|
||||
|
||||
{% block inner_content %}
|
||||
<h1>
|
||||
Verify user {{ user.fullName }}
|
||||
</h1>
|
||||
|
||||
<h3>
|
||||
User information
|
||||
</h3>
|
||||
<div>
|
||||
<div><b>Full name:</b> {{ user.fullName }}</div>
|
||||
<div><b>Email:</b> {{ user.email }}</div>
|
||||
<div><b>Phone:</b> {{ user.phoneNumber }}</div>
|
||||
</div>
|
||||
|
||||
<h3>
|
||||
Billing information
|
||||
</h3>
|
||||
<div>
|
||||
<div><b>Subscription type:</b> {{ subscription.subscriptionType.value }}</div>
|
||||
<div><b>Plan:</b> {{ plan.name }}</div>
|
||||
</div>
|
||||
|
||||
{%- if organization is not null -%}
|
||||
<h3>
|
||||
Organization information
|
||||
</h3>
|
||||
|
||||
<div>
|
||||
<div><b>Name: </b> {{ organization.name }}</div>
|
||||
<div><b>Address: </b> {{ subscription.organizationAddress }}</div>
|
||||
<div><b>Email: </b> {{ subscription.organizationEmail }}</div>
|
||||
<div><b>Phone: </b> {{ subscription.organizationPhone }}</div>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
||||
<hr>
|
||||
|
||||
<form action="{{ path('admin_userverification_show', { 'id': user.id }) }}" method="post">
|
||||
<div>
|
||||
<button name="verify" type="submit" class="btn btn-success">Verify</button>
|
||||
<button name="reject" type="submit" class="btn btn-danger">Reject</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock inner_content %}
|
||||
@@ -0,0 +1,66 @@
|
||||
{% extends "AdminBundle::layout.html.twig" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-2">
|
||||
<div>
|
||||
<a href="{{ path('admin_dashboard') }}" class="list-group-item">
|
||||
Dashboard
|
||||
</a>
|
||||
<hr>
|
||||
{% if is_granted('ROLE_SUPER_ADMIN') %}
|
||||
<a href="{{ path('admin_configuration_index') }}" class="list-group-item">
|
||||
Configuration
|
||||
</a>
|
||||
<hr>
|
||||
<a href="{{ path('admin_plan_index') }}" class="list-group-item">
|
||||
Billing plans
|
||||
</a>
|
||||
<a href="{{ path('admin_payment_index') }}" class="list-group-item">
|
||||
Payments
|
||||
</a>
|
||||
<hr>
|
||||
<a href="{{ path('admin_user_superadmin_edit') }}" class="list-group-item">
|
||||
Super admin edit
|
||||
</a>
|
||||
<a href="{{ path('admin_user_admin_index') }}" class="list-group-item">
|
||||
Admins
|
||||
</a>
|
||||
<hr>
|
||||
{% endif %}
|
||||
<a href="{{ path('admin_organization_index') }}" class="list-group-item">
|
||||
Organizations
|
||||
</a>
|
||||
<hr>
|
||||
<a href="{{ path('admin_userverification_index') }}" class="list-group-item">
|
||||
Not verified users
|
||||
</a>
|
||||
<a href="{{ path('admin_user_master_index') }}" class="list-group-item">
|
||||
Master Users
|
||||
</a>
|
||||
<a href="{{ path('admin_user_subscriber_index') }}" class="list-group-item">
|
||||
Subscribers
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-10">
|
||||
{%- for message in app.session.flashBag.get('admin_success') -%}
|
||||
<div class="alert alert-success">
|
||||
{{- message -}}
|
||||
</div>
|
||||
{%- endfor -%}
|
||||
|
||||
|
||||
{%- for message in app.session.flashBag.get('admin_error') -%}
|
||||
<div class="alert alert-error">
|
||||
{{- message -}}
|
||||
</div>
|
||||
{%- endfor -%}
|
||||
|
||||
{%- block inner_content -%}
|
||||
{%- endblock -%}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,24 @@
|
||||
{% extends "::base.html.twig" %}
|
||||
|
||||
{%- block stylesheets -%}
|
||||
{{ parent() }}
|
||||
<link rel="stylesheet" href="{{ asset('libs/css/bootstrap.min.css') }}"/>
|
||||
<link rel="stylesheet" href="{{ asset('libs/css/font-awesome.min.css') }}"/>
|
||||
<style>
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
</style>
|
||||
{%- endblock -%}
|
||||
|
||||
{% block body %}
|
||||
<div class="container" style="margin-top: 30px;">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
|
||||
<script src="{{ asset('libs/js/bootstrap.js') }}"></script>
|
||||
{% endblock javascripts %}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace AdminBundle\Twig;
|
||||
|
||||
use UserBundle\Entity\User;
|
||||
use UserBundle\Enum\UserRoleEnum;
|
||||
|
||||
/**
|
||||
* Class AdminTwigExtension
|
||||
*
|
||||
* @package AdminBundle\Twig
|
||||
*/
|
||||
class AdminTwigExtension extends \Twig_Extension
|
||||
{
|
||||
|
||||
/**
|
||||
* Map between role name and human readable role title.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $rolesMap = [
|
||||
UserRoleEnum::ADMIN => 'Admin',
|
||||
UserRoleEnum::MASTER_USER => 'Master User',
|
||||
UserRoleEnum::SUPER_ADMIN => 'Super Admin',
|
||||
UserRoleEnum::SUBSCRIBER => 'Subscriber',
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns a list of filters to add to the existing list.
|
||||
*
|
||||
* @return \Twig_SimpleFilter[]
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return [
|
||||
new \Twig_SimpleFilter('humanReadableRoles', function (User $user) {
|
||||
$roles = $user->getRoles();
|
||||
$result = [];
|
||||
foreach ($roles as $role) {
|
||||
if (array_key_exists($role, self::$rolesMap)) {
|
||||
$result[$role] = self::$rolesMap[$role];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle;
|
||||
|
||||
use ApiBundle\DependencyInjection\Compiler\InspectorsCollectCompilerPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
/**
|
||||
* Class ApiBundle
|
||||
* @package ApiBundle
|
||||
*/
|
||||
class ApiBundle extends Bundle
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the bundle.
|
||||
*
|
||||
* It is only ever called once when the cache is empty.
|
||||
*
|
||||
* @param ContainerBuilder $container A ContainerBuilder instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function build(ContainerBuilder $container)
|
||||
{
|
||||
parent::build($container);
|
||||
$container->addCompilerPass(new InspectorsCollectCompilerPass());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle;
|
||||
|
||||
/**
|
||||
* Class ApiBundleServices
|
||||
* @package ApiBundle
|
||||
*/
|
||||
class ApiBundleServices
|
||||
{
|
||||
|
||||
/**
|
||||
* Check access to entity for current user.
|
||||
*
|
||||
* Must implements {@see \ApiBundle\Security\AccessChecker\AccessCheckerInterface}
|
||||
* interface.
|
||||
*/
|
||||
const ACCESS_CHECKER = 'api.access_checker';
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Controller;
|
||||
|
||||
use ApiBundle\ApiBundleServices;
|
||||
use ApiBundle\Response\View;
|
||||
use ApiBundle\Security\AccessChecker\AccessCheckerInterface;
|
||||
use Doctrine\Bundle\DoctrineBundle\Registry;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractApiController
|
||||
* @package ApiBundle\Controller
|
||||
*/
|
||||
class AbstractApiController
|
||||
{
|
||||
|
||||
/**
|
||||
* Default page value in pagination if 'page' filter not provided.
|
||||
*/
|
||||
const DEFAULT_PAGE = 1;
|
||||
|
||||
/**
|
||||
* Default limit (max entities per page) value in pagination if 'limit'
|
||||
* filter not provided.
|
||||
*/
|
||||
const DEFAULT_LIMIT = 100;
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container A ContainerInterface instance.
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get service from container.
|
||||
*
|
||||
* @param string $id The service identifier.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
protected function get($id)
|
||||
{
|
||||
return $this->container->get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a container configuration parameter by its name.
|
||||
*
|
||||
* @param string $name The parameter name.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getParameter($name)
|
||||
{
|
||||
return $this->container->getParameter($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a Form instance from the type of the form.
|
||||
*
|
||||
* @param string $type The fully qualified class name of the form type.
|
||||
* @param mixed $data The initial data for the form.
|
||||
* @param array $options Options for the form.
|
||||
*
|
||||
* @return FormInterface
|
||||
*/
|
||||
protected function createForm($type, $data = null, array $options = [])
|
||||
{
|
||||
return $this->container->get('form.factory')
|
||||
->create($type, $data, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return default entity manager.
|
||||
*
|
||||
* @return ObjectManager
|
||||
*/
|
||||
protected function getManager()
|
||||
{
|
||||
/** @var Registry $doctrine */
|
||||
$doctrine = $this->container->get('doctrine');
|
||||
|
||||
$manager = $doctrine->getManager();
|
||||
|
||||
if (! $manager instanceof ObjectManager) {
|
||||
throw new \LogicException('Should be instance of '. ObjectManager::class);
|
||||
}
|
||||
|
||||
return $manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current User entity instance.
|
||||
*
|
||||
* @return \UserBundle\Entity\User
|
||||
*/
|
||||
protected function getCurrentUser()
|
||||
{
|
||||
/** @var \Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface $storage */
|
||||
$storage = $this->container->get('security.token_storage');
|
||||
|
||||
return $storage->getToken()->getUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action Action name.
|
||||
* @param object|object[] $entity A Entity instance or array of entity instances.
|
||||
*
|
||||
* @return string[] Array of restriction reasons.
|
||||
*/
|
||||
protected function checkAccess($action, $entity)
|
||||
{
|
||||
/** @var AccessCheckerInterface $checker */
|
||||
$checker = $this->container->get(ApiBundleServices::ACCESS_CHECKER);
|
||||
|
||||
if ($entity instanceof \Traversable) {
|
||||
$entity = iterator_to_array($entity);
|
||||
} elseif (is_object($entity)) {
|
||||
$entity = [ $entity ];
|
||||
}
|
||||
|
||||
if (! is_array($entity)) {
|
||||
throw new \InvalidArgumentException('Expects single object or array of objects.');
|
||||
}
|
||||
|
||||
$grantChecker = \nspl\f\partial([ $checker, 'isGranted' ], $action);
|
||||
|
||||
return \nspl\a\flatten(\nspl\a\map($grantChecker, $entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate proper server response.
|
||||
*
|
||||
* @param mixed $data A data sent to client.
|
||||
* @param integer $code Response http code.
|
||||
* @param array $groups Serialization groups.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
protected function generateResponse(
|
||||
$data = null,
|
||||
$code = null,
|
||||
array $groups = []
|
||||
) {
|
||||
return new View($data, $groups, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate given data.
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
* @param mixed $results Any values which have proper pagination listener.
|
||||
* @param integer $defaultLimit Default limit.
|
||||
*
|
||||
* @return \Knp\Component\Pager\Pagination\PaginationInterface
|
||||
*/
|
||||
protected function paginate(Request $request, $results, $defaultLimit = self::DEFAULT_LIMIT)
|
||||
{
|
||||
/** @var PaginatorInterface $paginator */
|
||||
$paginator = $this->get('knp_paginator');
|
||||
|
||||
$page = $request->query->getInt('page', self::DEFAULT_PAGE);
|
||||
$limit = $request->query->getInt('limit', $defaultLimit);
|
||||
|
||||
return $paginator->paginate($results, $page, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards the request to another controller.
|
||||
*
|
||||
* @param string $controller The controller name (a string like BlogBundle:Post:index).
|
||||
* @param array $path An array of path parameters.
|
||||
* @param array $query An array of query parameters.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response A Response instance
|
||||
*/
|
||||
protected function forward($controller, array $path = [], array $query = [])
|
||||
{
|
||||
$request = $this->container->get('request_stack')->getCurrentRequest();
|
||||
$path['_forwarded'] = $request->attributes;
|
||||
$path['_controller'] = $controller;
|
||||
$subRequest = $request->duplicate($query, null, $path);
|
||||
|
||||
return $this->container
|
||||
->get('http_kernel')
|
||||
->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Controller;
|
||||
|
||||
use ApiBundle\Entity\ManageableEntityInterface;
|
||||
use ApiBundle\Form\EntitiesBatchType;
|
||||
use ApiBundle\Security\Inspector\InspectorInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Class AbstractApiController
|
||||
* @package AppBundle\Controller
|
||||
*/
|
||||
class AbstractCRUDController extends AbstractApiController
|
||||
{
|
||||
|
||||
/**
|
||||
* Entity fqcn.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container A ContainerInterface instance.
|
||||
* @param string $entity Managed entity fqcn.
|
||||
*/
|
||||
public function __construct(
|
||||
ContainerInterface $container,
|
||||
$entity
|
||||
) {
|
||||
parent::__construct($container);
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new entity.
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
* @param ManageableEntityInterface $entity A ManageableEntityInterface
|
||||
* instance.
|
||||
*
|
||||
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
protected function createEntity(Request $request, ManageableEntityInterface $entity)
|
||||
{
|
||||
$form = $this->createForm($entity->getCreateFormClass(), $entity);
|
||||
|
||||
// Submit data into form.
|
||||
$form->submit($request->request->all());
|
||||
if ($form->isValid()) {
|
||||
// Check that current user can create this entity.
|
||||
// If user don't have rights to create this entity we should send all
|
||||
// founded restrictions to client.
|
||||
$reasons = $this->checkAccess(InspectorInterface::CREATE, $entity);
|
||||
if (count($reasons) > 0) {
|
||||
// User don't have rights to create this entity so send all
|
||||
// founded restriction reasons to client.
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
$em = $this->getManager();
|
||||
$em->persist($entity);
|
||||
$em->flush();
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
// Client send invalid data.
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about single entity.
|
||||
*
|
||||
* @param integer|ManageableEntityInterface|null $id A entity id.
|
||||
*
|
||||
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
protected function getEntity($id)
|
||||
{
|
||||
$foundedEntity = $id;
|
||||
if (is_numeric($id)) {
|
||||
$repository = $this->getManager()->getRepository($this->entity);
|
||||
|
||||
$foundedEntity = $repository->find($id);
|
||||
}
|
||||
|
||||
if ($foundedEntity === null) {
|
||||
$name = \app\c\getShortName($this->entity);
|
||||
// Remove 'Abstract' prefix if it exists.
|
||||
if (strpos($name, 'Abstract') !== false) {
|
||||
$name = substr($name, 8);
|
||||
}
|
||||
|
||||
return $this->generateResponse("Can't find {$name} with id {$id}.", 404);
|
||||
}
|
||||
|
||||
// Check that current user can read this entity.
|
||||
// If user don't have rights to read this entity we should send all
|
||||
// founded restrictions to client.
|
||||
$reasons = $this->checkAccess(InspectorInterface::READ, $foundedEntity);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
return $foundedEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update entity.
|
||||
*
|
||||
* @param Request $request A Request instance.
|
||||
* @param integer|ManageableEntityInterface|null $entity A entity id.
|
||||
*
|
||||
* @return \ApiBundle\Entity\ManageableEntityInterface|\ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
protected function putEntity(Request $request, $entity)
|
||||
{
|
||||
$em = $this->getManager();
|
||||
|
||||
$foundedEntity = $entity;
|
||||
if (is_numeric($entity)) {
|
||||
$repository = $em->getRepository($this->entity);
|
||||
/** @var \ApiBundle\Entity\ManageableEntityInterface $entity */
|
||||
$foundedEntity = $repository->find($entity);
|
||||
}
|
||||
|
||||
if ($foundedEntity === null) {
|
||||
$name = \app\c\getShortName($this->entity);
|
||||
// Remove 'Abstract' prefix if it exists.
|
||||
if (strpos($name, 'Abstract') !== false) {
|
||||
$name = substr($name, 8);
|
||||
}
|
||||
|
||||
return $this->generateResponse("Can't find {$name} with id {$entity}.", 404);
|
||||
}
|
||||
|
||||
$form = $this->createForm($foundedEntity->getUpdateFormClass(), $foundedEntity, [
|
||||
'method' => 'PUT',
|
||||
]);
|
||||
$form->submit($request->request->all());
|
||||
if ($form->isValid()) {
|
||||
// Check that current user can update this entity.
|
||||
// If user don't have rights to update this entity we should send all
|
||||
// founded restrictions to client.
|
||||
$reasons = $this->checkAccess(InspectorInterface::UPDATE, $foundedEntity);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
$em->persist($foundedEntity);
|
||||
$em->flush();
|
||||
|
||||
return $foundedEntity;
|
||||
}
|
||||
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete entity.
|
||||
*
|
||||
* @param integer|ManageableEntityInterface|null $entity A entity id.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
protected function deleteEntity($entity)
|
||||
{
|
||||
$em = $this->getManager();
|
||||
|
||||
$foundedEntity = $entity;
|
||||
if (is_numeric($entity)) {
|
||||
$repository = $em->getRepository($this->entity);
|
||||
/** @var \ApiBundle\Entity\ManageableEntityInterface $entity */
|
||||
$foundedEntity = $repository->find($entity);
|
||||
}
|
||||
|
||||
if ($foundedEntity === null) {
|
||||
$name = \app\c\getShortName($this->entity);
|
||||
// Remove 'Abstract' prefix if it exists.
|
||||
if (strpos($name, 'Abstract') !== false) {
|
||||
$name = substr($name, 8);
|
||||
}
|
||||
|
||||
return $this->generateResponse("Can't find {$name} with id {$entity}.", 404);
|
||||
}
|
||||
// Check that current user can delete this entity.
|
||||
// If user don't have rights to delete this entity we should send all
|
||||
// founded restrictions to client.
|
||||
$reasons = $this->checkAccess(InspectorInterface::DELETE, $foundedEntity);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
$em->remove($foundedEntity);
|
||||
$em->flush();
|
||||
|
||||
return $this->generateResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request A Request instance.
|
||||
* @param string|callable $permission A requested permission.
|
||||
* @param string $formClass Form class fqcn.
|
||||
* @param callable $processor Function which process founded entities.
|
||||
*
|
||||
* @return \ApiBundle\Response\ViewInterface
|
||||
*/
|
||||
protected function batchProcessing(
|
||||
Request $request,
|
||||
$permission,
|
||||
$formClass,
|
||||
callable $processor
|
||||
) {
|
||||
$this->checkFormClass($formClass);
|
||||
|
||||
$form = $this->createForm($formClass, null, [ 'class' => $this->entity ]);
|
||||
|
||||
$form->submit($request->request->all());
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$data = $form->getData();
|
||||
|
||||
if (is_callable($permission)) {
|
||||
$permission = call_user_func_array($permission, $data);
|
||||
}
|
||||
|
||||
if (! is_string($permission)) {
|
||||
throw new \InvalidArgumentException('$permission should be string or callable');
|
||||
}
|
||||
|
||||
$reasons = $this->checkAccess($permission, $data['entities']);
|
||||
if (count($reasons) > 0) {
|
||||
return $this->generateResponse($reasons, 403);
|
||||
}
|
||||
|
||||
$response = call_user_func_array($processor, $data);
|
||||
if ($response === null) {
|
||||
$response = $this->generateResponse();
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $this->generateResponse($form, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $formClass Form class fqcn.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function checkFormClass($formClass)
|
||||
{
|
||||
if (! is_string($formClass) || ! class_exists($formClass)) {
|
||||
throw new \InvalidArgumentException('$formClass should be fqcn');
|
||||
}
|
||||
|
||||
if (($formClass !== EntitiesBatchType::class)
|
||||
&& ! in_array(EntitiesBatchType::class, class_parents($formClass), true)) {
|
||||
throw new \InvalidArgumentException('Invalid form class '. $formClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Controller\Annotation;
|
||||
|
||||
use Common\Annotation\AbstractAppAnnotation;
|
||||
|
||||
/**
|
||||
* Class Roles
|
||||
* @package ApiBundle\Controller\Annotation
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class Roles extends AbstractAppAnnotation
|
||||
{
|
||||
|
||||
/**
|
||||
* Expected role or array of roles.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $roles;
|
||||
|
||||
/**
|
||||
* Return name of default property.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefault()
|
||||
{
|
||||
return 'roles';
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize annotation parameters.
|
||||
* Called after all parameters set in constrictor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function normalize()
|
||||
{
|
||||
$this->roles = (array) $this->roles;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\DependencyInjection\Compiler;
|
||||
|
||||
use ApiBundle\Security\Inspector\Factory\LazyInspectorFactory;
|
||||
use ApiBundle\Security\Inspector\InspectorInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
|
||||
/**
|
||||
* Class InspectorsCollectCompilerPass
|
||||
* Register inspectors into LazyInspectorFactory.
|
||||
* All inspectors must be tagged by 'socialhose.inspector'.
|
||||
*
|
||||
* @package ApiBundle\DependencyInjection\Compiler
|
||||
*/
|
||||
class InspectorsCollectCompilerPass implements CompilerPassInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* You can modify the container here before it is dumped to PHP code.
|
||||
*
|
||||
* @param ContainerBuilder $container A ContainerBuilder instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (! $container->hasDefinition('api.inspector_factory')) {
|
||||
// Work only if we have definition of inspector factory.
|
||||
return;
|
||||
}
|
||||
|
||||
$factory = $container->getDefinition('api.inspector_factory');
|
||||
if ($factory->getClass() !== LazyInspectorFactory::class) {
|
||||
// Works only for lazy inspector factory.
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all tagged inspectors and create map between supported class and
|
||||
// inspector service id.
|
||||
$inspectorsIds = [];
|
||||
$inspectors = $container->findTaggedServiceIds('socialhose.inspector');
|
||||
$inspectors = array_keys($inspectors);
|
||||
|
||||
foreach ($inspectors as $id) {
|
||||
/** @var InspectorInterface $class */
|
||||
$class = $container->getDefinition($id)->getClass();
|
||||
|
||||
$reflection = new \ReflectionClass($class);
|
||||
if (! $reflection->implementsInterface(InspectorInterface::class)) {
|
||||
// Tagged service not implements inspector interface.
|
||||
$message = "Inspector {$id} must implements "
|
||||
. InspectorInterface::class;
|
||||
throw new \InvalidArgumentException($message);
|
||||
}
|
||||
|
||||
$supported = (array) $class::supportedClass();
|
||||
foreach ($supported as $item) {
|
||||
$inspectorsIds[$item] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
// Inject founded inspectors into factory.
|
||||
$factory->replaceArgument(1, $inspectorsIds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Entity;
|
||||
|
||||
use AppBundle\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Interface ManageableEntityInterface
|
||||
* Interface for entities which can be managed by api methods.
|
||||
*
|
||||
* @package ApiBundle\Entity
|
||||
*/
|
||||
interface ManageableEntityInterface extends EntityInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Return fqcn of form used for creating this entity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCreateFormClass();
|
||||
|
||||
/**
|
||||
* Return fqcn of form used for updating this entity.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUpdateFormClass();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Entity;
|
||||
|
||||
/**
|
||||
* Interface NormalizableEntityInterface
|
||||
* @package ApiBundle\Entity
|
||||
*/
|
||||
interface NormalizableEntityInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Return metadata for current entity.
|
||||
*
|
||||
* @return \ApiBundle\Serializer\Metadata\Metadata
|
||||
*/
|
||||
public function getMetadata();
|
||||
|
||||
/**
|
||||
* Return default normalization groups.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function defaultGroups();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\EventListener;
|
||||
|
||||
use Common\Annotation\AppAnnotationInterface;
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
|
||||
|
||||
/**
|
||||
* Class AnnotationFetchListener
|
||||
* @package ApiBundle\EventListener
|
||||
*/
|
||||
class AnnotationFetchListener
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Reader
|
||||
*/
|
||||
private $reader;
|
||||
|
||||
/**
|
||||
* AnnotationFetchListener constructor.
|
||||
*
|
||||
* @param Reader $reader A Reader instance.
|
||||
*/
|
||||
public function __construct(Reader $reader)
|
||||
{
|
||||
$this->reader = $reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FilterControllerEvent $event A FilterControllerEvent instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(FilterControllerEvent $event)
|
||||
{
|
||||
$controller = $event->getController();
|
||||
|
||||
$className = ClassUtils::getClass($controller[0]);
|
||||
$class = new \ReflectionClass($className);
|
||||
$method = $class->getMethod($controller[1]);
|
||||
|
||||
$classAnnotation = $this
|
||||
->getAnnotations($this->reader->getClassAnnotations($class));
|
||||
$methodAnnotation = $this
|
||||
->getAnnotations($this->reader->getMethodAnnotations($method));
|
||||
|
||||
$annotations = array_merge($classAnnotation, $methodAnnotation);
|
||||
$request = $event->getRequest();
|
||||
foreach ($annotations as $key => $annotation) {
|
||||
$request->attributes->set($key, $annotation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get annotations from array of fetched annotations.
|
||||
*
|
||||
* @param array $annotations Array of fetched annotations.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAnnotations(array $annotations)
|
||||
{
|
||||
$cwAnnotation = [];
|
||||
foreach ($annotations as $annotation) {
|
||||
if ($annotation instanceof AppAnnotationInterface) {
|
||||
$key = '_'. strtolower(\app\c\getShortName($annotation));
|
||||
$cwAnnotation[$key] = $annotation;
|
||||
}
|
||||
}
|
||||
|
||||
return $cwAnnotation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\EventListener;
|
||||
|
||||
use ApiBundle\Entity\NormalizableEntityInterface;
|
||||
use ApiBundle\Response\ViewInterface;
|
||||
use AppBundle\HttpFoundation\AppResponse;
|
||||
use Knp\Component\Pager\Pagination\AbstractPagination;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
/**
|
||||
* Class ApiSubscriber
|
||||
* @package ApiBundle\EventListener
|
||||
*/
|
||||
class ApiSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var NormalizerInterface
|
||||
*/
|
||||
private $normalizer;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* ApiSubscriber constructor.
|
||||
*
|
||||
* @param NormalizerInterface $normalizer A NormalizerInterface instance.
|
||||
* @param LoggerInterface $logger A LoggerInterface instance.
|
||||
*/
|
||||
public function __construct(
|
||||
NormalizerInterface $normalizer,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->normalizer = $normalizer;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of event names this subscriber wants to listen to.
|
||||
*
|
||||
* The array keys are event names and the value can be:
|
||||
*
|
||||
* * The method name to call (priority defaults to 0)
|
||||
* * An array composed of the method name to call and the priority
|
||||
* * An array of arrays composed of the method names to call and
|
||||
* respective
|
||||
* priorities, or 0 if unset
|
||||
*
|
||||
* For instance:
|
||||
*
|
||||
* * array('eventName' => 'methodName')
|
||||
* * array('eventName' => array('methodName', $priority))
|
||||
* * array('eventName' => array(array('methodName1', $priority),
|
||||
* array('methodName2')))
|
||||
*
|
||||
* @return array The event names to listen to
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
KernelEvents::REQUEST => [ 'onRequest', 100 ],
|
||||
KernelEvents::VIEW => 'onView',
|
||||
KernelEvents::EXCEPTION => 'onException',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process application/json request.
|
||||
* Fetch json data, parse and store them as request parameters.
|
||||
*
|
||||
* @param GetResponseEvent $event A GetResponseEvent instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws HttpException Then receive invalid json.
|
||||
*/
|
||||
public function onRequest(GetResponseEvent $event)
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
$uri = $request->getUri();
|
||||
|
||||
$isApiMethod = (strpos($uri, '/api') !== false)
|
||||
|| (strpos($uri, '/security/') !== false);
|
||||
$content = trim($request->getContent());
|
||||
|
||||
if ($isApiMethod && (strlen($content) > 0)) {
|
||||
// Make transformation only for api methods.
|
||||
$content = json_decode($content, true);
|
||||
if (isset($content['_format'])) {
|
||||
unset($content['_format']);
|
||||
}
|
||||
|
||||
// Check json parse error.
|
||||
$code = json_last_error();
|
||||
if ($code !== JSON_ERROR_NONE) {
|
||||
$event->setResponse(AppResponse::badRequest(
|
||||
'Invalid json ('. $code .'): '. json_last_error_msg()
|
||||
));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$request->request->replace($content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GetResponseForControllerResultEvent $event A
|
||||
* GetResponseForControllerResultEvent
|
||||
* instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onView(GetResponseForControllerResultEvent $event)
|
||||
{
|
||||
// Works only if current response returned by one of api controllers.
|
||||
$uri = $event->getRequest()->getUri();
|
||||
|
||||
$isApiEndpoint = (strpos($uri, '/api') !== false) || (strpos($uri, '/security') !== false);
|
||||
if ($isApiEndpoint) {
|
||||
$result = $event->getControllerResult();
|
||||
|
||||
if ($result instanceof ViewInterface) {
|
||||
$event->setResponse($result->serialize($this->normalizer));
|
||||
} else {
|
||||
$result = $this->normalize($result);
|
||||
$event->setResponse(AppResponse::create($result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GetResponseForExceptionEvent $event A
|
||||
* GetResponseForExceptionEvent
|
||||
* instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onException(GetResponseForExceptionEvent $event)
|
||||
{
|
||||
$exception = $event->getException();
|
||||
$request = $event->getRequest();
|
||||
$uri = $request->getUri();
|
||||
|
||||
if (strpos($uri, '/api') !== false) {
|
||||
if ($exception instanceof HttpException) {
|
||||
//
|
||||
// Handle throwException which have status code.
|
||||
// We just get error message and status code, form it in proper
|
||||
// structure and send to client.
|
||||
//
|
||||
// But for 'no route found' message we create 405 response instead
|
||||
// of 404.
|
||||
//
|
||||
if ($exception->getPrevious()
|
||||
&& ($exception->getPrevious() instanceof ResourceNotFoundException)) {
|
||||
$response = AppResponse::create('Unknown method.', 405);
|
||||
} else {
|
||||
$response = AppResponse::create()
|
||||
->setStatusCode($exception->getStatusCode())
|
||||
->setData($exception->getMessage());
|
||||
}
|
||||
|
||||
$event->setResponse($response);
|
||||
} else {
|
||||
//
|
||||
// If throwException occurred in one of api methods, we log it and send
|
||||
// some message to client.
|
||||
//
|
||||
$response = AppResponse::create(null, 500);
|
||||
|
||||
$message = $exception->getMessage() . ' in '
|
||||
. $exception->getFile() . ' at ' . $exception->getLine()
|
||||
. ' occurred while processing '
|
||||
. $request->attributes->get('_controller') . ':'
|
||||
. $request->attributes->get('_action');
|
||||
|
||||
$response->setData($message);
|
||||
|
||||
// To log message we also add serialized request.
|
||||
$this->logger->error($message, [
|
||||
'trace' => $exception->getTrace(),
|
||||
]);
|
||||
|
||||
$event->setResponse($response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize response data.
|
||||
*
|
||||
* @param mixed $data Response data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function normalize($data)
|
||||
{
|
||||
$groups = [];
|
||||
|
||||
switch (true) {
|
||||
case is_array($data):
|
||||
return array_map([ $this, 'normalize' ], $data);
|
||||
|
||||
case $data instanceof AbstractPagination:
|
||||
$entity = $data->current();
|
||||
if ($entity instanceof NormalizableEntityInterface) {
|
||||
$groups = $entity->defaultGroups();
|
||||
}
|
||||
|
||||
return $this->normalizer->normalize($data, null, $groups);
|
||||
|
||||
case is_object($data):
|
||||
if ($data instanceof NormalizableEntityInterface) {
|
||||
$groups = $data->defaultGroups();
|
||||
}
|
||||
|
||||
return $this->normalizer->normalize($data, null, $groups);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\EventListener;
|
||||
|
||||
use ApiBundle\Controller\Annotation\Roles;
|
||||
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
|
||||
use Symfony\Component\Security\Core\Role\RoleInterface;
|
||||
|
||||
/**
|
||||
* Class RolesListener
|
||||
* @package ApiBundle\EventListener
|
||||
*/
|
||||
class RolesListener
|
||||
{
|
||||
|
||||
/**
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
private $storage;
|
||||
|
||||
/**
|
||||
* @var RoleHierarchyInterface
|
||||
*/
|
||||
private $hierarchy;
|
||||
|
||||
/**
|
||||
* RolesListener constructor.
|
||||
*
|
||||
* @param TokenStorageInterface $storage A TokenStorageInterface instance.
|
||||
* @param RoleHierarchyInterface $hierarchy A RoleHierarchyInterface instance.
|
||||
*/
|
||||
public function __construct(
|
||||
TokenStorageInterface $storage,
|
||||
RoleHierarchyInterface $hierarchy
|
||||
) {
|
||||
$this->storage = $storage;
|
||||
$this->hierarchy = $hierarchy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FilterControllerEvent $event A FilterControllerEvent instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(FilterControllerEvent $event)
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
$roles = $request->attributes->get('_roles');
|
||||
if (! $roles instanceof Roles) {
|
||||
return;
|
||||
}
|
||||
|
||||
$token = $this->storage->getToken();
|
||||
|
||||
$expected = (array) $roles->roles;
|
||||
$actual = array_map(function (RoleInterface $role) {
|
||||
return $role->getRole();
|
||||
}, $this->hierarchy->getReachableRoles($token->getRoles()));
|
||||
|
||||
if (count(array_diff($expected, $actual)) > 0) {
|
||||
$message = 'You don\'t have enough roles to call this method. Required '
|
||||
. implode(', ', $expected) .'.';
|
||||
throw new AccessDeniedHttpException($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Form;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* Class ActivatedEntitiesBatchType
|
||||
*
|
||||
* @package ApiBundle\Form
|
||||
*/
|
||||
class ActivatedEntitiesBatchType extends EntitiesBatchType
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
$builder->add('active', CheckboxType::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Form;
|
||||
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Class EntitiesBatchType
|
||||
*
|
||||
* Base form type for batch processing form types.
|
||||
*
|
||||
* @package ApiBundle\Form
|
||||
*/
|
||||
class EntitiesBatchType extends AbstractType implements DataMapperInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('ids', EntityType::class, [
|
||||
'class' => $options['class'],
|
||||
'multiple' => true,
|
||||
])
|
||||
->setDataMapper($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the options for this type.
|
||||
*
|
||||
* @param OptionsResolver $resolver The resolver for the options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setRequired('class');
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps properties of some data to a list of forms.
|
||||
*
|
||||
* @param mixed $data Structured data.
|
||||
* @param FormInterface[]|array $forms A list of {@link FormInterface} instances.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function mapDataToForms($data, $forms)
|
||||
{
|
||||
// Do nothing because it's not necessary.
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the data of a list of forms into the properties of some data.
|
||||
*
|
||||
* @param FormInterface[]|\Traversable $forms A list of {@link FormInterface}
|
||||
* instances.
|
||||
* @param mixed $data Structured data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mapFormsToData($forms, &$data)
|
||||
{
|
||||
/** @var FormInterface[] $forms */
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
$data = [];
|
||||
$data['entities'] = $forms['ids']->getData();
|
||||
unset($forms['ids']);
|
||||
|
||||
foreach ($forms as $form) {
|
||||
$data[$form->getName()] = $form->getData();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Form;
|
||||
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use UserBundle\Repository\NotificationRepository;
|
||||
|
||||
/**
|
||||
* Class NotificationSubscribeBatchType
|
||||
*
|
||||
* @package ApiBundle\Form
|
||||
*/
|
||||
class NotificationSubscribeBatchType extends EntitiesBatchType
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
//
|
||||
// Use custom query builder.
|
||||
//
|
||||
$idsOptions = $builder->get('ids')->getOptions();
|
||||
$idsOptions['query_builder'] = function (NotificationRepository $repository) {
|
||||
return $repository->getQueryBuilderForSubscription();
|
||||
};
|
||||
|
||||
$builder->remove('ids')->add('ids', EntityType::class, $idsOptions);
|
||||
$builder->add('subscribed', CheckboxType::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Form;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* Class PublishedEntitiesBatchType
|
||||
*
|
||||
* @package ApiBundle\Form
|
||||
*/
|
||||
class PublishedEntitiesBatchType extends EntitiesBatchType
|
||||
{
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
$builder->add('published', CheckboxType::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Form;
|
||||
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use UserBundle\Entity\Notification\Notification;
|
||||
use UserBundle\Entity\User;
|
||||
use UserBundle\Repository\NotificationRepository;
|
||||
|
||||
/**
|
||||
* Class SubscribeToNotificationsBatchType
|
||||
*
|
||||
* @package ApiBundle\Form
|
||||
*/
|
||||
class SubscribeToNotificationsBatchType extends EntitiesBatchType
|
||||
{
|
||||
|
||||
/**
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
private $storage;
|
||||
|
||||
/**
|
||||
* SubscribeToNotificationsBatchType constructor.
|
||||
*
|
||||
* @param TokenStorageInterface $storage A TokenStorageInterface instance.
|
||||
*/
|
||||
public function __construct(TokenStorageInterface $storage)
|
||||
{
|
||||
$this->storage = $storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the form.
|
||||
*
|
||||
* This method is called for each type in the hierarchy starting from the
|
||||
* top most type. Type extensions can further modify the form.
|
||||
*
|
||||
* @see FormTypeExtensionInterface::buildForm()
|
||||
*
|
||||
* @param FormBuilderInterface $builder The form builder.
|
||||
* @param array $options The options.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
parent::buildForm($builder, $options);
|
||||
|
||||
$builder->remove('ids')->add('ids', EntityType::class, [
|
||||
'class' => Notification::class,
|
||||
'multiple' => true,
|
||||
'query_builder' => function (NotificationRepository $repository) {
|
||||
$user = \app\op\invokeIf($this->storage->getToken(), 'getUser');
|
||||
if ($user instanceof User) {
|
||||
return $repository->getQueryBuilderForForm($user);
|
||||
}
|
||||
|
||||
return $repository->createQueryBuilder('Notification');
|
||||
},
|
||||
]);
|
||||
$builder->add('subscribe', CheckboxType::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
services:
|
||||
#
|
||||
# Base api controllers.
|
||||
#
|
||||
api.controller.abstract:
|
||||
class: 'ApiBundle\Controller\AbstractApiController'
|
||||
arguments:
|
||||
- '@service_container'
|
||||
abstract: true
|
||||
|
||||
#
|
||||
# Base CRUD controllers.
|
||||
#
|
||||
api.controller.abstract_crud:
|
||||
class: 'ApiBundle\Controller\AbstractCRUDController'
|
||||
arguments:
|
||||
- '@service_container'
|
||||
- # injects by concrete class.
|
||||
abstract: true
|
||||
@@ -0,0 +1,18 @@
|
||||
services:
|
||||
#
|
||||
# Inspectors factory.
|
||||
#
|
||||
api.inspector_factory:
|
||||
class: 'ApiBundle\Security\Inspector\Factory\LazyInspectorFactory'
|
||||
arguments:
|
||||
- '@service_container'
|
||||
- # injected by compiler pass.
|
||||
|
||||
#
|
||||
# Access checker.
|
||||
#
|
||||
api.access_checker:
|
||||
class: 'ApiBundle\Security\AccessChecker\AccessChecker'
|
||||
arguments:
|
||||
- '@security.token_storage'
|
||||
- '@api.inspector_factory'
|
||||
@@ -0,0 +1,56 @@
|
||||
services:
|
||||
#
|
||||
# Entity normalizer.
|
||||
#
|
||||
api.normalizer.entity:
|
||||
class: 'ApiBundle\Serializer\Normalizer\EntityNormalizer'
|
||||
tags:
|
||||
- { name: serializer.normalizer }
|
||||
|
||||
#
|
||||
# Enum normalizer.
|
||||
#
|
||||
api.normalizer.enum:
|
||||
class: 'ApiBundle\Serializer\Normalizer\EnumNormalizer'
|
||||
tags:
|
||||
- { name: serializer.normalizer }
|
||||
|
||||
#
|
||||
# ThemeOptionFont normalizer.
|
||||
#
|
||||
api.normalizer.theme_option_font:
|
||||
class: 'ApiBundle\Serializer\Normalizer\ThemeOptionFontNormalizer'
|
||||
tags:
|
||||
- { name: serializer.normalizer }
|
||||
|
||||
#
|
||||
# Form normalizer.
|
||||
#
|
||||
api.normalizer.form:
|
||||
class: 'ApiBundle\Serializer\Normalizer\FormNormalizer'
|
||||
tags:
|
||||
- { name: serializer.normalizer }
|
||||
|
||||
#
|
||||
# Paginator normalizer.
|
||||
#
|
||||
api.normalizer.paginator:
|
||||
class: 'ApiBundle\Serializer\Normalizer\PaginationNormalizer'
|
||||
tags:
|
||||
- { name: serializer.normalizer }
|
||||
|
||||
#
|
||||
# Index document normalizer.
|
||||
#
|
||||
api.normalizer.index_document:
|
||||
class: 'ApiBundle\Serializer\Normalizer\DocumentNormalizer'
|
||||
tags:
|
||||
- { name: serializer.normalizer }
|
||||
|
||||
#
|
||||
# Empty object normalizer.
|
||||
#
|
||||
api.normalizer.empty_object:
|
||||
class: 'ApiBundle\Serializer\Normalizer\EmptyObjectNormalizer'
|
||||
tags:
|
||||
- { name: serializer.normalizer }
|
||||
@@ -0,0 +1,62 @@
|
||||
imports:
|
||||
- { resource: controllers.yml }
|
||||
- { resource: inspector.yml }
|
||||
- { resource: normalizers.yml }
|
||||
|
||||
services:
|
||||
#
|
||||
# Subscriber for api events.
|
||||
#
|
||||
api.listeners.api:
|
||||
class: 'ApiBundle\EventListener\ApiSubscriber'
|
||||
arguments:
|
||||
- '@serializer'
|
||||
- '@monolog.logger.api_error'
|
||||
tags:
|
||||
- { name: kernel.event_subscriber }
|
||||
|
||||
#
|
||||
# App annotation fetch listener.
|
||||
#
|
||||
api.listeners.annotation:
|
||||
class: 'ApiBundle\EventListener\AnnotationFetchListener'
|
||||
arguments:
|
||||
- '@annotation_reader'
|
||||
tags:
|
||||
-
|
||||
name: kernel.event_listener
|
||||
event: kernel.controller
|
||||
method: handle
|
||||
|
||||
|
||||
#
|
||||
# Roles annotation checker listener.
|
||||
#
|
||||
api.listeners.security:
|
||||
class: 'ApiBundle\EventListener\RolesListener'
|
||||
arguments:
|
||||
- '@security.token_storage'
|
||||
- '@security.role_hierarchy'
|
||||
tags:
|
||||
-
|
||||
name: kernel.event_listener
|
||||
event: kernel.controller
|
||||
method: handle
|
||||
priority: -10
|
||||
|
||||
#
|
||||
# Formatter for api errors log.
|
||||
#
|
||||
api.log_formatter.api_errors:
|
||||
class: Monolog\Formatter\LineFormatter
|
||||
arguments:
|
||||
- "[%%datetime%%] %%message%%\n%%context.headers%%\n%%context.request%%\n\n"
|
||||
- ~
|
||||
- true
|
||||
|
||||
api.form.subscribe_to_notifications:
|
||||
class: 'ApiBundle\Form\SubscribeToNotificationsBatchType'
|
||||
arguments:
|
||||
- '@security.token_storage'
|
||||
tags:
|
||||
- { name: form.type }
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Response;
|
||||
|
||||
use AppBundle\HttpFoundation\AppResponse;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
/**
|
||||
* Class View
|
||||
* Abstract under controller results and api response.
|
||||
*
|
||||
* @package ApiBundle\Response
|
||||
*/
|
||||
class View implements ViewInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $groups;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $code;
|
||||
|
||||
/**
|
||||
* View constructor.
|
||||
*
|
||||
* @param mixed $data Response data.
|
||||
* @param array $groups Serialization groups.
|
||||
* @param integer $code HTTP status code.
|
||||
*/
|
||||
public function __construct(
|
||||
$data,
|
||||
array $groups = [],
|
||||
$code = null
|
||||
) {
|
||||
if (($data === null) && ($code === null)) {
|
||||
$code = AppResponse::HTTP_NO_CONTENT;
|
||||
}
|
||||
|
||||
$this->data = $data;
|
||||
$this->groups = $groups;
|
||||
$this->code = $code ?: AppResponse::HTTP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize this response into proper response.
|
||||
*
|
||||
* @param NormalizerInterface $normalizer A NormalizerInterface instance.
|
||||
*
|
||||
* @return AppResponse
|
||||
*/
|
||||
public function serialize(NormalizerInterface $normalizer)
|
||||
{
|
||||
if (($this->data === null)
|
||||
|| (is_array($this->data) && (count($this->data) === 0))) {
|
||||
// We got empty response, send without serialization.
|
||||
return AppResponse::create(null, $this->code);
|
||||
}
|
||||
|
||||
if (is_array($this->data) || is_object($this->data)) {
|
||||
//
|
||||
// TODO: refactor it. Low priority.
|
||||
//
|
||||
if (($this->code >= 400) && ! is_array($this->data)
|
||||
&& (! $this->data instanceof FormInterface)) {
|
||||
$this->data = [ $this->data ];
|
||||
}
|
||||
|
||||
return AppResponse::create(
|
||||
$normalizer->normalize($this->data, null, $this->groups),
|
||||
$this->code
|
||||
);
|
||||
}
|
||||
|
||||
// Scalar values we just return.
|
||||
return AppResponse::create($this->data, $this->code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Response;
|
||||
|
||||
use AppBundle\HttpFoundation\AppResponse;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
/**
|
||||
* Interface ViewInterface
|
||||
* @package ApiBundle\Response
|
||||
*/
|
||||
interface ViewInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Serialize this response into proper response.
|
||||
*
|
||||
* @param NormalizerInterface $normalizer A NormalizerInterface instance.
|
||||
*
|
||||
* @return AppResponse
|
||||
*/
|
||||
public function serialize(NormalizerInterface $normalizer);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Security\AccessChecker;
|
||||
|
||||
use ApiBundle\Security\Inspector\Factory\InspectorFactoryInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
|
||||
/**
|
||||
* Class AccessChecker
|
||||
* @package ApiBundle\Security\AccessChecker
|
||||
*/
|
||||
class AccessChecker implements AccessCheckerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
private $storage;
|
||||
|
||||
/**
|
||||
* @var InspectorFactoryInterface
|
||||
*/
|
||||
private $factory;
|
||||
|
||||
/**
|
||||
* AccessChecker constructor.
|
||||
*
|
||||
* @param TokenStorageInterface $storage A TokenStorageInterface instance.
|
||||
* @param InspectorFactoryInterface $factory A InspectorFactoryInterface
|
||||
* instance.
|
||||
*/
|
||||
public function __construct(
|
||||
TokenStorageInterface $storage,
|
||||
InspectorFactoryInterface $factory
|
||||
) {
|
||||
$this->storage = $storage;
|
||||
$this->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that current user can make given action with specified entity.
|
||||
*
|
||||
* @param string $action Action name.
|
||||
* @param object $entity A Entity instance.
|
||||
*
|
||||
* @return string[] Array of restriction reasons.
|
||||
*/
|
||||
public function isGranted($action, $entity)
|
||||
{
|
||||
$inspector = $this->factory->create($entity);
|
||||
$user = $this->storage->getToken()->getUser();
|
||||
|
||||
return $inspector->inspect($user, $entity, $action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Security\AccessChecker;
|
||||
|
||||
/**
|
||||
* Interface AccessCheckerInterface
|
||||
* Check access to entity for current user.
|
||||
*
|
||||
* @package ApiBundle\Security\AccessChecker
|
||||
*/
|
||||
interface AccessCheckerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Checks that given user can make given action with specified entity.
|
||||
*
|
||||
* @param string $action Action name.
|
||||
* @param object $entity A Entity instance.
|
||||
*
|
||||
* @return string[] Array of restriction reasons.
|
||||
*/
|
||||
public function isGranted($action, $entity);
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Security\Inspector;
|
||||
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Class InspectorInterface
|
||||
* Base class for all inspectors.
|
||||
*
|
||||
* @package ApiBundle\Security\Inspector
|
||||
*/
|
||||
abstract class AbstractInspector implements InspectorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $reasons = [];
|
||||
|
||||
/**
|
||||
* Checks that given user can make given action with specified entity.
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
* @param object $entity A Entity instance or array of instances.
|
||||
* @param string $action Action name.
|
||||
*
|
||||
* @return string[] Array of restriction reasons.
|
||||
*/
|
||||
public function inspect(User $user, $entity, $action)
|
||||
{
|
||||
$classes = (array) static::supportedClass();
|
||||
|
||||
$checker = \nspl\f\partial('\app\op\isInstanceOf', $entity);
|
||||
if (! \nspl\a\any($classes, $checker)) {
|
||||
throw new \InvalidArgumentException('Can inspect only '. implode(', ', $classes));
|
||||
}
|
||||
|
||||
// Clear reasons.
|
||||
$this->reasons = [];
|
||||
switch ($action) {
|
||||
case self::CREATE:
|
||||
$this->canCreate($user, $entity);
|
||||
break;
|
||||
|
||||
case self::READ:
|
||||
$this->canRead($user, $entity);
|
||||
break;
|
||||
|
||||
case self::UPDATE:
|
||||
$this->canUpdate($user, $entity);
|
||||
break;
|
||||
|
||||
case self::DELETE:
|
||||
$this->canDelete($user, $entity);
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->reasons;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $reason Restriction reason.
|
||||
*
|
||||
* @return AbstractInspector
|
||||
*/
|
||||
protected function addReason($reason)
|
||||
{
|
||||
$this->reasons[] = $reason;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add reason only if condition is true.
|
||||
*
|
||||
* @param string $reason Restriction reason.
|
||||
* @param boolean $condition Some boolean condition.
|
||||
*
|
||||
* @return AbstractInspector
|
||||
*/
|
||||
protected function addReasonIf($reason, $condition)
|
||||
{
|
||||
if ($condition) {
|
||||
$this->reasons[] = $reason;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that user can create specified entity.
|
||||
*
|
||||
* @param User $user A user who try to create entity.
|
||||
* @param object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function canCreate(User $user, $entity);
|
||||
|
||||
/**
|
||||
* Check that user can read specified entity.
|
||||
*
|
||||
* @param User $user A user who try to read entity.
|
||||
* @param object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function canRead(User $user, $entity);
|
||||
|
||||
/**
|
||||
* Check that user can update specified entity.
|
||||
*
|
||||
* @param User $user A user who try to update entity.
|
||||
* @param object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
abstract protected function canUpdate(User $user, $entity);
|
||||
|
||||
/**
|
||||
* Check that user can delete specified entity.
|
||||
*
|
||||
* @param User $user A user who try to delete entity.
|
||||
* @param object $entity A Entity instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function canDelete(User $user, $entity);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Security\Inspector\Factory;
|
||||
|
||||
use ApiBundle\Security\Inspector\InspectorInterface;
|
||||
|
||||
/**
|
||||
* Interface InspectorFactoryInterface
|
||||
* Create entity inspector instance.
|
||||
*
|
||||
* @package ApiBundle\Security\Inspector\Factory
|
||||
*/
|
||||
interface InspectorFactoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Create proper inspector for given entity instance.
|
||||
*
|
||||
* @param object|string $class A Entity instance or fqcn.
|
||||
*
|
||||
* @return InspectorInterface
|
||||
*/
|
||||
public function create($class);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Security\Inspector\Factory;
|
||||
|
||||
use ApiBundle\Security\Inspector\InspectorInterface;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class LazyInspectorFactory
|
||||
* Default implementation of InspectorFactoryInterface.
|
||||
* Use lazy loading for creating inspectors.
|
||||
*
|
||||
* @package ApiBundle\Security\Inspector\Factory
|
||||
*/
|
||||
class LazyInspectorFactory implements InspectorFactoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $inspectorsIds;
|
||||
|
||||
/**
|
||||
* InspectorFactory constructor.
|
||||
*
|
||||
* @param ContainerInterface $container A ContainerInterface instance.
|
||||
* @param array $inspectorsIds Registered inspectors services
|
||||
* ids.
|
||||
*/
|
||||
public function __construct(ContainerInterface $container, array $inspectorsIds)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->inspectorsIds = $inspectorsIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create proper inspector for given entity instance.
|
||||
*
|
||||
* @param object|string $class A Entity instance or fqcn.
|
||||
*
|
||||
* @return InspectorInterface
|
||||
*/
|
||||
public function create($class)
|
||||
{
|
||||
if (is_object($class)) {
|
||||
$class = get_class($class);
|
||||
}
|
||||
$class = ClassUtils::getRealClass($class);
|
||||
|
||||
if (!is_string($class) || ! class_exists($class)) {
|
||||
throw new \InvalidArgumentException('Expects object or valid fqcn.');
|
||||
}
|
||||
|
||||
if (! array_key_exists($class, $this->inspectorsIds)) {
|
||||
$message = "Can't find inspector for entity '{$class}'";
|
||||
throw new \InvalidArgumentException($message);
|
||||
}
|
||||
|
||||
$inspector = $this->container->get($this->inspectorsIds[$class]);
|
||||
if (! $inspector instanceof InspectorInterface) {
|
||||
$message = 'Inspector must implements '. InspectorInterface::class;
|
||||
throw new \RuntimeException($message);
|
||||
}
|
||||
|
||||
return $inspector;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Security\Inspector;
|
||||
|
||||
use UserBundle\Entity\User;
|
||||
|
||||
/**
|
||||
* Interface InspectorInterface
|
||||
* Inspect entity and decides to give access or not to inspected entity.
|
||||
*
|
||||
* @package ApiBundle\Security\Inspector
|
||||
*/
|
||||
interface InspectorInterface
|
||||
{
|
||||
|
||||
const CREATE = 'create';
|
||||
const READ = 'read';
|
||||
const UPDATE = 'update';
|
||||
const DELETE = 'delete';
|
||||
|
||||
/**
|
||||
* Checks that given user can make given action with specified entity.
|
||||
*
|
||||
* @param User $user A User entity instance.
|
||||
* @param object $entity A Entity instance or array of instances.
|
||||
* @param string $action Action name.
|
||||
*
|
||||
* @return string[] Array of restriction reasons.
|
||||
*/
|
||||
public function inspect(User $user, $entity, $action);
|
||||
|
||||
/**
|
||||
* Return supported entity fqcn.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function supportedClass();
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Serializer\Metadata;
|
||||
|
||||
/**
|
||||
* Class Metadata
|
||||
* @package ApiBundle\Serializer\Metadata
|
||||
*/
|
||||
class Metadata
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $fqcn;
|
||||
|
||||
/**
|
||||
* @var PropertyMetadata[]
|
||||
*/
|
||||
private $properties;
|
||||
|
||||
/**
|
||||
* Metadata constructor.
|
||||
*
|
||||
* @param string $fqcn Entity fqcn.
|
||||
* @param array $properties Array of PropertyMetadata's.
|
||||
*/
|
||||
public function __construct($fqcn, array $properties = [])
|
||||
{
|
||||
$this->fqcn = $fqcn;
|
||||
$this->properties = $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFqcn()
|
||||
{
|
||||
return $this->fqcn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $interfaceName Full qualified interface name.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function implementsInterface($interfaceName)
|
||||
{
|
||||
return in_array($interfaceName, class_implements($this->fqcn), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all properties metadata or filter by it specified groups if it's set.
|
||||
*
|
||||
* @param array|string|null $groups A serialized group.
|
||||
*
|
||||
* @return PropertyMetadata[]
|
||||
*/
|
||||
public function getProperties($groups = null)
|
||||
{
|
||||
if ($groups === null) {
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
$groups = (array) $groups;
|
||||
return array_filter(
|
||||
$this->properties,
|
||||
function (PropertyMetadata $metadata) use ($groups) {
|
||||
return count(array_intersect($metadata->getGroups(), $groups)) > 0;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to this metadata properties from specified metadata which not exists
|
||||
* in this.
|
||||
*
|
||||
* @param Metadata $metadata A Metadata instance.
|
||||
*
|
||||
* @return Metadata
|
||||
*/
|
||||
public function admix(Metadata $metadata)
|
||||
{
|
||||
$registeredProperties = array_map(function (PropertyMetadata $property) {
|
||||
return $property->getName();
|
||||
}, $this->properties);
|
||||
|
||||
$filter = function (PropertyMetadata $property) use ($registeredProperties) {
|
||||
return ! in_array($property->getName(), $registeredProperties, true);
|
||||
};
|
||||
|
||||
$uniqueProperties = array_filter($metadata->getProperties(), $filter);
|
||||
$this->properties = array_merge($this->properties, $uniqueProperties);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $metadataList Array of Metadata instances.
|
||||
*
|
||||
* @return Metadata
|
||||
*/
|
||||
public function admixList(array $metadataList)
|
||||
{
|
||||
/** @var Metadata $metadata */
|
||||
foreach ($metadataList as $metadata) {
|
||||
$this->admix($metadata);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,514 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Serializer\Metadata;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
||||
/**
|
||||
* Class PropertyMetadata
|
||||
* @package ApiBundle\Serializer\Metadata
|
||||
*/
|
||||
class PropertyMetadata
|
||||
{
|
||||
|
||||
/**
|
||||
* Integer scalar value.
|
||||
*/
|
||||
const TYPE_INTEGER = 'integer';
|
||||
|
||||
/**
|
||||
* Float (double) scalar value.
|
||||
*/
|
||||
const TYPE_DOUBLE = 'double';
|
||||
|
||||
/**
|
||||
* String scalar value.
|
||||
*/
|
||||
const TYPE_STRING = 'string';
|
||||
|
||||
/**
|
||||
* Boolean scalar value.
|
||||
*/
|
||||
const TYPE_BOOLEAN = 'boolean';
|
||||
|
||||
/**
|
||||
* Array value.
|
||||
* Elements maybe other array or any scalar values.
|
||||
*/
|
||||
const TYPE_ARRAY = 'array';
|
||||
|
||||
/**
|
||||
* Array of associated entities.
|
||||
* Metadata must contains associated entity fqcn in 'actualType' field.
|
||||
*/
|
||||
const TYPE_COLLECTION = 'collection';
|
||||
|
||||
/**
|
||||
* Single associated entity.
|
||||
* Metadata must contains associated entity fqcn in 'actualType' field.
|
||||
*/
|
||||
const TYPE_ENTITY = 'entity';
|
||||
|
||||
/**
|
||||
* One of enum instance.
|
||||
*
|
||||
* @see \AppBundle\Enum\AbstractEnum
|
||||
*/
|
||||
const TYPE_ENUM = 'enum';
|
||||
|
||||
/**
|
||||
* \DateTime instance.
|
||||
*/
|
||||
const TYPE_DATE = 'date';
|
||||
|
||||
/**
|
||||
* Some object.
|
||||
*/
|
||||
const TYPE_OBJECT = 'object';
|
||||
|
||||
/**
|
||||
* Custom object.
|
||||
*/
|
||||
const TYPE_GROUP = 'group';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* Actual property type, used for object type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $actualType;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $field;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $groups;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
private $nullable = false;
|
||||
|
||||
/**
|
||||
* @var PropertyMetadata[]
|
||||
*/
|
||||
private $subProperties = [];
|
||||
|
||||
/**
|
||||
* PropertyMetadata constructor.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @param string $type Property type.
|
||||
* @param \Closure|callable|string $field Entity field name or function for
|
||||
* fetching data.
|
||||
* @param array $groups Serialized group names.
|
||||
*/
|
||||
public function __construct($name, $type, $field, array $groups)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->field = $field;
|
||||
$this->groups = array_map('trim', $groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create property metadata for integer field.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @param array $groups Serialized group names.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public static function createInteger($name, array $groups)
|
||||
{
|
||||
return new PropertyMetadata($name, self::TYPE_INTEGER, $name, $groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create property metadata for string field.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @param array $groups Serialized group names.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public static function createString($name, array $groups)
|
||||
{
|
||||
return new PropertyMetadata($name, self::TYPE_STRING, $name, $groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create property metadata for string field.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @param array $groups Serialized group names.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public static function createDouble($name, array $groups)
|
||||
{
|
||||
return new PropertyMetadata($name, self::TYPE_DOUBLE, $name, $groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create property metadata for object field.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @param string $actualType Entity fqcn.
|
||||
* @param array $groups Serialized group names.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public static function createEntity($name, $actualType, array $groups)
|
||||
{
|
||||
$property = new PropertyMetadata($name, self::TYPE_ENTITY, $name, $groups);
|
||||
|
||||
return $property->setActualType($actualType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create property metadata for string field.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @param string $enumClass Enum class.
|
||||
* @param array $groups Serialized group names.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public static function createEnum($name, $enumClass, array $groups)
|
||||
{
|
||||
$property = new PropertyMetadata($name, self::TYPE_ENUM, $name, $groups);
|
||||
|
||||
return $property->setActualType($enumClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create property metadata for array field.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @param string $actualType Entity fqcn.
|
||||
* @param array $groups Serialized group names.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public static function createCollection($name, $actualType, array $groups)
|
||||
{
|
||||
$property = new PropertyMetadata($name, self::TYPE_COLLECTION, $name, $groups);
|
||||
|
||||
return $property->setActualType($actualType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create property metadata for array field.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @param array $groups Serialized group names.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public static function createArray($name, array $groups)
|
||||
{
|
||||
return new PropertyMetadata($name, self::TYPE_ARRAY, $name, $groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create property metadata for boolean field.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @param array $groups Serialized group names.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public static function createBoolean($name, array $groups)
|
||||
{
|
||||
return new PropertyMetadata($name, self::TYPE_BOOLEAN, $name, $groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create property metadata for date field.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @param array $groups Serialized group names.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public static function createDate($name, array $groups)
|
||||
{
|
||||
return new PropertyMetadata($name, self::TYPE_DATE, $name, $groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create property metadata for date field.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @param array $groups Serialized group names.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public static function createObject($name, array $groups)
|
||||
{
|
||||
return new PropertyMetadata($name, self::TYPE_OBJECT, $name, $groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create property metadata for date field.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
* @param array $subProperties Sub properties metadata.
|
||||
* @param array $groups Serialized group names.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public static function groupProperties($name, array $subProperties, array $groups)
|
||||
{
|
||||
$instance = new PropertyMetadata($name, self::TYPE_GROUP, null, $groups);
|
||||
|
||||
return $instance->setSubProperties($subProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name.
|
||||
*
|
||||
* @param string $name Property name.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set type.
|
||||
*
|
||||
* @param string $type Property type.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public function setType($type)
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set actual type.
|
||||
*
|
||||
* @param string $actualType Actual property type.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public function setActualType($actualType)
|
||||
{
|
||||
$this->actualType = $actualType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get actual type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getActualType()
|
||||
{
|
||||
return $this->actualType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isScalar()
|
||||
{
|
||||
return ($this->type !== self::TYPE_COLLECTION)
|
||||
&& ($this->type !== self::TYPE_ENTITY)
|
||||
&& ($this->type !== self::TYPE_GROUP)
|
||||
&& ($this->type !== self::TYPE_OBJECT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set field.
|
||||
*
|
||||
* @param \Closure|callable|string $field Entity field name or function for
|
||||
* fetching data.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public function setField($field)
|
||||
{
|
||||
$this->field = $field;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field.
|
||||
*
|
||||
* @return \Closure|callable|string
|
||||
*/
|
||||
public function getField()
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $object Object instance on with we need getter.
|
||||
*
|
||||
* @return \Closure
|
||||
*/
|
||||
public function getGetter($object)
|
||||
{
|
||||
$getter = $this->field;
|
||||
|
||||
if (is_string($getter)) {
|
||||
//
|
||||
// We got concrete field name.
|
||||
// Create getter for it.
|
||||
//
|
||||
$getter = function () use ($getter) {
|
||||
$value = $this->{$getter};
|
||||
|
||||
if ($value instanceof Collection) {
|
||||
// Convert doctrine collection into array.
|
||||
$value = $value->toArray();
|
||||
} elseif ($value instanceof \DateTimeInterface) {
|
||||
// Format date time instances.
|
||||
$value = $value->format('c');
|
||||
}
|
||||
|
||||
return $value;
|
||||
};
|
||||
} elseif ($this->type === self::TYPE_GROUP) {
|
||||
//
|
||||
// For object we should iterate other sub properties and get values
|
||||
// from it.
|
||||
//
|
||||
// We should store current sub properties in order to inject them
|
||||
// into closure.
|
||||
//
|
||||
$subProperties = $this->subProperties;
|
||||
$getter = function () use ($object, $subProperties) {
|
||||
$results = [];
|
||||
|
||||
foreach ($subProperties as $subProperty) {
|
||||
$getter = $subProperty->getGetter($object);
|
||||
$results[$subProperty->getName()] = $getter();
|
||||
}
|
||||
|
||||
return $results;
|
||||
};
|
||||
}
|
||||
|
||||
// Field is function.
|
||||
return $getter->bindTo($object, $object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set groups.
|
||||
*
|
||||
* @param array $groups Serialization groups.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public function setGroups(array $groups)
|
||||
{
|
||||
$this->groups = $groups;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get groups.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getGroups()
|
||||
{
|
||||
return $this->groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set nullable.
|
||||
*
|
||||
* @param boolean $nullable Can property value be null.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public function setNullable($nullable)
|
||||
{
|
||||
$this->nullable = $nullable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get nullable
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isNullable()
|
||||
{
|
||||
return $this->nullable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sub properties
|
||||
*
|
||||
* @param array $properties Array of PropertyMetadata instances.
|
||||
*
|
||||
* @return PropertyMetadata
|
||||
*/
|
||||
public function setSubProperties(array $properties)
|
||||
{
|
||||
$this->subProperties = $properties;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sub properties
|
||||
*
|
||||
* @return PropertyMetadata[]
|
||||
*/
|
||||
public function getSubProperties()
|
||||
{
|
||||
return $this->subProperties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Serializer\Normalizer;
|
||||
|
||||
use CacheBundle\Comment\Manager\CommentManagerInterface;
|
||||
use IndexBundle\Model\AbstractDocument;
|
||||
use IndexBundle\Model\ArticleDocumentInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
/**
|
||||
* Class DocumentNormalizer
|
||||
* @package ApiBundle\Serializer\Normalizer
|
||||
*/
|
||||
class DocumentNormalizer implements NormalizerInterface, NormalizerAwareInterface
|
||||
{
|
||||
|
||||
use NormalizerAwareTrait;
|
||||
|
||||
/**
|
||||
* Checks whether the given class is supportedClass for normalization by this
|
||||
* normalizer.
|
||||
*
|
||||
* @param mixed $data Data to normalize.
|
||||
* @param string $format The format being (de-)serialized from or into.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function supportsNormalization($data, $format = null)
|
||||
{
|
||||
return $data instanceof AbstractDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an object into a set of arrays/scalars.
|
||||
*
|
||||
* @param object|AbstractDocument $object Object to normalize.
|
||||
* @param string $format Format the normalization
|
||||
* result will be encoded as.
|
||||
* @param array $context Context options for the
|
||||
* normalizer.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function normalize($object, $format = null, array $context = array())
|
||||
{
|
||||
if ($object instanceof ArticleDocumentInterface) {
|
||||
$object->addNormalizerListener(function (array $data) use ($format) {
|
||||
$data['comments'] = [
|
||||
'data' => $this->normalizer->normalize($data['comments'], $format, [
|
||||
'id',
|
||||
'comment',
|
||||
]),
|
||||
'count' => count($data['comments']),
|
||||
'totalCount' => $data['commentsCount'],
|
||||
'limit' => CommentManagerInterface::NEW_COMMENT_POOL_SIZE,
|
||||
];
|
||||
unset($data['commentsCount']);
|
||||
|
||||
return \nspl\a\map(function ($value) {
|
||||
if ($value instanceof \DateTimeInterface) {
|
||||
$value = $value->format('c');
|
||||
}
|
||||
|
||||
return $value;
|
||||
}, $data);
|
||||
});
|
||||
}
|
||||
|
||||
return $object->getNormalizedData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Serializer\Normalizer;
|
||||
|
||||
use IndexBundle\Model\AbstractDocument;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
/**
|
||||
* Class EmptyObjectNormalizer
|
||||
* @package ApiBundle\Serializer\Normalizer
|
||||
*/
|
||||
class EmptyObjectNormalizer implements NormalizerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Checks whether the given class is supportedClass for normalization by this
|
||||
* normalizer.
|
||||
*
|
||||
* @param mixed $data Data to normalize.
|
||||
* @param string $format The format being (de-)serialized from or into.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function supportsNormalization($data, $format = null)
|
||||
{
|
||||
if (! $data instanceof \stdClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return count(get_object_vars($data)) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an object into a set of arrays/scalars.
|
||||
*
|
||||
* @param object|AbstractDocument $object Object to normalize.
|
||||
* @param string $format Format the normalization
|
||||
* result will be encoded as.
|
||||
* @param array $context Context options for the
|
||||
* normalizer.
|
||||
*
|
||||
* @return object
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function normalize($object, $format = null, array $context = array())
|
||||
{
|
||||
return (object) [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Serializer\Normalizer;
|
||||
|
||||
use ApiBundle\Entity\NormalizableEntityInterface;
|
||||
use ApiBundle\Serializer\Metadata\PropertyMetadata;
|
||||
use AppBundle\Entity\EntityInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
/**
|
||||
* Class EntityNormalizer
|
||||
* @package ApiBundle\Serializer\Normalizer
|
||||
*/
|
||||
class EntityNormalizer implements NormalizerInterface, NormalizerAwareInterface
|
||||
{
|
||||
|
||||
use NormalizerAwareTrait;
|
||||
|
||||
/**
|
||||
* Checks whether the given class is supportedClass for normalization by this
|
||||
* normalizer.
|
||||
*
|
||||
* @param mixed $data Data to normalize.
|
||||
* @param string $format The format being (de-)serialized from or into.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function supportsNormalization($data, $format = null)
|
||||
{
|
||||
return $data instanceof NormalizableEntityInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an object into a set of arrays/scalars.
|
||||
*
|
||||
* @param object|NormalizableEntityInterface $object Object to normalize.
|
||||
* @param string $format Format the normalization
|
||||
* result will be encoded as.
|
||||
* @param array $context Context options for the
|
||||
* normalizer.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function normalize($object, $format = null, array $context = array())
|
||||
{
|
||||
$metadata = $object->getMetadata()->getProperties($context);
|
||||
$result = [];
|
||||
|
||||
// If we normalize entity we should add 'type' property to it.
|
||||
if ($object instanceof EntityInterface) {
|
||||
$result['type'] = $object->getEntityType();
|
||||
}
|
||||
|
||||
foreach ($metadata as $property) {
|
||||
$getter = $property->getGetter($object);
|
||||
$value = $getter();
|
||||
|
||||
//
|
||||
// Normalize non scalar value such as:
|
||||
// - Collection of associated entity.
|
||||
// - Single associated entity.
|
||||
// - Array of scalar values. Just in case.
|
||||
//
|
||||
if (! $property->isScalar()) {
|
||||
$value = $this->normalizer->normalize($value, $format, $context);
|
||||
if ($property->getType() === PropertyMetadata::TYPE_OBJECT) {
|
||||
$value = (object) $value;
|
||||
}
|
||||
} elseif ($property->getType() === PropertyMetadata::TYPE_ENUM) {
|
||||
$value = (string) $value;
|
||||
}
|
||||
|
||||
$result[$property->getName()] = $value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Serializer\Normalizer;
|
||||
|
||||
use AppBundle\Enum\AbstractEnum;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
/**
|
||||
* Class EnumNormalizer
|
||||
* @package ApiBundle\Serializer\Normalizer
|
||||
*/
|
||||
class EnumNormalizer implements NormalizerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Checks whether the given class is supportedClass for normalization by this
|
||||
* normalizer.
|
||||
*
|
||||
* @param mixed $data Data to normalize.
|
||||
* @param string $format The format being (de-)serialized from or into.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function supportsNormalization($data, $format = null)
|
||||
{
|
||||
return $data instanceof AbstractEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an object into a set of arrays/scalars.
|
||||
*
|
||||
* @param object|AbstractEnum $object Object to normalize.
|
||||
* @param string $format Format the normalization result will
|
||||
* be encoded as.
|
||||
* @param array $context Context options for the normalizer.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function normalize($object, $format = null, array $context = [])
|
||||
{
|
||||
return $object->getValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Serializer\Normalizer;
|
||||
|
||||
use AppBundle\Utils\TransKey\TransKeyGeneratorInterface;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Validator\Constraints\Form as FormConstraint;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
use Symfony\Component\Validator\Constraints\AbstractComparison;
|
||||
use Symfony\Component\Validator\ConstraintViolation;
|
||||
use Symfony\Component\Validator\Constraints;
|
||||
|
||||
/**
|
||||
* Class FormNormalizer
|
||||
* @package ApiBundle\Serializer\Normalizer
|
||||
*/
|
||||
class FormNormalizer implements NormalizerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Already founded form element keys.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $transKeysCache = [];
|
||||
|
||||
/**
|
||||
* Map between comparison constraint fqcn and generator config.
|
||||
*
|
||||
* Config has next keys:
|
||||
* * key - it value must start from upper case character, and should by an
|
||||
* antonym for constraint.
|
||||
* * valueName - name of value in response with which we got conflict, for
|
||||
* example: bound value for comparison. May be omitted.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private static $comparisonConfig = [
|
||||
Constraints\GreaterThanOrEqual::class => [
|
||||
'key' => 'Lower',
|
||||
'valueName' => 'than',
|
||||
],
|
||||
Constraints\GreaterThan::class => [
|
||||
'key' => 'LowerOrEqual',
|
||||
'valueName' => 'than',
|
||||
],
|
||||
Constraints\LessThan::class => [
|
||||
'key' => 'GreaterOrEqual',
|
||||
'valueName' => 'than',
|
||||
],
|
||||
Constraints\LessThanOrEqual::class => [
|
||||
'key' => 'Greater',
|
||||
'valueName' => 'than',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Checks whether the given class is supportedClass for normalization by this
|
||||
* normalizer.
|
||||
*
|
||||
* @param mixed $data Data to normalize.
|
||||
* @param string $format The format being (de-)serialized from or into.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function supportsNormalization($data, $format = null)
|
||||
{
|
||||
return $data instanceof FormInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an object into a set of arrays/scalars.
|
||||
*
|
||||
* @param object|FormInterface $object Object to normalize.
|
||||
* @param string $format Format the normalization result will
|
||||
* be encoded as.
|
||||
* @param array $context Context options for the normalizer.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function normalize($object, $format = null, array $context = [])
|
||||
{
|
||||
if (! $object->isSubmitted()) {
|
||||
return [ 'Form not submitted.' ];
|
||||
}
|
||||
|
||||
return array_map(function (FormError $error) {
|
||||
return $this->explainError($error);
|
||||
}, iterator_to_array($object->getErrors(true, true)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate form key in camelCase.
|
||||
*
|
||||
* @param FormInterface $form A FormInterface instance.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getTransKey(FormInterface $form)
|
||||
{
|
||||
$hash = spl_object_hash($form);
|
||||
|
||||
if (! isset($this->transKeysCache[$hash])) {
|
||||
/** @var TransKeyGeneratorInterface $generator */
|
||||
$generator = $form->getConfig()->getOption('key');
|
||||
$this->transKeysCache[$hash] = $generator->generate($form);
|
||||
}
|
||||
|
||||
return $this->transKeysCache[$hash];
|
||||
}
|
||||
|
||||
/**
|
||||
* Explain occurred form error.
|
||||
*
|
||||
* @param FormError $error A FormError instance.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function explainError(FormError $error)
|
||||
{
|
||||
$form = $error->getOrigin();
|
||||
$cause = $error->getCause();
|
||||
|
||||
//
|
||||
// Prepare current translation key and translation parameters.
|
||||
//
|
||||
$transKey = $this->getTransKey($form);
|
||||
$parameters = [];
|
||||
|
||||
//
|
||||
// Check cause.
|
||||
//
|
||||
if ($cause instanceof ConstraintViolation) {
|
||||
//
|
||||
// We got constraint violation so we should check that we known about
|
||||
// constrain which spawned this violation and make proper explanation.
|
||||
//
|
||||
$isOrder = is_numeric($form->getName());
|
||||
if ($isOrder || ($form->getParent() && is_numeric(\app\op\invokeIf($form->getParent(), 'getName')))) {
|
||||
//
|
||||
// Entry type of CollectionType had order number instead of name
|
||||
// and we should store this number in parameters and send to
|
||||
// client.
|
||||
//
|
||||
$order = (int) ($isOrder ? $form->getName() : \app\op\invokeIf($form->getParent(), 'getName'));
|
||||
$parameters['order'] = $order;
|
||||
}
|
||||
|
||||
$parameters = array_merge($parameters, $this->getParametersForViolation(
|
||||
$cause,
|
||||
$form,
|
||||
$transKey
|
||||
));
|
||||
}
|
||||
|
||||
return [
|
||||
'message' => $error->getMessage(),
|
||||
'transKey' => $transKey,
|
||||
// This hardcoded by requesting from Frontend Developers.
|
||||
'type' => 'error',
|
||||
'parameters' => $parameters,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConstraintViolation $violation Founded violation.
|
||||
* @param FormInterface $form Form on which violation found.
|
||||
* @param string $transKey Translation key for this form.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getParametersForViolation(
|
||||
ConstraintViolation $violation,
|
||||
FormInterface $form,
|
||||
&$transKey
|
||||
) {
|
||||
$parameters = $this->getParametersForViolationByCode($violation, $form, $transKey);
|
||||
if ($parameters === null) {
|
||||
$parameters = $this->getParametersForViolationByClass($violation, $transKey);
|
||||
}
|
||||
|
||||
return $parameters !== null ? $parameters : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConstraintViolation $violation Founded violation.
|
||||
* @param FormInterface $form Form on which violation found.
|
||||
* @param string $transKey Translation key for this form.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private function getParametersForViolationByCode(
|
||||
ConstraintViolation $violation,
|
||||
FormInterface $form,
|
||||
&$transKey
|
||||
) {
|
||||
$code = $violation->getCode();
|
||||
$current = $violation->getInvalidValue();
|
||||
|
||||
$parameters = null;
|
||||
switch (true) {
|
||||
//
|
||||
// Firstly we should check possible form constraint errors.
|
||||
//
|
||||
case $code === FormConstraint::NO_SUCH_FIELD_ERROR:
|
||||
$transKey .= 'UnknownField';
|
||||
$parameters = [
|
||||
'name' => key($current),
|
||||
];
|
||||
break;
|
||||
|
||||
case $code === FormConstraint::NOT_SYNCHRONIZED_ERROR:
|
||||
$transKey .= 'Invalid';
|
||||
$parameters = [
|
||||
'current' => $current,
|
||||
];
|
||||
|
||||
if ($this->isChoiceType($form)) {
|
||||
// We should return available choice values and also should
|
||||
// return only invalid values.
|
||||
$choices = $form->getConfig()->getOption('choices');
|
||||
|
||||
$parameters['available'] = $choices;
|
||||
if (is_array($choices) && $form->getConfig()->getOption('multiple')) {
|
||||
$parameters['invalid'] = array_diff($current, $choices);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
//
|
||||
// Finally we should check UniqueEntity constraint.
|
||||
// For some reasons violation with 'Form::NO_SUCH_FIELD_ERROR'
|
||||
// code has UniqueEntity constraint so we should check check
|
||||
// 'Form::NO_SUCH_FIELD_ERROR' first to avoid strange error
|
||||
// messages.
|
||||
//
|
||||
case $code === UniqueEntity::NOT_UNIQUE_ERROR:
|
||||
$transKey .= 'NotUnique';
|
||||
$parameters = [
|
||||
'current' => $current,
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConstraintViolation $violation Founded violation.
|
||||
* @param string $transKey Translation key for this form.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private function getParametersForViolationByClass(
|
||||
ConstraintViolation $violation,
|
||||
&$transKey
|
||||
) {
|
||||
$constraint = $violation->getConstraint();
|
||||
$class = get_class($constraint);
|
||||
$current = $violation->getInvalidValue();
|
||||
|
||||
$parameters = null;
|
||||
|
||||
switch (true) {
|
||||
//
|
||||
// Length constraint.
|
||||
//
|
||||
case $constraint instanceof Constraints\Length:
|
||||
$transKey .= 'TooShort';
|
||||
$parameters = [
|
||||
'min' => $constraint->min,
|
||||
];
|
||||
break;
|
||||
|
||||
//
|
||||
// Next we check comparison errors.
|
||||
// For comparison error we also should check that we hav config
|
||||
// for it.
|
||||
//
|
||||
case ($constraint instanceof AbstractComparison)
|
||||
&& isset(self::$comparisonConfig[$class]):
|
||||
$config = self::$comparisonConfig[$class];
|
||||
$transKey .= $config['key'];
|
||||
$parameters = [
|
||||
'current' => $current,
|
||||
$config['valueName'] => $constraint->value,
|
||||
];
|
||||
break;
|
||||
|
||||
//
|
||||
// Required field is blank.
|
||||
//
|
||||
case ($constraint instanceof Constraints\NotBlank):
|
||||
$transKey .= 'Empty';
|
||||
$parameters = [
|
||||
'current' => $current,
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that specified form is choice type or extend it.
|
||||
*
|
||||
* @param FormInterface $form A FormInterface instance.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private function isChoiceType(FormInterface $form)
|
||||
{
|
||||
$type = $form->getConfig()->getType();
|
||||
$innerType = $type->getInnerType();
|
||||
|
||||
return ($type instanceof ChoiceType)
|
||||
|| ($innerType instanceof ChoiceType)
|
||||
|| ($innerType->getParent() === ChoiceType::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Serializer\Normalizer;
|
||||
|
||||
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
use Knp\Bundle\PaginatorBundle\Pagination\SlidingPagination;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
/**
|
||||
* Class PaginationNormalizer
|
||||
* @package ApiBundle\Serializer\Normalizer
|
||||
*/
|
||||
class PaginationNormalizer implements NormalizerInterface, NormalizerAwareInterface
|
||||
{
|
||||
|
||||
use NormalizerAwareTrait;
|
||||
|
||||
/**
|
||||
* Checks whether the given class is supportedClass for normalization by this
|
||||
* normalizer.
|
||||
*
|
||||
* @param mixed $data Data to normalize.
|
||||
* @param string $format The format being (de-)serialized from or into.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function supportsNormalization($data, $format = null)
|
||||
{
|
||||
return ($data instanceof SlidingPagination) || ($data instanceof Paginator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an object into a set of arrays/scalars.
|
||||
*
|
||||
* @param object $object Object to normalize.
|
||||
* @param string $format Format the normalization result
|
||||
* will be encoded as.
|
||||
* @param array $context Context options for the normalizer.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function normalize($object, $format = null, array $context = [])
|
||||
{
|
||||
if ($object instanceof Paginator) {
|
||||
$data = iterator_to_array($object);
|
||||
|
||||
$normalizedData = [
|
||||
'data' => $this->normalizer->normalize($data, $format, $context),
|
||||
'count' => count($data),
|
||||
'totalCount' => count($object),
|
||||
'limit' => $object->getQuery()->getMaxResults(),
|
||||
];
|
||||
} elseif ($object instanceof SlidingPagination) {
|
||||
$normalizedData = [
|
||||
'data' => $this->normalizer
|
||||
->normalize(iterator_to_array($object), $format, $context),
|
||||
'count' => count($object),
|
||||
'totalCount' => $object->getTotalItemCount(),
|
||||
'page' => $object->getCurrentPageNumber(),
|
||||
'limit' => $object->getItemNumberPerPage(),
|
||||
];
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Expect one of paginator.');
|
||||
}
|
||||
|
||||
return $normalizedData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace ApiBundle\Serializer\Normalizer;
|
||||
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
use UserBundle\Entity\Notification\ThemeOption\ThemeOptionFont;
|
||||
|
||||
/**
|
||||
* Class ThemeOptionFontNormalizer
|
||||
* @package ApiBundle\Serializer\Normalizer
|
||||
*/
|
||||
class ThemeOptionFontNormalizer implements
|
||||
NormalizerInterface,
|
||||
NormalizerAwareInterface
|
||||
{
|
||||
|
||||
use NormalizerAwareTrait;
|
||||
|
||||
/**
|
||||
* Checks whether the given class is supportedClass for normalization by this
|
||||
* normalizer.
|
||||
*
|
||||
* @param mixed $data Data to normalize.
|
||||
* @param string $format The format being (de-)serialized from or into.
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function supportsNormalization($data, $format = null)
|
||||
{
|
||||
return $data instanceof ThemeOptionFont;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes an object into a set of arrays/scalars.
|
||||
*
|
||||
* @param object|ThemeOptionFont $object Object to normalize.
|
||||
* @param string $format Format the normalization result will
|
||||
* be encoded as.
|
||||
* @param array $context Context options for the normalizer.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function normalize($object, $format = null, array $context = [])
|
||||
{
|
||||
return [
|
||||
'family' => $object->getFamily(),
|
||||
'size' => $object->getSize(),
|
||||
'style' => $this->normalizer->normalize($object->getStyle()),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace ApiDocBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
/**
|
||||
* Class ApiDocBundle
|
||||
* @package ApiDocBundle
|
||||
*/
|
||||
class ApiDocBundle extends Bundle
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns the bundle parent name.
|
||||
*
|
||||
* @return string The Bundle parent name it overrides or null if no parent
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return 'NelmioApiDocBundle';
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user