<?php
namespace Platform\ControlPanelBundle\Controller\Dashboard;
use App\Entity\System\Sendgrid\SendgridConfig;
use App\Service\Messaging\MessagingContext;
use Cms\CoreBundle\Entity\OneRosterJob;
use Cms\CoreBundle\Entity\OneRosterSync;
use Cms\CoreBundle\Model\Contexts\GlobalContext;
use Cms\CoreBundle\Model\Scenes\DashboardScenes\DocumentScene;
use Cms\CoreBundle\Model\Search\Pagination;
use Cms\CoreBundle\Model\Search\Search;
use Cms\CoreBundle\Service\OneRoster\AbstractOneRosterApi;
use Cms\CoreBundle\Service\OneRosterService;
use Cms\TenantBundle\Doctrine\TenantRepository;
use Cms\TenantBundle\Entity\Tenant;
use Cms\TenantBundle\Service\Search\TenantSearcher;
use Cms\TenantBundle\Service\TenantService;
use Doctrine\ORM\QueryBuilder;
use Platform\ControlPanelBundle\Controller\DashboardController;
use Platform\ControlPanelBundle\Form\OneRosterSyncType;
use Platform\ControlPanelBundle\Form\TenantEditType;
use Platform\ControlPanelBundle\Form\TenantType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* Class ControlPanelController
* @package Platform\ControlPanelBundle\Dashboard\Controller
*/
final class TenantController extends DashboardController
{
public const ROUTES__INDEX = 'platform.control_panel.dashboard.tenant.index';
public const ROUTES__LIST = 'platform.control_panel.dashboard.tenant.list';
public const ROUTES__CREATE = 'platform.control_panel.dashboard.tenant.create';
public const ROUTES__EDIT = 'platform.control_panel.dashboard.tenant.edit';
public const ROUTES__ONE_ROSTER_LANDING = 'platform.control_panel.dashboard.tenant.one_roster_landing';
public const ROUTES__ONE_ROSTER_SETUP = 'platform.control_panel.dashboard.tenant.one_roster_setup';
public const ROUTES__ONE_ROSTER_EXPLORER = 'platform.control_panel.dashboard.tenant.one_roster_explorer';
public const ROUTES__ONE_ROSTER_DESTROY = 'platform.control_panel.dashboard.tenant.one_roster_destroy';
public const ROUTES__ONE_ROSTER_ACTION = 'platform.control_panel.dashboard.tenant.one_roster_action';
public const ROUTES__NOTIFICATIONS_ONBOARDING = 'platform.control_panel.dashboard.tenant.notifications_onboarding';
public const ROUTES__PHPINFO = 'platform.control_panel.dashboard.tenant.phpinfo';
public const ROUTES__EXCP = 'platform.control_panel.dashboard.tenant.excp';
public const ROUTES__ONE_ROSTER_SYNCS = 'platform.control_panel.dashboard.tenant.one_roster_syncs';
public const ROUTES__ONE_ROSTER_SYNCS_ACTION = 'platform.control_panel.dashboard.tenant.one_roster_syncs_action';
public const ROUTES__ONE_ROSTER_DELETE = 'platform.control_panel.dashboard.tenant.one_roster_delete';
public const ROUTES__ONE_ROSTER_TOGGLE_SYNC = 'platform.control_panel.dashboard.tenant.one_roster_toggle_sync';
/**
* @return TenantRepository
*/
private function getTenantRepository()
{
return $this->getEntityManager()->getRepository(Tenant::class);
}
/**
* @return TenantService|object
*/
private function getTenantService(): TenantService
{
return $this->get(__METHOD__);
}
/**
* @return OneRosterService|object
*/
private function getOneRosterService(): OneRosterService
{
return $this->get(__METHOD__);
}
/**
* @return TenantSearcher|object
*/
private function getTenantSearcher(): TenantSearcher
{
return $this->get(__METHOD__);
}
/**
* @return MessagingContext|object
*/
private function getMessagingContext(): MessagingContext
{
return $this->get(__METHOD__);
}
/**
* @Route(
* "/phpinfo",
* name = TenantController::ROUTES__PHPINFO
* )
*/
public function phpinfoAction()
{
phpinfo();exit;
}
/**
* @Route(
* "/excp",
* name = TenantController::ROUTES__EXCP
* )
*/
public function excpAction()
{
throw new \Exception('Rollbar test.');
}
/**
* @return RedirectResponse
*
* @Route(
* "",
* name = TenantController::ROUTES__INDEX
* )
*/
public function indexAction()
{
return $this->redirectToRoute(self::ROUTES__LIST);
}
/**
* @return DocumentScene
*
* @Route(
* "/oneroster",
* name = TenantController::ROUTES__ONE_ROSTER_SYNCS
* )
*/
public function oneRosterSyncsAction()
{
ini_set('memory_limit','256M');
$syncs = $this->getContextManager()->getGlobalContext()->transactional(
function (GlobalContext $gc) {
$gc->setTenant(null);
return $this->getEntityManager()->getRepository(OneRosterSync::class)
->createQueryBuilder('syncs')
->leftJoin('syncs.tenant', 'tenant')
->addOrderBy('tenant.name', 'ASC')
->getQuery()
->getResult();
}
);
return $this->view([
'syncs' => $syncs
]);
}
/**
* @param string $action
* @return RedirectResponse
*
* @Route(
* "/oneroster/action/{action}",
* name = TenantController::ROUTES__ONE_ROSTER_SYNCS_ACTION
* )
*/
public function onerosterSyncsActionAction($action)
{
ini_set('memory_limit','256M');
$syncs = $this->getContextManager()->getGlobalContext()->transactional(
function (GlobalContext $gc) {
$gc->setTenant(null);
return $this->getEntityManager()->getRepository(OneRosterSync::class)->findRunnable();
}
);
foreach ($syncs as $sync) {
// TODO: check that the sync isn't currently running
$this->getContextManager()->getGlobalContext()->transactional(
function (GlobalContext $gc) use ($sync, $action) {
$gc->setTenant($sync->getTenant());
$this->getOneRosterService()->queue(
$sync,
OneRosterJob::PHASES[$action]
);
}
);
}
return $this->redirectToRoute(self::ROUTES__ONE_ROSTER_SYNCS);
}
/**
* @param Request $request
* @param string|null $primary
* @param string|null $secondary
* @return DocumentScene
*
* @Route(
* "/tenants/list",
* name = TenantController::ROUTES__LIST
* )
* @Route(
* "/tenants/list/{primary}",
* name = TenantController::ROUTES__LIST
* )
* @Route(
* "/tenants/list/{primary}/{secondary}",
* name = TenantController::ROUTES__LIST
* )
*/
public function listAction(Request $request, $primary = null, $secondary = null)
{
$search = $this->getTenantSearcher()->handleRequest($request);
if ($search instanceof RedirectResponse) {
return $search;
}
// TODO: this is a hack for now...
$search->addPagination(new Pagination('pagination:1x1000'));
$search->registerPreFilter(function (QueryBuilder $qb, Search $search) use ($primary, $secondary) {
if ($primary === 'pending-removal') {
$qb
->andWhere('nodes.status = :status')
->setParameter('status', Tenant::STATUS__INACTIVE);
} else {
$qb
->andWhere('nodes.status IN (:statuses)')
->setParameter('statuses', [
Tenant::STATUS__OK,
Tenant::STATUS__SUSPENDED,
]);
if ( ! empty($primary)) {
$qb
->andWhere('nodes.type.primary = :primary')
->setParameter('primary', $primary);
if ( ! empty($secondary)) {
$qb
->andWhere('nodes.type.secondary = :secondary')
->setParameter('secondary', $secondary);
}
}
}
});
return $this->view(array(
'domain' => $this->getParameter('dashboard.hostname'),
'tenants' => $this->getTenantRepository()->search($search),
'current' => $this->getContextManager()->getGlobalContext()->getTenant(),
'search' => $search,
));
}
/**
* @param Request $request
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/tenants/create",
* name = TenantController::ROUTES__CREATE
* )
*/
public function createAction(Request $request)
{
//tenant creation form
$form = $this->createForm(TenantType::class);
if ($this->handleForm($form, $request)) {
//create new tenant
try {
$this
->getTenantService()
->createTenant(
$form->get('name')->getData(),
$form->get('slug')->getData(),
$this->getParameter('app.security.csadmin.username'),
$this->getParameter('app.security.csadmin.password'),
$form->get('teamworkTasklist')->getData()
);
} catch (\Exception $e) {
$this->addFlash('error', $e->getMessage());
return $this->view(array(
'form' => $form->createView()
));
}
return $this->redirectToRoute(self::ROUTES__INDEX);
}
return $this->view(array(
'form' => $form->createView()
));
}
/**
* @param Request $request
* @param int $tenantId
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/tenants/{tenantId}/edit",
* name = TenantController::ROUTES__EDIT,
* requirements = {
* "tenantId" = "[0-9]+"
* }
* )
*/
public function editAction(Request $request, $tenantId)
{
$tenant = $this->getTenantRepository()->findExact($tenantId);
$form = $this->createForm(TenantEditType::class, $tenant, []);
if ($this->handleForm($form, $request)) {
// save tenant
$this->getEntityManager()->save($tenant);
// redirect to tenant list
return $this->redirectToRoute(self::ROUTES__INDEX);
}
return $this->view(array(
'tenant' => $tenant,
'form' => $form->createView(),
));
}
/**
* @param Tenant $tenant
* @return DocumentScene
*
* @Route(
* "/tenants/{tenant}/oneroster",
* name = TenantController::ROUTES__ONE_ROSTER_LANDING
* )
*
* @ParamConverter(
* "tenant",
* class = Tenant::class,
* )
*/
public function onerosterLandingAction(Tenant $tenant)
{
$this->getContextManager()->getGlobalContext()->unlock()->setTenant($tenant)->lock();
return $this->view(
array(
'tenant' => $tenant,
'sync' => $this->getEntityManager()->getRepository(OneRosterSync::class)->findOneBy(array(
'tenant' => $tenant,
)),
)
);
}
/**
* @param Tenant $tenant
* @return DocumentScene|RedirectResponse
*
* @Route(
* "/tenants/{tenant}/oneroster/setup",
* name = TenantController::ROUTES__ONE_ROSTER_SETUP
* )
*
* @ParamConverter(
* "tenant",
* class = Tenant::class,
* )
*/
public function onerosterSetupAction(Tenant $tenant)
{
$this->getContextManager()->getGlobalContext()->unlock()->setTenant($tenant)->lock();
$sync = $this->getEntityManager()->getRepository(OneRosterSync::class)->findOneBy(array(
'tenant' => $tenant,
));
if (empty($sync)) {
$sync = new OneRosterSync();
}
$form = $this->createForm(
OneRosterSyncType::class,
$sync,
[]
);
if ($this->handleForm($form)) {
$this->getContextManager()->getGlobalContext()->transactional(
function (GlobalContext $gc) use ($tenant, $sync) {
$gc->setTenant($tenant);
$this->getEntityManager()->save($sync);
}
);
return $this->redirectToRoute(
self::ROUTES__ONE_ROSTER_LANDING,
array(
'tenant' => $tenant->getId(),
)
);
}
return $this->view(
array(
'tenant' => $tenant,
'sync' => $sync,
'form' => $form->createView(),
)
);
}
/**
* @param Tenant $tenant
* @return DocumentScene|RedirectResponse
*
* @Route(
* "/tenants/{tenant}/oneroster/explorer",
* name = TenantController::ROUTES__ONE_ROSTER_EXPLORER,
* )
*
* @ParamConverter(
* "tenant",
* class = Tenant::class,
* )
*/
public function onerosterExplorerAction(Tenant $tenant)
{
$this->getContextManager()->getGlobalContext()->unlock()->setTenant($tenant)->lock();
$sync = $this->getEntityManager()->getRepository(OneRosterSync::class)->findOneBy(array(
'tenant' => $tenant,
));
if (empty($sync)) {
return $this->redirectToRoute(self::ROUTES__ONE_ROSTER_LANDING, [
'tenant' => $tenant->getId(),
]);
}
$endpoint = $this->getRequest()->query->get('endpoint');
$form = null;
$vars = [];
if ($endpoint) {
$form = $this->createFormBuilder([], [
'action' => $this->generateUrl(self::ROUTES__ONE_ROSTER_EXPLORER, [
'tenant' => $tenant->getId(),
'endpoint' => $endpoint,
]),
]);
preg_match_all('/{(.+?)}/', $endpoint, $vars);
foreach ($vars[1] as $var) {
$form->add($var, TextType::class, []);
}
if (AbstractOneRosterApi::ENDPOINTS[$endpoint] === true) {
$form
->add('offset', IntegerType::class, [
'required' => false,
])
->add('limit', IntegerType::class, [
'required' => false,
]);
}
$form = $form->getForm();
} else {
$this->getEntityManager()->save(
$sync->setApiToken(
$this->getOneRosterService()->api($sync)->token(true)
)
);
}
$result = null;
if ($endpoint && $form && $this->handleForm($form)) {
$enpt = $endpoint;
foreach ($vars[1] as $var) {
$enpt = str_replace(
sprintf(
'{%s}',
$var
),
$form->get($var)->getData(),
$enpt
);
}
$result = $this->getOneRosterService()->api($sync)->raw(
$enpt,
array_diff_key($form->getData(), $vars[1])
);
}
return $this->view(
array(
'tenant' => $tenant,
'selection' => $endpoint,
'endpoints' => array_keys(AbstractOneRosterApi::ENDPOINTS),
'form' => ($form) ? $form->createView() : null,
'result' => $result,
)
);
}
/**
* @param Tenant $tenant
* @return DocumentScene
*
* @Route(
* "/tenants/{tenant}/oneroster/destroy",
* name = TenantController::ROUTES__ONE_ROSTER_DESTROY
* )
*
* @ParamConverter(
* "tenant",
* class = Tenant::class,
* )
*/
public function onerosterDestroyAction(Tenant $tenant)
{
$this->getContextManager()->getGlobalContext()->unlock()->setTenant($tenant)->lock();
return $this->view(
array(
'tenant' => $tenant,
'sync' => $this->getEntityManager()->getRepository(OneRosterSync::class)->findOneBy(array(
'tenant' => $tenant,
)),
)
);
}
/**
* @param Tenant $tenant
* @param string $action
* @return RedirectResponse
*
* @Route(
* "/tenants/{tenant}/oneroster/action/{action}",
* name = TenantController::ROUTES__ONE_ROSTER_ACTION
* )
*
* @ParamConverter(
* "tenant",
* class = Tenant::class,
* )
*/
public function onerosterActionAction(Tenant $tenant, $action)
{
$this->getContextManager()->getGlobalContext()->unlock()->setTenant($tenant)->lock();
$sync = $this->getEntityManager()->getRepository(OneRosterSync::class)->findOneBy(array(
'tenant' => $tenant,
));
if ($sync instanceof OneRosterSync) {
// TODO: check that sync is not currently running
$this->getOneRosterService()->queue(
$sync,
OneRosterJob::PHASES[$action]
);
}
return $this->redirectToRoute(
self::ROUTES__ONE_ROSTER_LANDING,
array(
'tenant' => $tenant->getId(),
)
);
}
/**
* @return DocumentScene
*
* @Route(
* "/notifications-onboarding",
* name = TenantController::ROUTES__NOTIFICATIONS_ONBOARDING,
* )
*/
public function notificationsOnboardingAction(): DocumentScene
{
$tenant = $this->getContextManager()->getGlobalContext()->getTenant();
$this->getContextManager()->getGlobalContext()->unlock()->setTenant(null)->lock();
$configs = $this->getMessagingContext()->getAllConfigs();
// HACK: need to preload the domains as tenant stuff kicks in and is blocking certain domains from being reported on
foreach ($configs as $config) {
if ($config instanceof SendgridConfig) {
$config->getDomains();
}
}
$this->getContextManager()->getGlobalContext()->unlock()->setTenant($tenant)->lock();
return $this->view(
array(
'configs' => $configs,
)
);
}
/**
* @param Request $request
* @param Tenant $tenant
* @return DocumentScene|RedirectResponse
*
* @Route(
* "/tenants/{tenant}/oneroster/delete",
* name = TenantController::ROUTES__ONE_ROSTER_DELETE,
* )
*/
public function onerosterDeleteAction(Request $request, Tenant $tenant)
{
$this->getContextManager()->getGlobalContext()->unlock()->setTenant($tenant)->lock();
if ($this->getContextManager()->getGlobalContext()->getTenant() !== $tenant) {
throw $this->createAccessDeniedException();
}
$sync = $this->getEntityManager()->getRepository(OneRosterSync::class)->findOneBy([
'tenant' => $tenant,
]);
if ($sync instanceof OneRosterSync && $request->isMethod('POST')) {
$this->getOneRosterService()->delete($sync);
return $this->redirectToRoute(
self::ROUTES__ONE_ROSTER_LANDING,
[
'tenant' => $tenant->getId(),
]
);
}
return $this->view(
[
'sync' => $sync,
]
);
}
/**
* @param Tenant $tenant
* @return RedirectResponse
*
* @Route(
* "/tenants/{tenant}/oneroster/toggle-sync-status",
* name = TenantController::ROUTES__ONE_ROSTER_TOGGLE_SYNC,
* )
*
* @ParamConverter(
* "tenant",
* class = Tenant::class,
* )
*/
public function toggleOnerosterSyncAction(Tenant $tenant): RedirectResponse
{
$this->getContextManager()->getGlobalContext()->unlock()->setTenant($tenant)->lock();
if ($this->getContextManager()->getGlobalContext()->getTenant() !== $tenant) {
throw $this->createAccessDeniedException();
}
$sync = $this->getEntityManager()->getRepository(OneRosterSync::class)->findOneBy([
'tenant' => $tenant,
]);
if ($sync instanceof OneRosterSync) {
$this->getEntityManager()->save($sync->setActive( ! $sync->isActive()));
}
return $this->redirectToRoute(
self::ROUTES__ONE_ROSTER_LANDING,
[
'tenant' => $tenant->getId(),
]
);
}
}