<?php
namespace Cms\TenantBundle\Controller\Dashboard;
use App\Entity\System\SocialAccount;
use App\Entity\System\SocialAccounts\FacebookSocialAccount;
use App\Entity\System\SocialAccounts\InstagramSocialAccount;
use App\Entity\System\SocialAccounts\TwitterSocialAccount;
use App\Service\Social\FacebookService;
use App\Service\Social\InstagramService;
use App\Service\Social\TwitterService;
use Cms\CoreBundle\Form\Type\ConfirmationType;
use Cms\CoreBundle\Form\Type\SwitchType;
use Cms\CoreBundle\Model\Scenes\DashboardScenes\DocumentScene;
use Cms\CoreBundle\Util\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* Class SocialController
* @package Cms\TenantBundle\Controller\Dashboard
*/
final class SocialController extends Controller
{
const ROUTES__MAIN = 'cms.tenant.dashboard.social.main';
const ROUTES__CREATE = 'cms.tenant.dashboard.social.create';
const ROUTES__CHOOSE_FACEBOOK = 'cms.tenant.dashboard.social.choose_facebook';
const ROUTES__CHOOSE_INSTAGRAM = 'cms.tenant.dashboard.social.choose_instagram';
const ROUTES__CHOOSE_TWITTER = 'cms.tenant.dashboard.social.choose_twitter';
const ROUTES__TOGGLE = 'cms.tenant.dashboard.social.toggle';
const ROUTES__DELETE = 'cms.tenant.dashboard.social.delete';
/**
* @return InstagramService|object
*/
protected function getInstagramService(): InstagramService
{
return $this->get(__METHOD__);
}
/**
* @return FacebookService|object
*/
protected function getFacebookService(): FacebookService
{
return $this->get(__METHOD__);
}
/**
* @return TwitterService|object
*/
protected function getTwitterService(): TwitterService
{
return $this->get(__METHOD__);
}
/**
* @return DocumentScene
*
* @Route(
* "",
* name = self::ROUTES__MAIN,
* )
*/
public function mainAction(): DocumentScene
{
// AUDIT
$this->denyAccessUnlessGranted('app.social.admin');
return $this->view(
[
'accounts' => $this->getEntityManager()->getRepository(SocialAccount::class)->findAll(),
]
);
}
/**
* @param string $type
* @return RedirectResponse
*
* @Route(
* "/accounts/create/{type}",
* name = self::ROUTES__CREATE,
* requirements = {
* "type" = "facebook|twitter|instagram",
* },
* )
*/
public function createAction(string $type): RedirectResponse
{
// AUDIT
$this->denyAccessUnlessGranted('app.social.admin');
switch ($type) {
case 'facebook':
// get url to redirect to for facebook auth
return $this->redirect(
$this->getFacebookService()->requestAuthenticationUrl(
sprintf(
'https://%s/callback/facebook',
$this->getGlobalContext()->getDashboard(true)
),
[
'tenant' => $this->getGlobalContext()->getTenant()->getSlug(),
'route' => self::ROUTES__CHOOSE_FACEBOOK,
]
)
);
case 'instagram':
// get url to redirect to for facebook auth
return $this->redirect(
$this->getInstagramService()->requestAuthenticationUrl(
sprintf(
'https://%s/callback/instagram',
$this->getGlobalContext()->getDashboard(true)
),
[
'tenant' => $this->getGlobalContext()->getTenant()->getSlug(),
'route' => self::ROUTES__CHOOSE_INSTAGRAM,
]
)
);
case 'twitter':
// generate the url
$callback = sprintf(
'https://%s/callback/twitter?tenant=%s&route=%s',
$this->getGlobalContext()->getDashboard(true),
$this->getGlobalContext()->getTenant()->getSlug(),
self::ROUTES__CHOOSE_TWITTER
);
// generate the redirect
return $this->redirect(
$this->getTwitterService()->setupAccount($callback)->getAbsoluteUri()
);
default:
throw new \Exception();
}
}
/**
* @param string|null $accessToken
* @return RedirectResponse
*
* @Route(
* "/accounts/create/facebook/callback/{accessToken}",
* name = self::ROUTES__CHOOSE_FACEBOOK,
* requirements = {
* "accessToken" = "[a-zA-Z0-9]+",
* },
* defaults = {
* "accessToken" = null,
* },
* )
*/
public function facebookCallbackAction(?string $accessToken = null): RedirectResponse
{
// AUDIT
$this->denyAccessUnlessGranted('app.social.admin');
// if we don't have an access token, user may have cancelled
// return to accounts
if (empty($accessToken)) {
$this->addFlash('danger',
'System error: Facebook access token was not given.'
);
return $this->redirectToRoute(self::ROUTES__MAIN);
}
// get details about the user from facebook
$me = $this->getFacebookService()->me($accessToken);
// get list of pages
$pages = $this->getFacebookService()->getAccounts($accessToken);
// handle no pages
if (empty($pages)) {
$this->addFlash('warning',
'Your Facebook account does not appear to manage any Facebook Pages.'
);
return $this->redirectToRoute(self::ROUTES__MAIN);
}
// FB page permissions are granted for all pages,
// so if not granted, we can not publish on all of them
if ( ! $this->getFacebookService()->hasRequiredScopes($accessToken)) {
$this->addFlash('warning',
'Your Facebook account does not appear to allow our application the proper permissions to manage your Pages. Please contact support for help with this issue.'
);
return $this->redirectToRoute(self::ROUTES__MAIN);
}
// holder for db objects
$accounts = [];
// loop over each chosen page
foreach ($pages as $page) {
// create the settings objects
$account = $this->getEntityManager()->getRepository(FacebookSocialAccount::class)->findOneBy([
'facebookPageId' => $page->id,
]);
// if not already created, make a new one
if (empty($account)) {
$account = (new FacebookSocialAccount())
->setFacebookPageId($page->id);
}
// set basic details
$account
->setName($page->name)
->setFacebookAccessToken($page->token)
->setFacebookUserId($me->id)
->setFacebookUserName($me->name);
// TODO: subscribe the page to the app?
//$this->getFacebookService()->subscribe($page->token, $page->id);
// track each
$accounts[] = $account;
}
// save them all
$this->getEntityManager()->saveAll($accounts);
// record log
$this->getActivityLogger()->createLogs($accounts);
// go back to listing page
return $this->redirectToRoute(self::ROUTES__MAIN);
}
/**
* @param string|null $accessToken
* @return RedirectResponse
*
* @Route(
* "/accounts/create/instagram/callback/{accessToken}",
* name = self::ROUTES__CHOOSE_INSTAGRAM,
* requirements = {
* "accessToken" = "[a-zA-Z0-9]+",
* },
* defaults = {
* "accessToken" = null,
* },
* )
*/
public function instagramCallbackAction(?string $accessToken = null): RedirectResponse
{
// AUDIT
$this->denyAccessUnlessGranted('app.social.admin');
// if we don't have an access token, user may have cancelled
// return to accounts
if (empty($accessToken)) {
$this->addFlash('danger',
'System error: Instagram access token was not given.'
);
return $this->redirectToRoute(self::ROUTES__MAIN);
}
// get details about the user from facebook
$me = $this->getInstagramService()->me($accessToken);
// get list of pages
$profiles = $this->getInstagramService()->getAccounts($accessToken);
// handle no pages
if (empty($profiles)) {
$this->addFlash('warning',
'Your Meta account does not appear to manage any Instagram Profiles.'
);
return $this->redirectToRoute(self::ROUTES__MAIN);
}
// FB page permissions are granted for all pages,
// so if not granted, we can not publish on all of them
if ( ! $this->getInstagramService()->hasRequiredScopes($accessToken)) {
$this->addFlash('warning',
'Your Meta account does not appear to allow our application the proper permissions to manage your Instagram Profiles. Please contact support for help with this issue.'
);
return $this->redirectToRoute(self::ROUTES__MAIN);
}
// holder for db objects
$accounts = [];
// loop over each chosen page
foreach ($profiles as $profile) {
// create the settings objects
$account = $this->getEntityManager()->getRepository(InstagramSocialAccount::class)->findOneBy([
'instagramProfileId' => $profile->id,
]);
// if not already created, make a new one
if (empty($account)) {
$account = (new InstagramSocialAccount())
->setInstagramProfileId($profile->id);
}
// set basic details
$account
->setName($profile->username)
->setInstagramAccessToken($profile->token)
->setInstagramUserId($me->id)
->setInstagramUserName($me->name);
// track each
$accounts[] = $account;
}
// save them all
$this->getEntityManager()->saveAll($accounts);
// record log
$this->getActivityLogger()->createLogs($accounts);
// go back to listing page
return $this->redirectToRoute(self::ROUTES__MAIN);
}
/**
* @param Request $request
* @return RedirectResponse
*
* @Route(
* "/accounts/create/twitter/callback",
* name = self::ROUTES__CHOOSE_TWITTER,
* )
*/
public function twitterCallbackAction(Request $request): RedirectResponse
{
// AUDIT
$this->denyAccessUnlessGranted('app.social.admin');
// generate the callback url
$callback = sprintf(
'https://%s/callback/twitter?tenant=%s&route=%s',
$this->getGlobalContext()->getDashboard(true),
$this->getGlobalContext()->getTenant()->getSlug(),
self::ROUTES__CHOOSE_TWITTER
);
// should have some request data
if ( ! empty($request->query->get('oauth_verifier'))) {
// obtain info about the account
$data = $this->getTwitterService()->verifyCredentials(
$request->query->get('oauth_token'),
$request->query->get('oauth_verifier'),
$callback
);
// try to find the new settings
$account = $this->getEntityManager()->getRepository(TwitterSocialAccount::class)->findOneBy([
'twitterUserId' => $data['id'],
]);
// create the new settings if we don't have one
if (empty($account)) {
$account = (new TwitterSocialAccount())
->setTwitterUserId($data['id']);
}
// set basic data
$account
->setName($data['name'])
->setTwitterUserName($data['screenName'])
->setTwitterAccessToken($data['token'])
->setTwitterTokenSecret($data['tokenSecret']);
// save it
$this->getEntityManager()->save($account);
// record log
$this->getActivityLogger()->createLog($account);
} else {
// notify of issue
$this->addFlash('danger',
'System error: Twitter account could not be registered.'
);
}
return $this->redirectToRoute(self::ROUTES__MAIN);
}
/**
* @param SocialAccount $account
* @return DocumentScene|RedirectResponse
*
* @Route(
* "/accounts/toggle/{account}",
* requirements = {
* "account" = "[1-9]\d*",
* },
* name = self::ROUTES__TOGGLE,
* )
*
* @ParamConverter(
* "account",
* class = "App\Entity\System\SocialAccount",
* )
*/
public function toggleAction(SocialAccount $account)
{
// AUDIT
$this->denyAccessUnlessGranted('app.social.admin');
// generate the form we will be using
$form = $this->createFormBuilder($account->getUsesAsToggles(), [])
->add('name', TextType::class, [
'mapped' => false,
'disabled' => true,
'data' => $account->getName(),
]);
foreach ($account::USES as $name => $bitmask) {
$form->add($name, SwitchType::class, [
'required' => false,
]);
}
$form = $form->getForm();
// handle submission
if ($this->handleForm($form)) {
// handle the toggling of the individual features
$toggles = $form->getData();
foreach ($toggles as $name => $value) {
$account->toggleUses($account::USES[$name], ($value == true));
}
$this->getEntityManager()->save($account);
// record log
$this->getActivityLogger()->createLog($account);
return $this->redirectToRoute(self::ROUTES__MAIN);
}
return $this->view(
[
'account' => $account,
'form' => $form->createView(),
]
);
}
/**
* @param SocialAccount $account
* @return DocumentScene|RedirectResponse
*
* @Route(
* "/accounts/delete/{account}",
* name = self::ROUTES__DELETE,
* requirements = {
* "account" = "[1-9]\d*",
* },
* )
*
* @ParamConverter(
* "account",
* class = "App\Entity\System\SocialAccount",
* )
*/
public function deleteAction(SocialAccount $account)
{
// AUDIT
$this->denyAccessUnlessGranted('app.social.admin');
// create form, use common type
$form = $this->createForm(ConfirmationType::class, [], []);
// handle submission
if ($this->handleForm($form)) {
// extract the id for use later
$id = $account->getId();
// remove the account
$this->getEntityManager()->delete($account);
// record log
$this->getActivityLogger()->createLog($account, $id);
return $this->redirectToRoute(self::ROUTES__MAIN);
}
return $this->view(
[
'account' => $account,
'form' => $form->createView(),
]
);
}
}