Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
# Workflow name:
|
||||
name: Close Pull Requests
|
||||
|
||||
# Workflow triggers:
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
# Workflow jobs:
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: superbrothers/close-pull-request@v3
|
||||
with:
|
||||
comment: |
|
||||
Thank you for submitting a pull request. :raised_hands:
|
||||
|
||||
We greatly appreciate your willingness to submit a contribution. However, we are not accepting pull requests against this repository, as all development happens on the [main project repository](https://github.com/mautic/mautic).
|
||||
|
||||
We kindly request that you submit this pull request against the [respective directory](https://github.com/mautic/mautic/blob/head/plugins/MauticSocialBundle) of the main repository where we'll review and provide feedback. If this is your first Mautic contribution, be sure to read the [contributing guide](https://github.com/mautic/mautic/blob/4.x/.github/CONTRIBUTING.md) which provides guidelines and instructions for submitting contributions.
|
||||
|
||||
Thank you again, and we look forward to receiving your contribution! :smiley:
|
||||
|
||||
Best,
|
||||
The Mautic team
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,122 @@
|
||||
Mautic.getNetworkFormAction = function(networkType) {
|
||||
// removes errors when network type properties has changed
|
||||
if (networkType && mQuery(networkType).val() && mQuery(networkType).closest('.form-group').hasClass('has-error')) {
|
||||
mQuery(networkType).closest('.form-group').removeClass('has-error');
|
||||
if (mQuery(networkType).next().hasClass('help-block')) {
|
||||
mQuery(networkType).next().remove();
|
||||
}
|
||||
}
|
||||
|
||||
Mautic.activateLabelLoadingIndicator('monitoring_networkType');
|
||||
|
||||
var query = "action=plugin:mauticSocial:getNetworkForm&networkType=" + mQuery(networkType).val();
|
||||
|
||||
mQuery.ajax({
|
||||
url: mauticAjaxUrl,
|
||||
type: "POST",
|
||||
data: query,
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
if (typeof response.html != 'undefined') {
|
||||
// pushes response into container element
|
||||
mQuery('#properties-container').html(response.html);
|
||||
|
||||
// sends markup through core js parsers
|
||||
if (response.html != '') {
|
||||
Mautic.onPageLoad('#properties-container', response);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
error: function (request, textStatus, errorThrown) {
|
||||
Mautic.processAjaxError(request, textStatus, errorThrown);
|
||||
},
|
||||
complete: function() {
|
||||
Mautic.removeLabelLoadingIndicator();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* watches the compose field and updates various parts of the modal and text area
|
||||
*/
|
||||
Mautic.composeSocialWatcher = function() {
|
||||
// the text area
|
||||
var input = mQuery('textarea.tweet-message');
|
||||
|
||||
// on load
|
||||
Mautic.updateCharacterCount();
|
||||
|
||||
// watch the text area keyup
|
||||
input.on('keyup', function(){
|
||||
Mautic.updateCharacterCount();
|
||||
});
|
||||
|
||||
var pageId = mQuery('select.tweet-insert-page');
|
||||
var assetId = mQuery('select.tweet-insert-asset');
|
||||
var handle = mQuery('button.tweet-insert-handle');
|
||||
|
||||
pageId.on('change', function() {
|
||||
Mautic.insertSocialLink(pageId.val(), 'pagelink', false);
|
||||
});
|
||||
|
||||
assetId.on('change', function() {
|
||||
Mautic.insertSocialLink(assetId.val(), 'assetlink', false);
|
||||
});
|
||||
|
||||
handle.on('click', function() {
|
||||
Mautic.insertSocialLink(false, 'twitter_handle', true);
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* gets the count of the text area and returns (140 - count)
|
||||
*/
|
||||
Mautic.getCharacterCount = function() {
|
||||
var tweetLenght = 280;
|
||||
|
||||
var currentLength = mQuery('textarea#twitter_tweet_text');
|
||||
|
||||
return (tweetLenght - currentLength.val().length);
|
||||
};
|
||||
|
||||
/*
|
||||
* sets the content of the character count span
|
||||
*/
|
||||
Mautic.updateCharacterCount = function() {
|
||||
var tweetCount = Mautic.getCharacterCount();
|
||||
|
||||
var countContainer = mQuery('#character-count span');
|
||||
|
||||
countContainer.text(tweetCount);
|
||||
};
|
||||
|
||||
/*
|
||||
* inserts a link placeholder into the text box.
|
||||
*
|
||||
* @id the id of the link placeholder
|
||||
* @type the type of link to insert
|
||||
* @skipId if the id is blank and this is true it'll still insert the link
|
||||
*/
|
||||
Mautic.insertSocialLink = function(id, type, skipId) {
|
||||
|
||||
// if there is no id and skipID is false then exit
|
||||
if (! id && ! skipId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we need to skip the id state just leave it out
|
||||
if (skipId) {
|
||||
var link = '{' + type + '}';
|
||||
}
|
||||
else {
|
||||
var link = '{' + type + '=' + id + '}';
|
||||
}
|
||||
|
||||
var textarea = mQuery('textarea.tweet-message');
|
||||
var currentVal = textarea.val();
|
||||
var newVal = (currentVal) ? currentVal + ' ' + link : link;
|
||||
textarea.val(newVal);
|
||||
|
||||
Mautic.updateCharacterCount();
|
||||
};
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Command;
|
||||
|
||||
use MauticPlugin\MauticSocialBundle\Entity\MonitoringRepository;
|
||||
use MauticPlugin\MauticSocialBundle\Model\MonitoringModel;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'mautic:social:monitoring',
|
||||
description: 'Looks at the records of monitors and iterates through them.'
|
||||
)]
|
||||
class MauticSocialMonitoringCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private MonitoringModel $monitoringModel,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->addOption('mid', 'i', InputOption::VALUE_OPTIONAL, 'The id of a specific monitor record to process')
|
||||
->addOption(
|
||||
'batch-size',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'The maximum number of iterations the cron runs per cycle. This value gets distributed by the number of monitor records published'
|
||||
)
|
||||
->addOption('query-count', null, InputOption::VALUE_OPTIONAL, 'The number of records to search for per iteration. Default is 100.', 100);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
// get the mid from the cli
|
||||
$batchSize = $input->getOption('batch-size');
|
||||
|
||||
// monitor record
|
||||
$monitorId = $input->getOption('mid');
|
||||
$monitorList = $this->getMonitors($monitorId);
|
||||
|
||||
// no mid found, quit now
|
||||
if (!$monitorList->count()) {
|
||||
$output->writeln('No published monitors found. Make sure the id you supplied is published');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
if (!is_numeric($batchSize)) {
|
||||
$output->writeln('batch-size is not number.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
// max iterations
|
||||
$maxPerIterations = ceil((int) $batchSize / count($monitorList));
|
||||
|
||||
foreach ($monitorList as $monitor) {
|
||||
$output->writeln('Executing Monitor Item '.$monitor->getId());
|
||||
$resultCode = $this->processMonitorListItem($monitor, $maxPerIterations, $input, $output);
|
||||
$output->writeln('Result Code: '.$resultCode);
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Tools\Pagination\Paginator
|
||||
*/
|
||||
protected function getMonitors($id = null)
|
||||
{
|
||||
$filter = [
|
||||
'start' => 0,
|
||||
'limit' => 100,
|
||||
];
|
||||
|
||||
/** @var MonitoringRepository $repository */
|
||||
$repository = $this->monitoringModel->getRepository();
|
||||
|
||||
if (null !== $id) {
|
||||
$filter['filter'] = [
|
||||
'force' => [
|
||||
[
|
||||
'column' => $repository->getTableAlias().'.id',
|
||||
'expr' => 'eq',
|
||||
'value' => (int) $id,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $repository->getPublishedEntities($filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|int
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function processMonitorListItem($listItem, float $maxPerIterations, InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
// @todo set this up to use the command type per-monitor record.
|
||||
$networkType = $listItem->getNetworkType();
|
||||
|
||||
$commandName = '';
|
||||
|
||||
// hashtag command
|
||||
if ('twitter_hashtag' == $networkType) {
|
||||
$commandName = 'social:monitor:twitter:hashtags';
|
||||
}
|
||||
|
||||
// mention command
|
||||
if ('twitter_handle' == $networkType) {
|
||||
$commandName = 'social:monitor:twitter:mentions';
|
||||
}
|
||||
|
||||
if ('' == $commandName) {
|
||||
$output->writeln('Matching command not found.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// monitor hash command
|
||||
$command = $this->getApplication()->find($commandName);
|
||||
|
||||
// create command options
|
||||
$cliArgs = [
|
||||
'command' => $commandName,
|
||||
'--mid' => $listItem->getId(),
|
||||
'--max-runs' => $maxPerIterations,
|
||||
'--query-count' => $input->getOption('query-count'),
|
||||
];
|
||||
|
||||
// execute the command
|
||||
$returnCode = $command->run(new ArrayInput($cliArgs), $output);
|
||||
|
||||
return $returnCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Command;
|
||||
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\Monitoring;
|
||||
use MauticPlugin\MauticSocialBundle\Event\SocialMonitorEvent;
|
||||
use MauticPlugin\MauticSocialBundle\Helper\TwitterCommandHelper;
|
||||
use MauticPlugin\MauticSocialBundle\Integration\TwitterIntegration;
|
||||
use MauticPlugin\MauticSocialBundle\SocialEvents;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
abstract class MonitorTwitterBaseCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var TwitterIntegration
|
||||
*/
|
||||
protected $twitter;
|
||||
|
||||
/**
|
||||
* @var InputInterface
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $maxRuns = 5;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $runCount = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $queryCount = 100;
|
||||
|
||||
public function __construct(
|
||||
protected EventDispatcherInterface $dispatcher,
|
||||
protected Translator $translator,
|
||||
protected IntegrationHelper $integrationHelper,
|
||||
private TwitterCommandHelper $twitterCommandHelper,
|
||||
CoreParametersHelper $coreParametersHelper,
|
||||
) {
|
||||
$this->translator->setLocale($coreParametersHelper->get('locale', 'en_US'));
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Command configuration. Set the name, description, and options here.
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->addOption(
|
||||
'mid',
|
||||
'i',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'The id of the monitor record'
|
||||
)
|
||||
->addOption(
|
||||
'max-runs',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'The maximum number of recursive iterations permitted',
|
||||
5
|
||||
)
|
||||
->addOption(
|
||||
'query-count',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The number of records to search for per iteration.',
|
||||
100
|
||||
)
|
||||
->addOption(
|
||||
'show-posts',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Use this option to display the posts retrieved'
|
||||
)
|
||||
->addOption(
|
||||
'show-stats',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Use this option to display the stats of the tweets fetched'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in various areas to set name of the network being searched.
|
||||
*
|
||||
* @return string twitter|facebook etc..
|
||||
*/
|
||||
abstract public function getNetworkName();
|
||||
|
||||
/**
|
||||
* Search for tweets by creating your own search criteria.
|
||||
*
|
||||
* @param Monitoring $monitor
|
||||
*
|
||||
* @return array The results of makeRequest
|
||||
*/
|
||||
abstract protected function getTweets($monitor);
|
||||
|
||||
/**
|
||||
* Main execution method. Gets the integration settings, processes the search criteria.
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
$this->maxRuns = $this->input->getOption('max-runs');
|
||||
$this->queryCount = $this->input->getOption('query-count');
|
||||
$twitterIntegration = $this->integrationHelper->getIntegrationObject('Twitter');
|
||||
|
||||
if (false === $twitterIntegration || false === $twitterIntegration->getIntegrationSettings()->getIsPublished()) {
|
||||
$this->output->writeln($this->translator->trans('mautic.social.monitoring.twitter.not.published'));
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
\assert($twitterIntegration instanceof TwitterIntegration);
|
||||
$this->twitter = $twitterIntegration;
|
||||
|
||||
if (!$this->twitter->isAuthorized()) {
|
||||
$this->output->writeln($this->translator->trans('mautic.social.monitoring.twitter.not.configured'));
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// get the mid from the cli
|
||||
$mid = (int) $input->getOption('mid');
|
||||
|
||||
if (!$mid) {
|
||||
$this->output->writeln($this->translator->trans('mautic.social.monitoring.twitter.mid.empty'));
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->twitterCommandHelper->setOutput($output);
|
||||
|
||||
$monitor = $this->twitterCommandHelper->getMonitor($mid);
|
||||
|
||||
if (!$monitor || !$monitor->getId()) {
|
||||
$this->output->writeln($this->translator->trans('mautic.social.monitoring.twitter.monitor.does.not.exist', ['%id%' => $mid]));
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// process the monitor
|
||||
$this->processMonitor($monitor);
|
||||
|
||||
$this->dispatcher->dispatch(
|
||||
new SocialMonitorEvent($this->getNetworkName(), $monitor, $this->twitterCommandHelper->getManipulatedLeads(), $this->twitterCommandHelper->getNewLeadsCount(), $this->twitterCommandHelper->getUpdatedLeadsCount()),
|
||||
SocialEvents::MONITOR_POST_PROCESS
|
||||
);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the monitor record.
|
||||
*
|
||||
* @Note: Keeping this method here instead of in the twitterCommandHelper
|
||||
* so that the hashtag and mention commands can easily extend it.
|
||||
*
|
||||
* @param Monitoring $monitor
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function processMonitor($monitor)
|
||||
{
|
||||
$results = $this->getTweets($monitor);
|
||||
|
||||
if (false === $results || !isset($results['statuses'])) {
|
||||
$this->output->writeln('No statuses found');
|
||||
|
||||
if (!empty($results['errors'])) {
|
||||
foreach ($results['errors'] as $error) {
|
||||
$this->output->writeln($error['code'].': '.$error['message']);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count($results['statuses'])) {
|
||||
$this->twitterCommandHelper->createLeadsFromStatuses($results['statuses'], $monitor);
|
||||
} else {
|
||||
$this->output->writeln($this->translator->trans('mautic.social.monitoring.twitter.no.new.tweets'));
|
||||
}
|
||||
|
||||
$this->twitterCommandHelper->setMonitorStats($monitor, $results['search_metadata']);
|
||||
$this->printInformation($monitor, $results);
|
||||
|
||||
// get stats after being updated
|
||||
$stats = $monitor->getStats();
|
||||
|
||||
++$this->runCount;
|
||||
|
||||
// if we have stats and a next results request, process it here
|
||||
// @todo add a check for max iterations
|
||||
if (is_array($stats) && array_key_exists('max_id_str', $stats)
|
||||
&& ($this->runCount < $this->maxRuns)
|
||||
&& count($results['statuses'])
|
||||
) {
|
||||
// recursive
|
||||
$this->processMonitor($monitor);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints all the returned tweets.
|
||||
*
|
||||
* @param array $statuses
|
||||
*/
|
||||
protected function printTweets($statuses)
|
||||
{
|
||||
if (!$this->input->getOption('show-posts') && $this->output->getVerbosity() < OutputInterface::VERBOSITY_VERY_VERBOSE) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($statuses as $status) {
|
||||
$this->output->writeln('-- tweet -- ');
|
||||
$this->output->writeln('ID: '.$status['id']);
|
||||
$this->output->writeln('Message: '.$status['text']);
|
||||
$this->output->writeln('Handle: '.$status['user']['screen_name']);
|
||||
$this->output->writeln('Name: '.$status['user']['name']);
|
||||
$this->output->writeln('Location: '.$status['user']['location']);
|
||||
$this->output->writeln('Profile Img: '.$status['user']['profile_image_url']);
|
||||
$this->output->writeln('Profile Description: '.$status['user']['description']);
|
||||
$this->output->writeln('// tweet // ');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the search query metadata from twitter.
|
||||
* Only shows stats if explicitly requested or if we're in verbose mode.
|
||||
*
|
||||
* @param array $metadata
|
||||
*/
|
||||
protected function printQueryMetadata($metadata)
|
||||
{
|
||||
if (!$this->input->getOption('show-stats') && $this->output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->output->writeln('-- search meta -- ');
|
||||
$this->output->writeln('max_id_str: '.$metadata['max_id_str']);
|
||||
$this->output->writeln('since_id_str: '.$metadata['since_id_str']);
|
||||
$this->output->writeln('Page Count: '.$metadata['count']);
|
||||
$this->output->writeln('query: '.$metadata['query']);
|
||||
|
||||
if (array_key_exists('next_results', $metadata)) {
|
||||
$this->output->writeln('next results: '.$metadata['next_results']);
|
||||
}
|
||||
|
||||
$this->output->writeln('// search meta // ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a summary of the search query.
|
||||
* Only shows stats if explicitly requested or if we're in verbose mode.
|
||||
*
|
||||
* @param Monitoring $monitor
|
||||
* @param array $results
|
||||
*/
|
||||
protected function printInformation($monitor, $results)
|
||||
{
|
||||
if (!$this->input->getOption('show-stats') && $this->output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->output->writeln('------------------------');
|
||||
$this->output->writeln($monitor->getTitle());
|
||||
$this->output->writeln('Published '.$monitor->isPublished());
|
||||
$this->output->writeln($monitor->getNetworkType());
|
||||
$this->output->writeln('New Leads '.$this->twitterCommandHelper->getNewLeadsCount());
|
||||
$this->output->writeln('Updated Leads '.$this->twitterCommandHelper->getUpdatedLeadsCount());
|
||||
$this->printQueryMetadata($results['search_metadata']);
|
||||
$this->printTweets($results['statuses']);
|
||||
$this->output->writeln('------------------------');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Command;
|
||||
|
||||
use MauticPlugin\MauticSocialBundle\Entity\Monitoring;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'social:monitor:twitter:hashtags',
|
||||
description: 'Looks at our monitoring records and finds hashtags'
|
||||
)]
|
||||
class MonitorTwitterHashtagsCommand extends MonitorTwitterBaseCommand
|
||||
{
|
||||
/**
|
||||
* Search for tweets by hashtag.
|
||||
*
|
||||
* @param Monitoring $monitor
|
||||
*
|
||||
* @return bool|array False if missing the hashtag, otherwise the array response from Twitter
|
||||
*/
|
||||
protected function getTweets($monitor)
|
||||
{
|
||||
$params = $monitor->getProperties();
|
||||
$stats = $monitor->getStats();
|
||||
|
||||
if (!array_key_exists('hashtag', $params)) {
|
||||
$this->output->writeln('No hashtag was found!');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$searchUrl = $this->twitter->getApiUrl('search/tweets');
|
||||
$requestQuery = [
|
||||
'q' => '#'.$params['hashtag'],
|
||||
'count' => $this->queryCount,
|
||||
];
|
||||
|
||||
// if we have a max id string use it here
|
||||
if (is_array($stats) && array_key_exists('max_id_str', $stats) && $stats['max_id_str']) {
|
||||
$requestQuery['since_id'] = $stats['max_id_str'];
|
||||
}
|
||||
|
||||
return $this->twitter->makeRequest($searchUrl, $requestQuery);
|
||||
}
|
||||
|
||||
public function getNetworkName(): string
|
||||
{
|
||||
return 'twitter';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Command;
|
||||
|
||||
use MauticPlugin\MauticSocialBundle\Entity\Monitoring;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'social:monitor:twitter:mentions',
|
||||
description: 'Searches for mentioned tweets'
|
||||
)]
|
||||
class MonitorTwitterMentionsCommand extends MonitorTwitterBaseCommand
|
||||
{
|
||||
/**
|
||||
* Search for tweets by mention.
|
||||
*
|
||||
* @param Monitoring $monitor
|
||||
*
|
||||
* @return bool|array False if missing the twitter handle, otherwise the array response from Twitter
|
||||
*/
|
||||
protected function getTweets($monitor)
|
||||
{
|
||||
$params = $monitor->getProperties();
|
||||
$stats = $monitor->getStats();
|
||||
|
||||
if (!array_key_exists('handle', $params)) {
|
||||
$this->output->writeln('No twitter handle was found!');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$mentionsUrl = $this->twitter->getApiUrl('search/tweets');
|
||||
$requestQuery = [
|
||||
'q' => '@'.$params['handle'],
|
||||
'count' => $this->queryCount,
|
||||
];
|
||||
|
||||
// if we have a max id string use it here
|
||||
if (is_array($stats) && array_key_exists('max_id_str', $stats) && $stats['max_id_str']) {
|
||||
$requestQuery['since_id'] = $stats['max_id_str'];
|
||||
}
|
||||
|
||||
return $this->twitter->makeRequest($mentionsUrl, $requestQuery);
|
||||
}
|
||||
|
||||
public function getNetworkName(): string
|
||||
{
|
||||
return 'twitter';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'name' => 'Social Media',
|
||||
'description' => 'Enables integrations with Mautic supported social media services.',
|
||||
'version' => '1.0',
|
||||
'author' => 'Mautic',
|
||||
|
||||
'routes' => [
|
||||
'main' => [
|
||||
'mautic_social_index' => [
|
||||
'path' => '/monitoring/{page}',
|
||||
'controller' => 'MauticPlugin\MauticSocialBundle\Controller\MonitoringController::indexAction',
|
||||
],
|
||||
'mautic_social_action' => [
|
||||
'path' => '/monitoring/{objectAction}/{objectId}',
|
||||
'controller' => 'MauticPlugin\MauticSocialBundle\Controller\MonitoringController::executeAction',
|
||||
],
|
||||
'mautic_social_contacts' => [
|
||||
'path' => '/monitoring/view/{objectId}/contacts/{page}',
|
||||
'controller' => 'MauticPlugin\MauticSocialBundle\Controller\MonitoringController::contactsAction',
|
||||
],
|
||||
'mautic_tweet_index' => [
|
||||
'path' => '/tweets/{page}',
|
||||
'controller' => 'MauticPlugin\MauticSocialBundle\Controller\TweetController::indexAction',
|
||||
],
|
||||
'mautic_tweet_action' => [
|
||||
'path' => '/tweets/{objectAction}/{objectId}',
|
||||
'controller' => 'MauticPlugin\MauticSocialBundle\Controller\TweetController::executeAction',
|
||||
],
|
||||
],
|
||||
'api' => [
|
||||
'mautic_api_tweetsstandard' => [
|
||||
'standard_entity' => true,
|
||||
'name' => 'tweets',
|
||||
'path' => '/tweets',
|
||||
'controller' => MauticPlugin\MauticSocialBundle\Controller\Api\TweetApiController::class,
|
||||
],
|
||||
],
|
||||
'public' => [
|
||||
'mautic_social_js_generate' => [
|
||||
'path' => '/social/generate/{formName}.js',
|
||||
'controller' => 'MauticPlugin\MauticSocialBundle\Controller\JsController::generateAction',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'services' => [
|
||||
'others' => [
|
||||
'mautic.social.helper.campaign' => [
|
||||
'class' => MauticPlugin\MauticSocialBundle\Helper\CampaignEventHelper::class,
|
||||
'arguments' => [
|
||||
'mautic.helper.integration',
|
||||
'mautic.page.model.trackable',
|
||||
'mautic.page.helper.token',
|
||||
'mautic.asset.helper.token',
|
||||
'mautic.social.model.tweet',
|
||||
],
|
||||
],
|
||||
'mautic.social.helper.twitter_command' => [
|
||||
'class' => MauticPlugin\MauticSocialBundle\Helper\TwitterCommandHelper::class,
|
||||
'arguments' => [
|
||||
'mautic.lead.model.lead',
|
||||
'mautic.lead.model.field',
|
||||
'mautic.social.model.monitoring',
|
||||
'mautic.social.model.postcount',
|
||||
'translator',
|
||||
'doctrine.orm.entity_manager',
|
||||
'mautic.helper.core_parameters',
|
||||
],
|
||||
],
|
||||
],
|
||||
'integrations' => [
|
||||
'mautic.integration.facebook' => [
|
||||
'class' => MauticPlugin\MauticSocialBundle\Integration\FacebookIntegration::class,
|
||||
'arguments' => [
|
||||
'event_dispatcher',
|
||||
'mautic.helper.cache_storage',
|
||||
'doctrine.orm.entity_manager',
|
||||
'request_stack',
|
||||
'router',
|
||||
'translator',
|
||||
'monolog.logger.mautic',
|
||||
'mautic.helper.encryption',
|
||||
'mautic.lead.model.lead',
|
||||
'mautic.lead.model.company',
|
||||
'mautic.helper.paths',
|
||||
'mautic.core.model.notification',
|
||||
'mautic.lead.model.field',
|
||||
'mautic.lead.field.fields_with_unique_identifier',
|
||||
'mautic.plugin.model.integration_entity',
|
||||
'mautic.lead.model.dnc',
|
||||
'mautic.helper.integration',
|
||||
'mautic.lead.field.fields_with_unique_identifier',
|
||||
],
|
||||
],
|
||||
'mautic.integration.foursquare' => [
|
||||
'class' => MauticPlugin\MauticSocialBundle\Integration\FoursquareIntegration::class,
|
||||
'arguments' => [
|
||||
'event_dispatcher',
|
||||
'mautic.helper.cache_storage',
|
||||
'doctrine.orm.entity_manager',
|
||||
'request_stack',
|
||||
'router',
|
||||
'translator',
|
||||
'monolog.logger.mautic',
|
||||
'mautic.helper.encryption',
|
||||
'mautic.lead.model.lead',
|
||||
'mautic.lead.model.company',
|
||||
'mautic.helper.paths',
|
||||
'mautic.core.model.notification',
|
||||
'mautic.lead.model.field',
|
||||
'mautic.lead.field.fields_with_unique_identifier',
|
||||
'mautic.plugin.model.integration_entity',
|
||||
'mautic.lead.model.dnc',
|
||||
'mautic.helper.integration',
|
||||
'mautic.lead.field.fields_with_unique_identifier',
|
||||
],
|
||||
],
|
||||
'mautic.integration.instagram' => [
|
||||
'class' => MauticPlugin\MauticSocialBundle\Integration\InstagramIntegration::class,
|
||||
'arguments' => [
|
||||
'event_dispatcher',
|
||||
'mautic.helper.cache_storage',
|
||||
'doctrine.orm.entity_manager',
|
||||
'request_stack',
|
||||
'router',
|
||||
'translator',
|
||||
'monolog.logger.mautic',
|
||||
'mautic.helper.encryption',
|
||||
'mautic.lead.model.lead',
|
||||
'mautic.lead.model.company',
|
||||
'mautic.helper.paths',
|
||||
'mautic.core.model.notification',
|
||||
'mautic.lead.model.field',
|
||||
'mautic.lead.field.fields_with_unique_identifier',
|
||||
'mautic.plugin.model.integration_entity',
|
||||
'mautic.lead.model.dnc',
|
||||
'mautic.helper.integration',
|
||||
'mautic.lead.field.fields_with_unique_identifier',
|
||||
],
|
||||
],
|
||||
'mautic.integration.twitter' => [
|
||||
'class' => MauticPlugin\MauticSocialBundle\Integration\TwitterIntegration::class,
|
||||
'arguments' => [
|
||||
'event_dispatcher',
|
||||
'mautic.helper.cache_storage',
|
||||
'doctrine.orm.entity_manager',
|
||||
'request_stack',
|
||||
'router',
|
||||
'translator',
|
||||
'monolog.logger.mautic',
|
||||
'mautic.helper.encryption',
|
||||
'mautic.lead.model.lead',
|
||||
'mautic.lead.model.company',
|
||||
'mautic.helper.paths',
|
||||
'mautic.core.model.notification',
|
||||
'mautic.lead.model.field',
|
||||
'mautic.lead.field.fields_with_unique_identifier',
|
||||
'mautic.plugin.model.integration_entity',
|
||||
'mautic.lead.model.dnc',
|
||||
'mautic.helper.integration',
|
||||
'mautic.lead.field.fields_with_unique_identifier',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'menu' => [
|
||||
'main' => [
|
||||
'mautic.social.monitoring' => [
|
||||
'route' => 'mautic_social_index',
|
||||
'parent' => 'mautic.core.channels',
|
||||
'access' => 'mauticSocial:monitoring:view',
|
||||
'priority' => 0,
|
||||
'checks' => [
|
||||
'integration' => [
|
||||
'Twitter' => [
|
||||
'enabled' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'mautic.social.tweets' => [
|
||||
'route' => 'mautic_tweet_index',
|
||||
'access' => ['mauticSocial:tweets:viewown', 'mauticSocial:tweets:viewother'],
|
||||
'parent' => 'mautic.core.channels',
|
||||
'priority' => 80,
|
||||
'checks' => [
|
||||
'integration' => [
|
||||
'Twitter' => [
|
||||
'enabled' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'categories' => [
|
||||
'plugin:mauticSocial' => [
|
||||
'label' => 'mautic.social.monitoring',
|
||||
'class' => MauticPlugin\MauticSocialBundle\Entity\Monitoring::class,
|
||||
],
|
||||
],
|
||||
|
||||
'twitter' => [
|
||||
'tweet_request_count' => 100,
|
||||
],
|
||||
|
||||
'parameters' => [
|
||||
'twitter_handle_field' => 'twitter',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Mautic\CoreBundle\DependencyInjection\MauticCoreExtension;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return function (ContainerConfigurator $configurator): void {
|
||||
$services = $configurator->services()
|
||||
->defaults()
|
||||
->autowire()
|
||||
->autoconfigure()
|
||||
->public();
|
||||
|
||||
$excludes = [
|
||||
];
|
||||
|
||||
$services->load('MauticPlugin\\MauticSocialBundle\\', '../')
|
||||
->exclude('../{'.implode(',', array_merge(MauticCoreExtension::DEFAULT_EXCLUDES, $excludes)).'}');
|
||||
|
||||
$services->load('MauticPlugin\\MauticSocialBundle\\Entity\\', '../Entity/*Repository.php');
|
||||
|
||||
$services->alias('mautic.social.model.monitoring', MauticPlugin\MauticSocialBundle\Model\MonitoringModel::class);
|
||||
$services->alias('mautic.social.model.postcount', MauticPlugin\MauticSocialBundle\Model\PostCountModel::class);
|
||||
$services->alias('mautic.social.model.tweet', MauticPlugin\MauticSocialBundle\Model\TweetModel::class);
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Controller\AjaxController as CommonAjaxController;
|
||||
use Mautic\CoreBundle\Controller\AjaxLookupControllerTrait;
|
||||
use Mautic\CoreBundle\Helper\InputHelper;
|
||||
use MauticPlugin\MauticSocialBundle\Model\MonitoringModel;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class AjaxController extends CommonAjaxController
|
||||
{
|
||||
use AjaxLookupControllerTrait;
|
||||
|
||||
public function getNetworkFormAction(Request $request, MonitoringModel $monitoringModel, FormFactoryInterface $formFactory): \Symfony\Component\HttpFoundation\JsonResponse
|
||||
{
|
||||
// get the form type
|
||||
$type = InputHelper::clean($request->request->get('networkType'));
|
||||
|
||||
// default to empty
|
||||
$dataArray = [
|
||||
'html' => '',
|
||||
'success' => 0,
|
||||
];
|
||||
|
||||
if (!empty($type)) {
|
||||
// get the HTML for the form
|
||||
|
||||
$formType = $monitoringModel->getFormByType($type);
|
||||
|
||||
// get the network type form
|
||||
$form = $formFactory->create($formType, [], ['label' => false, 'csrf_protection' => false]);
|
||||
|
||||
$html = $this->renderView(
|
||||
'@MauticSocial/FormTheme/'.$type.'_widget.html.twig',
|
||||
['form' => $form->createView()]
|
||||
);
|
||||
|
||||
$html = str_replace(
|
||||
[
|
||||
$type.'[', // this is going to generate twitter_hashtag[ or twitter_mention[
|
||||
$type.'_', // this is going to generate twitter_hashtag_ or twitter_mention_
|
||||
$type,
|
||||
],
|
||||
[
|
||||
'monitoring[properties][',
|
||||
'monitoring_properties_',
|
||||
'monitoring',
|
||||
],
|
||||
$html
|
||||
);
|
||||
|
||||
$dataArray['html'] = $html;
|
||||
$dataArray['success'] = 1;
|
||||
}
|
||||
|
||||
return $this->sendJsonResponse($dataArray);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Controller\Api;
|
||||
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Mautic\ApiBundle\Controller\CommonApiController;
|
||||
use Mautic\ApiBundle\Helper\EntityResultHelper;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\AppVersion;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\Tweet;
|
||||
use MauticPlugin\MauticSocialBundle\Model\TweetModel;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
/**
|
||||
* @extends CommonApiController<Tweet>
|
||||
*/
|
||||
class TweetApiController extends CommonApiController
|
||||
{
|
||||
/**
|
||||
* @var TweetModel|null
|
||||
*/
|
||||
protected $model;
|
||||
|
||||
public function __construct(CorePermissions $security, Translator $translator, EntityResultHelper $entityResultHelper, RouterInterface $router, FormFactoryInterface $formFactory, AppVersion $appVersion, RequestStack $requestStack, ManagerRegistry $doctrine, ModelFactory $modelFactory, EventDispatcherInterface $dispatcher, CoreParametersHelper $coreParametersHelper)
|
||||
{
|
||||
$tweetModel = $modelFactory->getModel('social.tweet');
|
||||
\assert($tweetModel instanceof TweetModel);
|
||||
|
||||
$this->model = $tweetModel;
|
||||
$this->entityClass = Tweet::class;
|
||||
$this->entityNameOne = 'tweet';
|
||||
$this->entityNameMulti = 'tweets';
|
||||
|
||||
parent::__construct($security, $translator, $entityResultHelper, $router, $formFactory, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Controller\CommonController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class JsController extends CommonController
|
||||
{
|
||||
public function generateAction($formName): Response
|
||||
{
|
||||
$js = <<<JS
|
||||
|
||||
function openOAuthWindow(authUrl){
|
||||
if (authUrl) {
|
||||
var generator = window.open(authUrl, 'integrationauth', 'height=500,width=500');
|
||||
if (!generator || generator.closed || typeof generator.closed == 'undefined') {
|
||||
alert(mauticLang.popupBlockerMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function postAuthCallback(response){
|
||||
var elements = document.getElementById("mauticform_{$formName}").elements;
|
||||
var field, fieldName;
|
||||
values = JSON.parse(JSON.stringify(response));
|
||||
|
||||
for (var i = 0, element; element = elements[i++];) {
|
||||
field = element.name
|
||||
fieldName = field.replace("mauticform[","");
|
||||
fieldName = fieldName.replace("]","");
|
||||
var element = document.getElementsByName("mauticform["+fieldName+"]");
|
||||
|
||||
// Remove underscores, dashes, and f_ prefix for comparison
|
||||
fieldName = fieldName.replace("f_", "").replace(/_/g,"").replace(/-/g, "");
|
||||
for(var key in values) {
|
||||
var compareKey = key.replace(/_/g,"").replace(/-/g, "");
|
||||
if (key != 'id' && (key.indexOf(fieldName) >= 0 || fieldName.indexOf(key) >= 0) && element[0].value == "") {
|
||||
if (values[key].constructor === Array && values[key][0].value) {
|
||||
element[0].value = values[key][0].value;
|
||||
} else {
|
||||
element[0].value = values[key];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
JS;
|
||||
|
||||
return new Response(
|
||||
$js,
|
||||
200,
|
||||
[
|
||||
'Content-Type' => 'application/javascript',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,677 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Controller\FormController;
|
||||
use Mautic\CoreBundle\Factory\PageHelperFactoryInterface;
|
||||
use Mautic\CoreBundle\Form\Type\DateRangeType;
|
||||
use Mautic\CoreBundle\Helper\Chart\LineChart;
|
||||
use Mautic\CoreBundle\Helper\IpLookupHelper;
|
||||
use Mautic\CoreBundle\Model\AuditLogModel;
|
||||
use Mautic\LeadBundle\Controller\EntityContactsTrait;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\Monitoring;
|
||||
use MauticPlugin\MauticSocialBundle\Model\MonitoringModel;
|
||||
use Symfony\Component\Form\SubmitButton;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class MonitoringController extends FormController
|
||||
{
|
||||
use EntityContactsTrait;
|
||||
|
||||
/*
|
||||
* @param int $page
|
||||
*/
|
||||
public function indexAction(Request $request, MonitoringModel $model, $page = 1)
|
||||
{
|
||||
if (!$this->security->isGranted('mauticSocial:monitoring:view')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$session = $request->getSession();
|
||||
|
||||
$this->setListFilters();
|
||||
|
||||
// set limits
|
||||
$limit = $session->get('mautic.social.monitoring.limit', $this->getParameter('mautic.default_pagelimit'));
|
||||
$start = (1 === $page) ? 0 : (($page - 1) * $limit);
|
||||
if ($start < 0) {
|
||||
$start = 0;
|
||||
}
|
||||
|
||||
$search = $request->get('search', $session->get('mautic.social.monitoring.filter', ''));
|
||||
$session->set('mautic.social.monitoring.filter', $search);
|
||||
|
||||
$filter = ['string' => $search, 'force' => []];
|
||||
|
||||
$orderBy = $session->get('mautic.social.monitoring.orderby', 'e.title');
|
||||
$orderByDir = $session->get('mautic.social.monitoring.orderbydir', 'DESC');
|
||||
|
||||
$monitoringList = $model->getEntities(
|
||||
[
|
||||
'start' => $start,
|
||||
'limit' => $limit,
|
||||
'filter' => $filter,
|
||||
'orderBy' => $orderBy,
|
||||
'orderByDir' => $orderByDir,
|
||||
]
|
||||
);
|
||||
|
||||
$count = count($monitoringList);
|
||||
if ($count && $count < ($start + 1)) {
|
||||
// the number of entities are now less then the current asset so redirect to the last asset
|
||||
if (1 === $count) {
|
||||
$lastPage = 1;
|
||||
} else {
|
||||
$lastPage = (floor($limit / $count)) ?: 1;
|
||||
}
|
||||
$session->set('mautic.social.monitoring.page', $lastPage);
|
||||
$returnUrl = $this->generateUrl('mautic_social_index', ['page' => $lastPage]);
|
||||
|
||||
return $this->postActionRedirect(
|
||||
[
|
||||
'returnUrl' => $returnUrl,
|
||||
'viewParameters' => ['page' => $lastPage],
|
||||
'contentTemplate' => 'MauticPlugin\MauticSocialBundle\Controller\MonitoringController::indexAction',
|
||||
'passthroughVars' => [
|
||||
'activeLink' => '#mautic_social_index',
|
||||
'mauticContent' => 'monitoring',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// set what asset currently on so that we can return here after form submission/cancellation
|
||||
$session->set('mautic.social.monitoring.page', $page);
|
||||
|
||||
$tmpl = $request->isXmlHttpRequest() ? $request->get('tmpl', 'index') : 'index';
|
||||
|
||||
return $this->delegateView(
|
||||
[
|
||||
'viewParameters' => [
|
||||
'searchValue' => $search,
|
||||
'items' => $monitoringList,
|
||||
'limit' => $limit,
|
||||
'model' => $model,
|
||||
'tmpl' => $tmpl,
|
||||
'page' => $page,
|
||||
],
|
||||
'contentTemplate' => '@MauticSocial/Monitoring/list.html.twig',
|
||||
'passthroughVars' => [
|
||||
'activeLink' => '#mautic_social_index',
|
||||
'mauticContent' => 'monitoring',
|
||||
'route' => $this->generateUrl('mautic_social_index', ['page' => $page]),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates new form and processes post data.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
|
||||
*/
|
||||
public function newAction(Request $request, MonitoringModel $model, IpLookupHelper $ipLookupHelper)
|
||||
{
|
||||
if (!$this->security->isGranted('mauticSocial:monitoring:create')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$action = $this->generateUrl('mautic_social_action', ['objectAction' => 'new']);
|
||||
|
||||
$entity = $model->getEntity();
|
||||
$method = $request->getMethod();
|
||||
$session = $request->getSession();
|
||||
|
||||
// get the list of types from the model
|
||||
$networkTypes = $model->getNetworkTypes();
|
||||
|
||||
// get the network type from the request on submit. helpful for validation error
|
||||
// rebuilds structure of the form when it gets updated on submit
|
||||
$monitoring = $request->request->all()['monitoring'] ?? [];
|
||||
$networkType = 'POST' === $method ? ($monitoring['networkType'] ?? '') : '';
|
||||
|
||||
// build the form
|
||||
$form = $model->createForm(
|
||||
$entity,
|
||||
$this->formFactory,
|
||||
$action,
|
||||
[
|
||||
// pass through the types and the selected default type
|
||||
'networkTypes' => $networkTypes,
|
||||
'networkType' => $networkType,
|
||||
]
|
||||
);
|
||||
|
||||
// Set the page we came from
|
||||
$page = $session->get('mautic.social.monitoring.page', 1);
|
||||
// /Check for a submitted form and process it
|
||||
if ('POST' === $method) {
|
||||
$viewParameters = ['page' => $page];
|
||||
$template = 'MauticPlugin\MauticSocialBundle\Controller\MonitoringController::indexAction';
|
||||
$valid = false;
|
||||
if (!$cancelled = $this->isFormCancelled($form)) {
|
||||
if ($valid = $this->isFormValid($form)) {
|
||||
// form is valid so process the data
|
||||
$model->saveEntity($entity);
|
||||
|
||||
// update the audit log
|
||||
$this->updateAuditLog($entity, $ipLookupHelper, 'create');
|
||||
|
||||
$this->addFlashMessage(
|
||||
'mautic.core.notice.created',
|
||||
[
|
||||
'%name%' => $entity->getTitle(),
|
||||
'%menu_link%' => 'mautic_social_index',
|
||||
'%url%' => $this->generateUrl(
|
||||
'mautic_social_action',
|
||||
[
|
||||
'objectAction' => 'edit',
|
||||
'objectId' => $entity->getId(),
|
||||
]
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
if (!$this->getFormButton($form, ['buttons', 'save'])->isClicked()) {
|
||||
// return edit view so that all the session stuff is loaded
|
||||
return $this->editAction($request, $ipLookupHelper, $entity->getId(), true);
|
||||
}
|
||||
|
||||
$viewParameters = [
|
||||
'objectAction' => 'view',
|
||||
'objectId' => $entity->getId(),
|
||||
];
|
||||
$template = 'MauticPlugin\MauticSocialBundle\Controller\MonitoringController::viewAction';
|
||||
}
|
||||
}
|
||||
$returnUrl = $this->generateUrl('mautic_social_index', $viewParameters);
|
||||
|
||||
/** @var SubmitButton $saveSubmitButton */
|
||||
$saveSubmitButton = $form->get('buttons')->get('save');
|
||||
|
||||
if ($cancelled || ($valid && $saveSubmitButton->isClicked())) {
|
||||
return $this->postActionRedirect(
|
||||
[
|
||||
'returnUrl' => $returnUrl,
|
||||
'viewParameters' => $viewParameters,
|
||||
'contentTemplate' => $template,
|
||||
'passthroughVars' => [
|
||||
'activeLink' => 'mautic_social_index',
|
||||
'mauticContent' => 'monitoring',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->delegateView(
|
||||
[
|
||||
'viewParameters' => [
|
||||
'tmpl' => $request->isXmlHttpRequest() ? $request->get('tmpl', 'index') : 'index',
|
||||
'entity' => $entity,
|
||||
'form' => $form->createView(),
|
||||
],
|
||||
'contentTemplate' => '@MauticSocial/Monitoring/form.html.twig',
|
||||
'passthroughVars' => [
|
||||
'activeLink' => '#mautic_social_index',
|
||||
'mauticContent' => 'monitoring',
|
||||
'route' => $this->generateUrl(
|
||||
'mautic_social_action',
|
||||
[
|
||||
'objectAction' => 'new',
|
||||
'objectId' => $entity->getId(),
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse|Response
|
||||
*/
|
||||
public function editAction(Request $request, IpLookupHelper $ipLookupHelper, $objectId, bool $ignorePost = false)
|
||||
{
|
||||
if (!$this->security->isGranted('mauticSocial:monitoring:edit')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$action = $this->generateUrl('mautic_social_action', ['objectAction' => 'edit', 'objectId' => $objectId]);
|
||||
|
||||
/** @var MonitoringModel $model */
|
||||
$model = $this->getModel('social.monitoring');
|
||||
|
||||
$entity = $model->getEntity($objectId);
|
||||
$session = $request->getSession();
|
||||
|
||||
// Set the page we came from
|
||||
$page = $session->get('mautic.social.monitoring.page', 1);
|
||||
|
||||
// set the return URL
|
||||
$returnUrl = $this->generateUrl('mautic_social_index', ['page' => $page]);
|
||||
|
||||
$postActionVars = [
|
||||
'returnUrl' => $returnUrl,
|
||||
'viewParameters' => ['page' => $page],
|
||||
'contentTemplate' => 'MauticSocial:Monitoring:index',
|
||||
'passthroughVars' => [
|
||||
'activeLink' => 'mautic_social_index',
|
||||
'mauticContent' => 'monitoring',
|
||||
],
|
||||
];
|
||||
|
||||
// not found
|
||||
if (null === $entity) {
|
||||
return $this->postActionRedirect(
|
||||
array_merge(
|
||||
$postActionVars,
|
||||
[
|
||||
'flashes' => [
|
||||
[
|
||||
'type' => 'error',
|
||||
'msg' => 'mautic.social.monitoring.error.notfound',
|
||||
'msgVars' => ['%id%' => $objectId],
|
||||
],
|
||||
],
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// get the list of types from the model
|
||||
$networkTypes = $model->getNetworkTypes();
|
||||
|
||||
// get the network type from the request on submit. helpful for validation error
|
||||
// rebuilds structure of the form when it gets updated on submit
|
||||
$method = $request->getMethod();
|
||||
$monitoring = $request->request->all()['monitoring'] ?? [];
|
||||
$networkType = 'POST' === $method ? ($monitoring['networkType'] ?? '') : $entity->getNetworkType();
|
||||
|
||||
// build the form
|
||||
$form = $model->createForm(
|
||||
$entity,
|
||||
$this->formFactory,
|
||||
$action,
|
||||
[
|
||||
// pass through the types and the selected default type
|
||||
'networkTypes' => $networkTypes,
|
||||
'networkType' => $networkType,
|
||||
]
|
||||
);
|
||||
|
||||
// /Check for a submitted form and process it
|
||||
if (!$ignorePost && 'POST' === $method) {
|
||||
$valid = false;
|
||||
|
||||
/** @var SubmitButton $saveSubmitButton */
|
||||
$saveSubmitButton = $form->get('buttons')->get('save');
|
||||
|
||||
if (!$cancelled = $this->isFormCancelled($form)) {
|
||||
if ($valid = $this->isFormValid($form)) {
|
||||
// form is valid so process the data
|
||||
$model->saveEntity($entity, $saveSubmitButton->isClicked());
|
||||
|
||||
// update the audit log
|
||||
$this->updateAuditLog($entity, $ipLookupHelper, 'update');
|
||||
|
||||
$this->addFlashMessage(
|
||||
'mautic.core.notice.updated',
|
||||
[
|
||||
'%name%' => $entity->getTitle(),
|
||||
'%menu_link%' => 'mautic_email_index',
|
||||
'%url%' => $this->generateUrl(
|
||||
'mautic_social_action',
|
||||
[
|
||||
'objectAction' => 'edit',
|
||||
'objectId' => $entity->getId(),
|
||||
]
|
||||
),
|
||||
],
|
||||
'warning'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$model->unlockEntity($entity);
|
||||
}
|
||||
|
||||
if ($cancelled || ($valid && $saveSubmitButton->isClicked())) {
|
||||
$viewParameters = [
|
||||
'objectAction' => 'view',
|
||||
'objectId' => $entity->getId(),
|
||||
];
|
||||
|
||||
return $this->postActionRedirect(
|
||||
array_merge(
|
||||
$postActionVars,
|
||||
[
|
||||
'returnUrl' => $this->generateUrl('mautic_social_action', $viewParameters),
|
||||
'viewParameters' => $viewParameters,
|
||||
'contentTemplate' => 'MauticPlugin\MauticSocialBundle\Controller\MonitoringController::viewAction',
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// lock the entity
|
||||
$model->lockEntity($entity);
|
||||
}
|
||||
|
||||
return $this->delegateView(
|
||||
[
|
||||
'viewParameters' => [
|
||||
'tmpl' => $request->isXmlHttpRequest() ? $request->get('tmpl', 'index') : 'index',
|
||||
'entity' => $entity,
|
||||
'form' => $form->createView(),
|
||||
],
|
||||
'contentTemplate' => '@MauticSocial/Monitoring/form.html.twig',
|
||||
'passthroughVars' => [
|
||||
'activeLink' => '#mautic_social_index',
|
||||
'mauticContent' => 'monitoring',
|
||||
'route' => $this->generateUrl(
|
||||
'mautic_social_action',
|
||||
[
|
||||
'objectAction' => 'edit',
|
||||
'objectId' => $entity->getId(),
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a specific form into the detailed panel.
|
||||
*
|
||||
* @param int $objectId
|
||||
*
|
||||
* @return JsonResponse|Response
|
||||
*/
|
||||
public function viewAction(Request $request, $objectId)
|
||||
{
|
||||
if (!$this->security->isGranted('mauticSocial:monitoring:view')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$session = $request->getSession();
|
||||
|
||||
/** @var MonitoringModel $model */
|
||||
$model = $this->getModel('social.monitoring');
|
||||
|
||||
/** @var \MauticPlugin\MauticSocialBundle\Entity\PostCountRepository $postCountRepo */
|
||||
$postCountRepo = $this->getModel('social.postcount')->getRepository();
|
||||
|
||||
$security = $this->security;
|
||||
$monitoringEntity = $model->getEntity($objectId);
|
||||
|
||||
// set the asset we came from
|
||||
$page = $session->get('mautic.social.monitoring.page', 1);
|
||||
|
||||
$tmpl = $request->isXmlHttpRequest() ? $request->get('tmpl', 'details') : 'details';
|
||||
|
||||
if (null === $monitoringEntity) {
|
||||
// set the return URL
|
||||
$returnUrl = $this->generateUrl('mautic_social_index', ['page' => $page]);
|
||||
|
||||
return $this->postActionRedirect(
|
||||
[
|
||||
'returnUrl' => $returnUrl,
|
||||
'viewParameters' => ['page' => $page],
|
||||
'contentTemplate' => 'MauticPlugin\MauticSocialBundle\Controller\MonitoringController::indexAction',
|
||||
'passthroughVars' => [
|
||||
'activeLink' => '#mautic_social_index',
|
||||
'mauticContent' => 'monitoring',
|
||||
],
|
||||
'flashes' => [
|
||||
[
|
||||
'type' => 'error',
|
||||
'msg' => 'mautic.social.monitoring.error.notfound',
|
||||
'msgVars' => ['%id%' => $objectId],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// Audit Log
|
||||
$auditLogModel = $this->getModel('core.auditlog');
|
||||
\assert($auditLogModel instanceof AuditLogModel);
|
||||
$logs = $auditLogModel->getLogForObject('monitoring', $objectId);
|
||||
|
||||
$returnUrl = $this->generateUrl(
|
||||
'mautic_social_action',
|
||||
[
|
||||
'objectAction' => 'view',
|
||||
'objectId' => $monitoringEntity->getId(),
|
||||
]
|
||||
);
|
||||
|
||||
// Init the date range filter form
|
||||
$dateRangeValues = $request->get('daterange', []);
|
||||
$dateRangeForm = $this->formFactory->create(DateRangeType::class, $dateRangeValues, ['action' => $returnUrl]);
|
||||
$dateFrom = new \DateTime($dateRangeForm['date_from']->getData());
|
||||
$dateTo = new \DateTime($dateRangeForm['date_to']->getData());
|
||||
|
||||
$chart = new LineChart(null, $dateFrom, $dateTo);
|
||||
$leadStats = $postCountRepo->getLeadStatsPost(
|
||||
$dateFrom,
|
||||
$dateTo,
|
||||
['monitor_id' => $monitoringEntity->getId()]
|
||||
);
|
||||
$chart->setDataset($this->translator->trans('mautic.social.twitter.tweet.count'), $leadStats);
|
||||
|
||||
return $this->delegateView(
|
||||
[
|
||||
'returnUrl' => $returnUrl,
|
||||
'viewParameters' => [
|
||||
'activeMonitoring' => $monitoringEntity,
|
||||
'logs' => $logs,
|
||||
'isEmbedded' => $request->get('isEmbedded') ?: false,
|
||||
'tmpl' => $tmpl,
|
||||
'security' => $security,
|
||||
'leadStats' => $chart->render(),
|
||||
'monitorLeads' => $this->forward(
|
||||
'MauticPlugin\MauticSocialBundle\Controller\MonitoringController::contactsAction',
|
||||
[
|
||||
'objectId' => $monitoringEntity->getId(),
|
||||
'page' => $page,
|
||||
'ignoreAjax' => true,
|
||||
]
|
||||
)->getContent(),
|
||||
'dateRangeForm' => $dateRangeForm->createView(),
|
||||
],
|
||||
'contentTemplate' => '@MauticSocial/Monitoring/'.$tmpl.'.html.twig',
|
||||
'passthroughVars' => [
|
||||
'activeLink' => '#mautic_social_index',
|
||||
'mauticContent' => 'monitoring',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the entity.
|
||||
*
|
||||
* @param int $objectId
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function deleteAction(Request $request, IpLookupHelper $ipLookupHelper, $objectId)
|
||||
{
|
||||
if (!$this->security->isGranted('mauticSocial:monitoring:delete')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$session = $request->getSession();
|
||||
$page = $session->get('mautic.social.monitoring.page', 1);
|
||||
$returnUrl = $this->generateUrl('mautic_social_index', ['page' => $page]);
|
||||
$flashes = [];
|
||||
|
||||
$postActionVars = [
|
||||
'returnUrl' => $returnUrl,
|
||||
'viewParameters' => ['page' => $page],
|
||||
'contentTemplate' => 'MauticPlugin\MauticSocialBundle\Controller\MonitoringController::indexAction',
|
||||
'passthroughVars' => [
|
||||
'activeLink' => 'mautic_social_index',
|
||||
'mauticContent' => 'monitoring',
|
||||
],
|
||||
];
|
||||
|
||||
if ('POST' === $request->getMethod()) {
|
||||
/** @var MonitoringModel $model */
|
||||
$model = $this->getModel('social.monitoring');
|
||||
$entity = $model->getEntity($objectId);
|
||||
|
||||
if (null === $entity) {
|
||||
$flashes[] = [
|
||||
'type' => 'error',
|
||||
'msg' => 'mautic.social.monitoring.error.notfound',
|
||||
'msgVars' => ['%id%' => $objectId],
|
||||
];
|
||||
} elseif ($model->isLocked($entity)) {
|
||||
return $this->isLocked($postActionVars, $entity, 'plugin.mauticSocial.monitoring');
|
||||
}
|
||||
|
||||
// update the audit log
|
||||
$this->updateAuditLog($entity, $ipLookupHelper, 'delete');
|
||||
|
||||
// then delete the record
|
||||
$model->deleteEntity($entity);
|
||||
|
||||
$flashes[] = [
|
||||
'type' => 'notice',
|
||||
'msg' => 'mautic.core.notice.deleted',
|
||||
'msgVars' => [
|
||||
'%name%' => $entity->getTitle(),
|
||||
'%id%' => $objectId,
|
||||
],
|
||||
];
|
||||
} // else don't do anything
|
||||
|
||||
return $this->postActionRedirect(
|
||||
array_merge(
|
||||
$postActionVars,
|
||||
[
|
||||
'flashes' => $flashes,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a group of entities.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function batchDeleteAction(Request $request)
|
||||
{
|
||||
if (!$this->security->isGranted('mauticSocial:monitoring:delete')) {
|
||||
return $this->accessDenied();
|
||||
}
|
||||
|
||||
$session = $request->getSession();
|
||||
$page = $session->get('mautic.social.monitoring.page', 1);
|
||||
$returnUrl = $this->generateUrl('mautic_social_index', ['page' => $page]);
|
||||
$flashes = [];
|
||||
|
||||
$postActionVars = [
|
||||
'returnUrl' => $returnUrl,
|
||||
'viewParameters' => ['page' => $page],
|
||||
'contentTemplate' => 'MauticPlugin\MauticSocialBundle\Controller\MonitoringController::indexAction',
|
||||
'passthroughVars' => [
|
||||
'activeLink' => '#mautic_social_index',
|
||||
'mauticContent' => 'monitoring',
|
||||
],
|
||||
];
|
||||
|
||||
if ('POST' === $request->getMethod()) {
|
||||
/** @var MonitoringModel $model */
|
||||
$model = $this->getModel('social.monitoring');
|
||||
|
||||
$ids = json_decode($request->query->get('ids', ''));
|
||||
$deleteIds = [];
|
||||
|
||||
// Loop over the IDs to perform access checks pre-delete
|
||||
foreach ($ids as $objectId) {
|
||||
$entity = $model->getEntity($objectId);
|
||||
|
||||
if (null === $entity) {
|
||||
$flashes[] = [
|
||||
'type' => 'error',
|
||||
'msg' => 'mautic.social.monitoring.error.notfound',
|
||||
'msgVars' => ['%id%' => $objectId],
|
||||
];
|
||||
} elseif ($model->isLocked($entity)) {
|
||||
$flashes[] = $this->isLocked($postActionVars, $entity, 'monitoring', true);
|
||||
} else {
|
||||
$deleteIds[] = $objectId;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete everything we are able to
|
||||
if (!empty($deleteIds)) {
|
||||
$entities = $model->deleteEntities($deleteIds);
|
||||
|
||||
$flashes[] = [
|
||||
'type' => 'notice',
|
||||
'msg' => 'mautic.social.monitoring.notice.batch_deleted',
|
||||
'msgVars' => [
|
||||
'%count%' => count($entities),
|
||||
],
|
||||
];
|
||||
}
|
||||
} // else don't do anything
|
||||
|
||||
return $this->postActionRedirect(
|
||||
array_merge(
|
||||
$postActionVars,
|
||||
[
|
||||
'flashes' => $flashes,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $page
|
||||
*
|
||||
* @return JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse|Response
|
||||
*/
|
||||
public function contactsAction(
|
||||
Request $request,
|
||||
PageHelperFactoryInterface $pageHelperFactory,
|
||||
$objectId,
|
||||
$page = 1,
|
||||
) {
|
||||
return $this->generateContactsGrid(
|
||||
$request,
|
||||
$pageHelperFactory,
|
||||
$objectId,
|
||||
$page,
|
||||
'mauticSocial:monitoring:view',
|
||||
'social',
|
||||
'monitoring_leads',
|
||||
null, // @todo - implement when individual social channels are supported by the plugin
|
||||
'monitor_id'
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the audit log
|
||||
*/
|
||||
public function updateAuditLog(Monitoring $monitoring, IpLookupHelper $ipLookupHelper, $action): void
|
||||
{
|
||||
$log = [
|
||||
'bundle' => 'plugin.mauticSocial',
|
||||
'object' => 'monitoring',
|
||||
'objectId' => $monitoring->getId(),
|
||||
'action' => $action,
|
||||
'details' => ['name' => $monitoring->getTitle()],
|
||||
'ipAddress' => $ipLookupHelper->getIpAddressFromRequest(),
|
||||
];
|
||||
|
||||
$auditLog = $this->getModel('core.auditlog');
|
||||
\assert($auditLog instanceof AuditLogModel);
|
||||
$auditLog->writeToLog($log);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Controller\AbstractStandardFormController;
|
||||
use Mautic\CoreBundle\Controller\FormController;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class TweetController extends FormController
|
||||
{
|
||||
protected function getModelName(): string
|
||||
{
|
||||
return 'social.tweet';
|
||||
}
|
||||
|
||||
protected function getJsLoadMethodPrefix(): string
|
||||
{
|
||||
return 'socialTweet';
|
||||
}
|
||||
|
||||
protected function getRouteBase(): string
|
||||
{
|
||||
return 'mautic_tweet';
|
||||
}
|
||||
|
||||
protected function getSessionBase($objectId = null): string
|
||||
{
|
||||
return 'mautic_tweet';
|
||||
}
|
||||
|
||||
protected function getTemplateBase(): string
|
||||
{
|
||||
return '@MauticSocial/Tweet';
|
||||
}
|
||||
|
||||
protected function getTranslationBase(): string
|
||||
{
|
||||
return 'mautic.integration.Twitter';
|
||||
}
|
||||
|
||||
protected function getPermissionBase(): string
|
||||
{
|
||||
return 'mauticSocial:tweets';
|
||||
}
|
||||
|
||||
/**
|
||||
* Define options to pass to the form when it's being created.
|
||||
*/
|
||||
protected function getEntityFormOptions(): array
|
||||
{
|
||||
return [
|
||||
'update_select' => $this->getUpdateSelect(),
|
||||
'allow_extra_fields' => true,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get updateSelect value from request.
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getUpdateSelect()
|
||||
{
|
||||
$request = $this->getCurrentRequest();
|
||||
|
||||
return ('POST' === $request->getMethod())
|
||||
? ($request->request->all()['twitter_tweet']['updateSelect'] ?? false)
|
||||
: $request->get('updateSelect', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom form themes, etc.
|
||||
*
|
||||
* @param string $action
|
||||
*/
|
||||
protected function getFormView(FormInterface $form, $action): FormView
|
||||
{
|
||||
return $form->createView();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $page
|
||||
*/
|
||||
public function indexAction(Request $request, $page = 1): Response
|
||||
{
|
||||
return parent::indexStandard($request, $page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates new form and processes post data.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\JsonResponse|Response
|
||||
*/
|
||||
public function newAction(Request $request)
|
||||
{
|
||||
return parent::newStandard($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template file.
|
||||
*/
|
||||
protected function getTemplateName($file): string
|
||||
{
|
||||
if (('form.html.twig' === $file) && 1 == $this->getCurrentRequest()->get('modal')) {
|
||||
return '@MauticSocial/Tweet/form_modal.html.twig';
|
||||
}
|
||||
|
||||
return AbstractStandardFormController::getTemplateName($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates edit form and processes post data.
|
||||
*
|
||||
* @param int $objectId
|
||||
* @param bool $ignorePost
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\JsonResponse|Response
|
||||
*/
|
||||
public function editAction(Request $request, $objectId, $ignorePost = false)
|
||||
{
|
||||
return parent::editStandard($request, $objectId, $ignorePost);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $objectId
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse|Response
|
||||
*/
|
||||
public function cloneAction(Request $request, $objectId)
|
||||
{
|
||||
return parent::cloneStandard($request, $objectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the entity.
|
||||
*
|
||||
* @param int $objectId
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
public function deleteAction(Request $request, $objectId)
|
||||
{
|
||||
return parent::deleteStandard($request, $objectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a group of entities.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*/
|
||||
public function batchDeleteAction(Request $request)
|
||||
{
|
||||
return parent::batchDeleteStandard($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
|
||||
|
||||
class MauticSocialExtension extends Extension
|
||||
{
|
||||
/**
|
||||
* @param mixed[] $configs
|
||||
*/
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
$loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Config'));
|
||||
$loader->load('services.php');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
|
||||
|
||||
#[ORM\Table(name: 'monitoring_leads')]
|
||||
#[ORM\Entity(repositoryClass: LeadRepository::class)]
|
||||
class Lead
|
||||
{
|
||||
/**
|
||||
* @var Monitoring
|
||||
*/
|
||||
private $monitor;
|
||||
|
||||
/**
|
||||
* @var \Mautic\LeadBundle\Entity\Lead
|
||||
*/
|
||||
private $lead;
|
||||
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
private $dateAdded;
|
||||
|
||||
public static function loadMetadata(ORM\ClassMetadata $metadata): void
|
||||
{
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('monitoring_leads')
|
||||
->setCustomRepositoryClass(LeadRepository::class);
|
||||
|
||||
$builder->createManyToOne('monitor', 'Monitoring')
|
||||
->isPrimaryKey()
|
||||
->addJoinColumn('monitor_id', 'id', false, false, 'CASCADE')
|
||||
->build();
|
||||
|
||||
$builder->addLead(false, 'CASCADE', true);
|
||||
|
||||
$builder->addNamedField('dateAdded', 'datetime', 'date_added');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getDateAdded()
|
||||
{
|
||||
return $this->dateAdded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setDateAdded($dateAdded)
|
||||
{
|
||||
$this->dateAdded = $dateAdded;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLead()
|
||||
{
|
||||
return $this->lead;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setLead($lead)
|
||||
{
|
||||
$this->lead = $lead;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMonitor()
|
||||
{
|
||||
return $this->monitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setMonitor($monitor)
|
||||
{
|
||||
$this->monitor = $monitor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Entity;
|
||||
|
||||
use Mautic\CoreBundle\Entity\CommonRepository;
|
||||
|
||||
/**
|
||||
* @extends CommonRepository<Lead>
|
||||
*/
|
||||
class LeadRepository extends CommonRepository
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
|
||||
use Mautic\CoreBundle\Entity\FormEntity;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
|
||||
class Monitoring extends FormEntity
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* @var \Mautic\CategoryBundle\Entity\Category|null
|
||||
*/
|
||||
private $category;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $lists = [];
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $networkType;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $revision = 1;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $stats = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $properties = [];
|
||||
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
private $publishDown;
|
||||
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
private $publishUp;
|
||||
|
||||
public static function loadMetadata(ORM\ClassMetadata $metadata): void
|
||||
{
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('monitoring')
|
||||
->setCustomRepositoryClass(MonitoringRepository::class)
|
||||
->addLifecycleEvent('cleanMonitorData', 'preUpdate')
|
||||
->addLifecycleEvent('cleanMonitorData', 'prePersist');
|
||||
|
||||
$builder->addCategory();
|
||||
|
||||
$builder->addIdColumns('title');
|
||||
|
||||
$builder->addNullableField('lists', 'array');
|
||||
|
||||
$builder->addNamedField('networkType', 'string', 'network_type', true);
|
||||
|
||||
$builder->addField('revision', 'integer');
|
||||
|
||||
$builder->addNullableField('stats', 'array');
|
||||
|
||||
$builder->addNullableField('properties', 'array');
|
||||
|
||||
$builder->addPublishDates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constraints for required fields.
|
||||
*/
|
||||
public static function loadValidatorMetadata(ClassMetadata $metadata): void
|
||||
{
|
||||
$metadata->addPropertyConstraint('title', new Assert\NotBlank(
|
||||
['message' => 'mautic.core.title.required']
|
||||
));
|
||||
|
||||
$metadata->addPropertyConstraint('networkType', new Assert\NotBlank(
|
||||
['message' => 'mautic.social.network.type']
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCategory()
|
||||
{
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get lists.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLists()
|
||||
{
|
||||
return $this->lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get network type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNetworkType()
|
||||
{
|
||||
return $this->networkType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get revision.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRevision()
|
||||
{
|
||||
return $this->revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistics.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getStats()
|
||||
{
|
||||
return $this->stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getProperties()
|
||||
{
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get publishDown.
|
||||
*
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getPublishDown()
|
||||
{
|
||||
return $this->publishDown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get publishUp.
|
||||
*
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getPublishUp()
|
||||
{
|
||||
return $this->publishUp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the category id.
|
||||
*
|
||||
* @param \Mautic\CategoryBundle\Entity\Category|null $category
|
||||
*/
|
||||
public function setCategory($category): void
|
||||
{
|
||||
$this->isChanged('category', $category);
|
||||
$this->category = $category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set description.
|
||||
*
|
||||
* @param string $description
|
||||
*
|
||||
* @return Monitoring
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->isChanged('description', $description);
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the monitor lists.
|
||||
*
|
||||
* @return Monitoring
|
||||
*/
|
||||
public function setLists($lists)
|
||||
{
|
||||
$this->isChanged('lists', $lists);
|
||||
$this->lists = $lists;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the network type.
|
||||
*
|
||||
* @return Monitoring
|
||||
*/
|
||||
public function setNetworkType($networkType)
|
||||
{
|
||||
$this->isChanged('networkType', $networkType);
|
||||
$this->networkType = $networkType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the revision counter.
|
||||
*
|
||||
* @param int $revision
|
||||
*
|
||||
* @return Monitoring
|
||||
*/
|
||||
public function setRevision($revision)
|
||||
{
|
||||
$this->isChanged('revision', $revision);
|
||||
$this->revision = $revision;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the statistics.
|
||||
*
|
||||
* @param array $stats
|
||||
*
|
||||
* @return Monitoring
|
||||
*/
|
||||
public function setStats($stats)
|
||||
{
|
||||
$this->isChanged('stats', $stats);
|
||||
$this->stats = $stats;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name.
|
||||
*
|
||||
* @param string $title
|
||||
*
|
||||
* @return Monitoring
|
||||
*/
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->isChanged('title', $title);
|
||||
$this->title = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set properties.
|
||||
*
|
||||
* @param array $properties
|
||||
*
|
||||
* @return Monitoring
|
||||
*/
|
||||
public function setProperties($properties)
|
||||
{
|
||||
$this->isChanged('properties', $properties);
|
||||
$this->properties = $properties;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set publishDown.
|
||||
*
|
||||
* @param \DateTime $publishDown
|
||||
*
|
||||
* @return Monitoring
|
||||
*/
|
||||
public function setPublishDown($publishDown)
|
||||
{
|
||||
$this->isChanged('publishDown', $publishDown);
|
||||
$this->publishDown = $publishDown;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set publishUp.
|
||||
*
|
||||
* @param \DateTime $publishUp
|
||||
*
|
||||
* @return Monitoring
|
||||
*/
|
||||
public function setPublishUp($publishUp)
|
||||
{
|
||||
$this->isChanged('publishUp', $publishUp);
|
||||
$this->publishUp = $publishUp;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear out old properties data.
|
||||
*/
|
||||
public function cleanMonitorData(): void
|
||||
{
|
||||
$property = $this->getProperties();
|
||||
|
||||
if (!array_key_exists('checknames', $property)) {
|
||||
$property['checknames'] = 0;
|
||||
}
|
||||
|
||||
// clean up property array for the twitter handle
|
||||
if ('twitter_handle' == $this->getNetworkType()) {
|
||||
$this->setProperties(
|
||||
[
|
||||
'handle' => $property['handle'],
|
||||
'checknames' => $property['checknames'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// clean up property array for the hashtag
|
||||
if ('twitter_hashtag' == $this->getNetworkType()) {
|
||||
$this->setProperties(
|
||||
[
|
||||
'hashtag' => $property['hashtag'],
|
||||
'checknames' => $property['checknames'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// clean up clean up property array for the custom action
|
||||
if ('twitter_custom' == $this->getNetworkType()) {
|
||||
$this->setProperties(
|
||||
[
|
||||
'custom' => $property['custom'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// if the property is not new and the old property doesn't match the new one
|
||||
if (!$this->isNew() && $property != $this->getProperties()) {
|
||||
// reset stats on save of edited
|
||||
$this->setStats([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
use Mautic\CoreBundle\Entity\CommonRepository;
|
||||
|
||||
/**
|
||||
* @extends CommonRepository<Monitoring>
|
||||
*/
|
||||
class MonitoringRepository extends CommonRepository
|
||||
{
|
||||
/**
|
||||
* @param array $args
|
||||
*
|
||||
* @return Paginator
|
||||
*/
|
||||
public function getPublishedEntities($args = [])
|
||||
{
|
||||
$q = $this->createQueryBuilder($this->getTableAlias());
|
||||
$expr = $this->getPublishedByDateExpression($q);
|
||||
|
||||
$q->where($expr);
|
||||
$args['qb'] = $q;
|
||||
|
||||
return parent::getEntities($args);
|
||||
}
|
||||
|
||||
public function getPublishedEntitiesCount(): int
|
||||
{
|
||||
$q = $this->createQueryBuilder($this->getTableAlias());
|
||||
$expr = $this->getPublishedByDateExpression($q);
|
||||
$q->where($expr);
|
||||
$args['qb'] = $q;
|
||||
|
||||
return count(parent::getEntities($args));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q
|
||||
*/
|
||||
protected function addCatchAllWhereClause($q, $filter): array
|
||||
{
|
||||
return $this->addStandardCatchAllWhereClause(
|
||||
$q,
|
||||
$filter,
|
||||
[
|
||||
$this->getTableAlias().'.title',
|
||||
$this->getTableAlias().'.description',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q
|
||||
*/
|
||||
protected function addSearchCommandWhereClause($q, $filter): array
|
||||
{
|
||||
return $this->addStandardSearchCommandWhereClause($q, $filter);
|
||||
}
|
||||
|
||||
public function getTableAlias(): string
|
||||
{
|
||||
return 'e';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getSearchCommands(): array
|
||||
{
|
||||
return $this->getStandardSearchCommands();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
|
||||
|
||||
class PostCount
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var Monitoring|null
|
||||
*/
|
||||
private $monitor;
|
||||
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
private $postDate;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $postCount;
|
||||
|
||||
public static function loadMetadata(ORM\ClassMetadata $metadata): void
|
||||
{
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('monitor_post_count')
|
||||
->setCustomRepositoryClass(PostCountRepository::class);
|
||||
|
||||
$builder->addId();
|
||||
|
||||
$builder->createManyToOne('monitor', 'Monitoring')
|
||||
->addJoinColumn('monitor_id', 'id', true, false, 'CASCADE')
|
||||
->build();
|
||||
|
||||
$builder->addNamedField('postDate', 'date', 'post_date');
|
||||
|
||||
$builder->addNamedField('postCount', 'integer', 'post_count');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Monitoring
|
||||
*/
|
||||
public function getMonitor()
|
||||
{
|
||||
return $this->monitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Monitoring $monitor
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setMonitor($monitor)
|
||||
{
|
||||
$this->monitor = $monitor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPostCount()
|
||||
{
|
||||
return $this->postCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $postCount
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPostCount($postCount)
|
||||
{
|
||||
$this->postCount = $postCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getPostDate()
|
||||
{
|
||||
return $this->postDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setPostDate($postDate)
|
||||
{
|
||||
$this->postDate = $postDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Entity;
|
||||
|
||||
use Mautic\CoreBundle\Entity\CommonRepository;
|
||||
use Mautic\CoreBundle\Helper\Chart\ChartQuery;
|
||||
|
||||
/**
|
||||
* @extends CommonRepository<PostCount>
|
||||
*/
|
||||
class PostCountRepository extends CommonRepository
|
||||
{
|
||||
/**
|
||||
* Fetch Lead stats for some period of time.
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return PostCount[]
|
||||
*
|
||||
* @throws \Doctrine\ORM\NoResultException
|
||||
* @throws \Doctrine\ORM\NonUniqueResultException
|
||||
*/
|
||||
public function getLeadStatsPost($dateFrom, $dateTo, $options): array
|
||||
{
|
||||
$chartQuery = new ChartQuery($this->getEntityManager()->getConnection(), $dateFrom, $dateTo);
|
||||
|
||||
// Load points for selected periods
|
||||
$q = $chartQuery->prepareTimeDataQuery(MAUTIC_TABLE_PREFIX.'monitor_post_count', 'post_date', $options, 'post_count', 'sum');
|
||||
if (isset($options['monitor_id'])) {
|
||||
$q->andwhere($q->expr()->eq('t.monitor_id', (int) $options['monitor_id']));
|
||||
}
|
||||
|
||||
return $chartQuery->loadAndBuildTimeData($q);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Mautic\ApiBundle\Serializer\Driver\ApiMetadataDriver;
|
||||
use Mautic\AssetBundle\Entity\Asset;
|
||||
use Mautic\CategoryBundle\Entity\Category;
|
||||
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
|
||||
use Mautic\CoreBundle\Entity\FormEntity;
|
||||
use Mautic\PageBundle\Entity\Page;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Mapping\ClassMetadata;
|
||||
|
||||
#[ORM\Table(name: 'tweets')]
|
||||
#[ORM\Entity(repositoryClass: TweetRepository::class)]
|
||||
class Tweet extends FormEntity
|
||||
{
|
||||
/**
|
||||
* Internal Mautic ID of the tweet.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* ID of the Twitter media object attached to the tweet.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $mediaId;
|
||||
|
||||
/**
|
||||
* Path to the local media file.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $mediaPath;
|
||||
|
||||
/**
|
||||
* Internal Mautic name of the tweet.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* The actual messge of the tweet.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $text;
|
||||
|
||||
/**
|
||||
* Internal Mautic description.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $description;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $language = 'en';
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $sentCount = 0;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $favoriteCount = 0;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $retweetCount = 0;
|
||||
|
||||
/**
|
||||
* @var Page|null
|
||||
*/
|
||||
private $page;
|
||||
|
||||
/**
|
||||
* @var Asset|null
|
||||
*/
|
||||
private $asset;
|
||||
|
||||
/**
|
||||
* @var Category|null
|
||||
**/
|
||||
private $category;
|
||||
|
||||
/**
|
||||
* @var ArrayCollection<int, TweetStat>
|
||||
*/
|
||||
private $stats;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->stats = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->id = null;
|
||||
$this->sentCount = 0;
|
||||
$this->favoriteCount = 0;
|
||||
$this->retweetCount = 0;
|
||||
$this->stats = new ArrayCollection();
|
||||
|
||||
parent::__clone();
|
||||
}
|
||||
|
||||
public static function loadMetadata(ORM\ClassMetadata $metadata): void
|
||||
{
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('tweets')
|
||||
->setCustomRepositoryClass(TweetRepository::class)
|
||||
->addIndex(['sent_count'], 'sent_count_index')
|
||||
->addIndex(['favorite_count'], 'favorite_count_index')
|
||||
->addIndex(['retweet_count'], 'retweet_count_index');
|
||||
|
||||
$builder->addIdColumns();
|
||||
$builder->addCategory();
|
||||
$builder->addNullableField('mediaId', Types::STRING, 'media_id');
|
||||
$builder->addNullableField('mediaPath', Types::STRING, 'media_path');
|
||||
$builder->addField('text', Types::STRING, ['length' => 280]);
|
||||
$builder->addNullableField('sentCount', Types::INTEGER, 'sent_count');
|
||||
$builder->addNullableField('favoriteCount', Types::INTEGER, 'favorite_count');
|
||||
$builder->addNullableField('retweetCount', Types::INTEGER, 'retweet_count');
|
||||
$builder->addNullableField('language', Types::STRING, 'lang');
|
||||
|
||||
$builder->createManyToOne('page', Page::class)
|
||||
->addJoinColumn('page_id', 'id', true, false, 'SET NULL')
|
||||
->build();
|
||||
|
||||
$builder->createManyToOne('asset', Asset::class)
|
||||
->addJoinColumn('asset_id', 'id', true, false, 'SET NULL')
|
||||
->build();
|
||||
|
||||
$builder->createOneToMany('stats', 'TweetStat')
|
||||
->setIndexBy('id')
|
||||
->mappedBy('tweet')
|
||||
->cascadePersist()
|
||||
->fetchExtraLazy()
|
||||
->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the metadata for API usage.
|
||||
*/
|
||||
public static function loadApiMetadata(ApiMetadataDriver $metadata): void
|
||||
{
|
||||
$metadata->setGroupPrefix('tweet')
|
||||
->addListProperties(
|
||||
[
|
||||
'id',
|
||||
'name',
|
||||
'text',
|
||||
'language',
|
||||
'category',
|
||||
]
|
||||
)
|
||||
->addProperties(
|
||||
[
|
||||
'mediaId',
|
||||
'mediaPath',
|
||||
'sentCount',
|
||||
'favoriteCount',
|
||||
'retweetCount',
|
||||
'description',
|
||||
]
|
||||
)
|
||||
->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constraints for required fields.
|
||||
*/
|
||||
public static function loadValidatorMetadata(ClassMetadata $metadata): void
|
||||
{
|
||||
$metadata->addPropertyConstraint('text', new Assert\Length(
|
||||
[
|
||||
'max' => 280,
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->isChanged('name', $name);
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $description
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDescription($description)
|
||||
{
|
||||
$this->isChanged('description', $description);
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getMediaId()
|
||||
{
|
||||
return $this->mediaId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $mediaId
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setMediaId($mediaId)
|
||||
{
|
||||
$this->isChanged('mediaId', $mediaId);
|
||||
$this->mediaId = $mediaId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getMediaPath()
|
||||
{
|
||||
return $this->mediaPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $mediaPath
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setMediaPath($mediaPath)
|
||||
{
|
||||
$this->isChanged('mediaPath', $mediaPath);
|
||||
$this->mediaPath = $mediaPath;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getText()
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $text
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setText($text)
|
||||
{
|
||||
$this->isChanged('text', $text);
|
||||
$this->text = $text;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getSentCount()
|
||||
{
|
||||
return $this->sentCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setSentCount($sentCount)
|
||||
{
|
||||
$this->isChanged('sentCount', $sentCount);
|
||||
$this->sentCount = $sentCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add 1 to sentCount.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function sentCountUp()
|
||||
{
|
||||
$this->setSentCount($this->getSentCount() + 1);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getFavoriteCount()
|
||||
{
|
||||
return $this->favoriteCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $favoriteCount
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFavoriteCount($favoriteCount)
|
||||
{
|
||||
$this->isChanged('favoriteCount', $favoriteCount);
|
||||
$this->favoriteCount = $favoriteCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getRetweetCount()
|
||||
{
|
||||
return $this->retweetCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $retweetCount
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setRetweetCount($retweetCount)
|
||||
{
|
||||
$this->isChanged('retweetCount', $retweetCount);
|
||||
$this->retweetCount = $retweetCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $language
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setLanguage($language)
|
||||
{
|
||||
$this->isChanged('language', $language);
|
||||
$this->language = $language;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Asset|null
|
||||
*/
|
||||
public function getAsset()
|
||||
{
|
||||
return $this->asset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setAsset(Asset $asset)
|
||||
{
|
||||
$this->asset = $asset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Page|null
|
||||
*/
|
||||
public function getPage()
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setPage(Page $page)
|
||||
{
|
||||
$this->page = $page;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Category|null
|
||||
*/
|
||||
public function getCategory()
|
||||
{
|
||||
return $this->category;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setCategory(Category $category)
|
||||
{
|
||||
$this->category = $category;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getStats()
|
||||
{
|
||||
return $this->stats;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Entity;
|
||||
|
||||
use Mautic\CoreBundle\Entity\CommonRepository;
|
||||
|
||||
/**
|
||||
* @extends CommonRepository<Tweet>
|
||||
*/
|
||||
class TweetRepository extends CommonRepository
|
||||
{
|
||||
/**
|
||||
* @param string $search
|
||||
* @param int $limit
|
||||
* @param int $start
|
||||
* @param bool $viewOther
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTweetList($search = '', $limit = 10, $start = 0, $viewOther = false, array $ignoreIds = [])
|
||||
{
|
||||
$qb = $this->createQueryBuilder('t');
|
||||
$qb->select('partial t.{id, text, name, language}');
|
||||
|
||||
if (!empty($search)) {
|
||||
if (is_array($search)) {
|
||||
$search = array_map('intval', $search);
|
||||
$qb->andWhere($qb->expr()->in('t.id', ':search'))
|
||||
->setParameter('search', $search);
|
||||
} else {
|
||||
$qb->andWhere($qb->expr()->like('t.name', ':search'))
|
||||
->setParameter('search', "%{$search}%");
|
||||
}
|
||||
}
|
||||
|
||||
if (!$viewOther) {
|
||||
$qb->andWhere($qb->expr()->eq('t.createdBy', ':id'))
|
||||
->setParameter('id', $this->currentUser->getId());
|
||||
}
|
||||
|
||||
if (!empty($ignoreIds)) {
|
||||
$qb->andWhere($qb->expr()->notIn('t.id', ':ignoreIds'))
|
||||
->setParameter('ignoreIds', $ignoreIds);
|
||||
}
|
||||
|
||||
$qb->orderBy('t.name');
|
||||
|
||||
if (!empty($limit)) {
|
||||
$qb->setFirstResult($start)
|
||||
->setMaxResults($limit);
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getArrayResult();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Entity;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Mautic\ApiBundle\Serializer\Driver\ApiMetadataDriver;
|
||||
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
|
||||
use Mautic\LeadBundle\Entity\Lead as TheLead;
|
||||
|
||||
class TweetStat
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* ID of the tweet from Twitter.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $twitterTweetId;
|
||||
|
||||
/**
|
||||
* @var Tweet|null
|
||||
*/
|
||||
private $tweet;
|
||||
|
||||
/**
|
||||
* @var TheLead|null
|
||||
*/
|
||||
private $lead;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $handle;
|
||||
|
||||
/**
|
||||
* @var DateTime
|
||||
*/
|
||||
private $dateSent;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private $isFailed = false;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $retryCount = 0;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private $source;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $sourceId;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $favoriteCount = 0;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $retweetCount = 0;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private $responseDetails = [];
|
||||
|
||||
public static function loadMetadata(ORM\ClassMetadata $metadata): void
|
||||
{
|
||||
$builder = new ClassMetadataBuilder($metadata);
|
||||
|
||||
$builder->setTable('tweet_stats')
|
||||
->setCustomRepositoryClass(TweetStatRepository::class)
|
||||
->addIndex(['tweet_id', 'lead_id'], 'stat_tweet_search')
|
||||
->addIndex(['lead_id', 'tweet_id'], 'stat_tweet_search2')
|
||||
->addIndex(['is_failed'], 'stat_tweet_failed_search')
|
||||
->addIndex(['source', 'source_id'], 'stat_tweet_source_search')
|
||||
->addIndex(['favorite_count'], 'favorite_count_index')
|
||||
->addIndex(['retweet_count'], 'retweet_count_index')
|
||||
->addIndex(['date_sent'], 'tweet_date_sent')
|
||||
->addIndex(['twitter_tweet_id'], 'twitter_tweet_id_index');
|
||||
|
||||
$builder->addId();
|
||||
|
||||
$builder->createManyToOne('tweet', 'Tweet')
|
||||
->inversedBy('stats')
|
||||
->addJoinColumn('tweet_id', 'id', true, false, 'SET NULL')
|
||||
->build();
|
||||
|
||||
$builder->createField('twitterTweetId', 'string')
|
||||
->columnName('twitter_tweet_id')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->addLead(true, 'SET NULL');
|
||||
|
||||
$builder->createField('handle', 'string')
|
||||
->build();
|
||||
|
||||
$builder->createField('dateSent', 'datetime')
|
||||
->columnName('date_sent')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('isFailed', 'boolean')
|
||||
->columnName('is_failed')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('retryCount', 'integer')
|
||||
->columnName('retry_count')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('source', 'string')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('sourceId', 'integer')
|
||||
->columnName('source_id')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->addNullableField('favoriteCount', 'integer', 'favorite_count');
|
||||
$builder->addNullableField('retweetCount', 'integer', 'retweet_count');
|
||||
$builder->addNullableField('responseDetails', Types::JSON, 'response_details');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the metadata for API usage.
|
||||
*/
|
||||
public static function loadApiMetadata(ApiMetadataDriver $metadata): void
|
||||
{
|
||||
$metadata->setGroupPrefix('stat')
|
||||
->addProperties(
|
||||
[
|
||||
'id',
|
||||
'tweetId',
|
||||
'handle',
|
||||
'dateSent',
|
||||
'isFailed',
|
||||
'retryCount',
|
||||
'favoriteCount',
|
||||
'retweetCount',
|
||||
'source',
|
||||
'sourceId',
|
||||
'lead',
|
||||
'tweet',
|
||||
'responseDetails',
|
||||
]
|
||||
)
|
||||
->build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getTwitterTweetId()
|
||||
{
|
||||
return $this->twitterTweetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $twitterTweetId
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setTwitterTweetId($twitterTweetId)
|
||||
{
|
||||
$this->twitterTweetId = $twitterTweetId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getDateSent()
|
||||
{
|
||||
return $this->dateSent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $dateSent
|
||||
*/
|
||||
public function setDateSent($dateSent): void
|
||||
{
|
||||
$this->dateSent = $dateSent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Tweet
|
||||
*/
|
||||
public function getTweet()
|
||||
{
|
||||
return $this->tweet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $tweet
|
||||
*/
|
||||
public function setTweet(?Tweet $tweet = null): void
|
||||
{
|
||||
$this->tweet = $tweet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TheLead
|
||||
*/
|
||||
public function getLead()
|
||||
{
|
||||
return $this->lead;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $lead
|
||||
*/
|
||||
public function setLead(?TheLead $lead = null): void
|
||||
{
|
||||
$this->lead = $lead;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRetryCount()
|
||||
{
|
||||
return $this->retryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $retryCount
|
||||
*/
|
||||
public function setRetryCount($retryCount): void
|
||||
{
|
||||
$this->retryCount = $retryCount;
|
||||
}
|
||||
|
||||
public function retryCountUp(): void
|
||||
{
|
||||
$this->setRetryCount($this->getRetryCount() + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getFavoriteCount()
|
||||
{
|
||||
return $this->favoriteCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $favoriteCount
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFavoriteCount($favoriteCount)
|
||||
{
|
||||
$this->favoriteCount = $favoriteCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getRetweetCount()
|
||||
{
|
||||
return $this->retweetCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $retweetCount
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setRetweetCount($retweetCount)
|
||||
{
|
||||
$this->retweetCount = $retweetCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getIsFailed()
|
||||
{
|
||||
return $this->isFailed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $isFailed
|
||||
*/
|
||||
public function setIsFailed($isFailed): void
|
||||
{
|
||||
$this->isFailed = $isFailed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function isFailed()
|
||||
{
|
||||
return $this->getIsFailed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getHandle()
|
||||
{
|
||||
return $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $handle
|
||||
*/
|
||||
public function setHandle($handle): void
|
||||
{
|
||||
$this->handle = $handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSource()
|
||||
{
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $source
|
||||
*/
|
||||
public function setSource($source): void
|
||||
{
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSourceId()
|
||||
{
|
||||
return $this->sourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $sourceId
|
||||
*/
|
||||
public function setSourceId($sourceId): void
|
||||
{
|
||||
$this->sourceId = (int) $sourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResponseDetails()
|
||||
{
|
||||
return $this->responseDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $responseDetails
|
||||
*
|
||||
* @return Stat
|
||||
*/
|
||||
public function setResponseDetails($responseDetails)
|
||||
{
|
||||
$this->responseDetails = $responseDetails;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Entity;
|
||||
|
||||
use Mautic\CoreBundle\Entity\CommonRepository;
|
||||
|
||||
/**
|
||||
* @extends CommonRepository<TweetStat>
|
||||
*/
|
||||
class TweetStatRepository extends CommonRepository
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Event;
|
||||
|
||||
use Mautic\CoreBundle\Event\CommonEvent;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\Monitoring;
|
||||
|
||||
class SocialEvent extends CommonEvent
|
||||
{
|
||||
/**
|
||||
* @param bool $isNew
|
||||
*/
|
||||
public function __construct(Monitoring $monitoring, $isNew = false)
|
||||
{
|
||||
$this->entity = $monitoring;
|
||||
$this->isNew = $isNew;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Monitoring entity.
|
||||
*
|
||||
* @return Monitoring
|
||||
*/
|
||||
public function getMonitoring()
|
||||
{
|
||||
return $this->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Monitoring entity.
|
||||
*/
|
||||
public function setMonitoring(Monitoring $monitoring): void
|
||||
{
|
||||
$this->entity = $monitoring;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Event;
|
||||
|
||||
use Mautic\CoreBundle\Event\CommonEvent;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\Monitoring;
|
||||
|
||||
class SocialMonitorEvent extends CommonEvent
|
||||
{
|
||||
protected int $newLeadCount;
|
||||
|
||||
protected int $updatedLeadCount;
|
||||
|
||||
/**
|
||||
* @param string $integrationName
|
||||
* @param int $newLeadCount
|
||||
* @param int $updatedLeadCount
|
||||
*/
|
||||
public function __construct(
|
||||
protected $integrationName,
|
||||
Monitoring $monitoring,
|
||||
protected array $leadIds,
|
||||
$newLeadCount,
|
||||
$updatedLeadCount,
|
||||
) {
|
||||
$this->entity = $monitoring;
|
||||
$this->newLeadCount = (int) $newLeadCount;
|
||||
$this->updatedLeadCount = (int) $updatedLeadCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Monitoring entity.
|
||||
*
|
||||
* @return Monitoring
|
||||
*/
|
||||
public function getMonitoring()
|
||||
{
|
||||
return $this->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of new leads.
|
||||
*/
|
||||
public function getNewLeadCount(): int
|
||||
{
|
||||
return $this->newLeadCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of updated leads.
|
||||
*/
|
||||
public function getUpdatedLeadCount(): int
|
||||
{
|
||||
return $this->updatedLeadCount;
|
||||
}
|
||||
|
||||
public function getTotalLeadCount(): int
|
||||
{
|
||||
return $this->updatedLeadCount + $this->newLeadCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getLeadIds()
|
||||
{
|
||||
return $this->leadIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getIntegrationName()
|
||||
{
|
||||
return $this->integrationName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\EventListener;
|
||||
|
||||
use Mautic\CampaignBundle\CampaignEvents;
|
||||
use Mautic\CampaignBundle\Event\CampaignBuilderEvent;
|
||||
use Mautic\CampaignBundle\Event\CampaignExecutionEvent;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use MauticPlugin\MauticSocialBundle\Form\Type\TweetSendType;
|
||||
use MauticPlugin\MauticSocialBundle\Helper\CampaignEventHelper;
|
||||
use MauticPlugin\MauticSocialBundle\SocialEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class CampaignSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private CampaignEventHelper $campaignEventHelper,
|
||||
private IntegrationHelper $integrationHelper,
|
||||
private TranslatorInterface $translator,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
CampaignEvents::CAMPAIGN_ON_BUILD => ['onCampaignBuild', 0],
|
||||
SocialEvents::ON_CAMPAIGN_TRIGGER_ACTION => ['onCampaignAction', 0],
|
||||
];
|
||||
}
|
||||
|
||||
public function onCampaignBuild(CampaignBuilderEvent $event): void
|
||||
{
|
||||
$integration = $this->integrationHelper->getIntegrationObject('Twitter');
|
||||
if ($integration && $integration->getIntegrationSettings()->isPublished()) {
|
||||
$action = [
|
||||
'label' => 'mautic.social.twitter.tweet.event.open',
|
||||
'description' => 'mautic.social.twitter.tweet.event.open_desc',
|
||||
'eventName' => SocialEvents::ON_CAMPAIGN_TRIGGER_ACTION,
|
||||
'formTypeOptions' => ['update_select' => 'campaignevent_properties_channelId'],
|
||||
'formType' => TweetSendType::class,
|
||||
'channel' => 'social.tweet',
|
||||
'channelIdField' => 'channelId',
|
||||
];
|
||||
|
||||
$event->addAction('twitter.tweet', $action);
|
||||
}
|
||||
}
|
||||
|
||||
public function onCampaignAction(CampaignExecutionEvent $event)
|
||||
{
|
||||
$event->setChannel('social.twitter');
|
||||
if ($response = $this->campaignEventHelper->sendTweetAction($event->getLead(), $event->getEvent())) {
|
||||
return $event->setResult($response);
|
||||
}
|
||||
|
||||
return $event->setFailed(
|
||||
$this->translator->trans('mautic.social.twitter.error.handle_not_found')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\EventListener;
|
||||
|
||||
use Mautic\ChannelBundle\ChannelEvents;
|
||||
use Mautic\ChannelBundle\Event\ChannelEvent;
|
||||
use Mautic\ChannelBundle\Model\MessageModel;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use MauticPlugin\MauticSocialBundle\Form\Type\TweetListType;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class ChannelSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private IntegrationHelper $helper,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
ChannelEvents::ADD_CHANNEL => ['onAddChannel', 80],
|
||||
];
|
||||
}
|
||||
|
||||
public function onAddChannel(ChannelEvent $event): void
|
||||
{
|
||||
$integration = $this->helper->getIntegrationObject('Twitter');
|
||||
if ($integration && $integration->getIntegrationSettings()->isPublished()) {
|
||||
$event->addChannel(
|
||||
'tweet',
|
||||
[
|
||||
MessageModel::CHANNEL_FEATURE => [
|
||||
'campaignAction' => 'twitter.tweet',
|
||||
'campaignDecisionsSupported' => [
|
||||
'page.pagehit',
|
||||
'asset.download',
|
||||
'form.submit',
|
||||
],
|
||||
'lookupFormType' => TweetListType::class,
|
||||
'repository' => 'MauticSocialBundle:Tweet',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\EventListener;
|
||||
|
||||
use Mautic\ConfigBundle\ConfigEvents;
|
||||
use Mautic\ConfigBundle\Event\ConfigBuilderEvent;
|
||||
use Mautic\ConfigBundle\Event\ConfigEvent;
|
||||
use MauticPlugin\MauticSocialBundle\Form\Type\ConfigType;
|
||||
use MauticPlugin\MauticSocialBundle\Integration\Config;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class ConfigSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(private Config $config)
|
||||
{
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
ConfigEvents::CONFIG_ON_GENERATE => ['onConfigGenerate', 0],
|
||||
ConfigEvents::CONFIG_PRE_SAVE => ['onConfigSave', 0],
|
||||
];
|
||||
}
|
||||
|
||||
public function onConfigGenerate(ConfigBuilderEvent $event): void
|
||||
{
|
||||
if (!$this->config->isPublished()) {
|
||||
return;
|
||||
}
|
||||
$event->addForm(
|
||||
[
|
||||
'formAlias' => 'social_config',
|
||||
'formTheme' => '@MauticSocial/FormTheme/Config/_config_social_config_widget.html.twig',
|
||||
'formType' => ConfigType::class,
|
||||
'parameters' => $event->getParametersFromConfig('MauticSocialBundle'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function onConfigSave(ConfigEvent $event): void
|
||||
{
|
||||
/** @var array $values */
|
||||
$values = $event->getConfig();
|
||||
|
||||
// Manipulate the values
|
||||
if (!empty($values['social_config']['twitter_handle_field'])) {
|
||||
$values['social_config']['twitter_handle_field'] = htmlspecialchars($values['social_config']['twitter_handle_field']);
|
||||
}
|
||||
|
||||
// Set updated values
|
||||
$event->setConfig($values);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\EventListener;
|
||||
|
||||
use Mautic\FormBundle\Event\FormBuilderEvent;
|
||||
use Mautic\FormBundle\FormEvents;
|
||||
use MauticPlugin\MauticSocialBundle\Form\Type\SocialLoginType;
|
||||
use MauticPlugin\MauticSocialBundle\Integration\Config;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class FormSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(private Config $config)
|
||||
{
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
FormEvents::FORM_ON_BUILD => ['onFormBuild', 0],
|
||||
];
|
||||
}
|
||||
|
||||
public function onFormBuild(FormBuilderEvent $event): void
|
||||
{
|
||||
if (!$this->config->isPublished()) {
|
||||
return;
|
||||
}
|
||||
$action = [
|
||||
'label' => 'mautic.plugin.actions.socialLogin',
|
||||
'formType' => SocialLoginType::class,
|
||||
'template' => '@MauticSocial/Integration/login.html.twig',
|
||||
'builderOptions' => [
|
||||
'addLeadFieldList' => false,
|
||||
'addIsRequired' => false,
|
||||
'addDefaultValue' => false,
|
||||
'addSaveResult' => false,
|
||||
],
|
||||
];
|
||||
|
||||
$event->addFormField('plugin.loginSocial', $action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\EventListener;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Mautic\CoreBundle\EventListener\CommonStatsSubscriber;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\TweetStat;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\TweetStatRepository;
|
||||
|
||||
class StatsSubscriber extends CommonStatsSubscriber
|
||||
{
|
||||
public function __construct(CorePermissions $security, EntityManager $entityManager)
|
||||
{
|
||||
parent::__construct($security, $entityManager);
|
||||
|
||||
/** @var TweetStatRepository $repo */
|
||||
$repo = $entityManager->getRepository(TweetStat::class);
|
||||
$table = $repo->getTableName();
|
||||
$this->repositories[] = $repo;
|
||||
$this->permissions[$table] = ['tweet' => 'mauticSocial:tweets'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Exception;
|
||||
|
||||
class ExitMonitorException extends \Exception
|
||||
{
|
||||
public function __construct($message = 'Exit monitor requested', $code = 0, ?\Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Form\Type;
|
||||
|
||||
use Mautic\LeadBundle\Field\FieldList;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>>
|
||||
*/
|
||||
class ConfigType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private FieldList $fieldList,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$leadFields = $this->fieldList->getFieldList(false, false);
|
||||
|
||||
$builder->add(
|
||||
'twitter_handle_field',
|
||||
ChoiceType::class,
|
||||
[
|
||||
'choices' => array_flip($leadFields),
|
||||
'label' => 'mautic.social.config.twitter.field.label',
|
||||
'required' => false,
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => ['class' => 'form-control'],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'social_config';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Form\Type;
|
||||
|
||||
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>>
|
||||
*/
|
||||
class FacebookType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('layout', ChoiceType::class, [
|
||||
'choices' => [
|
||||
'mautic.integration.Facebook.share.layout.standard' => 'standard',
|
||||
'mautic.integration.Facebook.share.layout.buttoncount' => 'button_count',
|
||||
'mautic.integration.Facebook.share.layout.button' => 'button',
|
||||
'mautic.integration.Facebook.share.layout.boxcount' => 'box_count',
|
||||
'mautic.integration.Facebook.share.layout.icon' => 'icon',
|
||||
],
|
||||
'label' => 'mautic.integration.Facebook.share.layout',
|
||||
'required' => false,
|
||||
'placeholder' => false,
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => ['class' => 'form-control'],
|
||||
]);
|
||||
|
||||
$builder->add('action', ChoiceType::class, [
|
||||
'choices' => [
|
||||
'mautic.integration.Facebook.share.action.like' => 'like',
|
||||
'mautic.integration.Facebook.share.action.recommend' => 'recommend',
|
||||
'mautic.integration.Facebook.share.action.share' => 'share',
|
||||
],
|
||||
'label' => 'mautic.integration.Facebook.share.action',
|
||||
'required' => false,
|
||||
'placeholder' => false,
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => ['class' => 'form-control'],
|
||||
]);
|
||||
|
||||
$builder->add('showFaces', YesNoButtonGroupType::class, [
|
||||
'label' => 'mautic.integration.Facebook.share.showfaces',
|
||||
'data' => (!isset($options['data']['showFaces'])) ? 1 : $options['data']['showFaces'],
|
||||
]);
|
||||
|
||||
$builder->add('showShare', YesNoButtonGroupType::class, [
|
||||
'label' => 'mautic.integration.Facebook.share.showshare',
|
||||
'data' => (!isset($options['data']['showShare'])) ? 1 : $options['data']['showShare'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'socialmedia_facebook';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Form\Type;
|
||||
|
||||
use Mautic\CategoryBundle\Form\Type\CategoryListType;
|
||||
use Mautic\CoreBundle\Form\EventListener\CleanFormSubscriber;
|
||||
use Mautic\CoreBundle\Form\Type\FormButtonsType;
|
||||
use Mautic\CoreBundle\Form\Type\PublishDownDateType;
|
||||
use Mautic\CoreBundle\Form\Type\PublishUpDateType;
|
||||
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
|
||||
use Mautic\LeadBundle\Form\Type\LeadListType;
|
||||
use MauticPlugin\MauticSocialBundle\Model\MonitoringModel;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>>
|
||||
*/
|
||||
class MonitoringType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private MonitoringModel $monitoringModel,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->addEventSubscriber(new CleanFormSubscriber(['description' => 'html']));
|
||||
|
||||
$builder->add('title', TextType::class, [
|
||||
'label' => 'mautic.core.name',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => ['class' => 'form-control'],
|
||||
]);
|
||||
|
||||
$builder->add('description', TextareaType::class, [
|
||||
'label' => 'mautic.core.description',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => ['class' => 'form-control editor'],
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
$builder->add('isPublished', YesNoButtonGroupType::class);
|
||||
$builder->add('publishUp', PublishUpDateType::class);
|
||||
$builder->add('publishDown', PublishDownDateType::class);
|
||||
$builder->add('networkType', ChoiceType::class, [
|
||||
'label' => 'mautic.social.monitoring.type.list',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'onchange' => 'Mautic.getNetworkFormAction(this)',
|
||||
],
|
||||
'choices' => array_flip((array) $options['networkTypes']), // passed from the controller
|
||||
'placeholder' => 'mautic.core.form.chooseone',
|
||||
]);
|
||||
|
||||
// if we have a network type value add in the form
|
||||
if (!empty($options['networkType']) && array_key_exists($options['networkType'], $options['networkTypes'])) {
|
||||
// get the values from the entity function
|
||||
$properties = $options['data']->getProperties();
|
||||
|
||||
$formType = $this->monitoringModel->getFormByType($options['networkType']);
|
||||
|
||||
$builder->add('properties', $formType,
|
||||
[
|
||||
'label' => false,
|
||||
'data' => $properties,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$builder->add(
|
||||
'lists',
|
||||
LeadListType::class,
|
||||
[
|
||||
'label' => 'mautic.lead.lead.events.addtolists',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
],
|
||||
'multiple' => true,
|
||||
'expanded' => false,
|
||||
]
|
||||
);
|
||||
|
||||
// add category
|
||||
$builder->add('category', CategoryListType::class, [
|
||||
'bundle' => 'plugin:mauticSocial',
|
||||
]);
|
||||
|
||||
$builder->add('buttons', FormButtonsType::class);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => \MauticPlugin\MauticSocialBundle\Entity\Monitoring::class,
|
||||
]);
|
||||
|
||||
// allow network types to be sent through - list
|
||||
$resolver->setRequired(['networkTypes']);
|
||||
|
||||
// allow the specific network type - single
|
||||
$resolver->setDefined(['networkType']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Form\Type;
|
||||
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\FormBundle\Model\FormModel;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>>
|
||||
*/
|
||||
class SocialLoginType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private IntegrationHelper $helper,
|
||||
private FormModel $formModel,
|
||||
private CoreParametersHelper $coreParametersHelper,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$integrations = '';
|
||||
$integrationObjects = $this->helper->getIntegrationObjects(null, 'login_button');
|
||||
foreach ($integrationObjects as $integrationObject) {
|
||||
if ($integrationObject->getIntegrationSettings()->isPublished()) {
|
||||
$model = $this->formModel;
|
||||
$integrations .= $integrationObject->getName().',';
|
||||
$integration = [
|
||||
'integration' => $integrationObject->getName(),
|
||||
];
|
||||
|
||||
$builder->add(
|
||||
'authUrl_'.$integrationObject->getName(),
|
||||
HiddenType::class,
|
||||
[
|
||||
'data' => $model->buildUrl('mautic_integration_auth_user', $integration, true, []),
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'buttonImageUrl',
|
||||
HiddenType::class,
|
||||
[
|
||||
'data' => $this->coreParametersHelper->get('site_url').'/'.$this->coreParametersHelper->get('image_path').'/',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$builder->add(
|
||||
'integrations',
|
||||
HiddenType::class,
|
||||
[
|
||||
'data' => $integrations,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'sociallogin';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Form\Type;
|
||||
|
||||
use Mautic\CoreBundle\Form\Type\EntityLookupType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>>
|
||||
*/
|
||||
class TweetListType extends AbstractType
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults(
|
||||
[
|
||||
'modal_route' => 'mautic_tweet_action',
|
||||
'modal_header' => 'mautic.integration.Twitter.new.tweet',
|
||||
'model' => 'social.tweet',
|
||||
'model_lookup_method' => 'getLookupResults',
|
||||
'lookup_arguments' => fn (Options $options): array => [
|
||||
'type' => 'tweet',
|
||||
'filter' => '$data',
|
||||
'limit' => 0,
|
||||
'start' => 0,
|
||||
],
|
||||
'ajax_lookup_action' => fn (Options $options) => 'mauticSocial:getLookupChoiceList',
|
||||
'multiple' => true,
|
||||
'required' => false,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function getParent(): ?string
|
||||
{
|
||||
return EntityLookupType::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>>
|
||||
*/
|
||||
class TweetSendType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
protected RouterInterface $router,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add(
|
||||
'channelId',
|
||||
TweetListType::class,
|
||||
[
|
||||
'label' => 'mautic.integration.Twitter.send.selecttweet',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'tooltip' => 'mautic.integration.Twitter.send.selecttweet.desc',
|
||||
'onchange' => 'Mautic.disabledTweetAction()',
|
||||
],
|
||||
'multiple' => false,
|
||||
'required' => true,
|
||||
'constraints' => [
|
||||
new NotBlank(
|
||||
['message' => 'mautic.integration.Twitter.send.selecttweet.notblank']
|
||||
),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
if (!empty($options['update_select'])) {
|
||||
$windowUrl = $this->router->generate(
|
||||
'mautic_tweet_action',
|
||||
[
|
||||
'objectAction' => 'new',
|
||||
'contentOnly' => 1,
|
||||
'updateSelect' => $options['update_select'],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'newTweetButton',
|
||||
ButtonType::class,
|
||||
[
|
||||
'attr' => [
|
||||
'class' => 'btn btn-primary btn-nospin',
|
||||
'onclick' => 'Mautic.loadNewWindow({
|
||||
"windowUrl": "'.$windowUrl.'"
|
||||
})',
|
||||
'icon' => 'ri-add-line',
|
||||
],
|
||||
'label' => 'mautic.integration.Twitter.new.tweet',
|
||||
]
|
||||
);
|
||||
|
||||
// $tweet = $options['data']['channelId'];
|
||||
|
||||
// create button edit tweet
|
||||
// @todo: this button requires a JS to be injected to the campaign builder
|
||||
// $windowUrlEdit = $this->router->generate(
|
||||
// 'mautic_tweet_action',
|
||||
// [
|
||||
// 'objectAction' => 'edit',
|
||||
// 'objectId' => 'tweetId',
|
||||
// 'contentOnly' => 1,
|
||||
// 'updateSelect' => $options['update_select'],
|
||||
// ]
|
||||
// );
|
||||
|
||||
// $builder->add(
|
||||
// 'editTweetButton',
|
||||
// 'button',
|
||||
// [
|
||||
// 'attr' => [
|
||||
// 'class' => 'btn btn-primary btn-nospin',
|
||||
// 'onclick' => 'Mautic.loadNewWindow(Mautic.standardTweetUrl({"windowUrl": "'.$windowUrlEdit.'"}))',
|
||||
// 'disabled' => !isset($tweet),
|
||||
// 'icon' => 'ri-edit-line',
|
||||
// ],
|
||||
// 'label' => 'mautic.integration.Twitter.edit.tweet',
|
||||
// ]
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefined(['update_select']);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'tweetsend_list';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Form\Type;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Mautic\AssetBundle\Form\Type\AssetListType;
|
||||
use Mautic\CategoryBundle\Form\Type\CategoryListType;
|
||||
use Mautic\CoreBundle\Form\DataTransformer\IdToEntityModelTransformer;
|
||||
use Mautic\CoreBundle\Form\Type\FormButtonsType;
|
||||
use Mautic\PageBundle\Form\Type\PageListType;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\Tweet;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ButtonType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<Tweet>
|
||||
*/
|
||||
class TweetType extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
protected EntityManager $em,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add(
|
||||
'name',
|
||||
TextType::class,
|
||||
[
|
||||
'label' => 'mautic.social.monitoring.twitter.tweet.name',
|
||||
'required' => true,
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => [
|
||||
'tooltip' => 'mautic.social.monitoring.twitter.tweet.name.tooltip',
|
||||
'class' => 'form-control',
|
||||
],
|
||||
'constraints' => [
|
||||
new NotBlank(
|
||||
[
|
||||
'message' => 'mautic.core.name.required',
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'description',
|
||||
TextareaType::class,
|
||||
[
|
||||
'label' => 'mautic.social.monitoring.twitter.tweet.description',
|
||||
'required' => false,
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => [
|
||||
'tooltip' => 'mautic.social.monitoring.twitter.tweet.description.tooltip',
|
||||
'class' => 'form-control',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'text',
|
||||
TextareaType::class,
|
||||
[
|
||||
'label' => 'mautic.social.monitoring.twitter.tweet.text',
|
||||
'required' => true,
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => [
|
||||
'tooltip' => 'mautic.social.monitoring.twitter.tweet.text.tooltip',
|
||||
'class' => 'form-control tweet-message',
|
||||
],
|
||||
'constraints' => [
|
||||
new NotBlank(
|
||||
[
|
||||
'message' => 'mautic.core.value.required',
|
||||
]
|
||||
),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$transformer = new IdToEntityModelTransformer($this->em, \Mautic\AssetBundle\Entity\Asset::class, 'id');
|
||||
$builder->add(
|
||||
$builder->create(
|
||||
'asset',
|
||||
AssetListType::class,
|
||||
[
|
||||
'label' => 'mautic.social.monitoring.twitter.assets',
|
||||
'placeholder' => 'mautic.social.monitoring.list.choose',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'multiple' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-control tweet-insert-asset',
|
||||
'tooltip' => 'mautic.social.monitoring.twitter.assets.descr',
|
||||
],
|
||||
]
|
||||
)->addModelTransformer($transformer)
|
||||
);
|
||||
|
||||
$transformer = new IdToEntityModelTransformer($this->em, \Mautic\PageBundle\Entity\Page::class, 'id');
|
||||
$builder->add(
|
||||
$builder->create(
|
||||
'page',
|
||||
PageListType::class,
|
||||
[
|
||||
'label' => 'mautic.social.monitoring.twitter.pages',
|
||||
'placeholder' => 'mautic.social.monitoring.list.choose',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'multiple' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-control tweet-insert-page',
|
||||
'tooltip' => 'mautic.social.monitoring.twitter.pages.descr',
|
||||
],
|
||||
]
|
||||
)->addModelTransformer($transformer)
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'handle',
|
||||
ButtonType::class,
|
||||
[
|
||||
'label' => 'mautic.social.twitter.handle',
|
||||
'attr' => [
|
||||
'class' => 'form-control btn-primary tweet-insert-handle',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// add category
|
||||
$builder->add('category', CategoryListType::class, [
|
||||
'bundle' => 'plugin:mauticSocial',
|
||||
]);
|
||||
|
||||
if (!empty($options['update_select'])) {
|
||||
$builder->add(
|
||||
'buttons',
|
||||
FormButtonsType::class,
|
||||
[
|
||||
'apply_text' => false,
|
||||
]
|
||||
);
|
||||
$builder->add(
|
||||
'updateSelect',
|
||||
HiddenType::class,
|
||||
[
|
||||
'data' => $options['update_select'],
|
||||
'mapped' => false,
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$builder->add(
|
||||
'buttons',
|
||||
FormButtonsType::class
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($options['action'])) {
|
||||
$builder->setAction($options['action']);
|
||||
}
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefined(['update_select']);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'twitter_tweet';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>>
|
||||
*/
|
||||
class TwitterAbstractType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class TwitterHashtagType extends TwitterAbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('hashtag', TextType::class, [
|
||||
'label' => 'mautic.social.monitoring.twitter.hashtag',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => [
|
||||
'tooltip' => 'mautic.social.monitoring.twitter.hashtag.tooltip',
|
||||
'class' => 'form-control',
|
||||
'preaddon' => 'symbol-hashtag',
|
||||
],
|
||||
]);
|
||||
|
||||
$builder->add('checknames', ChoiceType::class, [
|
||||
'choices' => [
|
||||
'mautic.social.monitoring.twitter.no' => '0',
|
||||
'mautic.social.monitoring.twitter.yes' => '1',
|
||||
],
|
||||
'label' => 'mautic.social.monitoring.twitter.namematching',
|
||||
'required' => false,
|
||||
'placeholder' => false,
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'tooltip' => 'mautic.social.monitoring.twitter.namematching.tooltip',
|
||||
],
|
||||
]);
|
||||
|
||||
// pull in the parent type's form builder
|
||||
parent::buildForm($builder, $options);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'twitter_hashtag';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Form\Type;
|
||||
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class TwitterMentionType extends TwitterAbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('handle', TextType::class, [
|
||||
'label' => 'mautic.social.monitoring.twitter.handle',
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'tooltip' => 'mautic.social.monitoring.twitter.handle.tooltip',
|
||||
'preaddon' => 'ri-at-line',
|
||||
],
|
||||
]);
|
||||
|
||||
$builder->add('checknames', ChoiceType::class, [
|
||||
'choices' => [
|
||||
'mautic.social.monitoring.twitter.no' => '0',
|
||||
'mautic.social.monitoring.twitter.yes' => '1',
|
||||
],
|
||||
'label' => 'mautic.social.monitoring.twitter.namematching',
|
||||
'required' => false,
|
||||
'placeholder' => false,
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'tooltip' => 'mautic.social.monitoring.twitter.namematching.tooltip',
|
||||
],
|
||||
]);
|
||||
|
||||
// pull in the parent type's form builder
|
||||
parent::buildForm($builder, $options);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'twitter_handle';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Form\Type;
|
||||
|
||||
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* @extends AbstractType<array<mixed>>
|
||||
*/
|
||||
class TwitterType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder->add('count', ChoiceType::class, [
|
||||
'choices' => [
|
||||
'mautic.integration.Twitter.share.layout.horizontal' => 'horizontal',
|
||||
'mautic.integration.Twitter.share.layout.vertical' => 'vertical',
|
||||
'mautic.integration.Twitter.share.layout.none' => 'none',
|
||||
],
|
||||
'label' => 'mautic.integration.Twitter.share.layout',
|
||||
'required' => false,
|
||||
'placeholder' => false,
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'attr' => ['class' => 'form-control'],
|
||||
]);
|
||||
|
||||
$builder->add('text', TextType::class, [
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'label' => 'mautic.integration.Twitter.share.text',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'placeholder' => 'mautic.integration.Twitter.share.text.pagetitle',
|
||||
],
|
||||
]);
|
||||
|
||||
$builder->add('via', TextType::class, [
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'label' => 'mautic.integration.Twitter.share.via',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'placeholder' => 'mautic.integration.Twitter.share.username',
|
||||
'preaddon' => 'ri-at-line',
|
||||
],
|
||||
]);
|
||||
|
||||
$builder->add('related', TextType::class, [
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'label' => 'mautic.integration.Twitter.share.related',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'placeholder' => 'mautic.integration.Twitter.share.username',
|
||||
'preaddon' => 'ri-at-line',
|
||||
],
|
||||
]);
|
||||
|
||||
$builder->add('hashtags', TextType::class, [
|
||||
'label_attr' => ['class' => 'control-label'],
|
||||
'label' => 'mautic.integration.Twitter.share.hashtag',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'class' => 'form-control',
|
||||
'placeholder' => 'mautic.integration.Twitter.share.hashtag.placeholder',
|
||||
'preaddon' => 'symbol-hashtag',
|
||||
],
|
||||
]);
|
||||
|
||||
$builder->add('size', YesNoButtonGroupType::class, [
|
||||
'no_value' => 'medium',
|
||||
'yes_value' => 'large',
|
||||
'label' => 'mautic.integration.Twitter.share.largesize',
|
||||
'data' => (!empty($options['data']['size'])) ? $options['data']['size'] : 'medium',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'socialmedia_twitter';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Helper;
|
||||
|
||||
use Mautic\AssetBundle\Helper\TokenHelper as AssetTokenHelper;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Helper\TokenHelper;
|
||||
use Mautic\PageBundle\Entity\Trackable;
|
||||
use Mautic\PageBundle\Helper\TokenHelper as PageTokenHelper;
|
||||
use Mautic\PageBundle\Model\TrackableModel;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use MauticPlugin\MauticSocialBundle\Model\TweetModel;
|
||||
|
||||
class CampaignEventHelper
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $clickthrough = [];
|
||||
|
||||
public function __construct(
|
||||
protected IntegrationHelper $integrationHelper,
|
||||
protected TrackableModel $trackableModel,
|
||||
protected PageTokenHelper $pageTokenHelper,
|
||||
protected AssetTokenHelper $assetTokenHelper,
|
||||
protected TweetModel $tweetModel,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|false
|
||||
*/
|
||||
public function sendTweetAction(Lead $lead, array $event)
|
||||
{
|
||||
$tweetSent = false;
|
||||
$tweetEntity = $this->tweetModel->getEntity($event['channelId']);
|
||||
|
||||
if (!$tweetEntity) {
|
||||
return ['failed' => 1, 'response' => 'Tweet entity '.$event['channelId'].' not found'];
|
||||
}
|
||||
|
||||
/** @var \MauticPlugin\MauticSocialBundle\Integration\TwitterIntegration $twitterIntegration */
|
||||
$twitterIntegration = $this->integrationHelper->getIntegrationObject('Twitter');
|
||||
|
||||
// Setup clickthrough for URLs in tweet
|
||||
$this->clickthrough = [
|
||||
'source' => ['campaign', $event['campaign']['id']],
|
||||
];
|
||||
|
||||
$leadArray = $lead->getProfileFields();
|
||||
if (empty($leadArray['twitter'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tweetText = $tweetEntity->getText();
|
||||
$tweetText = $this->parseTweetText($tweetText, $leadArray, $tweetEntity->getId());
|
||||
$tweetUrl = $twitterIntegration->getApiUrl('statuses/update');
|
||||
$status = ['status' => $tweetText];
|
||||
|
||||
// fire the tweet
|
||||
$sendResponse = $twitterIntegration->makeRequest($tweetUrl, $status, 'POST', ['append_callback' => false]);
|
||||
|
||||
// verify the tweet was sent by checking for a tweet id
|
||||
if (is_array($sendResponse) && array_key_exists('id_str', $sendResponse)) {
|
||||
$tweetSent = true;
|
||||
}
|
||||
|
||||
if ($tweetSent) {
|
||||
$this->tweetModel->registerSend($tweetEntity, $lead, $sendResponse, 'campaign.event', $event['id']);
|
||||
|
||||
return ['timeline' => $tweetText, 'response' => $sendResponse];
|
||||
}
|
||||
|
||||
$response = ['failed' => 1, 'response' => $sendResponse];
|
||||
if (!empty($sendResponse['error']['message'])) {
|
||||
$response['reason'] = $sendResponse['error']['message'];
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* PreParse the twitter message and replace placeholders with values.
|
||||
*
|
||||
* @param string $text
|
||||
* @param array $lead
|
||||
* @param int $channelId
|
||||
*
|
||||
* @return string|string[]
|
||||
*/
|
||||
protected function parseTweetText($text, $lead, $channelId = -1): array|string
|
||||
{
|
||||
$tweetHandle = $lead['twitter'];
|
||||
$tokens = [
|
||||
'{twitter_handle}' => (str_contains($tweetHandle, '@')) ? $tweetHandle : "@$tweetHandle",
|
||||
];
|
||||
|
||||
$tokens = array_merge(
|
||||
$tokens,
|
||||
TokenHelper::findLeadTokens($text, $lead),
|
||||
$this->pageTokenHelper->findPageTokens($text, $this->clickthrough),
|
||||
$this->assetTokenHelper->findAssetTokens($text, $this->clickthrough)
|
||||
);
|
||||
|
||||
[$text, $trackables] = $this->trackableModel->parseContentForTrackables(
|
||||
$text,
|
||||
$tokens,
|
||||
'social_twitter',
|
||||
$channelId
|
||||
);
|
||||
|
||||
/**
|
||||
* @var string $token
|
||||
* @var Trackable $trackable
|
||||
*/
|
||||
foreach ($trackables as $token => $trackable) {
|
||||
$tokens[$token] = $this->trackableModel->generateTrackableUrl($trackable, array_merge($this->clickthrough, ['lead' => $lead['id']]));
|
||||
}
|
||||
|
||||
return str_replace(array_keys($tokens), array_values($tokens), $text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Helper;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Model\FieldModel;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\Monitoring;
|
||||
use MauticPlugin\MauticSocialBundle\Exception\ExitMonitorException;
|
||||
use MauticPlugin\MauticSocialBundle\Model\MonitoringModel;
|
||||
use MauticPlugin\MauticSocialBundle\Model\PostCountModel;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class TwitterCommandHelper
|
||||
{
|
||||
private ?OutputInterface $output = null;
|
||||
|
||||
private int $updatedLeads = 0;
|
||||
|
||||
private int $newLeads = 0;
|
||||
|
||||
private array $manipulatedLeads = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $twitterHandleField;
|
||||
|
||||
public function __construct(
|
||||
private LeadModel $leadModel,
|
||||
private FieldModel $fieldModel,
|
||||
private MonitoringModel $monitoringModel,
|
||||
private PostCountModel $postCountModel,
|
||||
private Translator $translator,
|
||||
private EntityManagerInterface $em,
|
||||
CoreParametersHelper $coreParametersHelper,
|
||||
) {
|
||||
$this->translator->setLocale($coreParametersHelper->get('locale', 'en_US'));
|
||||
$this->twitterHandleField = $coreParametersHelper->get('twitter_handle_field', 'twitter');
|
||||
}
|
||||
|
||||
public function getNewLeadsCount(): int
|
||||
{
|
||||
return $this->newLeads;
|
||||
}
|
||||
|
||||
public function getUpdatedLeadsCount(): int
|
||||
{
|
||||
return $this->updatedLeads;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getManipulatedLeads()
|
||||
{
|
||||
return $this->manipulatedLeads;
|
||||
}
|
||||
|
||||
public function setOutput(OutputInterface $output): void
|
||||
{
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param bool $newLine
|
||||
*/
|
||||
private function output($message, $newLine = true): void
|
||||
{
|
||||
if ($newLine) {
|
||||
$this->output->writeln($message);
|
||||
} else {
|
||||
$this->output->write($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a list of tweets and creates / updates leads in Mautic.
|
||||
*
|
||||
* @param array $statusList
|
||||
* @param Monitoring $monitor
|
||||
*/
|
||||
public function createLeadsFromStatuses($statusList, $monitor): int
|
||||
{
|
||||
$leadField = $this->fieldModel->getRepository()->findOneBy(['alias' => $this->twitterHandleField]);
|
||||
|
||||
if (!$leadField) {
|
||||
// Field has been deleted or something
|
||||
$this->output($this->translator->trans('mautic.social.monitoring.twitter.field.not.found'));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$handleFieldGroup = $leadField->getGroup();
|
||||
|
||||
// Just a means to let any LeadEvents listeners know that many leads are likely coming in case that matters to their logic
|
||||
defined('MASS_LEADS_MANIPULATION') or define('MASS_LEADS_MANIPULATION', 1);
|
||||
defined('SOCIAL_MONITOR_IMPORT') or define('SOCIAL_MONITOR_IMPORT', 1);
|
||||
|
||||
// Get a list of existing leads to tone down on queries
|
||||
$usersByHandles = [];
|
||||
$usersByName = ['firstnames' => [], 'lastnames' => []];
|
||||
$expr = $this->leadModel->getRepository()->createQueryBuilder('f')->expr();
|
||||
$monitorProperties = $monitor->getProperties();
|
||||
|
||||
if (!array_key_exists('checknames', $monitorProperties)) {
|
||||
$monitorProperties['checknames'] = 0;
|
||||
}
|
||||
|
||||
foreach ($statusList as $i => $status) {
|
||||
// If we don't have a screen_name, the rest is irrelevant. Remove from further processing
|
||||
if (empty($status['user']['screen_name'])) {
|
||||
unset($statusList[$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$usersByHandles[] = $expr->literal($status['user']['screen_name']);
|
||||
|
||||
// Split the twitter user's name into its parts if we're matching to contacts by name
|
||||
if ($monitorProperties['checknames'] && $status['user']['name'] && str_contains($status['user']['name'], ' ')) {
|
||||
[$firstName, $lastName] = $this->splitName($status['user']['name']);
|
||||
|
||||
if (!empty($firstName) && !empty($lastName)) {
|
||||
$usersByName['firstnames'][] = $expr->literal($firstName);
|
||||
$usersByName['lastnames'][] = $expr->literal($lastName);
|
||||
}
|
||||
|
||||
unset($firstName, $lastName);
|
||||
}
|
||||
}
|
||||
unset($expr);
|
||||
|
||||
if (!empty($usersByHandles)) {
|
||||
$leads = $this->leadModel->getRepository()->getEntities(
|
||||
[
|
||||
'filter' => [
|
||||
'force' => [
|
||||
[
|
||||
'column' => 'l.'.$this->twitterHandleField,
|
||||
'expr' => 'in',
|
||||
'value' => $usersByHandles,
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// Key by twitter handle
|
||||
$twitterLeads = [];
|
||||
foreach ($leads as $lead) {
|
||||
$fields = $lead->getFields();
|
||||
$twitterHandle = strtolower($fields[$handleFieldGroup][$this->twitterHandleField]['value']);
|
||||
$twitterLeads[$twitterHandle] = $lead;
|
||||
}
|
||||
|
||||
unset($leads);
|
||||
}
|
||||
|
||||
if ($monitorProperties['checknames']) {
|
||||
// Fetch existing contacts who have an unknown twitter
|
||||
// handle in Mautic but are found during monitoring.
|
||||
$leadsByName = $this->leadModel->getRepository()->getEntities(
|
||||
[
|
||||
'filter' => [
|
||||
'force' => [
|
||||
[
|
||||
'column' => 'l.firstname',
|
||||
'expr' => 'in',
|
||||
'value' => $usersByName['firstnames'],
|
||||
],
|
||||
[
|
||||
'column' => 'l.lastname',
|
||||
'expr' => 'in',
|
||||
'value' => $usersByName['lastnames'],
|
||||
],
|
||||
[
|
||||
'column' => 'l.'.$this->twitterHandleField,
|
||||
'expr' => 'isNull',
|
||||
],
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
// key by name
|
||||
$namedLeads = [];
|
||||
/** @var Lead $lead */
|
||||
foreach ($leadsByName as $lead) {
|
||||
$firstName = $lead->getFirstname();
|
||||
$lastName = $lead->getLastname();
|
||||
$namedLeads[$firstName.' '.$lastName] = $lead;
|
||||
}
|
||||
|
||||
unset($leadsByName, $firstName, $lastName);
|
||||
}
|
||||
|
||||
$processedLeads = [];
|
||||
foreach ($statusList as $status) {
|
||||
$handle = strtolower($status['user']['screen_name']);
|
||||
|
||||
/* @var \Mautic\LeadBundle\Entity\Lead $leadEntity */
|
||||
if (!isset($processedLeads[$handle])) {
|
||||
$processedLeads[$handle] = 1;
|
||||
$lastActive = new \DateTime($status['created_at']);
|
||||
|
||||
if (isset($namedLeads[$status['user']['name']])) {
|
||||
++$this->updatedLeads;
|
||||
|
||||
$isNew = false;
|
||||
$leadEntity = $namedLeads[$status['user']['name']];
|
||||
$fields = [
|
||||
$this->twitterHandleField => $handle,
|
||||
];
|
||||
|
||||
$this->leadModel->setFieldValues($leadEntity, $fields, false);
|
||||
|
||||
$this->output('Updating existing lead ID #'.$leadEntity->getId().' ('.$handle.'). Matched by first and last names.');
|
||||
} elseif (isset($twitterLeads[$handle])) {
|
||||
++$this->updatedLeads;
|
||||
|
||||
$isNew = false;
|
||||
$leadEntity = $twitterLeads[$handle];
|
||||
|
||||
$this->output('Updating existing lead ID #'.$leadEntity->getId().' ('.$handle.')');
|
||||
} else {
|
||||
++$this->newLeads;
|
||||
|
||||
$this->output('Creating new lead');
|
||||
|
||||
$isNew = true;
|
||||
$leadEntity = new Lead();
|
||||
$leadEntity->setNewlyCreated(true);
|
||||
|
||||
[$firstName, $lastName] = $this->splitName($status['user']['name']);
|
||||
|
||||
// build new lead fields
|
||||
$fields = [
|
||||
$this->twitterHandleField => $handle,
|
||||
'firstname' => $firstName,
|
||||
'lastname' => $lastName,
|
||||
'country' => $status['user']['location'],
|
||||
];
|
||||
|
||||
$this->leadModel->setFieldValues($leadEntity, $fields, false);
|
||||
|
||||
// mark as identified just to be sure
|
||||
$leadEntity->setDateIdentified(new \DateTime());
|
||||
}
|
||||
|
||||
$leadEntity->setPreferredProfileImage('Twitter');
|
||||
|
||||
// save the lead now
|
||||
$leadEntity->setLastActive($lastActive->format('Y-m-d H:i:s'));
|
||||
|
||||
try {
|
||||
// save the lead entity
|
||||
$this->leadModel->saveEntity($leadEntity);
|
||||
|
||||
// Note lead ids
|
||||
$this->manipulatedLeads[$leadEntity->getId()] = 1;
|
||||
|
||||
// add lead entity to the lead list
|
||||
$this->leadModel->addToLists($leadEntity, $monitor->getLists());
|
||||
|
||||
if ($isNew) {
|
||||
$this->setMonitorLeadStat($monitor, $leadEntity);
|
||||
}
|
||||
} catch (ExitMonitorException $e) {
|
||||
$this->output($e->getMessage());
|
||||
|
||||
return 0;
|
||||
} catch (\Exception $e) {
|
||||
$this->output($e->getMessage());
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Increment the post count
|
||||
$this->incrementPostCount($monitor, $status);
|
||||
}
|
||||
|
||||
unset($processedLeads);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the monitor's stat record with the metadata.
|
||||
*
|
||||
* @param array $searchMeta
|
||||
*/
|
||||
public function setMonitorStats(Monitoring $monitor, $searchMeta): void
|
||||
{
|
||||
$monitor->setStats($searchMeta);
|
||||
|
||||
$this->monitoringModel->saveEntity($monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get monitor record entity.
|
||||
*
|
||||
* @param int $mid
|
||||
*/
|
||||
public function getMonitor($mid): ?Monitoring
|
||||
{
|
||||
return $this->monitoringModel->getEntity($mid);
|
||||
}
|
||||
|
||||
/**
|
||||
* handles splitting a string handle into first / last name based on a space.
|
||||
*
|
||||
* @param string $name Space separated first & last name. Supports multiple first names
|
||||
*
|
||||
* @return array{0: string, 1?: string}
|
||||
*/
|
||||
private function splitName(string $name): array
|
||||
{
|
||||
// array the entire name
|
||||
$nameParts = explode(' ', $name);
|
||||
|
||||
// last part of the array is our last
|
||||
$lastName = array_pop($nameParts);
|
||||
|
||||
// push the rest of the name into first name
|
||||
$firstName = implode(' ', $nameParts);
|
||||
|
||||
return [$firstName, $lastName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new monitoring_leads record to track leads found via the search.
|
||||
*
|
||||
* @param Monitoring $monitor
|
||||
* @param Lead $lead
|
||||
*/
|
||||
private function setMonitorLeadStat($monitor, $lead): void
|
||||
{
|
||||
// track the lead in our monitor_leads table
|
||||
$monitorLead = new \MauticPlugin\MauticSocialBundle\Entity\Lead();
|
||||
$monitorLead->setMonitor($monitor);
|
||||
$monitorLead->setLead($lead);
|
||||
$monitorLead->setDateAdded(new \DateTime());
|
||||
|
||||
/* @var \MauticPlugin\MauticSocialBundle\Entity\LeadRepository $monitorRepository */
|
||||
$monitorRepository = $this->em->getRepository(\MauticPlugin\MauticSocialBundle\Entity\Lead::class);
|
||||
|
||||
$monitorRepository->saveEntity($monitorLead);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the post counter.
|
||||
*
|
||||
* @param Monitoring $monitor
|
||||
*/
|
||||
private function incrementPostCount($monitor, $tweet): void
|
||||
{
|
||||
$date = new \DateTime($tweet['created_at']);
|
||||
|
||||
$this->postCountModel->updatePostCount($monitor, $date);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Integration;
|
||||
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
|
||||
final class Config
|
||||
{
|
||||
public function __construct(private IntegrationHelper $integrationsHelper)
|
||||
{
|
||||
}
|
||||
|
||||
public function isPublished(): bool
|
||||
{
|
||||
$integration = $this->integrationsHelper->getIntegrationObject(TwitterIntegration::NAME);
|
||||
|
||||
return $integration && $integration->getIntegrationSettings()->getIsPublished();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Integration;
|
||||
|
||||
use MauticPlugin\MauticSocialBundle\Form\Type\FacebookType;
|
||||
|
||||
class FacebookIntegration extends SocialIntegration
|
||||
{
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Facebook';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getIdentifierFields(): array
|
||||
{
|
||||
return [
|
||||
'facebook',
|
||||
];
|
||||
}
|
||||
|
||||
public function getSupportedFeatures(): array
|
||||
{
|
||||
return [
|
||||
'share_button',
|
||||
'login_button',
|
||||
'public_profile',
|
||||
];
|
||||
}
|
||||
|
||||
public function getAuthenticationUrl(): string
|
||||
{
|
||||
return 'https://www.facebook.com/dialog/oauth';
|
||||
}
|
||||
|
||||
public function getAccessTokenUrl(): string
|
||||
{
|
||||
return 'https://graph.facebook.com/oauth/access_token';
|
||||
}
|
||||
|
||||
public function getAuthScope(): string
|
||||
{
|
||||
return 'email';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param bool $postAuthorization
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function parseCallbackResponse($data, $postAuthorization = false)
|
||||
{
|
||||
// Facebook is inconsistent in that it returns errors as json and data as parameter list
|
||||
$values = parent::parseCallbackResponse($data, $postAuthorization);
|
||||
|
||||
if (null === $values) {
|
||||
parse_str($data, $values);
|
||||
|
||||
$this->requestStack->getSession()->set($this->getName().'_tokenResponse', $values);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
public function getApiUrl($endpoint): string
|
||||
{
|
||||
return "https://graph.facebook.com/$endpoint";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get public data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUserData($identifier, &$socialCache)
|
||||
{
|
||||
$this->persistNewLead = false;
|
||||
$accessToken = $this->getContactAccessToken($socialCache);
|
||||
|
||||
if (!isset($accessToken['access_token'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = $this->getApiUrl('v2.8/me');
|
||||
$fields = array_keys($this->getAvailableLeadFields());
|
||||
|
||||
$parameters = [
|
||||
'access_token' => $accessToken['access_token'],
|
||||
'fields' => implode(',', $fields),
|
||||
];
|
||||
|
||||
$data = $this->makeRequest($url, $parameters, 'GET', ['auth_type' => 'rest']);
|
||||
|
||||
if (is_object($data) && isset($data->id)) {
|
||||
$info = $this->matchUpData($data);
|
||||
|
||||
if (isset($data->username)) {
|
||||
$info['profileHandle'] = $data->username;
|
||||
} elseif (isset($data->link)) {
|
||||
if (preg_match("/www.facebook.com\/(app_scoped_user_id\/)?(.*?)($|\/)/", $data->link, $matches)) {
|
||||
$info['profileHandle'] = $matches[2];
|
||||
}
|
||||
} else {
|
||||
$info['profileHandle'] = $data->id;
|
||||
}
|
||||
$info['profileImage'] = "https://graph.facebook.com/{$data->id}/picture?type=large";
|
||||
|
||||
$socialCache['id'] = $data->id;
|
||||
$socialCache['profile'] = $info;
|
||||
$socialCache['lastRefresh'] = new \DateTime();
|
||||
$socialCache['accessToken'] = $this->encryptApiKeys($accessToken);
|
||||
|
||||
$this->getMauticLead($info, $this->persistNewLead, $socialCache, $identifier);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getAvailableLeadFields($settings = []): array
|
||||
{
|
||||
return [
|
||||
'about' => ['type' => 'string'],
|
||||
'birthday' => ['type' => 'string'],
|
||||
'email' => ['type' => 'string'],
|
||||
'first_name' => ['type' => 'string'],
|
||||
'gender' => ['type' => 'string'],
|
||||
'last_name' => ['type' => 'string'],
|
||||
'link' => ['type' => 'string'],
|
||||
'locale' => ['type' => 'string'],
|
||||
'middle_name' => ['type' => 'string'],
|
||||
'name' => ['type' => 'string'],
|
||||
'political' => ['type' => 'string'],
|
||||
'quotes' => ['type' => 'string'],
|
||||
'religion' => ['type' => 'string'],
|
||||
'timezone' => ['type' => 'string'],
|
||||
'website' => ['type' => 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getFormType(): string
|
||||
{
|
||||
return FacebookType::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Integration;
|
||||
|
||||
class FoursquareIntegration extends SocialIntegration
|
||||
{
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Foursquare';
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getIdentifierFields(): array
|
||||
{
|
||||
return [
|
||||
'email',
|
||||
'twitter', // foursquare allows searching directly by twitter handle
|
||||
];
|
||||
}
|
||||
|
||||
public function getAuthenticationUrl(): string
|
||||
{
|
||||
return 'https://foursquare.com/oauth2/authenticate';
|
||||
}
|
||||
|
||||
public function getAccessTokenUrl(): string
|
||||
{
|
||||
return 'https://foursquare.com/oauth2/access_token';
|
||||
}
|
||||
|
||||
public function getAuthenticationType(): string
|
||||
{
|
||||
return 'oauth2';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $endpoint
|
||||
* @param string $m
|
||||
*/
|
||||
public function getApiUrl($endpoint, $m = 'foursquare'): string
|
||||
{
|
||||
return "https://api.foursquare.com/v2/$endpoint?v=20140806&m={$m}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parameters
|
||||
* @param string $method
|
||||
* @param array $settings
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function makeRequest($url, $parameters = [], $method = 'GET', $settings = [])
|
||||
{
|
||||
$settings[$this->getAuthTokenKey()] = 'oauth_token';
|
||||
|
||||
return parent::makeRequest($url, $parameters, $method, $settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get public data.
|
||||
*/
|
||||
public function getUserData($identifier, &$socialCache): void
|
||||
{
|
||||
if ($id = $this->getContactUserId($identifier, $socialCache)) {
|
||||
$url = $this->getApiUrl("users/{$id}");
|
||||
$data = $this->makeRequest($url);
|
||||
if (!empty($data) && isset($data->response->user)) {
|
||||
$result = $data->response->user;
|
||||
$socialCache['profile'] = $this->matchUpData($result);
|
||||
if (isset($result->photo)) {
|
||||
$socialCache['profile']['profileImage'] = $result->photo->prefix.'300x300'.$result->photo->suffix;
|
||||
}
|
||||
$socialCache['profile']['profileHandle'] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getPublicActivity($identifier, &$socialCache): void
|
||||
{
|
||||
if ($id = $this->getContactUserId($identifier, $socialCache)) {
|
||||
$activity = [
|
||||
// 'mayorships' => array(),
|
||||
'tips' => [],
|
||||
// 'lists' => array()
|
||||
];
|
||||
|
||||
/*
|
||||
//mayorships
|
||||
$url = $this->getApiUrl("users/{$id}/mayorships");
|
||||
$data = $this->makeRequest($url);
|
||||
|
||||
if (isset($data->response->mayorships) && count($data->response->mayorships->items)) {
|
||||
$limit = 5;
|
||||
foreach ($data->response->mayorships->items as $m) {
|
||||
if (empty($limit)) {
|
||||
break;
|
||||
}
|
||||
//find main category of venue
|
||||
$category = '';
|
||||
foreach ($m->venue->categories as $c) {
|
||||
if ($c->primary) {
|
||||
$category = $c->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$contact = (!empty($m->contact->formattedPhone)) ? $m->contact->formattedPhone : '';
|
||||
$activity['mayorships'][] = array(
|
||||
'venueName' => $m->venue->name,
|
||||
'venueLocation' => $m->venue->location->formattedAddress,
|
||||
'venueContact' => $contact,
|
||||
'venueCategory' => $category
|
||||
);
|
||||
$limit--;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// tips
|
||||
$url = $this->getApiUrl("users/{$id}/tips").'&limit=5&sort=recent';
|
||||
$data = $this->makeRequest($url);
|
||||
|
||||
if (isset($data->response->tips) && count($data->response->tips->items)) {
|
||||
foreach ($data->response->tips->items as $t) {
|
||||
// find main category of venue
|
||||
$category = '';
|
||||
foreach ($t->venue->categories as $c) {
|
||||
if ($c->primary) {
|
||||
$category = $c->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$contact = (!empty($t->contact->formattedPhone)) ? $t->contact->formattedPhone : '';
|
||||
$activity['tips'][] = [
|
||||
'createdAt' => $t->createdAt,
|
||||
'tipText' => $t->text,
|
||||
'tipUrl' => $t->canonicalUrl,
|
||||
'venueName' => $t->venue->name,
|
||||
'venueLocation' => $t->venue->location->formattedAddress,
|
||||
'venueContact' => $contact,
|
||||
'venueCategory' => $category,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//lists
|
||||
$url = $this->getApiUrl("users/{$id}/lists") . "&limit=5&group=created";
|
||||
$data = $this->makeRequest($url);
|
||||
|
||||
if (isset($data->response->lists) && count($data->response->lists->items)) {
|
||||
foreach ($data->response->lists->items as $l) {
|
||||
if (!$l->listItems->count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = array(
|
||||
'listName' => $l->name,
|
||||
'listDescription' => $l->description,
|
||||
'listUrl' => $l->canonicalUrl,
|
||||
'listCreatedAt' => (isset($l->createdAt)) ? $l->createdAt : '',
|
||||
'listUpdatedAt' => (isset($l->updatedAt)) ? $l->updatedAt : '',
|
||||
'listItems' => array()
|
||||
);
|
||||
|
||||
//get a sample of the list items
|
||||
$url = "https://api.foursquare.com/v2/lists/{$l->id}?limit=5&sort=recent&v=20140719&oauth_token={$keys['access_token']}";
|
||||
$listData = $this->makeRequest($url);
|
||||
|
||||
if (isset($listData->response->list->listItems) && count($listData->response->list->listItems->items)) {
|
||||
foreach ($listData->response->list->listItems->items as $li) {
|
||||
//find main category of venue
|
||||
$category = '';
|
||||
foreach ($li->venue->categories as $c) {
|
||||
if ($c->primary) {
|
||||
$category = $c->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$contact = (!empty($li->contact->formattedPhone)) ? $li->contact->formattedPhone : '';
|
||||
|
||||
$item['listItems'][] = array(
|
||||
'createdAt' => $li->createdAt,
|
||||
'venueName' => $li->venue->name,
|
||||
'venueLocation' => $li->venue->location->formattedAddress,
|
||||
'venueContact' => $contact,
|
||||
'venueCategory' => $category
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$activity['lists'][] = $item;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (!empty($activity)) {
|
||||
$socialCache['activity'] = $activity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getErrorsFromResponse($response): string
|
||||
{
|
||||
if (is_object($response) && isset($response->meta->errorDetail)) {
|
||||
return $response->meta->errorDetail.' ('.$response->meta->code.')';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function matchFieldName($field, $subfield = '')
|
||||
{
|
||||
if ('contact' == $field && in_array($subfield, ['facebook', 'twitter'])) {
|
||||
return $subfield.'ProfileHandle';
|
||||
}
|
||||
|
||||
return parent::matchFieldName($field, $subfield);
|
||||
}
|
||||
|
||||
public function getAvailableLeadFields($settings = []): array
|
||||
{
|
||||
return [
|
||||
'profileHandle' => ['type' => 'string'],
|
||||
'firstName' => ['type' => 'string'],
|
||||
'lastName' => ['type' => 'string'],
|
||||
'gender' => ['type' => 'string'],
|
||||
'homeCity' => ['type' => 'string'],
|
||||
'bio' => ['type' => 'string'],
|
||||
'contact' => [
|
||||
'type' => 'object',
|
||||
'fields' => [
|
||||
'twitter',
|
||||
'facebook',
|
||||
'phone',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getSupportedFeatures(): array
|
||||
{
|
||||
return [
|
||||
'public_profile',
|
||||
'public_activity',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function getContactUserId(&$identifier, &$socialCache)
|
||||
{
|
||||
if (!empty($socialCache['id'])) {
|
||||
return $socialCache['id'];
|
||||
} elseif (empty($identifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cleaned = $this->cleanIdentifier($identifier);
|
||||
|
||||
if (!is_array($cleaned)) {
|
||||
$cleaned = [$cleaned];
|
||||
}
|
||||
|
||||
foreach ($cleaned as $type => $c) {
|
||||
$url = $this->getApiUrl('users/search')."&{$type}={$c}";
|
||||
$data = $this->makeRequest($url);
|
||||
|
||||
if (!empty($data) && isset($data->response->results) && count($data->response->results)) {
|
||||
$socialCache['id'] = $data->response->results[0]->id;
|
||||
|
||||
return $socialCache['id'];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getFormType(): null
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Integration;
|
||||
|
||||
class InstagramIntegration extends SocialIntegration
|
||||
{
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Instagram';
|
||||
}
|
||||
|
||||
public function getSupportedFeatures(): array
|
||||
{
|
||||
return [
|
||||
'public_profile',
|
||||
'public_activity',
|
||||
];
|
||||
}
|
||||
|
||||
public function getIdentifierFields(): string
|
||||
{
|
||||
return 'instagram';
|
||||
}
|
||||
|
||||
public function getAuthenticationUrl(): string
|
||||
{
|
||||
return 'https://api.instagram.com/oauth/authorize';
|
||||
}
|
||||
|
||||
public function getAccessTokenUrl(): string
|
||||
{
|
||||
return 'https://api.instagram.com/oauth/access_token';
|
||||
}
|
||||
|
||||
public function getApiUrl($endpoint): string
|
||||
{
|
||||
return "https://api.instagram.com/v1/$endpoint";
|
||||
}
|
||||
|
||||
public function getUserData($identifier, &$socialCache): void
|
||||
{
|
||||
if ($id = $this->getContactUserId($identifier, $socialCache)) {
|
||||
$url = $this->getApiUrl('users/'.$id);
|
||||
$data = $this->makeRequest($url);
|
||||
|
||||
if (isset($data->data)) {
|
||||
$info = $this->matchUpData($data->data);
|
||||
|
||||
$info['profileImage'] = $data->data->profile_picture;
|
||||
$info['profileHandle'] = $data->data->username;
|
||||
$socialCache['profile'] = $info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getPublicActivity($identifier, &$socialCache): void
|
||||
{
|
||||
$socialCache['has']['activity'] = false;
|
||||
if ($id = $this->getContactUserId($identifier, $socialCache)) {
|
||||
// get more than 10 so we can weed out videos
|
||||
$data = $this->makeRequest($this->getApiUrl("users/$id/media/recent"), ['count' => 20]);
|
||||
|
||||
$socialCache['activity'] = [
|
||||
'photos' => [],
|
||||
'tags' => [],
|
||||
];
|
||||
|
||||
if (!empty($data->data)) {
|
||||
$socialCache['has']['activity'] = true;
|
||||
$count = 1;
|
||||
foreach ($data->data as $m) {
|
||||
if ($count > 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ('image' == $m->type) {
|
||||
$socialCache['activity']['photos'][] = [
|
||||
'url' => $m->images->standard_resolution->url,
|
||||
];
|
||||
|
||||
if (!empty($m->caption->text)) {
|
||||
preg_match_all("/#(\w+)/", $m->caption->text, $tags);
|
||||
foreach ($tags[1] as $tag) {
|
||||
if (isset($socialCache['activity']['tags'][$tag])) {
|
||||
++$socialCache['activity']['tags'][$tag]['count'];
|
||||
} else {
|
||||
$socialCache['activity']['tags'][$tag] = [
|
||||
'count' => 1,
|
||||
'url' => 'http://searchinstagram.com/'.$tag,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getAvailableLeadFields($settings = []): array
|
||||
{
|
||||
return [
|
||||
'full_name' => ['type' => 'string'],
|
||||
'bio' => ['type' => 'string'],
|
||||
'website' => ['type' => 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
private function getContactUserId(&$identifier, &$socialCache)
|
||||
{
|
||||
if (!empty($socialCache['id'])) {
|
||||
return $socialCache['id'];
|
||||
} elseif (empty($identifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->makeRequest($this->getApiUrl('users/search'), ['q' => $identifier]);
|
||||
|
||||
if (!empty($data->data)) {
|
||||
foreach ($data->data as $user) {
|
||||
// its possible that instagram may return multiple users if the username is a base of another
|
||||
// for example, search for alan may return alanh, alanhartless, etc
|
||||
if (strtolower($user->username) == strtolower($identifier)) {
|
||||
$socialCache['id'] = $user->id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (!empty($socialCache['id'])) ? $socialCache['id'] : false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getFormType(): null
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Integration;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Mautic\CoreBundle\Helper\CacheStorageHelper;
|
||||
use Mautic\CoreBundle\Helper\EncryptionHelper;
|
||||
use Mautic\CoreBundle\Helper\PathsHelper;
|
||||
use Mautic\CoreBundle\Model\NotificationModel;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Field\FieldsWithUniqueIdentifier;
|
||||
use Mautic\LeadBundle\Model\CompanyModel;
|
||||
use Mautic\LeadBundle\Model\DoNotContact;
|
||||
use Mautic\LeadBundle\Model\FieldModel;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Mautic\PluginBundle\Integration\AbstractIntegration;
|
||||
use Mautic\PluginBundle\Model\IntegrationEntityModel;
|
||||
use Monolog\Logger;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormBuilder;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Routing\Router;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
abstract class SocialIntegration extends AbstractIntegration
|
||||
{
|
||||
protected $persistNewLead = false;
|
||||
|
||||
/**
|
||||
* @var Translator
|
||||
*/
|
||||
protected TranslatorInterface $translator;
|
||||
|
||||
public function __construct(
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
CacheStorageHelper $cacheStorageHelper,
|
||||
EntityManager $entityManager,
|
||||
RequestStack $requestStack,
|
||||
Router $router,
|
||||
Translator $translator,
|
||||
Logger $logger,
|
||||
EncryptionHelper $encryptionHelper,
|
||||
LeadModel $leadModel,
|
||||
CompanyModel $companyModel,
|
||||
PathsHelper $pathsHelper,
|
||||
NotificationModel $notificationModel,
|
||||
FieldModel $fieldModel,
|
||||
FieldsWithUniqueIdentifier $fieldsWithUniqueIdentifier,
|
||||
IntegrationEntityModel $integrationEntityModel,
|
||||
DoNotContact $doNotContact,
|
||||
protected IntegrationHelper $integrationHelper,
|
||||
) {
|
||||
parent::__construct(
|
||||
$eventDispatcher,
|
||||
$cacheStorageHelper,
|
||||
$entityManager,
|
||||
$requestStack,
|
||||
$router,
|
||||
$translator,
|
||||
$logger,
|
||||
$encryptionHelper,
|
||||
$leadModel,
|
||||
$companyModel,
|
||||
$pathsHelper,
|
||||
$notificationModel,
|
||||
$fieldModel,
|
||||
$integrationEntityModel,
|
||||
$doNotContact,
|
||||
$fieldsWithUniqueIdentifier
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Mautic\PluginBundle\Integration\Form|FormBuilder $builder
|
||||
* @param array $data
|
||||
* @param string $formArea
|
||||
*/
|
||||
public function appendToForm(&$builder, $data, $formArea): void
|
||||
{
|
||||
if ('features' == $formArea) {
|
||||
$name = strtolower($this->getName());
|
||||
$formType = $this->getFormType();
|
||||
if ($formType) {
|
||||
$builder->add('shareButton', $formType, [
|
||||
'label' => 'mautic.integration.form.sharebutton',
|
||||
'required' => false,
|
||||
'data' => $data['shareButton'] ?? [],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $settings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFormLeadFields($settings = [])
|
||||
{
|
||||
static $fields = [];
|
||||
|
||||
if (empty($fields)) {
|
||||
$s = $this->getName();
|
||||
$available = $this->getAvailableLeadFields($settings);
|
||||
if (empty($available)) {
|
||||
return [];
|
||||
}
|
||||
// create social profile fields
|
||||
$socialProfileUrls = $this->integrationHelper->getSocialProfileUrlRegex();
|
||||
|
||||
foreach ($available as $field => $details) {
|
||||
$label = (!empty($details['label'])) ? $details['label'] : false;
|
||||
$fn = $this->matchFieldName($field);
|
||||
switch ($details['type']) {
|
||||
case 'string':
|
||||
case 'boolean':
|
||||
$fields[$fn] = (!$label)
|
||||
? $this->translator->transConditional("mautic.integration.common.{$fn}", "mautic.integration.{$s}.{$fn}")
|
||||
: $label;
|
||||
break;
|
||||
case 'object':
|
||||
if (isset($details['fields'])) {
|
||||
foreach ($details['fields'] as $f) {
|
||||
$fn = $this->matchFieldName($field, $f);
|
||||
$fields[$fn] = (!$label)
|
||||
? $this->translator->transConditional("mautic.integration.common.{$fn}", "mautic.integration.{$s}.{$fn}")
|
||||
: $label;
|
||||
}
|
||||
} else {
|
||||
$fields[$field] = (!$label)
|
||||
? $this->translator->transConditional("mautic.integration.common.{$fn}", "mautic.integration.{$s}.{$fn}")
|
||||
: $label;
|
||||
}
|
||||
break;
|
||||
case 'array_object':
|
||||
if ('urls' == $field || 'url' == $field) {
|
||||
foreach ($socialProfileUrls as $p => $d) {
|
||||
$fields["{$p}ProfileHandle"] = (!$label)
|
||||
? $this->translator->transConditional("mautic.integration.common.{$p}ProfileHandle", "mautic.integration.{$s}.{$p}ProfileHandle")
|
||||
: $label;
|
||||
}
|
||||
foreach ($details['fields'] as $f) {
|
||||
$fields["{$p}Urls"] = (!$label)
|
||||
? $this->translator->transConditional("mautic.integration.common.{$f}Urls", "mautic.integration.{$s}.{$f}Urls")
|
||||
: $label;
|
||||
}
|
||||
} elseif (isset($details['fields'])) {
|
||||
foreach ($details['fields'] as $f) {
|
||||
$fn = $this->matchFieldName($field, $f);
|
||||
$fields[$fn] = (!$label)
|
||||
? $this->translator->transConditional("mautic.integration.common.{$fn}", "mautic.integration.{$s}.{$fn}")
|
||||
: $label;
|
||||
}
|
||||
} else {
|
||||
$fields[$fn] = (!$label)
|
||||
? $this->translator->transConditional("mautic.integration.common.{$fn}", "mautic.integration.{$s}.{$fn}")
|
||||
: $label;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($this->sortFieldsAlphabetically()) {
|
||||
uasort($fields, 'strnatcmp');
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
public function getFormCompanyFields($settings = [])
|
||||
{
|
||||
$settings['feature_settings']['objects'] = ['Company'];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAuthenticationType()
|
||||
{
|
||||
return 'oauth2';
|
||||
}
|
||||
|
||||
public function getRequiredKeyFields()
|
||||
{
|
||||
return [
|
||||
'client_id' => 'mautic.integration.keyfield.clientid',
|
||||
'client_secret' => 'mautic.integration.keyfield.clientsecret',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array key for clientId.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getClientIdKey()
|
||||
{
|
||||
return 'client_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array key for client secret.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getClientSecretKey()
|
||||
{
|
||||
return 'client_secret';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param bool $postAuthorization
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function parseCallbackResponse($data, $postAuthorization = false)
|
||||
{
|
||||
if ($postAuthorization) {
|
||||
return json_decode($data, true);
|
||||
} else {
|
||||
return json_decode($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns notes specific to sections of the integration form (if applicable).
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getFormNotes($section)
|
||||
{
|
||||
return ['', 'info'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template for social profiles.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSocialProfileTemplate()
|
||||
{
|
||||
return "MauticSocialBundle:Integration/{$this->getName()}/Profile:view.html.twig";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the access token from session or socialCache.
|
||||
*
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
protected function getContactAccessToken(&$socialCache)
|
||||
{
|
||||
if (!$this->requestStack->getCurrentRequest()->hasSession()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->requestStack->getSession()->isStarted()) {
|
||||
return (isset($socialCache['accessToken'])) ? $this->decryptApiKeys($socialCache['accessToken']) : null;
|
||||
}
|
||||
|
||||
$accessToken = $this->requestStack->getSession()->get($this->getName().'_tokenResponse', []);
|
||||
if (!isset($accessToken[$this->getAuthTokenKey()])) {
|
||||
if (isset($socialCache['accessToken'])) {
|
||||
$accessToken = $this->decryptApiKeys($socialCache['accessToken']);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
$this->requestStack->getSession()->remove($this->getName().'_tokenResponse');
|
||||
$socialCache['accessToken'] = $this->encryptApiKeys($accessToken);
|
||||
|
||||
$this->persistNewLead = true;
|
||||
}
|
||||
|
||||
return $accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns form type.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
abstract public function getFormType();
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Integration;
|
||||
|
||||
use MauticPlugin\MauticSocialBundle\Form\Type\TwitterType;
|
||||
|
||||
class TwitterIntegration extends SocialIntegration
|
||||
{
|
||||
public const NAME = 'Twitter';
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 5000;
|
||||
}
|
||||
|
||||
public function getIdentifierFields(): string
|
||||
{
|
||||
return 'twitter';
|
||||
}
|
||||
|
||||
public function getSupportedFeatures(): array
|
||||
{
|
||||
return [
|
||||
'public_profile',
|
||||
'public_activity',
|
||||
'share_button',
|
||||
'login_button',
|
||||
];
|
||||
}
|
||||
|
||||
public function getAccessTokenUrl(): string
|
||||
{
|
||||
return 'https://api.twitter.com/oauth/access_token';
|
||||
}
|
||||
|
||||
public function getAuthLoginUrl(): string
|
||||
{
|
||||
$url = 'https://api.twitter.com/oauth/authorize';
|
||||
|
||||
// Get request token
|
||||
$requestToken = $this->getRequestToken();
|
||||
|
||||
if (isset($requestToken['oauth_token'])) {
|
||||
$url .= '?oauth_token='.$requestToken['oauth_token'];
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function getRequestTokenUrl(): string
|
||||
{
|
||||
return 'https://api.twitter.com/oauth/request_token';
|
||||
}
|
||||
|
||||
public function getAuthenticationType(): string
|
||||
{
|
||||
return 'oauth1a';
|
||||
}
|
||||
|
||||
public function prepareRequest($url, $parameters, $method, $settings, $authType)
|
||||
{
|
||||
// Prevent SSL issues
|
||||
$settings['ssl_verifypeer'] = false;
|
||||
|
||||
if (empty($settings['authorize_session']) && 'access_token' != $authType) {
|
||||
// Twitter requires oauth_token_secret to be part of composite key
|
||||
if (isset($this->keys['oauth_token_secret'])) {
|
||||
$settings['token_secret'] = $this->keys['oauth_token_secret'];
|
||||
}
|
||||
|
||||
// Twitter also requires double encoding of parameters in building base string
|
||||
$settings['double_encode_basestring_parameters'] = true;
|
||||
}
|
||||
|
||||
return parent::prepareRequest($url, $parameters, $method, $settings, $authType);
|
||||
}
|
||||
|
||||
public function getApiUrl($endpoint): string
|
||||
{
|
||||
return "https://api.twitter.com/1.1/$endpoint.json";
|
||||
}
|
||||
|
||||
public function getUserData($identifier, &$socialCache)
|
||||
{
|
||||
$accessToken = $this->getContactAccessToken($socialCache);
|
||||
|
||||
// Contact SSO
|
||||
if (isset($accessToken['oauth_token'])) {
|
||||
// note twitter requires params to be passed as strings
|
||||
$data = $this->makeRequest(
|
||||
$this->getApiUrl('account/verify_credentials'),
|
||||
[
|
||||
'include_email' => 'true',
|
||||
'include_entities' => 'false',
|
||||
'oauth_token' => $accessToken['oauth_token'],
|
||||
],
|
||||
'GET',
|
||||
['auth_type' => 'oauth1a']
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($data)) {
|
||||
// Try via user lookup
|
||||
$data = $this->makeRequest(
|
||||
$this->getApiUrl('users/lookup'),
|
||||
[
|
||||
'screen_name' => $this->cleanIdentifier($identifier),
|
||||
'include_entities' => 'false',
|
||||
]
|
||||
);
|
||||
|
||||
if (isset($data[0])) {
|
||||
$data = $data[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['id'])) {
|
||||
$socialCache['id'] = $data['id'];
|
||||
|
||||
$info = $this->matchUpData($data);
|
||||
$info['profileHandle'] = $data['screen_name'];
|
||||
// remove the size variant
|
||||
$image = $data['profile_image_url_https'];
|
||||
$image = str_replace(['_normal', '_bigger', '_mini'], '', $image);
|
||||
$info['profileImage'] = $image;
|
||||
|
||||
$socialCache['profile'] = $info;
|
||||
$socialCache['lastRefresh'] = new \DateTime();
|
||||
|
||||
$this->getMauticLead($info, $this->persistNewLead, $socialCache, $identifier);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getPublicActivity($identifier, &$socialCache): void
|
||||
{
|
||||
if (!isset($socialCache['id'])) {
|
||||
$this->getUserData($identifier, $socialCache);
|
||||
|
||||
if (!isset($socialCache['id'])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$id = $socialCache['id'];
|
||||
|
||||
// due to the way Twitter filters, get more than 10 tweets
|
||||
$data = $this->makeRequest($this->getApiUrl('/statuses/user_timeline'), [
|
||||
'user_id' => $id,
|
||||
'exclude_replies' => 'true',
|
||||
'count' => 25,
|
||||
'trim_user' => 'true',
|
||||
]);
|
||||
|
||||
if (!empty($data) && count($data)) {
|
||||
$socialCache['has']['activity'] = true;
|
||||
$socialCache['activity'] = [
|
||||
'tweets' => [],
|
||||
'photos' => [],
|
||||
'tags' => [],
|
||||
];
|
||||
|
||||
foreach ($data as $k => $d) {
|
||||
if (10 == $k) {
|
||||
break;
|
||||
}
|
||||
|
||||
$tweet = [
|
||||
'tweet' => $d['text'],
|
||||
'url' => "https://twitter.com/{$id}/status/{$d['id']}",
|
||||
'coordinates' => $d['coordinates'],
|
||||
'published' => $d['created_at'],
|
||||
];
|
||||
|
||||
$socialCache['activity']['tweets'][] = $tweet;
|
||||
|
||||
// images
|
||||
if (isset($d['entities']['media'])) {
|
||||
foreach ($d['entities']['media'] as $m) {
|
||||
if ('photo' == $m['type']) {
|
||||
$photo = [
|
||||
'url' => ($m['media_url_https'] ?? $m['media_url']),
|
||||
];
|
||||
|
||||
$socialCache['activity']['photos'][] = $photo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hastags
|
||||
if (isset($d['entities']['hashtags'])) {
|
||||
foreach ($d['entities']['hashtags'] as $h) {
|
||||
if (isset($socialCache['activity']['tags'][$h['text']])) {
|
||||
++$socialCache['activity']['tags'][$h['text']]['count'];
|
||||
} else {
|
||||
$socialCache['activity']['tags'][$h['text']] = [
|
||||
'count' => 1,
|
||||
'url' => 'https://twitter.com/search?q=%23'.$h['text'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getAvailableLeadFields($settings = []): array
|
||||
{
|
||||
return [
|
||||
'profileHandle' => ['type' => 'string'],
|
||||
'name' => ['type' => 'string'],
|
||||
'location' => ['type' => 'string'],
|
||||
'description' => ['type' => 'string'],
|
||||
'url' => ['type' => 'string'],
|
||||
'time_zone' => ['type' => 'string'],
|
||||
'lang' => ['type' => 'string'],
|
||||
'email' => ['type' => 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function cleanIdentifier($identifier): string
|
||||
{
|
||||
if (preg_match('#https?://twitter.com/(.*?)(/.*?|$)#i', $identifier, $match)) {
|
||||
// extract the handle
|
||||
$identifier = $match[1];
|
||||
} elseif (str_starts_with($identifier, '@')) {
|
||||
$identifier = substr($identifier, 1);
|
||||
}
|
||||
|
||||
return urlencode($identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param bool $postAuthorization
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function parseCallbackResponse($data, $postAuthorization = false)
|
||||
{
|
||||
if ($postAuthorization) {
|
||||
parse_str($data, $parsed);
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
return json_decode($data, true);
|
||||
}
|
||||
|
||||
public function getFormType(): string
|
||||
{
|
||||
return TwitterType::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle;
|
||||
|
||||
use Mautic\PluginBundle\Bundle\PluginBundleBase;
|
||||
|
||||
class MauticSocialBundle extends PluginBundleBase
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Model;
|
||||
|
||||
use Mautic\CoreBundle\Model\FormModel;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\Monitoring;
|
||||
use MauticPlugin\MauticSocialBundle\Event as Events;
|
||||
use MauticPlugin\MauticSocialBundle\Form\Type\MonitoringType;
|
||||
use MauticPlugin\MauticSocialBundle\Form\Type\TwitterHashtagType;
|
||||
use MauticPlugin\MauticSocialBundle\Form\Type\TwitterMentionType;
|
||||
use MauticPlugin\MauticSocialBundle\SocialEvents;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* @extends FormModel<Monitoring>
|
||||
*/
|
||||
class MonitoringModel extends FormModel
|
||||
{
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private array $networkTypes = [
|
||||
'twitter_handle' => [
|
||||
'label' => 'mautic.social.monitoring.type.list.twitter.handle',
|
||||
'form' => TwitterMentionType::class,
|
||||
],
|
||||
'twitter_hashtag' => [
|
||||
'label' => 'mautic.social.monitoring.type.list.twitter.hashtag',
|
||||
'form' => TwitterHashtagType::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @param object $entity
|
||||
* @param string|null $action
|
||||
* @param mixed[] $options
|
||||
*/
|
||||
public function createForm($entity, FormFactoryInterface $formFactory, $action = null, $options = []): \Symfony\Component\Form\FormInterface
|
||||
{
|
||||
if (!$entity instanceof Monitoring) {
|
||||
throw new MethodNotAllowedHttpException(['Monitoring']);
|
||||
}
|
||||
|
||||
if (!empty($action)) {
|
||||
$options['action'] = $action;
|
||||
}
|
||||
|
||||
return $formFactory->create(MonitoringType::class, $entity, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific entity or generate a new one if id is empty.
|
||||
*/
|
||||
public function getEntity($id = null): ?Monitoring
|
||||
{
|
||||
return $id ? parent::getEntity($id) : new Monitoring();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MethodNotAllowedHttpException
|
||||
*/
|
||||
protected function dispatchEvent($action, &$entity, $isNew = false, ?Event $event = null): ?Event
|
||||
{
|
||||
if (!$entity instanceof Monitoring) {
|
||||
throw new MethodNotAllowedHttpException(['Monitoring']);
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'pre_save':
|
||||
$name = SocialEvents::MONITOR_PRE_SAVE;
|
||||
break;
|
||||
case 'post_save':
|
||||
$name = SocialEvents::MONITOR_POST_SAVE;
|
||||
break;
|
||||
case 'pre_delete':
|
||||
$name = SocialEvents::MONITOR_PRE_DELETE;
|
||||
break;
|
||||
case 'post_delete':
|
||||
$name = SocialEvents::MONITOR_POST_DELETE;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->dispatcher->hasListeners($name)) {
|
||||
if (empty($event)) {
|
||||
$event = new Events\SocialEvent($entity, $isNew);
|
||||
}
|
||||
|
||||
$this->dispatcher->dispatch($event, $name);
|
||||
|
||||
return $event;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Monitoring $monitoringEntity
|
||||
* @param bool $unlock
|
||||
*/
|
||||
public function saveEntity($monitoringEntity, $unlock = true): void
|
||||
{
|
||||
// we're editing an existing record
|
||||
if (!$monitoringEntity->isNew()) {
|
||||
// increase the revision
|
||||
$revision = $monitoringEntity->getRevision();
|
||||
++$revision;
|
||||
$monitoringEntity->setRevision($revision);
|
||||
} // is new
|
||||
else {
|
||||
$now = new \DateTime();
|
||||
$monitoringEntity->setDateAdded($now);
|
||||
}
|
||||
|
||||
parent::saveEntity($monitoringEntity, $unlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \MauticPlugin\MauticSocialBundle\Entity\MonitoringRepository
|
||||
*/
|
||||
public function getRepository()
|
||||
{
|
||||
return $this->em->getRepository(Monitoring::class);
|
||||
}
|
||||
|
||||
public function getPermissionBase(): string
|
||||
{
|
||||
return 'mauticSocial:monitoring';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNetworkTypes(): array
|
||||
{
|
||||
$types = [];
|
||||
foreach ($this->networkTypes as $type => $data) {
|
||||
$types[$type] = $data['label'];
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFormByType($type)
|
||||
{
|
||||
return array_key_exists($type, $this->networkTypes) ? $this->networkTypes[$type]['form'] : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Model;
|
||||
|
||||
use Mautic\CoreBundle\Model\AbstractCommonModel;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\PostCount;
|
||||
|
||||
/**
|
||||
* @extends AbstractCommonModel<PostCount>
|
||||
*/
|
||||
class PostCountModel extends AbstractCommonModel
|
||||
{
|
||||
/**
|
||||
* Get a specific entity or generate a new one if id is empty.
|
||||
*/
|
||||
public function getEntity($id = null): ?PostCount
|
||||
{
|
||||
if (null !== $id) {
|
||||
$repo = $this->getRepository();
|
||||
if (method_exists($repo, 'getEntity')) {
|
||||
return $repo->getEntity($id);
|
||||
}
|
||||
|
||||
return $repo->find($id);
|
||||
}
|
||||
|
||||
return new PostCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this model's repository.
|
||||
*
|
||||
* @return \MauticPlugin\MauticSocialBundle\Entity\PostCountRepository
|
||||
*/
|
||||
public function getRepository()
|
||||
{
|
||||
return $this->em->getRepository(PostCount::class);
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates a monitor record's post count on a daily basis
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function updatePostCount($monitor, \DateTime $postDate): bool
|
||||
{
|
||||
// query the db for posts on this date
|
||||
$q = $this->getRepository()->createQueryBuilder($this->getRepository()->getTableAlias());
|
||||
$expr = $q->expr()->eq($this->getRepository()->getTableAlias().'.postDate', ':date');
|
||||
|
||||
$q->setParameter('date', $postDate, 'date');
|
||||
$q->where($expr);
|
||||
$args['qb'] = $q;
|
||||
|
||||
// ignore paginator so we can use the array later
|
||||
$args['ignore_paginator'] = true;
|
||||
|
||||
/** @var \MauticPlugin\MauticSocialBundle\Entity\PostCountRepository $postCountsRepository */
|
||||
$postCountsRepository = $this->getRepository();
|
||||
|
||||
// get any existing records
|
||||
$postCounts = $postCountsRepository->getEntities($args);
|
||||
|
||||
// if there isn't anything then create it
|
||||
if (!count($postCounts)) {
|
||||
/** @var PostCount $postCount */
|
||||
$postCount = $this->getEntity();
|
||||
$postCount->setMonitor($monitor);
|
||||
$postCount->setPostDate($postDate); // $postDate->format('m-d-Y')
|
||||
} else {
|
||||
// use the first record to increment it.
|
||||
$postCount = $this->getEntity($postCounts[0]->getId());
|
||||
}
|
||||
|
||||
// increment
|
||||
$postCount->setPostCount($postCount->getPostCount() + 1);
|
||||
|
||||
// now save it
|
||||
$postCountsRepository->saveEntity($postCount);
|
||||
|
||||
// nothing went wrong so return true here
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Model;
|
||||
|
||||
use Mautic\CoreBundle\Model\AjaxLookupModelInterface;
|
||||
use Mautic\CoreBundle\Model\FormModel;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\Tweet;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\TweetRepository;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\TweetStat;
|
||||
use MauticPlugin\MauticSocialBundle\Entity\TweetStatRepository;
|
||||
use MauticPlugin\MauticSocialBundle\Event as Events;
|
||||
use MauticPlugin\MauticSocialBundle\Form\Type\TweetType;
|
||||
use MauticPlugin\MauticSocialBundle\SocialEvents;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* @extends FormModel<Tweet>
|
||||
*
|
||||
* @implements AjaxLookupModelInterface<Tweet>
|
||||
*/
|
||||
class TweetModel extends FormModel implements AjaxLookupModelInterface
|
||||
{
|
||||
/**
|
||||
* @param string $filter
|
||||
* @param int $limit
|
||||
* @param int $start
|
||||
* @param array $options
|
||||
*/
|
||||
public function getLookupResults($type, $filter = '', $limit = 10, $start = 0, $options = []): array
|
||||
{
|
||||
$results = [];
|
||||
|
||||
switch ($type) {
|
||||
case 'social.tweet':
|
||||
case 'tweet':
|
||||
if (isset($filter['tweet_text'])) {
|
||||
// This tweet was created as the campaign action param and these params are not the filter. Clear the filter.
|
||||
$filter = '';
|
||||
}
|
||||
|
||||
$tweetRepo = $this->getRepository();
|
||||
$tweetRepo->setCurrentUser($this->userHelper->getUser());
|
||||
$entities = $tweetRepo->getTweetList(
|
||||
$filter,
|
||||
$limit,
|
||||
$start,
|
||||
$this->security->isGranted($this->getPermissionBase().':viewother')
|
||||
);
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$results[$entity['language']][$entity['id']] = $entity['name'];
|
||||
}
|
||||
|
||||
// sort by language
|
||||
ksort($results);
|
||||
|
||||
unset($entities);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create/update Tweet Stat and update sent count for Tweet.
|
||||
*
|
||||
* @param string $source
|
||||
* @param int $sourceId
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function registerSend(Tweet $tweet, Lead $lead, array $sendResponse, $source = null, $sourceId = null)
|
||||
{
|
||||
$statRepo = $this->getStatRepository();
|
||||
|
||||
// Update failed tweet
|
||||
$stat = $statRepo->findOneBy(
|
||||
[
|
||||
'lead' => $lead->getId(),
|
||||
'tweet' => $tweet->getId(),
|
||||
'source' => $source,
|
||||
'sourceId' => $sourceId,
|
||||
'isFailed' => true,
|
||||
]
|
||||
);
|
||||
|
||||
if (!$stat) {
|
||||
// Create new entity
|
||||
$stat = new TweetStat();
|
||||
} else {
|
||||
// Or add 1 to the retry count
|
||||
$stat->retryCountUp();
|
||||
}
|
||||
|
||||
$stat->setTweet($tweet);
|
||||
$stat->setLead($lead);
|
||||
$stat->setResponseDetails($sendResponse);
|
||||
$stat->setSource($source);
|
||||
$stat->setSourceId($sourceId);
|
||||
|
||||
$fields = $lead->getProfileFields();
|
||||
if (!empty($fields['twitter'])) {
|
||||
$stat->setHandle($fields['twitter']);
|
||||
}
|
||||
|
||||
if (!empty($sendResponse['id_str'])) {
|
||||
$stat->setDateSent(new \DateTime());
|
||||
$stat->setTwitterTweetId($sendResponse['id_str']);
|
||||
|
||||
$tweet->sentCountUp();
|
||||
$this->saveEntity($tweet);
|
||||
} else {
|
||||
$stat->setIsFailed(true);
|
||||
}
|
||||
|
||||
$statRepo->saveEntity($stat);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Tweet $entity
|
||||
* @param array<mixed> $options
|
||||
*/
|
||||
public function createForm($entity, FormFactoryInterface $formFactory, $action = null, $options = []): \Symfony\Component\Form\FormInterface
|
||||
{
|
||||
if (!$entity instanceof Tweet) {
|
||||
throw new MethodNotAllowedHttpException(['Tweet']);
|
||||
}
|
||||
|
||||
if (!empty($action)) {
|
||||
$options['action'] = $action;
|
||||
}
|
||||
|
||||
return $formFactory->create(TweetType::class, $entity, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific entity or generate a new one if id is empty.
|
||||
*
|
||||
* @param int $id
|
||||
*/
|
||||
public function getEntity($id = null): ?Tweet
|
||||
{
|
||||
if (null === $id) {
|
||||
return new Tweet();
|
||||
}
|
||||
|
||||
return parent::getEntity($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MethodNotAllowedHttpException
|
||||
*/
|
||||
protected function dispatchEvent($action, &$entity, $isNew = false, ?Event $event = null): ?Event
|
||||
{
|
||||
if (!$entity instanceof Tweet) {
|
||||
throw new MethodNotAllowedHttpException(['Tweet']);
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'pre_save':
|
||||
$name = SocialEvents::TWEET_PRE_SAVE;
|
||||
break;
|
||||
case 'post_save':
|
||||
$name = SocialEvents::TWEET_POST_SAVE;
|
||||
break;
|
||||
case 'pre_delete':
|
||||
$name = SocialEvents::TWEET_PRE_DELETE;
|
||||
break;
|
||||
case 'post_delete':
|
||||
$name = SocialEvents::TWEET_POST_DELETE;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->dispatcher->hasListeners($name)) {
|
||||
if (empty($event)) {
|
||||
$event = new Events\SocialEvent($entity, $isNew);
|
||||
}
|
||||
|
||||
$this->dispatcher->dispatch($event, $name);
|
||||
|
||||
return $event;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function getRepository(): TweetRepository
|
||||
{
|
||||
return $this->em->getRepository(Tweet::class);
|
||||
}
|
||||
|
||||
public function getStatRepository(): TweetStatRepository
|
||||
{
|
||||
return $this->em->getRepository(TweetStat::class);
|
||||
}
|
||||
|
||||
public function getPermissionBase(): string
|
||||
{
|
||||
return 'mauticSocial:tweets';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# Mautic bundle for Social plugin
|
||||
|
||||
## This plugin is managed centrally in https://github.com/mautic/mautic/blob/head/plugins/MauticSocialBundle and this is a read-only mirror repository.
|
||||
|
||||
**📣 Please make PRs and issues against Mautic Core, not here!**
|
||||
@@ -0,0 +1,25 @@
|
||||
<div id="compose-tweet">
|
||||
<div id="character-count" class="text-right small">
|
||||
{{ 'mautic.social.twitter.tweet.count'|trans }}
|
||||
<span></span>
|
||||
</div>
|
||||
{{ form_row(form.tweet_text) }}
|
||||
|
||||
<div class="row">
|
||||
<div id="handle" class="col-md-2">
|
||||
<label class="control-label">
|
||||
{{ 'mautic.social.twitter.tweet.handle'|trans }}
|
||||
</label>
|
||||
{{ form_row(form.handle) }}
|
||||
</div>
|
||||
|
||||
<div id="asset" class="col-md-5">
|
||||
{{ form_row(form.asset_link) }}
|
||||
</div>
|
||||
|
||||
<div id="page" class="col-md-5">
|
||||
{{ form_row(form.page_link) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ includeScript('plugins/MauticSocialBundle/Assets/js/social.js', 'composeSocialWatcher', 'composeSocialWatcher') }}
|
||||
@@ -0,0 +1,16 @@
|
||||
{% block _config_social_config_widget %}
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{{ 'mautic.config.tab.social_config'|trans }}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% for f in form.children %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{{ form_row(f) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-12">
|
||||
{{ form_row(form.custom) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-12">
|
||||
{{ form_row(form.handle) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-12">
|
||||
{{ form_row(form.checknames) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-12">
|
||||
{{ form_row(form.hashtag) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-12">
|
||||
{{ form_row(form.checknames) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
<div id="compose-tweet">
|
||||
<div id="character-count" class="text-right small">
|
||||
{{ 'mautic.social.twitter.tweet.count'|trans }}
|
||||
<span></span>
|
||||
</div>
|
||||
{{ form_row(form.tweet_text) }}
|
||||
|
||||
<div class="row">
|
||||
<div id="handle" class="col-md-2">
|
||||
<label class="control-label">
|
||||
{{ 'mautic.social.twitter.tweet.handle'|trans }}
|
||||
</label>
|
||||
{{ form_row(form.handle) }}
|
||||
</div>
|
||||
|
||||
<div id="asset" class="col-md-5">
|
||||
{{ form_row(form.asset_link) }}
|
||||
</div>
|
||||
|
||||
<div id="page" class="col-md-5">
|
||||
{{ form_row(form.page_link) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ includeScript('plugins/MauticSocialBundle/Assets/js/social.js', 'composeSocialWatcher', 'composeSocialWatcher') }}
|
||||
@@ -0,0 +1,8 @@
|
||||
{% import '@MauticSocial//macros.html.twig' as social %}
|
||||
<div class="media">
|
||||
{{ social.profileImage(profile) }}
|
||||
<div class="media-body">
|
||||
<h4 class="media-heading">{{ profile.name }}</h4>
|
||||
<p class="text-secondary"><a href="https://facebook.com/{{ profile.profileHandle }}" target="_blank">{{ profile.profileHandle }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="panel-body">
|
||||
{{ include('@MauticSocial/Integration/Facebook/Profile/profile.html.twig', {
|
||||
'lead': lead,
|
||||
'profile': details.profile,
|
||||
}) }}
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
{% set locale = app.request.locale %}
|
||||
{% set settings = field.properties|default([]) %}
|
||||
{% set layout = settings.layout|default('standard') %}
|
||||
{% set action = settings.action|default('like') %}
|
||||
{% set showFaces = settings.showFaces is not empty ? 'true' : 'false' %}
|
||||
{% set showShare = settings.showShare is not empty ? 'true' : 'false' %}
|
||||
{% set clientId = settings.keys.clientId|default('') %}
|
||||
|
||||
{# add FB's required OG tag #}
|
||||
<meta property="og:type" content="website" />
|
||||
<div class="fb-{% if 'share' == action %}share-button{% else %}like{% endif %} share-button facebook-share-button layout-{{ layout }} action-{{ action }}"
|
||||
data-{% if 'share' == action %}type{% else %}layout{% endif %}="{{ layout }}"
|
||||
{% if 'share' != action %}
|
||||
data-action="{{ action }}"
|
||||
data-show-faces="{{ showFaces }}"
|
||||
data-share="{{ showShare }}"
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="fb-root"></div>
|
||||
<script>(function(d, s, id) {
|
||||
var js, fjs = d.getElementsByTagName(s)[0];
|
||||
if (d.getElementById(id)) return;
|
||||
js = d.createElement(s); js.id = id;
|
||||
js.src = '//connect.facebook.net/{$locale}/sdk.js#xfbml=1&appId={{ clientId }}&version=v2.0';
|
||||
fjs.parentNode.insertBefore(js, fjs);
|
||||
}(document, 'script', 'facebook-jssdk'));</script>
|
||||
@@ -0,0 +1,17 @@
|
||||
{% import '@MauticSocial//macros.html.twig' as social %}
|
||||
{% set tableFields = ['gender', 'homeCity', 'bio'] %}
|
||||
<div class="media">
|
||||
{{ social.profileImage(profile) }}
|
||||
<div class="media-body">
|
||||
<h4 class="media-heading">{{ profile.firstName }} {{ profile.lastName }}</h4>
|
||||
<p class="text-secondary"><a href="https://foursquare.com/user/{{ profile.profileHandle }}" target="_blank">{{ profile.profileHandle }}</a></p>
|
||||
<table class="table table-condensed">
|
||||
{% for t in tableFields|filter(v => profile[v] is defined and profile[v] is not empty) %}
|
||||
<tr>
|
||||
<td>{{ translatorConditional('mautic.integration.common.' ~ t, 'mautic.integration.Foursquare.' ~ t) }}</td>
|
||||
<td>{{ profile[t] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
<ul class="list-group">
|
||||
{% for item in activity %}
|
||||
<li class="bdr-w-0 list-group-item">
|
||||
<h4 class="mt-10 mb-10 pb-10"><i class="ri-check-line-circle-o"></i> {{ item.tipText }}</h4>
|
||||
<p class="alert alert-warning">
|
||||
{{ item.venueName }}<br />
|
||||
{% for l in item.venueLocation %}
|
||||
{{ l }}<br />
|
||||
{% endfor %}
|
||||
</p>
|
||||
<p class="text-secondary"><i class="ri-time-line"></i> {{ dateToFull(item.createdAt, 'UTC', 'U') }}</p>
|
||||
{% if not loop.first %}<hr />{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
@@ -0,0 +1,29 @@
|
||||
<div class="panel-toolbar np">
|
||||
<ul class="nav nav-tabs nav-tabs-contained">
|
||||
<li class="active">
|
||||
<a href="#FoursquareProfile" role="tab" data-toggle="tab">
|
||||
{{ 'mautic.lead.lead.social.profile'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#FoursquareTips" role="tab" data-toggle="tab">
|
||||
{{ 'mautic.lead.lead.social.foursquare.tips'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="np panel-body tab-content">
|
||||
<div class="pa-20 tab-pane active" id="FoursquareProfile">
|
||||
{{ include('@MauticSocial/Integration/Foursquare/Profile/profile.html.twig', {
|
||||
'lead': lead,
|
||||
'profile': details.profile,
|
||||
}) }}
|
||||
</div>
|
||||
<div class="tab-pane" id="FoursquareTips">
|
||||
{{ include('@MauticSocial/Integration/Foursquare/Profile/tips.html.twig', {
|
||||
'lead': lead,
|
||||
'activity': details.activity.tips|default([]),
|
||||
}) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="container-fluid">
|
||||
<div class="img-grid row">
|
||||
{% for a in activity %}
|
||||
{% if loop.index0 is divisible by(3) %}</div><div class="row">{% endif %}
|
||||
<div class="col-xs-4 social-image">
|
||||
<a href="javascript: void(0);" onclick="Mautic.showSocialMediaImageModal('{{ a.url }}');">
|
||||
<img class="img-responsive img-thumbnail" src="{{ a.url }}" />
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
{% import '@MauticSocial//macros.html.twig' as social %}
|
||||
<div class="media">
|
||||
{{ social.profileImage(profile) }}
|
||||
<div class="media-body">
|
||||
<h4 class="media-title">{{ profile.full_name }}</h4>
|
||||
<p><a href="https://instagram.com/{{ profile.profileHandle }}" target="_blank">{{ profile.profileHandle }}</a></p>
|
||||
<p class="text-secondary">
|
||||
{{ profile.website }}
|
||||
</p>
|
||||
<p class="text-secondary">
|
||||
{{ profile.bio }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
<div class="panel-toolbar np">
|
||||
<ul class="nav nav-tabs nav-tabs-contained">
|
||||
<li class="active">
|
||||
<a href="#InstagramProfile" role="tab" data-toggle="tab">
|
||||
{{ 'mautic.lead.lead.social.profile'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#InstagramPhotos" role="tab" data-toggle="tab">
|
||||
{{ 'mautic.lead.lead.social.photos'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="np panel-body tab-content">
|
||||
<div class="pa-20 tab-pane active" id="InstagramProfile">
|
||||
{{ include('@MauticSocial/Integration/Instagram/Profile/profile.html.twig', {
|
||||
'profile': details.profile,
|
||||
}) }}
|
||||
</div>
|
||||
<div class="pa-20 tab-pane" id="InstagramPhotos">
|
||||
{{ include('@MauticSocial/Integration/Instagram/Profile/photos.html.twig', {
|
||||
'activity': details.activity.photos|default([]),
|
||||
}) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
{% import '@MauticSocial//macros.html.twig' as social %}
|
||||
<div class="media">
|
||||
{{ social.profileImage(profile) }}
|
||||
<div class="media-body">
|
||||
<h4 class="media-title">{{ profile.formattedName }}</h4>
|
||||
<p><a href="https://www.linkedin.com/{{ profile.profileHandle }}" target="_blank">{{ profile.profileHandle }}</a></p>
|
||||
<p class="text-secondary">
|
||||
{{ profile.headline }}
|
||||
</p>
|
||||
<p class="text-secondary">
|
||||
{{ profile.summary }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
<div class="panel-toolbar np">
|
||||
<ul class="nav nav-tabs nav-tabs-contained">
|
||||
<li class="active">
|
||||
<a href="#LinkedInProfile" role="tab" data-toggle="tab">
|
||||
{{ 'mautic.lead.lead.social.profile'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#LinkedInPhotos" role="tab" data-toggle="tab">
|
||||
{{ 'mautic.lead.lead.social.photos'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="np panel-body tab-content">
|
||||
<div class="pa-20 tab-pane active" id="LinkedInProfile">
|
||||
{{ include('@MauticSocial/Integration/LinkedIn/Profile/profile.html.twig', {
|
||||
'profile': details.profile,
|
||||
}) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
{% set locale = app.request.locale %}
|
||||
{% set counter = settings.counter|default('none') %}
|
||||
<div class="share-button linkedin-share-button layout-{{ counter }}">
|
||||
<script type="IN/Share" {% if 'none' != counter %}data-counter="{{ settings.counter }}"{% endif %}></script>
|
||||
</div>
|
||||
<script src="//platform.linkedin.com/in.js" type="text/javascript">
|
||||
lang: {{ locale }}
|
||||
</script>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="container-fluid">
|
||||
<div class="img-grid row">
|
||||
{% for a in activity %}
|
||||
{% if not loop.first and loop.index0 is divisible by(3) %}</div><div class="row">{% endif %}
|
||||
<div class="col-xs-4 social-image">
|
||||
<a href="javascript: void(0);" onclick="Mautic.showSocialMediaImageModal('{{ a.url }}');"><img class="img-responsive img-thumbnail" src="{{ a.url }}" /></a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
{% import '@MauticSocial//macros.html.twig' as social %}
|
||||
<div class="media">
|
||||
{{ social.profileImage(profile) }}
|
||||
<div class="media-body">
|
||||
<h4 class="media-title">{{ profile.name }}</h4>
|
||||
<p><a href="https://twitter.com/{{ profile.profileHandle }}" target="_blank">{{ profile.profileHandle }}</a></p>
|
||||
<p class="text-secondary">
|
||||
{{ profile.location }}
|
||||
</p>
|
||||
<p class="text-secondary">
|
||||
{{ profile.description|purify }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
<ul class="twitter-tags tag-cloud">
|
||||
{% for tag, t in activity %}
|
||||
{% if (t.count / 10) < 1 %}
|
||||
{% set fontSize = (t.count / 10) + 1 %}
|
||||
{% elseif (t.count / 10) > 2 %}
|
||||
{% set fontSize = 2 %}
|
||||
{% else %}
|
||||
{% set fontSize = t.count / 10 %}
|
||||
{% endif %}
|
||||
<li style="font-size: {{ fontSize }}em"><a href="{{ t.url }}" target="_new">{{ tag }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
@@ -0,0 +1,10 @@
|
||||
<ul class="list-group">
|
||||
{% for item in activity %}
|
||||
{% set border = 'bdr-b bdr-l-wdh-0 bdr-r-wdh-0' %}
|
||||
{% if loop.first or loop.last %}{% set border = 'bdr-w-0' %}{% endif %}
|
||||
<li class="{{ border }} pa-15 list-group-item">
|
||||
<p>{{ item.tweet }}</p>
|
||||
<span class="text-secondary"><i class="ri-time-line"></i> {{ dateToFull(item.published) }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
@@ -0,0 +1,50 @@
|
||||
<div class="panel-toolbar np">
|
||||
<ul class="nav nav-tabs nav-tabs-contained">
|
||||
<li class="active">
|
||||
<a href="#TwitterProfile" role="tab" data-toggle="tab">
|
||||
{{ 'mautic.lead.lead.social.profile'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#TwitterTweets" role="tab" data-toggle="tab">
|
||||
{{ 'mautic.lead.lead.social.twitter.tweets'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#TwitterPhotos" role="tab" data-toggle="tab">
|
||||
{{ 'mautic.lead.lead.social.photos'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#TwitterTags" role="tab" data-toggle="tab">
|
||||
{{ 'mautic.lead.lead.social.tags'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="np panel-body tab-content">
|
||||
<div class="pa-20 tab-pane active" id="TwitterProfile">
|
||||
{{ include('@MauticSocial/Integration/Twitter/Profile/profile.html.twig', {
|
||||
'lead': lead,
|
||||
'profile': details.profile,
|
||||
}) }}
|
||||
</div>
|
||||
<div class="tab-pane" id="TwitterTweets">
|
||||
{{ include('@MauticSocial/Integration/Twitter/Profile/tweets.html.twig', {
|
||||
'lead': lead,
|
||||
'activity': details.activity.tweets|default([]),
|
||||
}) }}
|
||||
</div>
|
||||
<div class="pa-20 tab-pane" id="TwitterPhotos">
|
||||
{{ include('@MauticSocial/Integration/Twitter/Profile/photos.html.twig', {
|
||||
'lead': lead,
|
||||
'activity': details.activity.photos|default([]),
|
||||
}) }}
|
||||
</div>
|
||||
<div class="pa-20 tab-pane" id="TwitterTags">
|
||||
{{ include('@MauticSocial/Integration/Twitter/Profile/tags.html.twig', {
|
||||
'lead': lead,
|
||||
'activity': details.activity.tags|default([]),
|
||||
}) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="share-button twitter-share-button layout-{{ settings.count|default('0') }}">
|
||||
<a href="https://twitter.com/share"
|
||||
{% if settings.text is defined and settings.text is not empty %}data-text="{{ settings.text }}"{% endif %}
|
||||
{% if settings.via is defined and settings.via is not empty %}data-via="{{ settings.via }}"{% endif %}
|
||||
{% if settings.related is defined and settings.related is not empty %}data-related="{{ settings.related }}"{% endif %}
|
||||
{% if settings.hashtags is defined and settings.hashtags is not empty %}data-hashtags="{{ settings.hashtags }}"{% endif %}
|
||||
{% if settings.size is defined and settings.size is not empty %}data-size="{{ settings.size }}"{% endif %}
|
||||
{% if settings.count is defined and settings.count is not empty %}data-count="{{ settings.count }}"{% endif %}
|
||||
class="twitter-share-button share-button">{{ 'mautic.integration.Twitter.share.tweet'|trans }}</a>
|
||||
</div>
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
|
||||
@@ -0,0 +1,169 @@
|
||||
{#
|
||||
Varables
|
||||
- field
|
||||
- formName (optional, string)
|
||||
- fieldPage
|
||||
- contactFields
|
||||
- companyFields
|
||||
- inBuilder
|
||||
- fields
|
||||
- inForm (optional, bool)
|
||||
- required (optional, bool)
|
||||
#}
|
||||
{% set defaultInputClass = 'button' %}
|
||||
{% set containerType = 'div-wrapper' %}
|
||||
|
||||
{# start: field_helper #}
|
||||
{% set defaultInputFormClass = defaultInputFormClass|default('') %}
|
||||
{% set defaultLabelClass = defaultLabelClass|default('label') %}
|
||||
{% set formName = formName|default('') %}
|
||||
{% set defaultInputClass = 'mauticform-' ~ defaultInputClass %}
|
||||
{% set defaultLabelClass = 'mauticform-' ~ defaultLabelClass %}
|
||||
{% set containerClass = containerClass|default(containerType) %}
|
||||
{% set order = field.order|default(0) %}
|
||||
{% set validationMessage = '' %}
|
||||
|
||||
{% set inputAttributes = htmlAttributesStringToArray(field.inputAttributes|default('')) %}
|
||||
{% set labelAttributes = htmlAttributesStringToArray(field.labelAttributes|default('')) %}
|
||||
{% set containerAttributes = htmlAttributesStringToArray(field.containerAttributes|default('')) %}
|
||||
|
||||
{% if ignoreName is not defined or (ignoreName is defined and ignoreName is empty) %}
|
||||
{% set inputName = 'mauticform[' ~ field.alias ~ ']' %}
|
||||
{% if field.properties.multiple is defined %}
|
||||
{% set inputName = inputName ~ '[]' %}
|
||||
{% endif %}
|
||||
{% set inputAttributes = inputAttributes|merge({
|
||||
'name': inputName,
|
||||
}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if field.type not in ['checkboxgrp', 'radiogrp', 'textarea'] %}
|
||||
{% set inputAttributes = inputAttributes|merge({
|
||||
'value': field.defaultValue|default(''),
|
||||
}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if ignoreId is not defined or (ignoreId is defined and ignoreId is empty) %}
|
||||
{% set inputAttributes = inputAttributes|merge({
|
||||
'id': 'mauticform_input' ~ formName ~ '_' ~ field.alias,
|
||||
}) %}
|
||||
{% set labelAttributes = labelAttributes|merge({
|
||||
'id': 'mauticform_label' ~ formName ~ '_' ~ field.alias,
|
||||
'for': 'mauticform_input' ~ formName ~ '_' ~ field.alias,
|
||||
}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if field.properties.placeholder is defined and field.properties.placeholder is not empty %}
|
||||
{% set inputAttributes = inputAttributes|merge({
|
||||
'placeholder': field.properties.placeholder,
|
||||
}) %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{# Label and input #}
|
||||
{% if inForm is defined and (true == inForm or inForm is not empty) %}
|
||||
{% if field.type in ['button', 'pagebreak'] %}
|
||||
{% set defaultInputFormClass = defaultInputFormClass ~ ' btn btn-ghost' %}
|
||||
{% endif %}
|
||||
|
||||
{% set labelAttributes = labelAttributes|merge({
|
||||
'class': labelAttributes.class|default([])|merge([defaultLabelClass]),
|
||||
}) %}
|
||||
{% set inputAttributes = inputAttributes|merge({
|
||||
'disabled': 'disabled',
|
||||
'class': inputAttributes.class|default([])|merge([defaultInputClass, defaultInputFormClass]),
|
||||
}) %}
|
||||
{% else %}
|
||||
{% set labelAttributes = labelAttributes|merge({
|
||||
'class': labelAttributes.class|default([])|merge([defaultLabelClass]),
|
||||
}) %}
|
||||
{% set inputAttributes = inputAttributes|merge({
|
||||
'class': inputAttributes.class|default([])|merge([defaultInputClass]),
|
||||
}) %}
|
||||
{% endif %}
|
||||
|
||||
{# Container #}
|
||||
{% set containerAttributes = containerAttributes|merge({
|
||||
'id': 'mauticform' ~ formName|default('') ~ '_' ~ id,
|
||||
'class': containerAttributes.class|default([])|merge([
|
||||
'mauticform-row',
|
||||
'mauticform-' ~ containerClass,
|
||||
'mauticform-field-' ~ order,
|
||||
]),
|
||||
}) %}
|
||||
{% if field.parent and fields[field.parent] is defined %}
|
||||
{% set values = field.conditions.values|join('|') %}
|
||||
|
||||
{% if field.conditions.any is not empty and 'notIn' != field.conditions.expr %}
|
||||
{% set values = '*' %}
|
||||
{% endif %}
|
||||
|
||||
{% set containerAttributes = containerAttributes|merge({
|
||||
'data-mautic-form-show-on': fields[field.parent].alias ~ ':' ~ values,
|
||||
'data-mautic-form-expr': field.conditions.expr,
|
||||
'class': containerAttributes.class|merge([
|
||||
'mauticform-field-hidden',
|
||||
]),
|
||||
}) %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{# Field is required #}
|
||||
{% if field.isRequired is defined and field.isRequired %}
|
||||
{% set required = true %}
|
||||
{% set validationMessage = field.validationMessage %}
|
||||
{% if validationMessage is empty %}
|
||||
{% set validationMessage = 'mautic.form.field.generic.required'|trans([], 'validators') %}
|
||||
{% endif %}
|
||||
{% set containerAttributes = containerAttributes|merge({
|
||||
'class': containerAttributes.class|default([])|merge([
|
||||
'mauticform-required',
|
||||
]),
|
||||
'data-validate': field.alias,
|
||||
'data-validation-type': field.type,
|
||||
}) %}
|
||||
{% if field.properties.multiple is defined and field.properties.multiple is not empty %}
|
||||
{% set containerAttributes = containerAttributes|merge({
|
||||
'data-validate-multiple': 'true',
|
||||
}) %}
|
||||
{% endif %}
|
||||
{% elseif required is defined and true == required %}
|
||||
{# Forced to be required #}
|
||||
{% set containerAttributes = containerAttributes|merge({
|
||||
'class': containerAttributes.class|default([])|merge([
|
||||
'mauticform-required',
|
||||
]),
|
||||
}) %}
|
||||
{% endif %}
|
||||
{# end: field_helper #}
|
||||
|
||||
{% set action = app.request.get('objectAction') %}
|
||||
{% set settings = field.properties %}
|
||||
|
||||
{% set integrations = [] %}
|
||||
{% if settings.integrations is defined and settings.integrations is not empty %}
|
||||
{% set integrations = settings.integrations[0:-1]|split(',') %}
|
||||
{% endif %}
|
||||
|
||||
{% set formName = formName|replace({'_': ''})|default('mauticform') %}
|
||||
|
||||
<script src="{{ url('mautic_social_js_generate', {'formName': formName}) }}" type="text/javascript" charset="utf-8" async="async"></script>
|
||||
<div {% for attrName, attrValue in containerAttributes %}{{ attrName }}="{% if attrValue is iterable %}{{ attrValue|join(' ') }}{% else %}{{ attrValue }}{% endif %}"{% endfor %}>
|
||||
{% if inForm is defined and (true == inForm or inForm is not empty) %}
|
||||
{{ include('@MauticForm/Builder/_actions.html.twig', {
|
||||
'deleted': false,
|
||||
'id': id,
|
||||
'formId': formId,
|
||||
'formName': formName,
|
||||
'disallowDelete': false,
|
||||
}, with_context=false) }}
|
||||
{% endif %}
|
||||
|
||||
{% if field.showLabel %}<label {% for attrName, attrValue in labelAttributes %}{{ attrName }}="{% if attrValue is iterable %}{{ attrValue|join(' ') }}{% else %}{{ attrValue }}{% endif %}"{% endfor %}>{{ field.label }}</label>{% endif %}
|
||||
|
||||
{% for integration in integrations %}
|
||||
{% if settings.buttonImageUrl is defined and settings.buttonImageUrl is not empty and integration is not empty %}
|
||||
<a href="#" onclick="openOAuthWindow('{{ settings['authUrl_' ~ integration ~ ''] }}')"><img src="{{ settings['buttonImageUrl'] }}btn_{{ integration }}.png"></a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -0,0 +1,81 @@
|
||||
{% if items|length > 0 %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover monitoring-list" id="monitoringTable">
|
||||
<thead>
|
||||
<tr>
|
||||
{{ include('@MauticCore/Helper/tableheader.html.twig', {
|
||||
'checkall': 'true',
|
||||
'target': '#monitoringTable',
|
||||
}) }}
|
||||
|
||||
{{ include('@MauticCore/Helper/tableheader.html.twig', {
|
||||
'sessionVar': 'social.monitoring',
|
||||
'orderBy': 'e.title',
|
||||
'text': 'mautic.core.title',
|
||||
'class': 'col-monitoring-title',
|
||||
'default': true,
|
||||
}) }}
|
||||
|
||||
{{ include('@MauticCore/Helper/tableheader.html.twig', {
|
||||
'sessionVar': 'social.monitoring',
|
||||
'orderBy': 'e.id',
|
||||
'text': 'mautic.core.id',
|
||||
'class': 'visible-md visible-lg col-asset-id',
|
||||
}) }}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for k, item in items %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ include('@MauticCore/Helper/list_actions.html.twig', {
|
||||
'item': item,
|
||||
'templateButtons': {
|
||||
'edit': securityIsGranted('mauticSocial:monitoring:edit'),
|
||||
'delete': securityIsGranted('mauticSocial:monitoring:delete'),
|
||||
},
|
||||
'routeBase': 'social',
|
||||
'langVar': 'mautic.social.monitoring',
|
||||
'nameGetter': 'getTitle',
|
||||
}) }}
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
{{ include('@MauticCore/Helper/publishstatus_icon.html.twig', {
|
||||
'item': item,
|
||||
'model': 'social.monitoring',
|
||||
}) }}
|
||||
<a href="{{ path('mautic_social_action', {'objectAction': 'view', 'objectId': item.id}) }}" data-toggle="ajax">
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{ include('@MauticCore/Helper/description--inline.html.twig', {
|
||||
'description': item.description
|
||||
}) }}
|
||||
</td>
|
||||
<td class="visible-md visible-lg">{{ item.id }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
{{ include('@MauticCore/Helper/pagination.html.twig', {
|
||||
'totalItems': items|length,
|
||||
'page': page,
|
||||
'limit': limit,
|
||||
'menuLinkId': 'mautic_campaign_index',
|
||||
'baseUrl': path('mautic_social_index'),
|
||||
'sessionVar': 'social.monitoring',
|
||||
'routeBase': 'social',
|
||||
}) }}
|
||||
</div>
|
||||
{% else %}
|
||||
{{ include('@MauticCore/Helper/noresults.html.twig', {'tip': 'mautic.mautic.social.monitoring.noresults.tip'}) }}
|
||||
{% endif %}
|
||||
|
||||
{{ include('@MauticCore/Helper/modal.html.twig', {
|
||||
'id': 'MonitoringPreviewModal',
|
||||
'header': false,
|
||||
}) }}
|
||||
@@ -0,0 +1,128 @@
|
||||
{#
|
||||
Variables
|
||||
- isEmbeded
|
||||
- activeMonitoring
|
||||
#}
|
||||
{% extends isEmbedded ? '@MauticCore/Default/raw_output.html.twig' : '@MauticCore/Default/content.html.twig' %}
|
||||
|
||||
{% block mauticContent 'monitoring' %}
|
||||
|
||||
{% block headerTitle activeMonitoring.title %}
|
||||
|
||||
{% block preHeader %}
|
||||
{{- include('@MauticCore/Helper/page_actions.html.twig',
|
||||
{
|
||||
'item' : activeMonitoring,
|
||||
'templateButtons' : {
|
||||
'close' : securityIsGranted('mauticSocial:monitoring:view'),
|
||||
},
|
||||
'routeBase' : 'social',
|
||||
'langVar' : 'monitoring',
|
||||
'targetLabel' : 'mautic.social.monitoring'|trans
|
||||
}
|
||||
) -}}
|
||||
{% endblock %}
|
||||
|
||||
{% block actions %}
|
||||
{{- include('@MauticCore/Helper/page_actions.html.twig', {
|
||||
'item': activeMonitoring,
|
||||
'templateButtons': {
|
||||
'edit': securityIsGranted('mauticSocial:monitoring:edit'),
|
||||
'delete': securityIsGranted('mauticSocial:monitoring:delete'),
|
||||
},
|
||||
'routeBase': 'social',
|
||||
'langVar': 'monitoring',
|
||||
'nameGetter': 'getTitle',
|
||||
}) -}}
|
||||
{% endblock %}
|
||||
|
||||
{% block publishStatus %}
|
||||
{{ include('@MauticCore/Helper/publishstatus_badge.html.twig', {'entity': activeMonitoring}) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ includeScript('plugins/MauticSocialBundle/Assets/js/social.js') }}
|
||||
<!-- start: box layout -->
|
||||
<div class="box-layout">
|
||||
<!-- left section -->
|
||||
<div class="col-md-9 height-auto">
|
||||
<div>
|
||||
<!-- monitoring detail header -->
|
||||
{% include '@MauticCore/Helper/description--expanded.html.twig' with {'description': activeMonitoring.description} %}
|
||||
<!--/ monitoring detail header -->
|
||||
<!-- monitoring detail collapseable -->
|
||||
<div class="collapse pr-md pl-md" id="asset-details">
|
||||
<div class="pr-md pl-md pb-md">
|
||||
<div class="panel shd-none mb-0">
|
||||
<table class="table table-hover mb-0">
|
||||
<tbody>
|
||||
{{ include('@MauticCore/Helper/details.html.twig', {'entity': activeMonitoring}) }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--/ monitoring collapseable -->
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- stats -->
|
||||
<div class="pa-md">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="panel">
|
||||
<div class="panel-body box-layout">
|
||||
<div class="col-md-3 va-m">
|
||||
<h5 class="text-white dark-md fw-sb mb-xs">
|
||||
<span class="ri-twitter-x-line"></span>
|
||||
{{ ('mautic.social.monitoring.' ~ activeMonitoring.networkType ~ '.popularity')|trans }}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-md-9 va-m">
|
||||
{{ include('@MauticCore/Helper/graph_dateselect.html.twig', {'dateRangeForm': dateRangeForm, 'class': 'pull-right'}) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex fd-column pt-0 pl-15 pb-15 pr-15 min-h-256">
|
||||
{{ include('@MauticCore/Helper/chart.html.twig', {'chartData': leadStats, 'chartType': 'line', 'chartHeight': 300}) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--/ stats -->
|
||||
|
||||
{{ customContent('details.stats.graph.below', _context) }}
|
||||
|
||||
<!-- tabs controls -->
|
||||
<ul class="nav nav-tabs nav-tabs-contained">
|
||||
<li class="active">
|
||||
<a href="#leads-container" role="tab" data-toggle="tab">
|
||||
{{ 'mautic.lead.leads'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!--/ tabs controls -->
|
||||
</div>
|
||||
|
||||
<!-- start: tab-content -->
|
||||
<div class="tab-content pa-md">
|
||||
<!-- #events-container -->
|
||||
<div class="tab-pane active fade in bdr-w-0 page-list" id="leads-container">
|
||||
{{ monitorLeads|raw }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!--/ left section -->
|
||||
|
||||
<!-- right section -->
|
||||
<div class="col-md-3 bdr-l height-auto">
|
||||
<!-- recent activity -->
|
||||
{{ include('@MauticCore/Helper/recentactivity.html.twig', {'logs': logs}) }}
|
||||
</div>
|
||||
<!--/ right section -->
|
||||
|
||||
<input id="itemId" type="hidden" value="{{ activeMonitoring.id }}" />
|
||||
</div>
|
||||
<!--/ end: box layout -->
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,68 @@
|
||||
{#
|
||||
Variables
|
||||
- entity
|
||||
- form
|
||||
#}
|
||||
{% extends '@MauticCore/Default/content.html.twig' %}
|
||||
|
||||
{% block mauticContent 'monitoring' %}
|
||||
|
||||
{% block headerTitle %}
|
||||
{% if entity.id %}
|
||||
{{ 'mautic.social.monitoring.menu.edit'|trans({'%name%': entity.title|trans}) }}
|
||||
{% else %}
|
||||
{{ 'mautic.social.monitoring.menu.new'|trans }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modal %}
|
||||
{{ include('@MauticCore/Helper/modal.html.twig', {
|
||||
'id': 'formComponentModal',
|
||||
'header': false,
|
||||
'footerButtons': true,
|
||||
}) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ includeScript('plugins/MauticSocialBundle/Assets/js/social.js') }}
|
||||
|
||||
{{ form_start(form) }}
|
||||
<!-- start: box layout -->
|
||||
<div class="box-layout">
|
||||
<!-- container -->
|
||||
<div class="col-md-9 height-auto bdr-r">
|
||||
<div class="pa-md">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="row">
|
||||
<div class="col-md-6">{{ form_row(form.title) }}</div>
|
||||
<div class="col-md-6">{{ form_row(form.networkType) }}</div>
|
||||
</div>
|
||||
<div id="properties-container">
|
||||
<div class="row">
|
||||
{% if form.properties is defined %}
|
||||
{% for child in form.properties %}
|
||||
<div class="col-md-6">
|
||||
{{ form_row(child) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{{ form_row(form.description) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 height-auto">
|
||||
<div class="pr-lg pl-lg pt-md pb-md">
|
||||
{{ form_row(form.isPublished) }}
|
||||
{{ form_row(form.publishUp) }}
|
||||
{{ form_row(form.publishDown) }}
|
||||
{{ form_row(form.category) }}
|
||||
{{ form_row(form.lists) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,65 @@
|
||||
{% set isIndex = 'index' == tmpl ? true : false %}
|
||||
{% set tmpl = 'list' %}
|
||||
{% extends isIndex ? '@MauticCore/Default/content.html.twig' : '@MauticCore/Default/raw_output.html.twig' %}
|
||||
|
||||
{% block mauticContent 'monitoring' %}
|
||||
|
||||
{% block headerTitle %}{{ 'mautic.social.monitoring'|trans }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if isIndex %}
|
||||
<div id="page-list-wrapper" class="panel panel-default">
|
||||
{{ include('@MauticCore/Helper/list_toolbar.html.twig', {
|
||||
'searchValue': searchValue,
|
||||
'action': currentRoute,
|
||||
'page_actions': {
|
||||
'templateButtons': {
|
||||
'new': securityIsGranted('mauticSocial:monitoring:create'),
|
||||
},
|
||||
'routeBase': 'social',
|
||||
'langVar': 'monitoring',
|
||||
},
|
||||
'bulk_actions': {
|
||||
'langVar': 'mautic.social.monitoring',
|
||||
'routeBase': 'social',
|
||||
'templateButtons': {
|
||||
'delete': securityIsGranted('mauticSocial:monitoring:delete'),
|
||||
},
|
||||
},
|
||||
'quickFilters': [
|
||||
{
|
||||
'search': 'mautic.core.searchcommand.ispublished',
|
||||
'label': 'mautic.core.form.active',
|
||||
'tooltip': 'mautic.core.searchcommand.ispublished.description',
|
||||
'icon': 'ri-check-line'
|
||||
},
|
||||
{
|
||||
'search': 'mautic.core.searchcommand.isunpublished',
|
||||
'label': 'mautic.core.form.inactive',
|
||||
'tooltip': 'mautic.core.searchcommand.isunpublished.description',
|
||||
'icon': 'ri-close-line'
|
||||
},
|
||||
{
|
||||
'search': 'mautic.core.searchcommand.isuncategorized',
|
||||
'label': 'mautic.core.form.uncategorized',
|
||||
'tooltip': 'mautic.core.searchcommand.isuncategorized.description',
|
||||
'icon': 'ri-folder-unknow-line'
|
||||
},
|
||||
{
|
||||
'search': 'mautic.core.searchcommand.ismine',
|
||||
'label': 'mautic.core.searchcommand.ismine.label',
|
||||
'tooltip': 'mautic.core.searchcommand.ismine.description',
|
||||
'icon': 'ri-user-line'
|
||||
}
|
||||
]
|
||||
}) }}
|
||||
<div class="page-list">
|
||||
{% endif %}
|
||||
|
||||
{{ include('@MauticSocial/Monitoring/_list.html.twig') }}
|
||||
|
||||
{% if isIndex %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1 @@
|
||||
{{ includeScript('plugins/MauticSocialBundle/Assets/js/social.js', 'composeSocialWatcher', 'composeSocialWatcher') }}
|
||||
@@ -0,0 +1,71 @@
|
||||
<!-- Contact Monitoring tokens -->
|
||||
<li class="panel">
|
||||
<a role="button" id="headingContactMonitoring" class="accordion-heading collapsed" data-toggle="collapse"
|
||||
data-parent="#tokensAccordion" href="#collapseContactMonitoring" aria-expanded="false"
|
||||
aria-controls="collapseContactMonitoring">
|
||||
<i class="ri-arrow-down-s-line accordion-arrow"></i>
|
||||
<span class="accordion-title">{{ 'mautic.placeholder_tokens.contact_monitoring'|trans }}</span>
|
||||
</a>
|
||||
<div id="collapseContactMonitoring" class="collapse accordion-wrapper" role="tabpanel"
|
||||
aria-labelledby="headingContactMonitoring">
|
||||
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'mautic.placeholder_tokens.variable_name'|trans }}</th>
|
||||
<th>{{ 'mautic.placeholder_tokens.variable_syntax'|trans }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ 'mautic.placeholder_tokens.monitoring.language'|trans }}</td>
|
||||
<td><code>{language}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'mautic.placeholder_tokens.monitoring.title'|trans }}</td>
|
||||
<td><code>{title}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'mautic.placeholder_tokens.monitoring.landing_page_title'|trans }}</td>
|
||||
<td><code>{page_title}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'mautic.placeholder_tokens.monitoring.url'|trans }}</td>
|
||||
<td><code>{url}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'mautic.placeholder_tokens.monitoring.landing_page_url'|trans }}</td>
|
||||
<td><code>{page_url}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'mautic.placeholder_tokens.monitoring.referrer'|trans }}</td>
|
||||
<td><code>{referrer}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'mautic.placeholder_tokens.monitoring.tracking_pixel'|trans }}</td>
|
||||
<td><code>{tracking_pixel}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'mautic.placeholder_tokens.monitoring.utm_campaign'|trans }}</td>
|
||||
<td><code>{utm_campaign}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'mautic.placeholder_tokens.monitoring.utm_content'|trans }}</td>
|
||||
<td><code>{utm_content}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'mautic.placeholder_tokens.monitoring.utm_medium'|trans }}</td>
|
||||
<td><code>{utm_medium}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'mautic.placeholder_tokens.monitoring.utm_source'|trans }}</td>
|
||||
<td><code>{utm_source}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'mautic.placeholder_tokens.monitoring.utm_term'|trans }}</td>
|
||||
<td><code>{utm_term}</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</li>
|
||||
@@ -0,0 +1,75 @@
|
||||
{% if items|length > 0 %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover tweet-list" id="tweetTable">
|
||||
<thead>
|
||||
<tr>
|
||||
{{ include('@MauticCore/Helper/tableheader.html.twig', {
|
||||
'checkall': 'true',
|
||||
'target': '#tweetTable',
|
||||
}) }}
|
||||
|
||||
{{ include('@MauticCore/Helper/tableheader.html.twig', {
|
||||
'sessionVar': 'social.tweet',
|
||||
'orderBy': 'e.name',
|
||||
'text': 'mautic.core.name',
|
||||
'class': 'col-tweet-name',
|
||||
'default': true,
|
||||
}) }}
|
||||
|
||||
{{ include('@MauticCore/Helper/tableheader.html.twig', {
|
||||
'sessionVar': 'social.tweet',
|
||||
'orderBy': 'e.id',
|
||||
'text': 'mautic.core.id',
|
||||
'class': 'visible-md visible-lg col-asset-id',
|
||||
}) }}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ include('@MauticCore/Helper/list_actions.html.twig', {
|
||||
'item': item,
|
||||
'templateButtons': {
|
||||
'edit': securityIsGranted('mauticSocial:tweets:edit'),
|
||||
'delete': securityIsGranted('mauticSocial:tweets:delete'),
|
||||
},
|
||||
'routeBase': 'mautic_tweet',
|
||||
'langVar': 'mautic.integration.Twitter',
|
||||
'nameGetter': 'getName',
|
||||
}) }}
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
{{ include('@MauticCore/Helper/publishstatus_icon.html.twig', {
|
||||
'item': item,
|
||||
'model': 'social.tweet',
|
||||
}) }}
|
||||
<a href="{{ path('mautic_tweet_action', {'objectAction': 'edit', 'objectId': item.id}) }}" data-toggle="ajax">
|
||||
{{ item.name }}
|
||||
</a>
|
||||
</div>
|
||||
{{ include('@MauticCore/Helper/description--inline.html.twig', {
|
||||
'description': item.description
|
||||
}) }}
|
||||
</td>
|
||||
<td class="visible-md visible-lg">{{ item.id }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
{{ include('@MauticCore/Helper/pagination.html.twig', {
|
||||
'totalItems': items|length,
|
||||
'page': page,
|
||||
'limit': limit,
|
||||
'menuLinkId': 'mautic_tweet_index',
|
||||
'baseUrl': path('mautic_tweet_index'),
|
||||
'sessionVar': 'social.tweet',
|
||||
'routeBase': 'tweet',
|
||||
}) }}
|
||||
</div>
|
||||
{% else %}
|
||||
{{ include('@MauticCore/Helper/noresults.html.twig', {'tip': 'mautic.mautic.social.tweet.noresults.tip'}) }}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,62 @@
|
||||
{% extends '@MauticCore/Default/content.html.twig' %}
|
||||
|
||||
{% block mauticContent 'tweet' %}
|
||||
|
||||
{% block headerTitle %}
|
||||
{% if entity.id %}
|
||||
{{ 'mautic.social.tweet.menu.edit'|trans({'%name%': entity.name|trans}) }}
|
||||
{% else %}
|
||||
{{ 'mautic.social.tweet.menu.new'|trans }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ includeScript('plugins/MauticSocialBundle/Assets/js/social.js', 'composeSocialWatcher', 'composeSocialWatcher') }}
|
||||
|
||||
{{ form_start(form) }}
|
||||
<!-- start: box layout -->
|
||||
<div class="box-layout">
|
||||
<!-- container -->
|
||||
<div class="col-md-9 height-auto bdr-r">
|
||||
<div class="pa-md">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{{ form_row(form.name) }}
|
||||
{{ form_row(form.description) }}
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
{{ form_row(form.text) }}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label class="control-label">
|
||||
{{ 'mautic.social.twitter.tweet.handle'|trans }}
|
||||
</label>
|
||||
{{ form_row(form.handle) }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{{ form_row(form.asset) }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{{ form_row(form.page) }}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div id="character-count" class="text-right small">
|
||||
{{ 'mautic.social.twitter.tweet.count'|trans }}
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 height-auto">
|
||||
<div class="pr-lg pl-lg pt-md pb-md">
|
||||
{{ form_row(form.category) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,30 @@
|
||||
{% extends '@MauticCore/Default/content.html.twig' %}
|
||||
|
||||
{% block mauticContent 'tweet' %}
|
||||
|
||||
{% block content %}
|
||||
{{ includeScript('plugins/MauticSocialBundle/Assets/js/social.js', 'composeSocialWatcher', 'composeSocialWatcher') }}
|
||||
|
||||
{{ form_start(form) }}
|
||||
{{ form_row(form.name) }}
|
||||
{{ form_row(form.text) }}
|
||||
<div id="character-count" class="text-right small">
|
||||
{{ 'mautic.social.twitter.tweet.count'|trans }}
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<label class="control-label">
|
||||
{{ 'mautic.social.twitter.tweet.handle'|trans }}
|
||||
</label>
|
||||
{{ form_row(form.handle) }}
|
||||
</div>
|
||||
<div class="col-md-4">{{ form_row(form.asset) }}</div>
|
||||
<div class="col-md-4">{{ form_row(form.page) }}</div>
|
||||
</div>
|
||||
|
||||
{{ form_row(form.description) }}
|
||||
{{ form_row(form.category) }}
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,37 @@
|
||||
{%- set isIndex = 'index' == tmpl -%}
|
||||
{%- set tmpl = 'list' -%}
|
||||
{% extends isIndex ? '@MauticCore/Default/content.html.twig' : '@MauticCore/Default/raw_output.html.twig' %}
|
||||
|
||||
{% block mauticContent 'tweet' %}
|
||||
|
||||
{% block headerTitle 'mautic.social.tweets'|trans %}
|
||||
|
||||
{% block content %}
|
||||
{% if isIndex %}
|
||||
<div id="page-list-wrapper" class="panel panel-default">
|
||||
{{ include('@MauticCore/Helper/list_toolbar.html.twig', {
|
||||
'searchValue': searchValue,
|
||||
'action': currentRoute,
|
||||
'page_actions': {
|
||||
'templateButtons': {
|
||||
'new': securityIsGranted('mauticSocial:tweets:create'),
|
||||
},
|
||||
'routeBase': 'mautic_tweet',
|
||||
'langVar': 'tweet',
|
||||
},
|
||||
'bulk_actions': {
|
||||
'langVar': 'mautic.social.tweets',
|
||||
'routeBase': 'mautic_tweet',
|
||||
'templateButtons': {
|
||||
'delete': securityIsGranted('mauticSocial:tweets:delete'),
|
||||
},
|
||||
},
|
||||
}) }}
|
||||
<div class="page-list">
|
||||
{{- include('@MauticSocial/Tweet/_list.html.twig') -}}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{{- include('@MauticSocial/Tweet/_list.html.twig') -}}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,7 @@
|
||||
{% macro profileImage(profile) %}
|
||||
{% if profile.profileImage is defined and profile.profileImage is not empty %}
|
||||
<div class="pull-left thumbnail">
|
||||
<img src="{{ profile.profileImage }}" width="100px" class="media-object img-rounded" />
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Security\Permissions;
|
||||
|
||||
use Mautic\CoreBundle\Security\Permissions\AbstractPermissions;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class MauticSocialPermissions extends AbstractPermissions
|
||||
{
|
||||
public function __construct($params)
|
||||
{
|
||||
parent::__construct($params);
|
||||
$this->addStandardPermissions('categories');
|
||||
$this->addStandardPermissions('monitoring');
|
||||
$this->addExtendedPermissions('tweets');
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'mauticSocial';
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface &$builder, array $options, array $data): void
|
||||
{
|
||||
$this->addStandardFormFields('mauticSocial', 'categories', $builder, $data);
|
||||
$this->addStandardFormFields('mauticSocial', 'monitoring', $builder, $data);
|
||||
$this->addExtendedFormFields('mauticSocial', 'tweets', $builder, $data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle;
|
||||
|
||||
/**
|
||||
* Events available for MauticSocialBundle.
|
||||
*/
|
||||
final class SocialEvents
|
||||
{
|
||||
/**
|
||||
* The mautic.monitor_pre_save event is dispatched right before a monitor is persisted.
|
||||
*
|
||||
* The event listener receives a
|
||||
* MauticPlugin\MauticSocialBundle\Event\SocialEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const MONITOR_PRE_SAVE = 'mautic.monitor_pre_save';
|
||||
|
||||
/**
|
||||
* The mautic.monitor_post_save event is dispatched right after a monitor is persisted.
|
||||
*
|
||||
* The event listener receives a
|
||||
* MauticPlugin\MauticSocialBundle\Event\SocialEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const MONITOR_POST_SAVE = 'mautic.monitor_post_save';
|
||||
|
||||
/**
|
||||
* The mautic.monitor_pre_delete event is dispatched before a monitor item is deleted.
|
||||
*
|
||||
* The event listener receives a
|
||||
* MauticPlugin\MauticSocialBundle\Event\SocialEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const MONITOR_PRE_DELETE = 'mautic.monitor_pre_delete';
|
||||
|
||||
/**
|
||||
* The mautic.monitor_post_delete event is dispatched after a monitor is deleted.
|
||||
*
|
||||
* The event listener receives a
|
||||
* MauticPlugin\MauticSocialBundle\Event\SocialEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const MONITOR_POST_DELETE = 'mautic.monitor_post_delete';
|
||||
|
||||
/**
|
||||
* The mautic.monitor_post_process event is dispatched after a monitor is processed passing along the data gleaned.
|
||||
*
|
||||
* The event listener receives a
|
||||
* MauticPlugin\MauticSocialBundle\Event\SocialEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const MONITOR_POST_PROCESS = 'mautic.monitor_post_process';
|
||||
|
||||
/**
|
||||
* The mautic.tweet_pre_save event is dispatched right before a tweet is persisted.
|
||||
*
|
||||
* The event listener receives a
|
||||
* MauticPlugin\MauticSocialBundle\Event\SocialEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TWEET_PRE_SAVE = 'mautic.tweet_pre_save';
|
||||
|
||||
/**
|
||||
* The mautic.tweet_post_save event is dispatched right after a tweet is persisted.
|
||||
*
|
||||
* The event listener receives a
|
||||
* MauticPlugin\MauticSocialBundle\Event\SocialEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TWEET_POST_SAVE = 'mautic.tweet_post_save';
|
||||
|
||||
/**
|
||||
* The mautic.tweet_pre_delete event is dispatched before a tweet item is deleted.
|
||||
*
|
||||
* The event listener receives a
|
||||
* MauticPlugin\MauticSocialBundle\Event\SocialEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TWEET_PRE_DELETE = 'mautic.tweet_pre_delete';
|
||||
|
||||
/**
|
||||
* The mautic.tweet_post_delete event is dispatched after a tweet is deleted.
|
||||
*
|
||||
* The event listener receives a
|
||||
* MauticPlugin\MauticSocialBundle\Event\SocialEvent instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TWEET_POST_DELETE = 'mautic.tweet_post_delete';
|
||||
|
||||
/**
|
||||
* The mautic.social.on_campaign_trigger_action event is fired when the campaign action triggers.
|
||||
*
|
||||
* The event listener receives a
|
||||
* Mautic\CampaignBundle\Event\CampaignExecutionEvent
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ON_CAMPAIGN_TRIGGER_ACTION = 'mautic.social.on_campaign_trigger_action';
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace MauticPlugin\MauticSocialBundle\Tests\Functional\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\UserBundle\Entity\Role;
|
||||
use Mautic\UserBundle\Entity\User;
|
||||
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
|
||||
|
||||
class MonitoringControllerTest extends MauticMysqlTestCase
|
||||
{
|
||||
public const USERNAME = 'jhony';
|
||||
|
||||
public function testIndex(): void
|
||||
{
|
||||
$this->client->request('GET', '/s/monitoring');
|
||||
$response = $this->client->getResponse();
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testNew(): void
|
||||
{
|
||||
$this->client->request('GET', '/s/monitoring/new');
|
||||
$response = $this->client->getResponse();
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testEdit(): void
|
||||
{
|
||||
$this->client->request('GET', '/s/monitoring/edit/1');
|
||||
$response = $this->client->getResponse();
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testIndexWithoutPermission(): void
|
||||
{
|
||||
$this->createAndLoginUser();
|
||||
$this->client->request('GET', '/s/monitoring');
|
||||
$response = $this->client->getResponse();
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testNewWithoutPermission(): void
|
||||
{
|
||||
$this->createAndLoginUser();
|
||||
$this->client->request('GET', '/s/monitoring/new');
|
||||
$response = $this->client->getResponse();
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testEditWithoutPermission(): void
|
||||
{
|
||||
$this->createAndLoginUser();
|
||||
$this->client->request('GET', '/s/monitoring/edit/1');
|
||||
$response = $this->client->getResponse();
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
}
|
||||
|
||||
private function createAndLoginUser(): User
|
||||
{
|
||||
// Create non-admin role
|
||||
$role = $this->createRole();
|
||||
// Create non-admin user
|
||||
$user = $this->createUser($role);
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->detach($role);
|
||||
|
||||
$this->loginUser($user);
|
||||
$this->client->setServerParameter('PHP_AUTH_USER', self::USERNAME);
|
||||
$this->client->setServerParameter('PHP_AUTH_PW', 'Maut1cR0cks!');
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function createRole(bool $isAdmin = false): Role
|
||||
{
|
||||
$role = new Role();
|
||||
$role->setName('Role');
|
||||
$role->setIsAdmin($isAdmin);
|
||||
|
||||
$this->em->persist($role);
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
private function createUser(Role $role): User
|
||||
{
|
||||
$user = new User();
|
||||
$user->setFirstName('John');
|
||||
$user->setLastName('Doe');
|
||||
$user->setUsername(self::USERNAME);
|
||||
$user->setEmail('john.doe@email.com');
|
||||
$hasher = self::getContainer()->get('security.password_hasher_factory')->getPasswordHasher($user);
|
||||
\assert($hasher instanceof PasswordHasherInterface);
|
||||
$user->setPassword($hasher->hash('Maut1cR0cks!'));
|
||||
$user->setRole($role);
|
||||
|
||||
$this->em->persist($user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user