diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
old mode 100755
new mode 100644
diff --git a/.gitignore b/.gitignore
index d607a621127..3566606ab6d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,31 +2,37 @@
!.gitignore
!.htaccess
!.gitkeep
+/composer.phar
+
/app/bootstrap*
/app/tests.bootstrap*
+/app/phpunit.xml
+
/app/bundles/CoreBundle/Assets/css/app/less/**/*.css
/app/bundles/CoreBundle/Assets/css/libraries/**/*.css
/app/bundles/CoreBundle/Assets/css/libraries/Froala/plugins/*.css
!/app/bundles/CoreBundle/Assets/css/libraries/builder.css
!/app/bundles/CoreBundle/Assets/css/libraries/froala/plugins/gated_video.css
!/app/bundles/CoreBundle/Assets/css/*.css
+
/app/cache/*
!/app/cache/.gitkeep
-/app/logs/*
-!/app/logs/.gitkeep
+
/app/config/local*.php
/app/config/config_local.php
/app/config/paths_local.php
+
+/app/logs/*
+!/app/logs/.gitkeep
+
/app/Resources/SensioGeneratorBundle
-/app/phpunit.xml
/app/spool/*
+
/vendor/*
!/vendor/.htaccess
+
/bin/*
-/composer.phar
/bundles
-/media/files/*
-!/media/files/.htaccess
/node_modules
/build/packaging
/build/packages/*
@@ -34,7 +40,11 @@
/mockup
/upgrade
/upgrade_errors.txt
-/translations
+
+/media/dashboards/*
+!/media/dashboards/*.json
+/media/files/*
+!/media/files/.htaccess
/media/images/*
!/media/images/flags
!/media/images/apple-touch-icon.png
@@ -42,5 +52,31 @@
!/media/images/favicon.ico
!/media/images/mautic_logo*
!/media/images/.htaccess
-/media/dashboards/*
-!/media/dashboards/*.json
\ No newline at end of file
+
+/plugins/*
+!/plugins/MauticCitrixBundle
+!/plugins/MauticClearbitBundle
+!/plugins/MauticCloudStorageBundle
+!/plugins/MauticCrmBundle
+!/plugins/MauticEmailMarketingBundle
+!/plugins/MauticFocusBundle
+!/plugins/MauticFullContactBundle
+!/plugins/MauticGmailBundle
+!/plugins/MauticOutlookBundle
+!/plugins/MauticSocialBundle
+!/plugins/index.html
+
+/themes/*
+!/themes/blank
+!/themes/coffee
+!/themes/goldstar
+!/themes/Mauve
+!/themes/nature
+!/themes/neopolitan
+!/themes/oxygen
+!/themes/skyline
+!/themes/sunday
+!/themes/blank.png
+!/themes/blank-big.png
+
+/translations
diff --git a/.travis.yml b/.travis.yml
index dc57895ccc7..b71b9eef83c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,7 +7,10 @@ php:
#- 5.5
- 5.6
- 7.0
- - 7.1
+
+matrix:
+ allow_failures:
+ - php: 7.0
before_script:
- composer install
diff --git a/Gruntfile.js b/Gruntfile.js
index e01445a8f9b..88a2c6ba423 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -17,6 +17,7 @@ module.exports = function (grunt) {
mautic: {
// configurable paths
bundleAssets: 'app/bundles/**/Assets/css',
+ pluginAssets: 'plugins/**/Assets/css',
rootAssets: 'media/css'
},
@@ -31,7 +32,7 @@ module.exports = function (grunt) {
// Compiles less files in bundle's Assets/css root and single level directory to CSS
less: {
files: {
- src: ['<%= mautic.bundleAssets %>/*.less', '<%= mautic.bundleAssets %>/*/*.less', '<%= mautic.bundleAssets %>/../builder/*.less'],
+ src: ['<%= mautic.bundleAssets %>/*.less', '<%= mautic.pluginAssets %>/*.less', '<%= mautic.bundleAssets %>/*/*.less', '<%= mautic.bundleAssets %>/../builder/*.less'],
expand: true,
rename: function (dest, src) {
return dest + src.replace('.less', '.css')
diff --git a/README.md b/README.md
index 6b06a7fa2f6..178f5a6737e 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ Installing from source is only recommended if you are comfortable using the comm
#### Mautic requirements
1. See [Mautic requirements](https://www.mautic.org/download/requirements).
-2. PHP modules:
+2. PHP modules:
- required: `zip`, `xml`, `mcrypt`, `imap`, `mailparse`
- recommended: `openssl`, `opcache` / `apcu` / `memcached`
- recommended for development: `xdebug`
@@ -76,32 +76,32 @@ Before running these commands, please make a backup of your database.
If updating from a tagged release to a tagged release, schema changes will be included in a migrations file. To apply the changes, run
- `$ php app/console doctrine:migrations:migrate`
+ $ php app/console doctrine:migrations:migrate
If you are updating to the latest source (remember this is alpha), first run
- `$ php app/console doctrine:schema:update --dump-sql`
+ $ php app/console doctrine:schema:update --dump-sql
This will list out the queries Doctrine wants to execute in order to get the schema up-to-date (no queries are actually executed). Review the queries to ensure there is nothing detrimental to your data. If you have doubts about a query, submit an issue here and we'll verify it.
If you're satisfied with the queries, execute them with
- `$ php app/console doctrine:schema:update --force`
+ $ php app/console doctrine:schema:update --force
Your schema should now be up-to-date with the source.
# Usage
-Learning how to use marketing automation can be challenging. The first step is to understand what marketing automation is and how it can help your business be more successful. This quick usage outline is not meant to be comprehensive but will outline a few key areas of Mautic and how to use each of them.
+Learning how to use marketing automation can be challenging. The first step is to understand what marketing automation is and how it can help your business be more successful. This quick usage outline is not meant to be comprehensive but will outline a few key areas of Mautic and how to use each of them.
*You can find more detailed information at https://docs.mautic.org*
### 1. Monitoring
-The act of monitoring website traffic and visitors is often the first step in a marketing automation system. This step involves collecting details and information about each visitor to your website.
+The act of monitoring website traffic and visitors is often the first step in a marketing automation system. This step involves collecting details and information about each visitor to your website.
#### Visitor Types
-There are two types of visitor, **anonymous** and **known**.
+There are two types of visitor, **anonymous** and **known**.
**Anonymous visitors** are all visitors which browse to your website. These visitors are monitored and certain key pieces of information are collected. This information includes:
@@ -143,16 +143,16 @@ There are several ways to connect with your leads. The three most common are **e
**Landing pages** are usually the first step in the connection process as these are used to make initial contact with leads and collect the information to move them from an anonymous visitor to a known visitor. These pages are used to funnel visitors to a specific call to action. This call to action is usually a form to collect the visitor's information.
-###3. Automating
+### 3. Automating
-One of Mautic's main purposes is to enable automation of specific tasks. The task of connecting with leads is one such area where automation becomes increasingly valuable. Mautic allows you to define specific times, events, or actions when a connection should be triggered. Here is an example of an automation process.
+One of Mautic's main purposes is to enable automation of specific tasks. The task of connecting with leads is one such area where automation becomes increasingly valuable. Mautic allows you to define specific times, events, or actions when a connection should be triggered. Here is an example of an automation process.
**Example**
-A visitor fills out a call-to-action form on your landing page. This form collects their email address and automatically moves them from an **anonymous** to a **known** visitor. As a known visitor they are now added as a new lead to a specific campaign. This campaign will send the new lead an email you have pre-defined. You can then define additional actions to be taken based on the lead's response to your email.
+A visitor fills out a call-to-action form on your landing page. This form collects their email address and automatically moves them from an **anonymous** to a **known** visitor. As a known visitor they are now added as a new lead to a specific campaign. This campaign will send the new lead an email you have pre-defined. You can then define additional actions to be taken based on the lead's response to your email.
-This example demonstrates several uses of automation. First, the visitor is *automatically* moved from anonymous to known status. Second, the visitor is *automatically* added to a particular campaign. Lastly the visitor is sent an email *automatically* as a new lead.
+This example demonstrates several uses of automation. First, the visitor is *automatically* moved from anonymous to known status. Second, the visitor is *automatically* added to a particular campaign. Lastly the visitor is sent an email *automatically* as a new lead.
-There are many more ways in which automation can be used throughout Mautic to improve efficiency and reduce the time you spend connecting with your leads. As mentioned earlier, refer to [https://docs.mautic.org](https://docs.mautic.org) for more details.
+There are many more ways in which automation can be used throughout Mautic to improve efficiency and reduce the time you spend connecting with your leads. As mentioned earlier, refer to [https://docs.mautic.org](https://docs.mautic.org) for more details.
## Customizing - Plugins, Themes
@@ -168,7 +168,7 @@ Read more about API and webhooks in the [Mautic Developer Docummentation](https:
## Translations
-One benefit of using Mautic is the ability to modify and customize the solution to fit your needs. Mautic allows you to quickly change to your preferred language, or modify any string through the language files. These language files are available for the translation by the community at [Transifex](https://www.transifex.com/mautic/mautic/dashboard) and if you are interested you can add more languages, or help to translate the current ones.
+One benefit of using Mautic is the ability to modify and customize the solution to fit your needs. Mautic allows you to quickly change to your preferred language, or modify any string through the language files. These language files are available for the translation by the community at [Transifex](https://www.transifex.com/mautic/mautic/dashboard) and if you are interested you can add more languages, or help to translate the current ones.
## How to test a pull request
@@ -207,7 +207,7 @@ Every change to Mautic core happens via PRs. Every PR must have 2 successful tes
## Unit Tests
-The unit tests can be executed in the Mautic root directory with `phpunit --bootstrap vendor/autoload.php --configuration app/phpunit.xml.dist app/bundles` command.
+The unit tests can be executed in the Mautic root directory with `composer test` command.
# FAQ and Contact Information
Marketing automation has historically been a difficult tool to implement in a business. The Mautic community is a rich environment for you to learn from others and share your knowledge as well. Open source means more than open code. Open source is providing equality for all and a chance to improve. If you have questions then the Mautic community can help provide the answers.
diff --git a/app/AppKernel.php b/app/AppKernel.php
index 1d31f40e67f..7f4eb1133c7 100644
--- a/app/AppKernel.php
+++ b/app/AppKernel.php
@@ -34,7 +34,7 @@ class AppKernel extends Kernel
*
* @const integer
*/
- const MINOR_VERSION = 6;
+ const MINOR_VERSION = 8;
/**
* Patch version number.
@@ -181,6 +181,7 @@ public function registerBundles()
new Mautic\WebhookBundle\MauticWebhookBundle(),
new LightSaml\SymfonyBridgeBundle\LightSamlSymfonyBridgeBundle(),
new LightSaml\SpBundle\LightSamlSpBundle(),
+ new Ivory\OrderedFormBundle\IvoryOrderedFormBundle(),
];
//dynamically register Mautic Plugin Bundles
diff --git a/app/bundles/ApiBundle/Translations/en_US/messages.ini b/app/bundles/ApiBundle/Translations/en_US/messages.ini
index 170b41fbf80..64d4c62b5df 100644
--- a/app/bundles/ApiBundle/Translations/en_US/messages.ini
+++ b/app/bundles/ApiBundle/Translations/en_US/messages.ini
@@ -7,7 +7,7 @@ mautic.api.auth.error.signature_method_rejected="The signature method used is un
mautic.api.auth.error.signature_invalid="The signature provided does not match the one calculated by the service."
mautic.api.auth.error.consumer_key_unknown="The consumer key provided is unsupported."
mautic.api.auth.error.token_expired="The access token provided is valid, but has expired."
-mautic.api.auth.error.token_rejected="The token provided does not have the right format.";
+mautic.api.auth.error.token_rejected="The token provided does not have the right format."
mautic.api.auth.error.additional_authorization_required="The access token does not have the correct access scopes."
permission_denied="The access session handle (ASH) has expired or is invalid."
mautic.api.call.notfound="Object not found."
diff --git a/app/bundles/AssetBundle/EventListener/ReportSubscriber.php b/app/bundles/AssetBundle/EventListener/ReportSubscriber.php
index 79bfcada4d1..af5b9081016 100644
--- a/app/bundles/AssetBundle/EventListener/ReportSubscriber.php
+++ b/app/bundles/AssetBundle/EventListener/ReportSubscriber.php
@@ -68,7 +68,13 @@ public function onReportBuilder(ReportBuilderEvent $event)
],
];
- $columns = array_merge($columns, $event->getStandardColumns($prefix, ['name'], 'mautic_asset_action'), $event->getCategoryColumns());
+ $columns = array_merge(
+ $columns,
+ $event->getStandardColumns($prefix, ['name'], 'mautic_asset_action'),
+ $event->getCategoryColumns(),
+ $event->getCampaignByChannelColumns()
+ );
+
$event->addTable(
'assets',
[
@@ -107,7 +113,12 @@ public function onReportBuilder(ReportBuilderEvent $event)
'asset.downloads',
[
'display_name' => 'mautic.asset.report.downloads.table',
- 'columns' => array_merge($columns, $downloadColumns, $event->getLeadColumns(), $event->getIpColumn()),
+ 'columns' => array_merge(
+ $columns,
+ $downloadColumns,
+ $event->getLeadColumns(),
+ $event->getIpColumn()
+ ),
],
'assets'
);
@@ -135,6 +146,7 @@ public function onReportGenerate(ReportGeneratorEvent $event)
if ($context == 'assets') {
$queryBuilder->from(MAUTIC_TABLE_PREFIX.'assets', 'a');
$event->addCategoryLeftJoin($queryBuilder, 'a');
+ $event->addCampaignByChannelJoin($queryBuilder, 'a', 'asset');
} elseif ($context == 'asset.downloads') {
$event->applyDateFilters($queryBuilder, 'date_download', 'ad');
@@ -143,6 +155,7 @@ public function onReportGenerate(ReportGeneratorEvent $event)
$event->addCategoryLeftJoin($queryBuilder, 'a');
$event->addLeadLeftJoin($queryBuilder, 'ad');
$event->addIpAddressLeftJoin($queryBuilder, 'ad');
+ $event->addCampaignByChannelJoin($queryBuilder, 'a', 'asset');
}
$event->setQueryBuilder($queryBuilder);
diff --git a/app/bundles/CampaignBundle/Config/config.php b/app/bundles/CampaignBundle/Config/config.php
index 2ed53c09267..b3edd8d31ed 100644
--- a/app/bundles/CampaignBundle/Config/config.php
+++ b/app/bundles/CampaignBundle/Config/config.php
@@ -200,7 +200,7 @@
],
'mautic.form.type.campaignconfig' => [
'class' => 'Mautic\CampaignBundle\Form\Type\ConfigType',
- 'arguments' => 'mautic.factory',
+ 'arguments' => 'translator',
'alias' => 'campaignconfig',
],
],
diff --git a/app/bundles/CampaignBundle/Entity/LeadEventLogRepository.php b/app/bundles/CampaignBundle/Entity/LeadEventLogRepository.php
index 969b199722a..2166efd4064 100644
--- a/app/bundles/CampaignBundle/Entity/LeadEventLogRepository.php
+++ b/app/bundles/CampaignBundle/Entity/LeadEventLogRepository.php
@@ -92,8 +92,8 @@ public function getLeadLogs($leadId, array $options = [])
->leftJoin('ll', MAUTIC_TABLE_PREFIX.'campaign_events', 'e', 'll.event_id = e.id')
->leftJoin('ll', MAUTIC_TABLE_PREFIX.'campaigns', 'c', 'll.campaign_id = c.id')
->where('ll.lead_id = '.(int) $leadId)
- ->andWhere('e.event_type = :eventType')
- ->setParameter('eventType', 'action');
+ ->andWhere('e.event_type != :eventType')
+ ->setParameter('eventType', 'decision');
if (isset($options['scheduledState'])) {
if ($options['scheduledState']) {
@@ -176,8 +176,14 @@ public function getUpcomingEvents(array $options = null)
}
if (isset($options['eventType'])) {
- $query->andwhere('e.event_type = :eventType')
- ->setParameter('eventType', $options['eventType']);
+ if (is_array($options['eventType'])) {
+ $query->andWhere(
+ $query->expr()->in('e.event_type', array_map([$query->expr(), 'literal'], $options['eventType']))
+ );
+ } else {
+ $query->andwhere('e.event_type = :eventTypes')
+ ->setParameter('eventTypes', $options['eventType']);
+ }
}
if (isset($options['limit'])) {
diff --git a/app/bundles/CampaignBundle/Form/Type/ConfigType.php b/app/bundles/CampaignBundle/Form/Type/ConfigType.php
index a6b35b8b08c..79c0f15d823 100644
--- a/app/bundles/CampaignBundle/Form/Type/ConfigType.php
+++ b/app/bundles/CampaignBundle/Form/Type/ConfigType.php
@@ -1,5 +1,14 @@
logger->debug('CAMPAIGN: '.ucfirst($child['eventType']).' ID# '.$child['id'].' has a decision path of no');
} else {
$this->logger->debug('CAMPAIGN: '.ucfirst($child['eventType']).' ID# '.$child['id'].' is being processed');
}
@@ -626,8 +627,9 @@ public function triggerStartingEvents(
++$totalEventCount;
$event['campaign'] = [
- 'id' => $campaign->getId(),
- 'name' => $campaign->getName(),
+ 'id' => $campaign->getId(),
+ 'name' => $campaign->getName(),
+ 'createdBy' => $campaign->getCreatedBy(),
];
$decisionEvent = [
@@ -769,8 +771,7 @@ public function triggerScheduledEvents(
) {
defined('MAUTIC_CAMPAIGN_SYSTEM_TRIGGERED') or define('MAUTIC_CAMPAIGN_SYSTEM_TRIGGERED', 1);
- $campaignId = $campaign->getId();
- $campaignName = $campaign->getName();
+ $campaignId = $campaign->getId();
$this->logger->debug('CAMPAIGN: Triggering scheduled events');
@@ -903,8 +904,9 @@ public function triggerScheduledEvents(
// Set campaign ID
$event['campaign'] = [
- 'id' => $campaignId,
- 'name' => $campaignName,
+ 'id' => $campaign->getId(),
+ 'name' => $campaign->getName(),
+ 'createdBy' => $campaign->getCreatedBy(),
];
// Execute event
@@ -1011,8 +1013,6 @@ public function triggerNegativeEvents(
$this->logger->debug('CAMPAIGN: Triggering negative events');
$campaignId = $campaign->getId();
- $campaignName = $campaign->getName();
-
$repo = $this->getRepository();
$campaignRepo = $this->getCampaignRepository();
$logRepo = $this->getLeadEventLogRepository();
@@ -1262,8 +1262,9 @@ public function triggerNegativeEvents(
// Set event
$event = $events[$id];
$event['campaign'] = [
- 'id' => $campaignId,
- 'name' => $campaignName,
+ 'id' => $campaign->getId(),
+ 'name' => $campaign->getName(),
+ 'createdBy' => $campaign->getCreatedBy(),
];
// Set lead in case this is triggered by the system
@@ -1542,9 +1543,11 @@ public function executeEvent(
}
// Set campaign ID
+
$event['campaign'] = [
- 'id' => $campaign->getId(),
- 'name' => $campaign->getName(),
+ 'id' => $campaign->getId(),
+ 'name' => $campaign->getName(),
+ 'createdBy' => $campaign->getCreatedBy(),
];
// Ensure properties is an array
@@ -1648,6 +1651,17 @@ public function executeEvent(
//trigger the action
$response = $this->invokeEventCallback($event, $thisEventSettings, $lead, null, true, $log);
+ // Check if the lead wasn't deleted during the event callback
+ if (null === $lead->getId() && $response === true) {
+ ++$executedEventCount;
+
+ $this->logger->debug(
+ 'CAMPAIGN: Contact was deleted while executing '.ucfirst($event['eventType']).' ID# '.$event['id']
+ );
+
+ return true;
+ }
+
$eventTriggered = false;
if ($response instanceof LeadEventLog) {
// Listener handled the event and returned a log entry
@@ -1691,31 +1705,7 @@ public function executeEvent(
$repo->deleteEntity($log);
}
- // Notify the lead owner if there is one otherwise campaign creator that there was a failure
- if (!$owner = $lead->getOwner()) {
- $ownerId = $campaign->getCreatedBy();
- $owner = $this->userModel->getEntity($ownerId);
- }
-
- if ($owner && $owner->getId()) {
- $this->notificationModel->addNotification(
- $campaign->getName().' / '.$event['name'],
- 'error',
- false,
- $this->translator->trans(
- 'mautic.campaign.event.failed',
- [
- '%contact%' => ''.$lead->getPrimaryIdentifier().'',
- ]
- ),
- null,
- null,
- $owner
- );
- }
+ $this->notifyOfFailure($lead, $campaign->getCreatedBy(), $campaign->getName().' / '.$event['name']);
$this->logger->debug($debug);
} else {
@@ -1818,7 +1808,9 @@ public function invokeEventCallback($event, $settings, $lead = null, $eventDetai
'lead' => $lead,
'systemTriggered' => $systemTriggered,
'config' => $event['properties'],
- ], true, $log
+ ],
+ true,
+ $log
);
$eventName = array_key_exists('eventName', $settings) ? $settings['eventName'] : null;
@@ -2079,6 +2071,40 @@ public function getEventLineChartData($unit, \DateTime $dateFrom, \DateTime $dat
return $chart->render();
}
+ /**
+ * @param Lead $lead
+ * @param $campaignCreatedBy
+ * @param $header
+ */
+ public function notifyOfFailure(Lead $lead, $campaignCreatedBy, $header)
+ {
+ // Notify the lead owner if there is one otherwise campaign creator that there was a failure
+ if (!$owner = $lead->getOwner()) {
+ $ownerId = (int) $campaignCreatedBy;
+ $owner = $this->userModel->getEntity($ownerId);
+ }
+
+ if ($owner && $owner->getId()) {
+ $this->notificationModel->addNotification(
+ $header,
+ 'error',
+ false,
+ $this->translator->trans(
+ 'mautic.campaign.event.failed',
+ [
+ '%contact%' => ''.$lead->getPrimaryIdentifier().'',
+ ]
+ ),
+ null,
+ null,
+ $owner
+ );
+ }
+ }
+
/**
* Handles condition type events.
*
diff --git a/app/bundles/CampaignBundle/Translations/en_US/messages.ini b/app/bundles/CampaignBundle/Translations/en_US/messages.ini
index 999ec51208d..51eb31af5de 100644
--- a/app/bundles/CampaignBundle/Translations/en_US/messages.ini
+++ b/app/bundles/CampaignBundle/Translations/en_US/messages.ini
@@ -1,5 +1,6 @@
mautic.campaign.add_new_source="Add a contact source..."
mautic.campaign.campaign="Campaign"
+mautic.campaign.campaign.id="Campaign ID"
mautic.campaign.campaign.addremovelead="Add / remove contact"
mautic.campaign.campaign.description="Campaign description: %description%"
mautic.campaign.campaign.launch.builder="Launch Campaign Builder"
@@ -24,6 +25,7 @@ mautic.campaign.event.inline.triggerimmediately="immediately"
mautic.campaign.event.inline.triggerinterval="+ %interval% %unit%"
mautic.campaign.event.last_error="Last execution error"
mautic.campaign.event.failed="Failed to execute campaign event for %contact%."
+mautic.campaign.event.failed.timeline="Generic error."
mautic.campaign.event.intervalunit.choice.d="day(s)"
mautic.campaign.event.intervalunit.choice.h="hour(s)"
mautic.campaign.event.intervalunit.choice.i="minute(s)"
@@ -94,7 +96,7 @@ mautic.campaign.rebuild.no_lists="There are no lists to rebuild from."
mautic.campaign.rebuild.not_found="Campaign #%id% does not exist"
mautic.campaign.rebuild.to_be_added="%leads% total contact(s) to be added in batches of %batch%"
mautic.campaign.rebuild.to_be_removed="%leads% total contact(s) to be removed in batches of %batch%"
-mautic.campaign.scheduled="Campaign action scheduled"
+mautic.campaign.scheduled="Campaign event scheduled"
mautic.campaign.trigger.event_count="%events% total events(s) to be processed in batches of %batch%"
mautic.campaign.trigger.events_executed="%events% event(s) executed"
mautic.campaign.trigger.lead_count_processed="%leads% total contact(s) to be processed in batches of %batch%"
diff --git a/app/bundles/CategoryBundle/Entity/Category.php b/app/bundles/CategoryBundle/Entity/Category.php
index 6daa200a014..3b0c88a86e5 100644
--- a/app/bundles/CategoryBundle/Entity/Category.php
+++ b/app/bundles/CategoryBundle/Entity/Category.php
@@ -117,10 +117,6 @@ public static function loadApiMetadata(ApiMetadataDriver $metadata)
'alias',
'description',
'color',
- ]
- )
- ->addProperties(
- [
'bundle',
]
)
diff --git a/app/bundles/CategoryBundle/Helper/MenuHelper.php b/app/bundles/CategoryBundle/Helper/MenuHelper.php
index b0eec836275..9441dda3b46 100644
--- a/app/bundles/CategoryBundle/Helper/MenuHelper.php
+++ b/app/bundles/CategoryBundle/Helper/MenuHelper.php
@@ -26,6 +26,6 @@ class MenuHelper
*/
public static function addCategoryMenuItems(&$items, $bundleName, CorePermissions $security)
{
- @trigger_error('Individual category menu items are no longer used.', E_DEPRECATED);
+ @trigger_error('Individual category menu items are no longer used.', E_USER_DEPRECATED);
}
}
diff --git a/app/bundles/ChannelBundle/Entity/Message.php b/app/bundles/ChannelBundle/Entity/Message.php
index 2e9b44f39f6..e0afe6c06ca 100644
--- a/app/bundles/ChannelBundle/Entity/Message.php
+++ b/app/bundles/ChannelBundle/Entity/Message.php
@@ -115,6 +115,7 @@ public static function loadApiMetadata(ApiMetadataDriver $metadata)
'publishUp',
'publishDown',
'channels',
+ 'category',
]
)
->build();
diff --git a/app/bundles/ChannelBundle/Entity/MessageQueueRepository.php b/app/bundles/ChannelBundle/Entity/MessageQueueRepository.php
index 44025fe4f24..e81c8122523 100644
--- a/app/bundles/ChannelBundle/Entity/MessageQueueRepository.php
+++ b/app/bundles/ChannelBundle/Entity/MessageQueueRepository.php
@@ -18,6 +18,11 @@
*/
class MessageQueueRepository extends CommonRepository
{
+ /**
+ * @param $channel
+ * @param $channelId
+ * @param $leadId
+ */
public function findMessage($channel, $channelId, $leadId)
{
$results = $this->createQueryBuilder('mq')
@@ -72,4 +77,38 @@ public function getQueuedMessages($limit, $processStarted, $channel = null, $cha
return $results;
}
+
+ /**
+ * @param $channel
+ * @param array|null $ids
+ *
+ * @return bool|string
+ */
+ public function getQueuedChannelCount($channel, array $ids = null)
+ {
+ $q = $this->getEntityManager()->getConnection()->createQueryBuilder();
+
+ $expr = $q->expr()->andX(
+ $q->expr()->eq($this->getTableAlias().'.channel', ':channel'),
+ $q->expr()->neq($this->getTableAlias().'.status', ':status')
+ );
+
+ if (!empty($ids)) {
+ $expr->add(
+ $q->expr()->in($this->getTableAlias().'.channel_id', $ids)
+ );
+ }
+
+ return (int) $q->select('count(*)')
+ ->from(MAUTIC_TABLE_PREFIX.'message_queue', $this->getTableAlias())
+ ->where($expr)
+ ->setParameters(
+ [
+ 'channel' => $channel,
+ 'status' => MessageQueue::STATUS_SENT,
+ ]
+ )
+ ->execute()
+ ->fetchColumn();
+ }
}
diff --git a/app/bundles/ChannelBundle/Model/MessageModel.php b/app/bundles/ChannelBundle/Model/MessageModel.php
index 899fb7172db..38c127f6d2d 100644
--- a/app/bundles/ChannelBundle/Model/MessageModel.php
+++ b/app/bundles/ChannelBundle/Model/MessageModel.php
@@ -57,6 +57,26 @@ public function __construct(ChannelListHelper $channelListHelper, CampaignModel
$this->campaignModel = $campaignModel;
}
+ /**
+ * @param Message $entity
+ * @param bool $unlock
+ */
+ public function saveEntity($entity, $unlock = true)
+ {
+ $isNew = $entity->isNew();
+
+ parent::saveEntity($entity, $unlock);
+
+ if (!$isNew) {
+ // Update the channels
+ $channels = $entity->getChannels();
+ foreach ($channels as $channel) {
+ $channel->setMessage($entity);
+ }
+ $this->getRepository()->saveEntities($channels);
+ }
+ }
+
/**
* @return string
*/
@@ -220,6 +240,13 @@ public function getLeadStatsPost($messageId, $dateFrom = null, $dateTo = null, $
);
}
+ /**
+ * @param $messageId
+ * @param null $dateFrom
+ * @param null $dateTo
+ *
+ * @return mixed
+ */
public function getMarketingMessagesEventLogs($messageId, $dateFrom = null, $dateTo = null)
{
$eventLog = $this->campaignModel->getCampaignLeadEventLogRepository();
diff --git a/app/bundles/ChannelBundle/Model/MessageQueueModel.php b/app/bundles/ChannelBundle/Model/MessageQueueModel.php
index 1a5a280b73d..cdc63a50825 100644
--- a/app/bundles/ChannelBundle/Model/MessageQueueModel.php
+++ b/app/bundles/ChannelBundle/Model/MessageQueueModel.php
@@ -362,6 +362,15 @@ public function rescheduleMessage($message, $rescheduleInterval = null, $leadId
}
}
+ /**
+ * @param $channel
+ * @param array $channelIds
+ */
+ public function getQueuedChannelCount($channel, $channelIds = [])
+ {
+ return $this->getRepository()->getQueuedChannelCount($channel, $channelIds);
+ }
+
/**
* {@inheritdoc}
*
@@ -411,8 +420,12 @@ protected function dispatchEvent($action, &$entity, $isNew = false, Event $event
*
* @param $action
* @param $event
+ * @param $entity
+ * @param $isNew
*
* @throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
+ *
+ * @return Event|null
*/
protected function dispatchDeprecatedEvent($action, Event $event = null, $entity = null, $isNew = null)
{
diff --git a/app/bundles/ChannelBundle/Translations/en_US/messages.ini b/app/bundles/ChannelBundle/Translations/en_US/messages.ini
index f68eb2dd4a9..32914fe981a 100644
--- a/app/bundles/ChannelBundle/Translations/en_US/messages.ini
+++ b/app/bundles/ChannelBundle/Translations/en_US/messages.ini
@@ -20,4 +20,5 @@ mautic.channel.choose.messages_descr="Marketing messages"
mautic.email.send.edit.message="Edit Message"
mautic.channel.message.send.marketing.message="Send marketing message"
mautic.channel.message.send.marketing.message.descr="Send a message through the configured channels withing the marketing message selected."
-mautic.messages.processed.messages="Messages Sent by Channel"
\ No newline at end of file
+mautic.messages.processed.messages="Messages Sent by Channel"
+mautic.channel.message.form.confirmdelete="Delete this message?"
diff --git a/app/bundles/ChannelBundle/Views/Message/list_item.html.php b/app/bundles/ChannelBundle/Views/Message/list_item.html.php
index 9afc2328b38..512eeb09dc2 100644
--- a/app/bundles/ChannelBundle/Views/Message/list_item.html.php
+++ b/app/bundles/ChannelBundle/Views/Message/list_item.html.php
@@ -14,7 +14,7 @@
$channels = [];
if ($messageChannels) {
foreach ($messageChannels as $channelName => $channel) {
- if (!$channel->getChannelId()) {
+ if (!$channel->isEnabled()) {
continue;
}
diff --git a/app/bundles/CoreBundle/Assets/css/app.css b/app/bundles/CoreBundle/Assets/css/app.css
index 46d7935b55b..fc4ea9897e3 100644
--- a/app/bundles/CoreBundle/Assets/css/app.css
+++ b/app/bundles/CoreBundle/Assets/css/app.css
@@ -4258,23 +4258,29 @@ lesshat-selector { -lh-property: 0 ;
-moz-hyphens: auto;
hyphens: auto;
}
-.builder {
+.builder,
+.builder-slot {
position: relative;
}
-.builder [data-token] {
+.builder [data-token],
+.builder-slot [data-token] {
cursor: pointer;
}
-.builder .btn-block.ui-draggable-dragging {
+.builder .btn-block.ui-draggable-dragging,
+.builder-slot .btn-block.ui-draggable-dragging {
width: 104px;
height: 68px;
margin: 6px 12px;
white-space: normal;
}
-.builder .builder-panel-top {
+.builder .builder-panel-top,
+.builder-slot .builder-panel-top {
margin-bottom: 10px;
}
.builder .template-dnd-help,
-.builder .custom-dnd-help {
+.builder-slot .template-dnd-help,
+.builder .custom-dnd-help,
+.builder-slot .custom-dnd-help {
display: table-cell;
vertical-align: middle;
width: 100%;
@@ -4325,6 +4331,38 @@ lesshat-selector { -lh-property: 0 ;
.builder-panel .panel a.btn {
white-space: normal;
}
+/********** SLOT ************/
+.builder-active-slot {
+ background-color: #fff;
+ z-index: 1030;
+}
+.builder-panel-slot {
+ width: 50%;
+ padding: 15px;
+ background-color: #d5d4d4;
+ overflow-y: auto;
+}
+.builder-panel-slot .btn-close-builder {
+ width: 100%;
+}
+.builder-content-slot {
+ left: 50%;
+ width: 50%;
+}
+.code-mode .builder-panel-slot {
+ width: 50%;
+}
+.code-mode .builder-content-slot {
+ width: 50%;
+}
+.code-mode .btn-close-builder {
+ width: 150px;
+ float: right;
+}
+.builder-panel-slot .panel a.btn {
+ white-space: normal;
+}
+/************* END SLOT ******************/
.ui-draggable-iframeFix {
z-index: 9999 !important;
}
@@ -4332,6 +4370,9 @@ lesshat-selector { -lh-property: 0 ;
border: 1px solid #eee;
height: auto;
}
+#slot_codemode .CodeMirror {
+ height: 200px;
+}
.CodeMirror-hints {
position: absolute;
z-index: 9999 !important;
@@ -4692,3 +4733,21 @@ td.col-id,
th.col-id {
width: 75px;
}
+.badge-wrapper {
+ float: right;
+ vertical-align: middle;
+ margin-right: -10px;
+}
+span.slot-caption {
+ font-size: 12px;
+}
+.imagecard-caption,
+figcaption {
+ font-size: 16px;
+}
+.imagecard {
+ background-color: #ddd !important;
+}
+.imagecard .imagecard-caption {
+ background-color: #bbb !important;
+}
diff --git a/app/bundles/CoreBundle/Assets/css/app/less/components/builder.less b/app/bundles/CoreBundle/Assets/css/app/less/components/builder.less
index 382859b254a..0f83752d588 100644
--- a/app/bundles/CoreBundle/Assets/css/app/less/components/builder.less
+++ b/app/bundles/CoreBundle/Assets/css/app/less/components/builder.less
@@ -1,5 +1,5 @@
-.builder {
+.builder, .builder-slot {
position: relative;
[data-token] {
@@ -78,6 +78,48 @@
white-space: normal;
}
+/********** SLOT ************/
+
+.builder-active-slot {
+ background-color: #fff;
+ z-index: 1030;
+}
+
+.builder-panel-slot {
+ width: 50%;
+ padding: 15px;
+ background-color: #d5d4d4;
+ overflow-y: auto;
+
+ .btn-close-builder {
+ width: 100%;
+ }
+}
+
+.builder-content-slot {
+ left: 50%;
+ width: 50%;
+}
+
+.code-mode {
+ .builder-panel-slot {
+ width: 50%;
+ }
+ .builder-content-slot {
+ width: 50%;
+ }
+ .btn-close-builder {
+ width: 150px;
+ float: right;
+ }
+}
+
+.builder-panel-slot .panel a.btn {
+ white-space: normal;
+}
+
+/************* END SLOT ******************/
+
.ui-draggable-iframeFix {
z-index: 9999 !important;
}
diff --git a/app/bundles/CoreBundle/Assets/css/app/less/custom.less b/app/bundles/CoreBundle/Assets/css/app/less/custom.less
index b552246fe59..b6822d53309 100644
--- a/app/bundles/CoreBundle/Assets/css/app/less/custom.less
+++ b/app/bundles/CoreBundle/Assets/css/app/less/custom.less
@@ -264,4 +264,22 @@ div[data-filter-container] .panel.in-group {
td.col-id, th.col-id {
width: 75px;
-}
\ No newline at end of file
+}
+
+.badge-wrapper {
+ float: right;
+ vertical-align: middle;
+ margin-right: -10px;
+}
+span.slot-caption {
+ font-size: 12px;
+}
+.imagecard-caption, figcaption {
+ font-size: 16px;
+}
+.imagecard {
+ background-color: #ddd !important;
+}
+.imagecard .imagecard-caption {
+ background-color: #bbb !important;
+}
diff --git a/app/bundles/CoreBundle/Assets/css/libraries/builder.css b/app/bundles/CoreBundle/Assets/css/libraries/builder.css
index 48f5096151a..1e4fee71fbf 100644
--- a/app/bundles/CoreBundle/Assets/css/libraries/builder.css
+++ b/app/bundles/CoreBundle/Assets/css/libraries/builder.css
@@ -5354,6 +5354,7 @@ div[data-slot-toolbar] {
background-color: #4e5e9e;
border-left: 1px solid #4e5e9e;
border-right: 1px solid #4e5e9e;
+ padding-top: 2px;
}
div[data-slot-toolbar] .btn {
width: 20px;
@@ -5375,8 +5376,8 @@ div[data-slot-toolbar] .btn {
font-size: 11px;
line-height: 1.456;
float: right;
- margin-right: 5px;
- color: #ffffff;
+ margin-right: 2px;
+ color: #fff;
}
div[data-slot-toolbar] .btn .fa {
padding-top: 4px;
@@ -5385,11 +5386,11 @@ div[data-slot],
[data-section-wrapper] {
position: relative;
}
-div[data-slot="image"] {
+div[data-slot^="image"] {
padding-top: 1px;
padding-bottom: 1px;
}
-div[data-slot="image"] img {
+div[data-slot^="image"] img {
z-index: 2;
position: relative;
}
@@ -5433,13 +5434,19 @@ div[data-slot].ui-sortable-helper {
color: inherit !important;
min-height: inherit !important;
}
-.slot-type-handle.btn {
+.slot-type-handle.btn,
+.section-type-handle.btn {
float: left;
width: 111px;
margin: 2px;
- height: 62px;
+ height: 75px;
+ padding-left: 5px;
+ padding-right: 5px;
+ text-align: center;
+ word-wrap: break-word;
}
-.slot-type-handle.ui-draggable-dragging {
+.slot-type-handle.ui-draggable-dragging,
+.section-type-handle.ui-draggable-dragging {
color: #5d6c7c;
background-color: #f5f5f5;
border-color: #d3d3d3;
@@ -5456,3 +5463,9 @@ div[data-slot].ui-sortable-helper {
.theme-list .select-theme-link {
margin-top: 5px;
}
+.theme-list .select-theme-selected {
+ margin-top: 5px;
+}
+[data-slot="dynamicContent"] {
+ z-index: 50;
+}
diff --git a/app/bundles/CoreBundle/Assets/css/libraries/builder.less b/app/bundles/CoreBundle/Assets/css/libraries/builder.less
index a2694bddf9d..f19be330424 100644
--- a/app/bundles/CoreBundle/Assets/css/libraries/builder.less
+++ b/app/bundles/CoreBundle/Assets/css/libraries/builder.less
@@ -109,6 +109,7 @@ div[data-slot-toolbar] {
background-color: @mautic-primary;
border-left: 1px solid @mautic-primary;
border-right: 1px solid @mautic-primary;
+ padding-top:2px;
}
div[data-slot-toolbar] .btn {
@@ -131,8 +132,8 @@ div[data-slot-toolbar] .btn {
font-size: 11px;
line-height: 1.456;
float: right;
- margin-right: 5px;
- color: #ffffff;
+ margin-right: 2px;
+ color: #fff;
}
div[data-slot-toolbar] .btn .fa {
@@ -143,12 +144,12 @@ div[data-slot], [data-section-wrapper] {
position: relative;
}
-div[data-slot="image"] {
+div[data-slot^="image"] {
padding-top: 1px; // prevent z-index of image from hiding the bottom border of a slot with focus above it
padding-bottom: 1px;
}
-div[data-slot="image"] img {
+div[data-slot^="image"] img {
z-index: 2;
position: relative;
}
@@ -201,14 +202,18 @@ div[data-slot].ui-sortable-helper {
}
}
-.slot-type-handle.btn {
+.slot-type-handle.btn, .section-type-handle.btn {
float: left;
width: 111px;
margin: 2px;
- height: 62px;
+ height: 75px;
+ padding-left:5px;
+ padding-right:5px;
+ text-align:center;
+ word-wrap: break-word;
}
-.slot-type-handle.ui-draggable-dragging {
+.slot-type-handle.ui-draggable-dragging, .section-type-handle.ui-draggable-dragging{
color: #5d6c7c;
background-color: #f5f5f5;
border-color: #d3d3d3;
@@ -227,4 +232,11 @@ div[data-slot].ui-sortable-helper {
.select-theme-link {
margin-top: 5px;
}
+ .select-theme-selected {
+ margin-top:5px;
+ }
}
+
+[data-slot="dynamicContent"] {
+ z-index:50;
+}
\ No newline at end of file
diff --git a/app/bundles/CoreBundle/Assets/js/1.core.js b/app/bundles/CoreBundle/Assets/js/1.core.js
index ca3cdde9132..1b6857fd354 100644
--- a/app/bundles/CoreBundle/Assets/js/1.core.js
+++ b/app/bundles/CoreBundle/Assets/js/1.core.js
@@ -394,10 +394,11 @@ var Mautic = {
if (mQuery(target).length) {
var hasBtn = mQuery(target).hasClass('btn');
var hasIcon = mQuery(target).hasClass('fa');
+ var dontspin = mQuery(target).hasClass('btn-nospin');
var i = (hasBtn && mQuery(target).find('i.fa').length) ? mQuery(target).find('i.fa') : target;
- if ((hasBtn && mQuery(target).find('i.fa').length) || hasIcon) {
+ if (!dontspin && ((hasBtn && mQuery(target).find('i.fa').length) || hasIcon)) {
var el = (hasIcon) ? target : mQuery(target).find('i.fa').first();
var identifierClass = (new Date).getTime();
MauticVars.iconClasses[identifierClass] = mQuery(el).attr('class');
diff --git a/app/bundles/CoreBundle/Assets/js/1a.content.js b/app/bundles/CoreBundle/Assets/js/1a.content.js
index edc6af920d5..e6dc8efa96a 100644
--- a/app/bundles/CoreBundle/Assets/js/1a.content.js
+++ b/app/bundles/CoreBundle/Assets/js/1a.content.js
@@ -517,7 +517,7 @@ Mautic.onPageLoad = function (container, response, inModal) {
}
if (textarea.hasClass('editor-dynamic-content')) {
- minButtons = ['undo', 'redo', '|', 'bold', 'italic', 'underline', 'fontFamily', 'fontSize', 'color', 'align', 'formatOL', 'formatUL', 'quote', 'clearFormatting', 'insertLink', 'insertImage'];
+ minButtons = ['undo', 'redo', '|', 'bold', 'italic', 'underline', 'paragraphFormat', 'fontFamily', 'fontSize', 'color', 'align', 'formatOL', 'formatUL', 'quote', 'clearFormatting', 'insertLink', 'insertImage', 'insertGatedVideo', 'insertTable', 'html', 'fullscreen'];
}
if (textarea.hasClass('editor-advanced') || textarea.hasClass('editor-basic-fullpage')) {
@@ -604,6 +604,31 @@ Mautic.onPageLoad = function (container, response, inModal) {
contentSpecific = mauticContent;
}
+ if (response && response.sidebar) {
+ var sidebarContent = mQuery('.app-sidebar.sidebar-left');
+ var newSidebar = mQuery(response.sidebar);
+ var nav = sidebarContent.find('li');
+
+ if (nav.length) {
+ var openNavIndex;
+
+ nav.each(function(i, el) {
+ var $el = mQuery(el);
+
+ if ($el.hasClass('open')) {
+ openNavIndex = i;
+ }
+ });
+
+ var openNav = mQuery(newSidebar.find('li')[openNavIndex]);
+
+ openNav.addClass('open');
+ openNav.find('ul').removeClass('collapse');
+ }
+
+ sidebarContent.html(newSidebar);
+ }
+
if (container == '#app-content' || container == 'body') {
//register global keyboard shortcuts
Mautic.bindGlobalKeyboardShortcuts();
@@ -617,11 +642,9 @@ Mautic.onPageLoad = function (container, response, inModal) {
}
if (contentSpecific && typeof Mautic[contentSpecific + "OnLoad"] == 'function') {
- if (typeof Mautic[contentSpecific + "OnLoad"] == 'function') {
- if (typeof Mautic.loadedContent[contentSpecific] == 'undefined') {
- Mautic.loadedContent[contentSpecific] = true;
- Mautic[contentSpecific + "OnLoad"](container, response);
- }
+ if (typeof Mautic.loadedContent[contentSpecific] == 'undefined') {
+ Mautic.loadedContent[contentSpecific] = true;
+ Mautic[contentSpecific + "OnLoad"](container, response);
}
}
@@ -735,7 +758,7 @@ Mautic.onPageUnload = function (container, response) {
Mautic[contentSpecific + "OnUnload"](container, response);
}
- if (typeof (Mautic.loadedContent[contentSpecific])) {
+ if (typeof Mautic.loadedContent[contentSpecific] !== 'undefined') {
delete Mautic.loadedContent[contentSpecific];
}
}
@@ -810,7 +833,8 @@ Mautic.ajaxifyLink = function (el, event) {
*
* @param el
*/
-Mautic.activateChosenSelect = function(el, ignoreGlobal) {
+Mautic.activateChosenSelect = function(el, ignoreGlobal, jQueryVariant) {
+ var mQuery = (typeof jQueryVariant != 'undefined') ? jQueryVariant : window.mQuery;
if (mQuery(el).parents('.no-chosen').length && !ignoreGlobal) {
// Globally ignored chosens because they are handled manually due to hidden elements, etc
return;
@@ -891,9 +915,9 @@ Mautic.activateChosenSelect = function(el, ignoreGlobal) {
* @param options
*/
Mautic.activateFieldTypeahead = function (field, target, options, action) {
- if (options) {
+ if (options && typeof options === 'String') {
var keys = values = [];
- //check to see if there is a key/value split
+
options = options.split('||');
if (options.length == 2) {
keys = options[1].split('|');
@@ -915,15 +939,18 @@ Mautic.activateFieldTypeahead = function (field, target, options, action) {
});
}
- mQuery(fieldTypeahead).on('typeahead:selected', function (event, datum) {
- if (mQuery("#" + field).length && datum["value"]) {
- mQuery("#" + field).val(datum["value"]);
- }
- }).on('typeahead:autocompleted', function (event, datum) {
+ var callback = function (event, datum) {
if (mQuery("#" + field).length && datum["value"]) {
mQuery("#" + field).val(datum["value"]);
+
+ var lookupCallback = mQuery('#' + field).data("lookup-callback");
+ if (lookupCallback && typeof Mautic[lookupCallback] == 'function') {
+ Mautic[lookupCallback](field, datum);
+ }
}
- });
+ };
+
+ mQuery(fieldTypeahead).on('typeahead:selected', callback).on('typeahead:autocompleted', callback);
};
/**
@@ -1346,7 +1373,7 @@ Mautic.activateListFilterSelect = function(el) {
* Converts an input to a color picker
* @param el
*/
-Mautic.activateColorPicker = function(el) {
+Mautic.activateColorPicker = function(el, options) {
var pickerOptions = mQuery(el).data('color-options');
if (!pickerOptions) {
pickerOptions = {
@@ -1357,6 +1384,10 @@ Mautic.activateColorPicker = function(el) {
};
}
+ if (typeof options == 'object') {
+ pickerOptions = mQuery.extend(pickerOptions, options);
+ }
+
mQuery(el).minicolors(pickerOptions);
};
diff --git a/app/bundles/CoreBundle/Assets/js/4.builder.js b/app/bundles/CoreBundle/Assets/js/4.builder.js
index b18d7405ee1..41134c201c2 100644
--- a/app/bundles/CoreBundle/Assets/js/4.builder.js
+++ b/app/bundles/CoreBundle/Assets/js/4.builder.js
@@ -2,6 +2,7 @@
* Launch builder
*
* @param formName
+ * @param actionName
*/
Mautic.launchBuilder = function (formName, actionName) {
var builder = mQuery('.builder');
@@ -64,17 +65,17 @@ Mautic.launchBuilder = function (formName, actionName) {
Mautic.keepPreviewAlive('builder-template-content');
}
- var builderPanel = mQuery('.builder-panel')
- builderContent = mQuery('.builder-content')
- btnCloseBuilder = mQuery('.btn-close-builder')
- panelHeight = (builderContent.css('right') == '0px') ? builderPanel.height() : 0,
- panelWidth = (builderContent.css('right') == '0px') ? 0 : builderPanel.width(),
- spinnerLeft = (mQuery(window).width() - panelWidth - 60) / 2,
- spinnerTop = (mQuery(window).height() - panelHeight - 60) / 2;
+ var builderPanel = mQuery('.builder-panel');
+ var builderContent = mQuery('.builder-content');
+ var btnCloseBuilder = mQuery('.btn-close-builder');
+ var panelHeight = (builderContent.css('right') == '0px') ? builderPanel.height() : 0;
+ var panelWidth = (builderContent.css('right') == '0px') ? 0 : builderPanel.width();
+ var spinnerLeft = (mQuery(window).width() - panelWidth - 60) / 2;
+ var spinnerTop = (mQuery(window).height() - panelHeight - 60) / 2;
// Blur and focus the focussed inputs to fix the browser autocomplete bug on scroll
builderPanel.on('scroll', function(e) {
- builderPanel.find('input:focus').blur().focus();
+ builderPanel.find('input:focus').blur();
});
var overlay = mQuery('
').css(builderCss).appendTo('.builder-content');
@@ -86,6 +87,9 @@ Mautic.launchBuilder = function (formName, actionName) {
var assets = Mautic.htmlspecialchars_decode(mQuery('[data-builder-assets]').html());
themeHtml = themeHtml.replace('', assets+'');
+ // Turn Dynamic Content Tokens into builder slots
+ themeHtml = Mautic.prepareDynamicContentBlocksForBuilder(themeHtml);
+
Mautic.buildBuilderIframe(themeHtml, 'builder-template-content', function() {
mQuery('#builder-overlay').addClass('hide');
btnCloseBuilder.prop('disabled', false);
@@ -144,8 +148,9 @@ Mautic.openServerBrowser = function(url, width, height) {
* Creates an iframe and keeps its content live from CodeMirror changes
*
* @param iframeId
+ * @param slot
*/
-Mautic.keepPreviewAlive = function(iframeId) {
+Mautic.keepPreviewAlive = function(iframeId, slot) {
var codeChanged = false;
// Watch for code changes
Mautic.builderCodeMirror.on('change', function(cm, change) {
@@ -154,7 +159,8 @@ Mautic.keepPreviewAlive = function(iframeId) {
window.setInterval(function() {
if (codeChanged) {
- Mautic.livePreviewInterval = Mautic.updateIframeContent(iframeId, Mautic.builderCodeMirror.getValue());
+ var value = (Mautic.builderCodeMirror)?Mautic.builderCodeMirror.getValue():'';
+ Mautic.livePreviewInterval = Mautic.updateIframeContent(iframeId, value, slot);
codeChanged = false;
}
}, 2000);
@@ -289,15 +295,20 @@ Mautic.initSelectTheme = function(themeField) {
/**
* Updates content of an iframe
*
- * @param iframe ID
- * @param HTML content
+ * @param iframeId ID
+ * @param content HTML content
+ * @param slot
*/
-Mautic.updateIframeContent = function(iframeId, content) {
- var iframe = document.getElementById(iframeId);
- var doc = iframe.contentDocument || iframe.contentWindow.document;
- doc.open();
- doc.write(content);
- doc.close();
+Mautic.updateIframeContent = function(iframeId, content, slot) {
+ if (iframeId) {
+ var iframe = document.getElementById(iframeId);
+ var doc = iframe.contentDocument || iframe.contentWindow.document;
+ doc.open();
+ doc.write(content);
+ doc.close();
+ } else if (slot) {
+ slot.html(content);
+ }
};
/**
@@ -356,6 +367,9 @@ Mautic.closeBuilder = function(model) {
customHtml = themeHtml.find('html').get(0).outerHTML
}
+ // Convert dynamic slot definitions into tokens
+ customHtml = Mautic.convertDynamicContentSlotsToTokens(customHtml);
+
// Store the HTML content to the HTML textarea
mQuery('.builder-html').val(customHtml);
} catch (error) {
@@ -642,7 +656,7 @@ Mautic.initSections = function() {
mQuery('#builder-template-content', parent.document).css('overflow', 'visible');
mQuery('#builder-template-content', parent.document).attr('scrolling', 'yes');
- },
+ }
}).disableSelection();
// Initialize the slots
@@ -667,7 +681,7 @@ Mautic.sectionBackgroundChanged = function(element, color) {
Mautic.setTextSlotEditorStyle(parent.mQuery('#slot_text_content'), focusedSlot);
}
});
-}
+};
Mautic.rgb2hex = function(orig) {
var rgb = orig.replace(/\s/g,'').match(/^rgba?\((\d+),(\d+),(\d+)/i);
@@ -675,7 +689,7 @@ Mautic.rgb2hex = function(orig) {
("0" + parseInt(rgb[1],10).toString(16)).slice(-2) +
("0" + parseInt(rgb[2],10).toString(16)).slice(-2) +
("0" + parseInt(rgb[3],10).toString(16)).slice(-2) : orig;
-}
+};
Mautic.initSlots = function(slotContainers) {
if (!slotContainers) {
@@ -689,6 +703,7 @@ Mautic.initSlots = function(slotContainers) {
// Make slots sortable
var bodyOverflow = {};
Mautic.sortActive = false;
+ Mautic.parentDocument = parent.document;
slotContainers.sortable({
helper: function(e, ui) {
@@ -740,7 +755,7 @@ Mautic.initSlots = function(slotContainers) {
}
Mautic.sortActive = false;
- },
+ }
});
// Allow to drag&drop new slots from the slot type menu
@@ -751,46 +766,132 @@ Mautic.initSlots = function(slotContainers) {
revert: 'invalid',
iframeOffset: iframe.offset(),
helper: function(e, ui) {
+ // fix for Uncaught TypeError: Cannot read property 'document' of null
// Fix body overflow that messes sortable up
- bodyOverflow.overflowX = mQuery('body', parent.document).css('overflow-x');
- bodyOverflow.overflowY = mQuery('body', parent.document).css('overflow-y');
- mQuery('body', parent.document).css({
+ bodyOverflow.overflowX = mQuery('body', Mautic.parentDocument).css('overflow-x');
+ bodyOverflow.overflowY = mQuery('body', Mautic.parentDocument).css('overflow-y');
+ mQuery('body', Mautic.parentDocument).css({
overflowX: 'hidden',
overflowY: 'hidden'
});
- var helper = mQuery(this).clone()
+ return mQuery(this).clone()
.css('height', mQuery(this).height())
.css('width', mQuery(this).width());
-
- return helper;
},
zIndex: 8000,
cursorAt: {top: 15, left: 15},
start: function(event, ui) {
- mQuery('#builder-template-content', parent.document).css('overflow', 'hidden');
- mQuery('#builder-template-content', parent.document).attr('scrolling', 'no');
- slotContainers.sortable('option', 'scroll', false);
+ mQuery('#builder-template-content', Mautic.parentDocument).css('overflow', 'hidden');
+ mQuery('#builder-template-content', Mautic.parentDocument).attr('scrolling', 'no');
+ // check if it is initialized first to prevent error
+ if (slotContainers.data('sortable')) slotContainers.sortable('option', 'scroll', false);
},
stop: function(event, ui) {
// Restore original overflow
- mQuery('body', parent.document).css(bodyOverflow);
+ mQuery('body', Mautic.parentDocument).css(bodyOverflow);
- mQuery('#builder-template-content', parent.document).css('overflow', 'visible');
- mQuery('#builder-template-content', parent.document).attr('scrolling', 'yes');
- slotContainers.sortable('option', 'scroll', true);
- },
+ mQuery('#builder-template-content', Mautic.parentDocument).css('overflow', 'visible');
+ mQuery('#builder-template-content', Mautic.parentDocument).attr('scrolling', 'yes');
+ // check if it is initialized first to prevent error
+ if (slotContainers.data('sortable')) slotContainers.sortable('option', 'scroll', true);
+ }
}).disableSelection();
iframe.on('scroll', function() {
- mQuery('#slot-type-container .slot-type-handle', parent.document).draggable("option", "cursorAt", { top: -1 * iframe.scrollTop() + 15 });
+ mQuery('#slot-type-container .slot-type-handle', Mautic.parentDocument).draggable("option", "cursorAt", { top: -1 * iframe.scrollTop() + 15 });
});
// Initialize the slots
slotContainers.find('[data-slot]').each(function() {
mQuery(this).trigger('slot:init', this);
});
-}
+};
+
+Mautic.getSlotToolbar = function() {
+ Mautic.builderContents.find('[data-slot-toolbar]').remove();
+
+ var slotToolbar = mQuery('').attr('data-slot-toolbar', true);
+ var deleteLink = Mautic.getSlotDeleteLink();
+
+ deleteLink.appendTo(slotToolbar);
+
+ return slotToolbar;
+};
+
+Mautic.getSlotDeleteLink = function() {
+ if (typeof Mautic.deleteLink == 'undefined') {
+ Mautic.deleteLink = mQuery('')
+ .attr('data-slot-action', 'delete')
+ .attr('alt', 'delete')
+ .addClass('btn btn-delete btn-default');
+ }
+
+ return Mautic.deleteLink;
+};
+
+Mautic.getSlotFocus = function() {
+ Mautic.builderContents.find('[data-slot-focus]').remove();
+
+ return mQuery('').attr('data-slot-focus', true);
+};
+
+Mautic.cloneFocusForm = function(decId, removeFroala) {
+ // reattach DEC
+ if (typeof Mautic.activeDEC !== 'undefined') {
+ var element = Mautic.activeDEC.detach();
+ element.hide();
+ Mautic.activeDECParent.append(element);
+ }
+ var focusForm = parent.mQuery('#emailform_dynamicContent_' + decId);
+ Mautic.activeDECParent = focusForm.parent();
+ // show if hidden
+ focusForm.removeClass('fade');
+ // remove delete default button
+ focusForm.find('.tab-pane:first').find('.remove-item').hide();
+ var element =focusForm.detach();
+ element.show();
+ Mautic.activeDEC = element;
+ return element;
+};
+
+Mautic.initEmailDynamicContentSlotEdit = function (clickedSlot) {
+ var decId = clickedSlot.attr('data-param-dec-id');
+
+ var focusForm;
+
+ if (decId || decId === 0) {
+ focusForm = Mautic.cloneFocusForm(decId);
+ }
+
+ var focusFormHeader = parent.mQuery('#customize-slot-panel').find('.panel-heading h4');
+ var newDynConButton = mQuery('')
+ .css('float', 'right')
+ .addClass('btn btn-success btn-xs');
+
+ newDynConButton.text('Add Variant');
+ newDynConButton.on('click', function(e) {
+ e.stopPropagation();
+ Mautic.createNewDynamicContentFilter('#dynamicContentFilterTabs_'+decId, parent.mQuery);
+ var focusForm = Mautic.cloneFocusForm(decId, false);
+ focusForm.insertAfter(parent.mQuery('#slot_dynamiccontent > div.has-error'));
+ });
+
+ focusFormHeader.append(newDynConButton);
+
+ return focusForm;
+};
+
+Mautic.removeAddVariantButton = function() {
+ // Remove the Add Variant button for dynamicContent slots
+ parent.mQuery('#customize-slot-panel').find('.panel-heading button').remove();
+ // reattach DEC
+ if (typeof Mautic.activeDEC !== 'undefined') {
+ var element = Mautic.activeDEC.detach();
+ element.hide();
+ Mautic.activeDECParent.append(element);
+ }
+};
Mautic.initSlotListeners = function() {
Mautic.activateGlobalFroalaOptions();
@@ -800,8 +901,7 @@ Mautic.initSlotListeners = function() {
Mautic.builderContents.on('slot:selected', function(event, slot) {
slot = mQuery(slot);
Mautic.builderContents.find('[data-slot-focus]').remove();
- var focus = mQuery('').attr('data-slot-focus', true);
- slot.append(focus);
+ mQuery(slot).append(Mautic.getSlotFocus());
});
Mautic.builderContents.on('slot:init', function(event, slot) {
@@ -809,17 +909,17 @@ Mautic.initSlotListeners = function() {
var type = slot.attr('data-slot');
// initialize the drag handle
- var slotToolbar = mQuery('').attr('data-slot-toolbar', true);
- var deleteLink = mQuery('')
- .attr('data-slot-action', 'delete')
- .attr('alt', 'delete')
- .addClass('btn btn-delete btn-default');
- deleteLink.appendTo(slotToolbar);
+ var slotToolbar = Mautic.getSlotToolbar();
+ var deleteLink = Mautic.getSlotDeleteLink();
+ var focus = Mautic.getSlotFocus();
- Mautic.builderContents.find('[data-slot-focus]').remove();
- var focus = mQuery('').attr('data-slot-focus', true);
+ slot.hover(function(e) {
+ e.stopPropagation();
+
+ // Get new copies of the focus, toolbar
+ slotToolbar = Mautic.getSlotToolbar();
+ focus = Mautic.getSlotFocus();
- slot.hover(function() {
if (Mautic.sortActive) {
// don't activate while sorting
@@ -828,6 +928,14 @@ Mautic.initSlotListeners = function() {
slot.append(focus);
deleteLink.click(function(e) {
+ // if slot is DEC, delete it from the outside form
+ if (type == 'dynamicContent') {
+ var dynConId = slot.attr('data-param-dec-id');
+ dynConId = '#emailform_dynamicContent_' + dynConId;
+ var dynConTarget = parent.mQuery(dynConId);
+ // clear name, so the slot:destroy event deletes it
+ dynConTarget.find(dynConId + '_tokenName').val('');
+ }
slot.trigger('slot:destroy', {slot: slot, type: type});
mQuery.each(Mautic.builderSlots, function(i, slotParams) {
if (slotParams.slot.is(slot)) {
@@ -858,7 +966,12 @@ Mautic.initSlotListeners = function() {
focus.remove();
});
- slot.on('click', function() {
+ slot.on('click', function(e) {
+ e.stopPropagation();
+
+ Mautic.deleteCodeModeSlot();
+ Mautic.removeAddVariantButton();
+
var clickedSlot = mQuery(this);
// Trigger the slot:change event
@@ -883,25 +996,61 @@ Mautic.initSlotListeners = function() {
// Update form in the Customize tab to the form of the focused slot type
var focusType = clickedSlot.attr('data-slot');
var focusForm = mQuery(parent.mQuery('script[data-slot-type-form="'+focusType+'"]').html());
- parent.mQuery('#slot-form-container').html(focusForm);
+ var slotFormContainer = parent.mQuery('#slot-form-container');
+
+ if (focusType == 'dynamicContent') {
+ var nff = Mautic.initEmailDynamicContentSlotEdit(clickedSlot);
+ // replace focusForm
+ nff.insertAfter(focusForm.find('#slot_dynamiccontent > div.has-error'));
+ }
+
+ slotFormContainer.html(focusForm);
// Prefill the form field values with the values from slot attributes if any
parent.mQuery.each(clickedSlot.get(0).attributes, function(i, attr) {
- var attrPrefix = 'data-param-';
var regex = /data-param-(.*)/;
var match = regex.exec(attr.name);
if (match !== null) {
+
focusForm.find('input[type="text"][data-slot-param="'+match[1]+'"]').val(attr.value);
- focusForm.find('input[type="radio"][data-slot-param="'+match[1]+'"][value="'+attr.value+'"]').prop('checked', 1);
+
+ var selectField = focusForm.find('select[data-slot-param="'+match[1]+'"]');
+
+ if (selectField.length) {
+ selectField.val(attr.value)
+ }
+
+ // URL fields
+ var urlField = focusForm.find('input[type="url"][data-slot-param="'+match[1]+'"]');
+
+ if (urlField.length) {
+ urlField.val(attr.value);
+ }
+
+ // Number fields
+ var numberField = focusForm.find('input[type="number"][data-slot-param="'+match[1]+'"]');
+
+ if (numberField.length) {
+ numberField.val(attr.value);
+ }
+
+ var radioField = focusForm.find('input[type="radio"][data-slot-param="'+match[1]+'"][value="'+attr.value+'"]');
+
+ if (radioField.length) {
+ radioField.parent('.btn').addClass('active');
+ radioField.attr('checked', true);
+ }
}
});
- focusForm.on('keyup', function(e) {
+ focusForm.on('keyup change', function(e) {
var field = mQuery(e.target);
// Store the slot settings as attributes
- clickedSlot.attr('data-param-'+field.attr('data-slot-param'), field.val());
+ if (field.attr('data-slot-param')) {
+ clickedSlot.attr('data-param-'+field.attr('data-slot-param'), field.val());
+ }
// Trigger the slot:change event
clickedSlot.trigger('slot:change', {slot: clickedSlot, field: field, type: focusType});
@@ -921,16 +1070,41 @@ Mautic.initSlotListeners = function() {
// Initialize the color picker
focusForm.find('input[data-toggle="color"]').each(function() {
- parent.Mautic.activateColorPicker(this);
+ parent.Mautic.activateColorPicker(this, {
+ change: function() {
+ var field = mQuery(this);
+
+ // Store the slot settings as attributes
+ clickedSlot.attr('data-param-'+field.attr('data-slot-param'), field.val());
+
+ clickedSlot.trigger('slot:change', {slot: clickedSlot, field: field, type: focusType});
+ }
+ });
});
- focusForm.find('textarea.editor').each(function() {
+ // initialize code mode slots
+ if ('codemode' === type) {
+ Mautic.codeMode = true;
+ var element = focusForm.find('#slot_codemode_content')[0];
+ if (element) {
+ Mautic.builderCodeMirror = CodeMirror.fromTextArea(element, {
+ lineNumbers: true,
+ mode: 'htmlmixed',
+ extraKeys: {"Ctrl-Space": "autocomplete"},
+ lineWrapping: true,
+ });
+ Mautic.builderCodeMirror.getDoc().setValue(slot.find('#codemodeHtmlContainer').html());
+ Mautic.keepPreviewAlive(null, slot.find('#codemodeHtmlContainer'));
+ }
+ }
+
+ focusForm.find('textarea.editor').each(function () {
var theEditor = this;
var slotHtml = parent.mQuery('').html(clickedSlot.html());
slotHtml.find('[data-slot-focus]').remove();
slotHtml.find('[data-slot-toolbar]').remove();
- var buttons = ['undo', 'redo', '|', 'bold', 'italic', 'underline', 'paragraphFormat', 'fontFamily', 'fontSize', 'color', 'align', 'formatOL', 'formatUL', 'quote', 'clearFormatting', 'insertLink', 'insertImage', 'insertGatedVideo', 'insertTable', 'html', 'fullscreen']
+ var buttons = ['undo', 'redo', '|', 'bold', 'italic', 'underline', 'paragraphFormat', 'fontFamily', 'fontSize', 'color', 'align', 'formatOL', 'formatUL', 'quote', 'clearFormatting', 'token', 'insertLink', 'insertImage', 'insertGatedVideo', 'insertTable', 'html', 'fullscreen'];
var builderEl = parent.mQuery('.builder');
@@ -945,39 +1119,40 @@ Mautic.initSlotListeners = function() {
toolbarButtonsMD: buttons,
toolbarButtonsSM: buttons,
toolbarButtonsXS: buttons,
+ toolbarSticky: false,
linkList: [], // TODO push here the list of tokens from Mautic.getPredefinedLinks
imageEditButtons: ['imageReplace', 'imageAlign', 'imageRemove', 'imageAlt', 'imageSize', '|', 'imageLink', 'linkOpen', 'linkEdit', 'linkRemove']
};
- // init AtWho in a froala editor
- parent.mQuery(this).on('froalaEditor.initialized', function (e, editor) {
- parent.Mautic.initAtWho(editor.$el, parent.Mautic.getBuilderTokensMethod(), editor);
-
- Mautic.setTextSlotEditorStyle(editor.$el, clickedSlot);
- });
+ // prevent overriding variant content in editor
+ if (focusType !== 'dynamicContent') {
+ // init AtWho in a froala editor
+ parent.mQuery(this).on('froalaEditor.initialized', function (e, editor) {
+ parent.Mautic.initAtWho(editor.$el, parent.Mautic.getBuilderTokensMethod(), editor);
+ Mautic.setTextSlotEditorStyle(editor.$el, clickedSlot);
+ });
+ }
parent.mQuery(this).on('froalaEditor.contentChanged', function (e, editor) {
- var slotHtml = mQuery('').append(parent.mQuery(theEditor).froalaEditor('html.get'));
- clickedSlot.html(slotHtml.html());
+ var slotHtml = mQuery('').append(editor.html.get());
+ // replace DEC with content from the first editor
+ if (!(focusType == 'dynamicContent' && mQuery(this).attr('id').match(/filters/))) {
+ clickedSlot.html(slotHtml.html());
+ }
});
- parent.mQuery(this).val(slotHtml.html());
+
+ // replace only the first editor content for DEC
+ if (!(focusType == 'dynamicContent' && mQuery(this).attr('id').match(/filters/))) {
+ parent.mQuery(this).val(slotHtml.html());
+ }
parent.mQuery(this).froalaEditor(parent.mQuery.extend({}, Mautic.basicFroalaOptions, froalaOptions));
});
- parent.mQuery('#slot-form-container').on('change.minicolors', function(e, hex) {
- var field = mQuery(e.target);
-
- // Store the slot settings as attributes
- clickedSlot.attr('data-param-'+field.attr('data-slot-param'), field.val());
-
- // Trigger the slot:change event
- clickedSlot.trigger('slot:change', {slot: clickedSlot, field: field, type: focusType});
- });
});
// Initialize different slot types
- if (type === 'image') {
+ if (type === 'image' || type === 'imagecaption' || type === 'imagecard') {
var image = slot.find('img');
// fix of badly destroyed image slot
image.removeAttr('data-froala.editor');
@@ -987,46 +1162,164 @@ Mautic.initSlotListeners = function() {
});
// Init Froala editor
- image.froalaEditor(mQuery.extend({}, Mautic.basicFroalaOptions, {
+ var froalaOptions = mQuery.extend({}, Mautic.basicFroalaOptions, {
linkList: [], // TODO push here the list of tokens from Mautic.getPredefinedLinks
imageEditButtons: ['imageReplace', 'imageAlign', 'imageAlt', 'imageSize', '|', 'imageLink', 'linkOpen', 'linkEdit', 'linkRemove'],
useClasses: false
}
- ));
+ );
+ image.froalaEditor(froalaOptions);
} else if (type === 'button') {
slot.find('a').click(function(e) {
e.preventDefault();
});
+ } else if (type === 'dynamicContent') {
+ if (slot.html().match(/__dynamicContent__/)) {
+ var decs = mQuery('[data-slot="dynamicContent"]');
+ var ids = mQuery.map(decs, function(e){return mQuery(e).attr('data-param-dec-id');})
+ var maxId = Math.max.apply(Math, ids);
+ if (isNaN(maxId) || Number.NEGATIVE_INFINITY == maxId) maxId = 0;
+ slot.attr('data-param-dec-id', maxId + 1);
+ slot.html('Dynamic Content');
+ Mautic.createNewDynamicContentItem(parent.mQuery);
+ }
}
// Store the slot to a global var
Mautic.builderSlots.push({slot: slot, type: type});
});
+ Mautic.getPredefinedLinks = function(callback) {
+ var linkList = [];
+ Mautic.getTokens(Mautic.getBuilderTokensMethod(), function(tokens) {
+ if (tokens.length) {
+ mQuery.each(tokens, function(token, label) {
+ if (token.startsWith('{pagelink=') ||
+ token.startsWith('{assetlink=') ||
+ token.startsWith('{webview_url') ||
+ token.startsWith('{unsubscribe_url')) {
+
+ linkList.push({
+ text: label,
+ href: token
+ });
+ }
+ });
+ }
+ return callback(linkList);
+ });
+ };
+
Mautic.builderContents.on('slot:change', function(event, params) {
// Change some slot styles when the values are changed in the slot edit form
var fieldParam = params.field.attr('data-slot-param');
+ var type = params.type;
+
+ if (type !== "dynamicContent") {
+ Mautic.removeAddVariantButton();
+ }
+
+ Mautic.clearSlotFormError(fieldParam);
+
if (fieldParam === 'padding-top' || fieldParam === 'padding-bottom') {
params.slot.css(fieldParam, params.field.val() + 'px');
+ } else if ('glink' === fieldParam || 'flink' === fieldParam || 'tlink' === fieldParam) {
+ params.slot.find('#'+fieldParam).attr('href', params.field.val());
} else if (fieldParam === 'href') {
- params.slot.find('a').attr('href', params.field.val());
+ params.slot.find('a').eq(0).attr('href', params.field.val());
} else if (fieldParam === 'link-text') {
- params.slot.find('a').text(params.field.val());
+ params.slot.find('a').eq(0).text(params.field.val());
} else if (fieldParam === 'float') {
var values = ['left', 'center', 'right'];
params.slot.find('a').parent().attr('align', values[params.field.val()]);
+ } else if (fieldParam === 'caption') {
+ params.slot.find('figcaption').text(params.field.val());
+ } else if (fieldParam === 'cardcaption') {
+ params.slot.find('td.imagecard-caption').text(params.field.val());
+ } else if (fieldParam === 'text-align') {
+ var values = ['left', 'center', 'right'];
+ if (type === 'imagecard') {
+ params.slot.find('.imagecard-caption').css(fieldParam, values[params.field.val()]);
+ } else if (type === 'imagecaption') {
+ params.slot.find('figcaption').css(fieldParam, values[params.field.val()]);
+ }
+ } else if (fieldParam === 'align') {
+ Mautic.builderContents.find('[data-slot-focus]').each( function() {
+ var focusedSlot = mQuery(this).closest('[data-slot]');
+ if (focusedSlot.attr('data-slot') == 'image') {
+ // Deactivate froala toolbar
+ focusedSlot.find('img').each( function() {
+ mQuery(this).froalaEditor('popups.hideAll');
+ });
+ Mautic.builderContents.find('.fr-image-resizer.fr-active').removeClass('fr-active');
+ }
+ });
+
+ var values = ['left', 'center', 'right'];
+ if ('socialfollow' === type) {
+ params.slot.find('div.socialfollow').css('text-align', values[params.field.val()]);
+ } else if ('imagecaption' === type) {
+ params.slot.find('figure').css('text-align', values[params.field.val()]);
+ } else if ('imagecard' === type) {
+ params.slot.find('td.imagecard-image').css('text-align', values[params.field.val()]);
+ } else {
+ params.slot.find('img').closest('div').css('text-align', values[params.field.val()]);
+ }
} else if (fieldParam === 'button-size') {
+ var bg_clr = params.slot.attr('data-param-background-color');
var values = [
- {padding: '10px 13px', fontSize: '14px'},
- {padding: '15px 20px', fontSize: '20px'},
- {padding: '22px 30px', fontSize: '30px'}
+ {borderWidth: '10px 20px', padding: '0', fontSize: '14px', borderColor : bg_clr, borderStyle: 'solid'},
+ {borderWidth: '20px 23px', padding: '0', fontSize: '20px', borderColor : bg_clr, borderStyle: 'solid'},
+ {borderWidth: '25px 40px', padding: '0', fontSize: '30px', borderColor : bg_clr, borderStyle: 'solid'}
];
params.slot.find('a').css(values[params.field.val()]);
+ } else if (fieldParam === 'caption-color') {
+ params.slot.find('.imagecard-caption').css('background-color', '#' + params.field.val());
} else if (fieldParam === 'background-color') {
- params.slot.find('a').css(fieldParam, '#'+params.field.val());
- params.slot.find('a').attr('background', '#'+params.field.val());
+ if ('imagecard' === type) {
+ params.slot.find('.imagecard').css(fieldParam, '#' + params.field.val());
+ } else {
+ params.slot.find('a').css(fieldParam, '#' + params.field.val());
+ params.slot.find('a').attr('background', '#' + params.field.val());
+ params.slot.find('a').css('border-color', '#' + params.field.val());
+ }
} else if (fieldParam === 'color') {
- params.slot.find('a').css(fieldParam, '#'+params.field.val());
+ if ('imagecard' === type) {
+ params.slot.find('.imagecard-caption').css(fieldParam, '#' + params.field.val());
+ } else if ('imagecaption' === type) {
+ params.slot.find('figcaption').css(fieldParam, '#' + params.field.val());
+ } else {
+ params.slot.find('a').css(fieldParam, '#' + params.field.val());
+ }
+ } else if (/gatedvideo/.test(fieldParam)) {
+ // Handle gatedVideo replacements
+ var toInsert = fieldParam.split('-')[1];
+ var insertVal = params.field.val();
+
+ if (toInsert === 'url') {
+ var videoProvider = Mautic.getVideoProvider(insertVal);
+
+ if (videoProvider == null) {
+ Mautic.slotFormError(fieldParam, 'Please enter a valid YouTube, Vimeo, or MP4 url.');
+ } else {
+ params.slot.find('source')
+ .attr('src', insertVal)
+ .attr('type', videoProvider);
+ }
+ } else if (toInsert === 'gatetime') {
+ params.slot.find('video').attr('data-gate-time', insertVal);
+ } else if (toInsert === 'formid') {
+ params.slot.find('video').attr('data-form-id', insertVal);
+ } else if (toInsert === 'height') {
+ params.slot.find('video').attr('height', insertVal);
+ } else if (toInsert === 'width') {
+ params.slot.find('video').attr('width', insertVal);
+ }
+ } else if (fieldParam === 'separator-color') {
+ params.slot.find('hr').css('border-color', '#' + params.field.val());
+ } else if (fieldParam === 'separator-thickness') {
+ var sep_color = params.slot.attr('data-param-separator-color');
+ params.slot.find('hr').css('border', params.field.val() + 'px solid #'+ sep_color);
}
if (params.type == 'text') {
@@ -1035,13 +1328,43 @@ Mautic.initSlotListeners = function() {
});
Mautic.builderContents.on('slot:destroy', function(event, params) {
- if (params.type === 'image') {
+ // reattach DEC
+ if (typeof Mautic.activeDEC !== 'undefined') {
+ var element = Mautic.activeDEC.detach();
+ Mautic.activeDECParent.append(element);
+ }
+
+ if (params.type === 'text') {
+ if (parent.mQuery('#slot_content').length) {
+ parent.mQuery('#slot_content').froalaEditor('destroy');
+ parent.mQuery('#slot_content').find('.atwho-inserted').atwho('destroy');
+ }
+ } else if (params.type === 'image') {
+ Mautic.deleteCodeModeSlot();
+
var image = params.slot.find('img');
if (typeof image !== 'undefined' && image.hasClass('fr-view')) {
image.froalaEditor('destroy');
image.removeAttr('data-froala.editor');
image.removeClass('fr-view');
}
+ } else if (params.type === 'dynamicContent') {
+ Mautic.removeAddVariantButton();
+ // remove new DEC if name is empty
+ var dynConId = params.slot.attr('data-param-dec-id');
+ dynConId = '#emailform_dynamicContent_'+dynConId;
+ if (Mautic.activeDEC.attr('id') === dynConId.substr(1)) {
+ delete Mautic.activeDEC;
+ delete Mautic.activeDECParent;
+ }
+ var dynConTarget = parent.mQuery(dynConId);
+ var dynConName = dynConTarget.find(dynConId+'_tokenName').val();
+ if (dynConName === '') {
+ dynConTarget.find('a.remove-item:first').click();
+ // remove vertical tab in outside form
+ parent.mQuery('.dynamicContentFilterContainer').find('a[href=' + dynConId + ']').parent().remove();
+ params.slot.remove();
+ }
}
// Remove Symfony toolbar
@@ -1049,6 +1372,67 @@ Mautic.initSlotListeners = function() {
});
};
+Mautic.deleteCodeModeSlot = function() {
+ Mautic.killLivePreview();
+ Mautic.destroyCodeMirror();
+ delete Mautic.codeMode;
+};
+
+Mautic.clearSlotFormError = function(field) {
+ var customizeSlotField = parent.mQuery('#customize-form-container').find('[data-slot-param="'+field+'"]');
+
+ if (customizeSlotField.length) {
+ customizeSlotField.attr('style', '');
+ customizeSlotField.next('[data-error]').remove();
+ }
+};
+
+Mautic.slotFormError = function (field, message) {
+ var customizeSlotField = parent.mQuery('#customize-form-container').find('[data-slot-param="'+field+'"]');
+
+ if (customizeSlotField.length) {
+ customizeSlotField.css('border-color', 'red');
+
+ if (message.length) {
+ var messageContainer = mQuery('')
+ .text(message)
+ .attr('data-error', 'true')
+ .css({
+ color: 'red',
+ padding: '5px 0'
+ });
+
+ messageContainer.insertAfter(customizeSlotField);
+ }
+ }
+};
+
+Mautic.getVideoProvider = function(url) {
+ var providers = [
+ {
+ test_regex: /^.*((youtu.be)|(youtube.com))\/((v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))?\??v?=?([^#\&\?]*).*/,
+ provider: 'video/youtube'
+ },
+ {
+ test_regex: /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/,
+ provider: 'video/vimeo'
+ },
+ {
+ test_regex: /mp4/,
+ provider: 'video/mp4'
+ }
+ ];
+
+ for (var i = 0; i < providers.length; i++) {
+ var vp = providers[i];
+ if (vp.test_regex.test(url)) {
+ return vp.provider;
+ }
+ }
+
+ return null;
+};
+
Mautic.setTextSlotEditorStyle = function(editorEl, slot)
{
// Set the editor CSS to that of the slot
@@ -1068,7 +1452,7 @@ Mautic.setTextSlotEditorStyle = function(editorEl, slot)
wrapper.css(style, overrideStyle);
}
});
-}
+};
Mautic.getSlotStyle = function(slot, styleName, fallback) {
if ('background-color' == styleName) {
@@ -1116,6 +1500,81 @@ Mautic.getBuilderTokensMethod = function() {
return method;
};
+Mautic.prepareDynamicContentBlocksForBuilder = function(builderHtml) {
+ for (var token in Mautic.builderTokens) {
+ // If this is a dynamic content token
+ if (Mautic.builderTokens.hasOwnProperty(token) && /\{dynamic/.test(token)) {
+ var defaultContent = Mautic.convertDynamicContentTokenToSlot(token);
+
+ builderHtml = builderHtml.replace(token, defaultContent);
+ }
+ }
+
+ return builderHtml;
+};
+
+Mautic.convertDynamicContentTokenToSlot = function(token) {
+ var dynConData = Mautic.getDynamicContentDataForToken(token);
+
+ if (dynConData) {
+ return '
'+dynConData.content+'
';
+ }
+
+ return token;
+};
+
+Mautic.getDynamicContentDataForToken = function(token) {
+ var dynConName = /\{dynamiccontent="(.*)"}/.exec(token)[1];
+ var dynConTabs = parent.mQuery('#dynamicContentTabs');
+ var dynConTarget = dynConTabs.find('a:contains("'+dynConName+'")').attr('href');
+ var dynConContainer = parent.mQuery(dynConTarget);
+
+ if (dynConContainer.html()) {
+ var dynConContent = dynConContainer.find(dynConTarget+'_content');
+
+ if (dynConContent.hasClass('editor')) {
+ dynConContent = dynConContent.froalaEditor('html.get');
+ } else {
+ dynConContent = dynConContent.html();
+ }
+
+ return {
+ id: parseInt(dynConTarget.replace(/[^0-9]/g, '')),
+ content: dynConContent
+ };
+ }
+
+ return null;
+};
+
+Mautic.convertDynamicContentSlotsToTokens = function (builderHtml) {
+ var dynConSlots = mQuery(builderHtml).find('[data-slot="dynamicContent"]');
+
+ if (dynConSlots.length) {
+ dynConSlots.each(function(i) {
+ var $this = mQuery(this);
+ // if ($this.parents('[data-slot]').length == 0) return; // prevent affecting standalone DEC slots
+ var dynConId = $this.attr('data-param-dec-id');
+
+ dynConId = '#emailform_dynamicContent_'+dynConId;
+
+ var dynConTarget = mQuery(dynConId);
+ var dynConName = dynConTarget.find(dynConId+'_tokenName').val();
+ var dynConToken = '{dynamiccontent="'+dynConName+'"}';
+
+ builderHtml = builderHtml.replace(this.outerHTML, dynConToken);
+
+ // If it's still wrapped in an atwho, remove that
+ if ($this.parent().hasClass('atwho-inserted')) {
+ var toReplace = $this.parent('.atwho-inserted').get(0).outerHTML;
+
+ builderHtml = builderHtml.replace(toReplace, dynConToken);
+ }
+ });
+ }
+
+ return builderHtml;
+};
Mautic.getPredefinedLinks = function(callback) {
var linkList = [];
@@ -1136,7 +1595,7 @@ Mautic.getPredefinedLinks = function(callback) {
}
return callback(linkList);
});
-}
+};
// Init inside the builder's iframe
mQuery(function() {
diff --git a/app/bundles/CoreBundle/Assets/js/6.forms.js b/app/bundles/CoreBundle/Assets/js/6.forms.js
index c4f7b259406..2a99ac2f160 100644
--- a/app/bundles/CoreBundle/Assets/js/6.forms.js
+++ b/app/bundles/CoreBundle/Assets/js/6.forms.js
@@ -398,7 +398,7 @@ Mautic.updateEntitySelect = function (response) {
if (mQueryParent(el).prop('disabled')) {
mQueryParent(el).prop('disabled', false);
- var emptyOption = mQuery('');
+ var emptyOption = mQuery('');
} else {
if (mQueryParent(el + ' option[value=""]').length) {
emptyOption = mQueryParent(el + ' option[value=""]').clone();
@@ -417,7 +417,7 @@ Mautic.updateEntitySelect = function (response) {
var optgroup = el + ' optgroup[label="'+response.group+'"]';
if (mQueryParent(optgroup).length) {
// update option when new option equal with option item in group.
- var firstOptionGroups = mQueryParent(optgroup);
+ var firstOptionGroups = mQueryParent(optgroup);
var isUpdateOption = false;
firstOptionGroups.each(function () {
var firstOptions = mQuery(this).children();
@@ -432,7 +432,7 @@ Mautic.updateEntitySelect = function (response) {
if (!isUpdateOption) {
//the optgroup exist so append to it
- mQueryParent(optgroup).append(newOption);
+ mQueryParent(optgroup).append(newOption);
}
} else {
//create the optgroup
@@ -534,6 +534,17 @@ Mautic.removeFormListOption = function (el) {
mQuery(sortableDiv).remove();
};
+/**
+ * Creates a select option element with a name and label
+ * @param value
+ * @param label
+ */
+Mautic.createOption = function (value, label) {
+ return mQuery('')
+ .attr('value', value)
+ .text(label);
+}
+
/**
* Updates operator select and value input format based on selected field and operator
*
@@ -564,26 +575,33 @@ Mautic.updateFieldOperatorValue = function(field, action) {
'id': valueField.attr('id'),
'name': valueField.attr('name'),
'autocomplete': valueField.attr('autocomplete'),
- 'value': valueField.attr('value')
+ 'value': valueField.val()
};
if (mQuery('#'+fieldPrefix+'value_chosen').length) {
- mQuery('#'+fieldPrefix+'value').chosen('destroy');
+ valueFieldAttrs['value'] = '';
+ valueField.chosen('destroy');
}
- if (!mQuery.isEmptyObject(response.options)) {
+ if (!mQuery.isEmptyObject(response.options) && response.fieldType !== 'number') {
var newValueField = mQuery('')
.attr('class', valueFieldAttrs['class'])
.attr('id', valueFieldAttrs['id'])
.attr('name', valueFieldAttrs['name'])
.attr('autocomplete', valueFieldAttrs['autocomplete'])
.attr('value', valueFieldAttrs['value']);
- mQuery.each(response.options, function(optionKey, optionVal) {
- var option = mQuery("")
- .attr('value', optionKey)
- .text(optionVal);
- newValueField.append(option);
+ mQuery.each(response.options, function(value, optgroup) {
+ if (typeof optgroup === 'object') {
+ var optgroupEl = mQuery('').attr('label', value);
+ mQuery.each(optgroup, function(optVal, label) {
+ optgroupEl.append(Mautic.createOption(optVal, label))
+ });
+ newValueField.append(optgroupEl);
+ } else {
+ newValueField.append(Mautic.createOption(value, optgroup));
+ }
});
+ newValueField.val(valueFieldAttrs['value']);
valueField.replaceWith(newValueField);
Mautic.activateChosenSelect(newValueField);
@@ -597,6 +615,7 @@ Mautic.updateFieldOperatorValue = function(field, action) {
.attr('value', valueFieldAttrs['value']);
if (response.disabled) {
+ newValueField.attr('value', '');
newValueField.prop('disabled', true);
}
@@ -608,17 +627,18 @@ Mautic.updateFieldOperatorValue = function(field, action) {
}
if (!mQuery.isEmptyObject(response.operators)) {
+ var operatorField = mQuery('#'+fieldPrefix+'operator');
+
if (mQuery('#'+fieldPrefix+'operator_chosen').length) {
- mQuery('#'+fieldPrefix+'operator').chosen('destroy');
+ operatorField.chosen('destroy');
}
- var operatorField = mQuery('#'+fieldPrefix+'operator');
var operatorFieldAttrs = {
'class': operatorField.attr('class'),
'id': operatorField.attr('id'),
'name': operatorField.attr('name'),
'autocomplete': operatorField.attr('autocomplete'),
- 'value': operatorField.attr('value')
+ 'value': operatorField.val()
};
var newOperatorField = mQuery('')
@@ -629,11 +649,9 @@ Mautic.updateFieldOperatorValue = function(field, action) {
.attr('value', operatorFieldAttrs['value'])
.attr('onchange', 'Mautic.updateLeadFieldValues(this)');
mQuery.each(response.operators, function(optionKey, optionVal) {
- var option = mQuery("")
- .attr('value', optionKey)
- .text(optionVal);
- newOperatorField.append(option);
+ newOperatorField.append(Mautic.createOption(optionKey, optionVal));
});
+ newOperatorField.val(operatorField.val());
operatorField.replaceWith(newOperatorField);
Mautic.activateChosenSelect(newOperatorField);
}
diff --git a/app/bundles/CoreBundle/Assets/js/9.modals.js b/app/bundles/CoreBundle/Assets/js/9.modals.js
index 0428d6b245f..1db66d30a56 100644
--- a/app/bundles/CoreBundle/Assets/js/9.modals.js
+++ b/app/bundles/CoreBundle/Assets/js/9.modals.js
@@ -83,8 +83,14 @@ Mautic.loadAjaxModal = function (target, route, method, header, footer, preventD
mQuery('body').removeClass('noscroll');
+ var response = {};
+ if (Mautic.modalMauticContent) {
+ response.mauticContent = Mautic.modalMauticContent;
+ delete Mautic.modalMauticContent;
+ }
+
//unload
- Mautic.onPageUnload(target);
+ Mautic.onPageUnload(target, response);
Mautic.resetModal(target);
});
@@ -229,23 +235,23 @@ Mautic.processModalContent = function (response, target) {
//activate content specific stuff
Mautic.onPageLoad(target, response, true);
-
+ Mautic.modalMauticContent = false;
if (response.closeModal) {
mQuery('body').removeClass('noscroll');
mQuery(target).modal('hide');
- if (!response.updateModalContent) {
- Mautic.onPageUnload(target, response);
- }
-
if (response.mauticContent) {
if (typeof Mautic[response.mauticContent + "OnLoad"] == 'function') {
- if (typeof Mautic.loadedContent[response.mauticContent] == 'undefined') {
- Mautic.loadedContent[response.mauticContent] = true;
- Mautic[response.mauticContent + "OnLoad"](target, response);
- }
+ Mautic[response.mauticContent + "OnLoad"](target, response);
}
}
+
+ if (!response.updateModalContent) {
+ Mautic.onPageUnload(target, response);
+ }
+ } else {
+ // Note for the hidden event
+ Mautic.modalMauticContent = response.mauticContent ? response.mauticContent : false;
}
}
};
diff --git a/app/bundles/CoreBundle/Assets/js/libraries/2.jquery.js b/app/bundles/CoreBundle/Assets/js/libraries/2.jquery.js
index ff76f1d90aa..987546412a0 100644
--- a/app/bundles/CoreBundle/Assets/js/libraries/2.jquery.js
+++ b/app/bundles/CoreBundle/Assets/js/libraries/2.jquery.js
@@ -5514,10 +5514,14 @@
var rnumnonpx = new RegExp("^(" + pnum + ")(?!px)[a-z%]+$", "i");
var getStyles = function (elem) {
- return elem.ownerDocument.defaultView.getComputedStyle(elem, null);
+ // prevent Uncaught error: call to getComputedStyle on undefined
+ if (elem.ownerDocument && elem.ownerDocument.defaultView)
+ return elem.ownerDocument.defaultView.getComputedStyle(elem, null);
+ else if (window.getComputedStyle)
+ return getComputedStyle(elem, null);
+ return {};
};
-
function curCSS(elem, name, computed) {
var width, minWidth, maxWidth, ret,
style = elem.style;
@@ -5617,8 +5621,11 @@
docElem.appendChild(container);
var divStyle = window.getComputedStyle(div, null);
- pixelPositionVal = divStyle.top !== "1%";
- boxSizingReliableVal = divStyle.width === "4px";
+ // fix for Uncaught TypeError: Cannot read property 'top' of undefined
+ if (divStyle) {
+ pixelPositionVal = divStyle.top !== "1%";
+ boxSizingReliableVal = divStyle.width === "4px";
+ }
docElem.removeChild(container);
}
diff --git a/app/bundles/CoreBundle/Assets/js/libraries/4j.codemirror.js b/app/bundles/CoreBundle/Assets/js/libraries/4j.codemirror.js
index 5439299b397..b0079f39b96 100644
--- a/app/bundles/CoreBundle/Assets/js/libraries/4j.codemirror.js
+++ b/app/bundles/CoreBundle/Assets/js/libraries/4j.codemirror.js
@@ -3586,12 +3586,16 @@
}
if (clickInGutter(cm, e)) return;
var start = posFromMouse(cm, e);
- window.focus();
+ var codeMode = typeof Mautic != 'undefined' && Mautic.codeMode === true;
+
+ if (!codeMode) {
+ window.focus();
+ }
switch (e_button(e)) {
case 1:
// #3261: make sure, that we're not starting a second selection
- if (cm.state.selectingText)
+ if (cm.state.selectingText && !codeMode)
cm.state.selectingText(e);
else if (start)
leftButtonDown(cm, e, start);
diff --git a/app/bundles/CoreBundle/Assets/js/libraries/froala/plugins/token.js b/app/bundles/CoreBundle/Assets/js/libraries/froala/plugins/token.js
index 07e130d3f34..1edf1c41c06 100644
--- a/app/bundles/CoreBundle/Assets/js/libraries/froala/plugins/token.js
+++ b/app/bundles/CoreBundle/Assets/js/libraries/froala/plugins/token.js
@@ -34,26 +34,83 @@
'use strict';
+ $.extend($.FE.DEFAULTS, {
+ tokens: {},
+ tokenSelection: false,
+ tokenDefaultSelection: 'Token'
+ });
+
$.FE.PLUGINS.token = function (editor) {
- function apply () {
- editor.html.insert(' {');
- editor.$el.keyup();
+ function apply (val) {
+ editor.html.insert(val);
}
return {
apply: apply
}
- }
+ };
// Register the token command.
$.FE.RegisterCommand('token', {
- title: 'Insert token',
- callback: function (cmd) {
- this.token.apply();
- },
- plugin: 'token'
- })
+ type: 'dropdown',
+ displaySelection: function (editor) {
+ return editor.opts.tokenSelection;
+ },
+ defaultSelection: function (editor) {
+ return editor.opts.tokenDefaultSelection;
+ },
+ displaySelectionWidth: 120,
+ html: function () {
+ var c = '
';
+ var self = this;
+ var method = location.href.match(/email/i)? 'email:getBuilderTokens' : 'page:getBuilderTokens';
+ Mautic.getTokens(method, function(tokens) {
+ mQuery.each(tokens, function(k,v){
+ if (k.match(/assetlink=/i)){
+ delete tokens[k];
+ var nv = v.replace('a:', '');
+ k = '' + nv + '';
+ tokens[k] = nv;
+ }
+ if (k.match(/pagelink=/i)){
+ delete tokens[k];
+ var nv = v.replace('a:', '');
+ k = '' + nv + '';
+ tokens[k] = nv;
+ }
+ if (k.match(/contactfield=company/i) && !v.match(/company/i)){
+ tokens[k] = 'Company ' + v;
+ }
+ });
+ self.opts.tokens = tokens;
+ });
+ var options = this.opts.tokens;
+ var k, keys = [];
+ for (k in options) {
+ if (options.hasOwnProperty(k)) {
+ keys.push(k);
+ }
+ }
+ keys.sort();
+ for (var i = 0; i < keys.length; i++) {
+ var val = keys[i];
+ var str = '
_BADGE_
';
+ var badge = (val.match(/page link/i))? str.replace(/_BADGE_/, 'page') : (val.match(/asset link/i))? str.replace(/_BADGE_/, 'asset') : (val.match(/form=/i))? str.replace(/_BADGE_/,'form') : (val.match(/focus=/i))? str.replace(/_BADGE_/,'focus') : (val.match(/dynamiccontent=/i))? str.replace(/_BADGE_/,'dynamic') : '';
+ var title = options[val];
+ if (title.length>24) title = title.substr(0, 24) + '...';
+ c += '
Lorem ipsum dolor sit amet, alterum definitiones eu est. Eos no scripta voluptatum necessitatibus, ea his case movet. Porro vivendo delicatissimi ea qui, in usu aliquam consulatu conclusionemque. Eu vel mazim periculis consequat, quo fastidii salutandi eu, et sed nibh exerci consequuntur. Cu diam efficiendi eum, pri eu delenit insolens. Usu nihil oporteat an, et stet mucius vix, ex nostro assueverit mel.
-
-
-
-
-
Lorem ipsum dolor sit amet, alterum definitiones eu est. Eos no scripta voluptatum necessitatibus, ea his case movet. Porro vivendo delicatissimi ea qui, in usu aliquam consulatu conclusionemque. Eu vel mazim periculis consequat, quo fastidii salutandi eu, et sed nibh exerci consequuntur. Cu diam efficiendi eum, pri eu delenit insolens. Usu nihil oporteat an, et stet mucius vix, ex nostro assueverit mel.
-
-
-
-
-
Lorem ipsum dolor sit amet, alterum definitiones eu est. Eos no scripta voluptatum necessitatibus, ea his case movet. Porro vivendo delicatissimi ea qui, in usu aliquam consulatu conclusionemque. Eu vel mazim periculis consequat, quo fastidii salutandi eu, et sed nibh exerci consequuntur. Cu diam efficiendi eum, pri eu delenit insolens. Usu nihil oporteat an, et stet mucius vix, ex nostro assueverit mel.
-
+
+
+
+
+
+
Lorem ipsum dolor sit amet, alterum definitiones eu est. Eos no scripta voluptatum necessitatibus, ea his case movet. Porro vivendo delicatissimi ea qui, in usu aliquam consulatu conclusionemque. Eu vel mazim periculis consequat, quo fastidii salutandi eu, et sed nibh exerci consequuntur. Cu diam efficiendi eum, pri eu delenit insolens. Usu nihil oporteat an, et stet mucius vix, ex nostro assueverit mel.
+
+
+
+
+
+
+
+
+
Lorem ipsum dolor sit amet, alterum definitiones eu est. Eos no scripta voluptatum necessitatibus, ea his case movet. Porro vivendo delicatissimi ea qui, in usu aliquam consulatu conclusionemque. Eu vel mazim periculis consequat, quo fastidii salutandi eu, et sed nibh exerci consequuntur. Cu diam efficiendi eum, pri eu delenit insolens. Usu nihil oporteat an, et stet mucius vix, ex nostro assueverit mel.
+
+
+
+
+
+
+
+
+
Lorem ipsum dolor sit amet, alterum definitiones eu est. Eos no scripta voluptatum necessitatibus, ea his case movet. Porro vivendo delicatissimi ea qui, in usu aliquam consulatu conclusionemque. Eu vel mazim periculis consequat, quo fastidii salutandi eu, et sed nibh exerci consequuntur. Cu diam efficiendi eum, pri eu delenit insolens. Usu nihil oporteat an, et stet mucius vix, ex nostro assueverit mel.
Lorem ipsum dolor sit amet, alterum definitiones eu est. Eos no scripta voluptatum necessitatibus, ea his case movet. Porro vivendo delicatissimi ea qui, in usu aliquam consulatu conclusionemque. Eu vel mazim periculis consequat, quo fastidii salutandi eu, et sed nibh exerci consequuntur. Cu diam efficiendi eum, pri eu delenit insolens. Usu nihil oporteat an, et stet mucius vix, ex nostro assueverit mel.
-
-
-
-
-
Lorem ipsum dolor sit amet, alterum definitiones eu est. Eos no scripta voluptatum necessitatibus, ea his case movet. Porro vivendo delicatissimi ea qui, in usu aliquam consulatu conclusionemque. Eu vel mazim periculis consequat, quo fastidii salutandi eu, et sed nibh exerci consequuntur. Cu diam efficiendi eum, pri eu delenit insolens. Usu nihil oporteat an, et stet mucius vix, ex nostro assueverit mel.
-
+
+
+
+
+
+
Lorem ipsum dolor sit amet, alterum definitiones eu est. Eos no scripta voluptatum necessitatibus, ea his case movet. Porro vivendo delicatissimi ea qui, in usu aliquam consulatu conclusionemque. Eu vel mazim periculis consequat, quo fastidii salutandi eu, et sed nibh exerci consequuntur. Cu diam efficiendi eum, pri eu delenit insolens. Usu nihil oporteat an, et stet mucius vix, ex nostro assueverit mel.
+
+
+
+
+
+
+
+
+
Lorem ipsum dolor sit amet, alterum definitiones eu est. Eos no scripta voluptatum necessitatibus, ea his case movet. Porro vivendo delicatissimi ea qui, in usu aliquam consulatu conclusionemque. Eu vel mazim periculis consequat, quo fastidii salutandi eu, et sed nibh exerci consequuntur. Cu diam efficiendi eum, pri eu delenit insolens. Usu nihil oporteat an, et stet mucius vix, ex nostro assueverit mel.
+
+
+
+
diff --git a/app/bundles/CoreBundle/Views/Slots/button.html.php b/app/bundles/CoreBundle/Views/Slots/button.html.php
index 8d26411ff48..35865c1339f 100644
--- a/app/bundles/CoreBundle/Views/Slots/button.html.php
+++ b/app/bundles/CoreBundle/Views/Slots/button.html.php
@@ -1,5 +1,4 @@
-
+
+
I want this!
-
+
\ No newline at end of file
diff --git a/app/bundles/CoreBundle/Views/Slots/codemode.html.php b/app/bundles/CoreBundle/Views/Slots/codemode.html.php
new file mode 100644
index 00000000000..df0af135675
--- /dev/null
+++ b/app/bundles/CoreBundle/Views/Slots/codemode.html.php
@@ -0,0 +1,15 @@
+
+
- errors($form); ?>
-
- children as $child): ?>
-
-
-
-
- row($child); ?>
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/bundles/PluginBundle/Views/FormTheme/Integration/fields_row.html.php b/app/bundles/PluginBundle/Views/FormTheme/Integration/fields_row.html.php
new file mode 100644
index 00000000000..e66116ab532
--- /dev/null
+++ b/app/bundles/PluginBundle/Views/FormTheme/Integration/fields_row.html.php
@@ -0,0 +1,184 @@
+
+
+
+
+
+ trans($specialInstructions); ?>
+
+
+ vars['errors'])): ?>
+
+ vars['errors'] as $error): ?>
+
getMessage() ?>
+
+
+
+
+
+
+
trans('mautic.plugins.integration.fields'); ?>
+
+
+
+
trans('mautic.plugins.mautic.fields'); ?>
+
+ children as $child):
+ $selected = false;
+ ?>
+ vars['attr']['data-required']); ?>
+
+
\ No newline at end of file
diff --git a/app/bundles/ReportBundle/Views/FormTheme/Report/_report_aggregators_entry_function_row.html.php b/app/bundles/ReportBundle/Views/FormTheme/Report/_report_aggregators_entry_function_row.html.php
new file mode 100644
index 00000000000..a79b8bef5c6
--- /dev/null
+++ b/app/bundles/ReportBundle/Views/FormTheme/Report/_report_aggregators_entry_function_row.html.php
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/bundles/ReportBundle/Views/FormTheme/Report/_report_aggregators_row.html.php b/app/bundles/ReportBundle/Views/FormTheme/Report/_report_aggregators_row.html.php
new file mode 100644
index 00000000000..436d9233d24
--- /dev/null
+++ b/app/bundles/ReportBundle/Views/FormTheme/Report/_report_aggregators_row.html.php
@@ -0,0 +1,13 @@
+vars['errors']);
+$feedbackClass = (!empty($hasErrors)) ? ' has-error' : '';
+?>
+
+
+ widget($form) ?>
+ errors($form) ?>
+
+
+
+
+
\ No newline at end of file
diff --git a/app/bundles/ReportBundle/Views/FormTheme/Report/aggregator_row.html.php b/app/bundles/ReportBundle/Views/FormTheme/Report/aggregator_row.html.php
new file mode 100644
index 00000000000..4ca591a6a7c
--- /dev/null
+++ b/app/bundles/ReportBundle/Views/FormTheme/Report/aggregator_row.html.php
@@ -0,0 +1,10 @@
+vars['label_attr']['class'])) ? 'control-label' : $form->vars['label_attr']['class'];
+?>
+
');Mautic.processUpdate(container,step+1,state);}else{mQuery('td[id=update-step-downloading-status]').append(mQuery('').addClass('pull-right fa fa-warning text-danger'));mQuery('div[id=main-update-panel]').removeClass('panel-default').addClass('panel-danger');mQuery('div#main-update-panel div.panel-body').prepend('
'+response.message+'
');}}},error:function(request,textStatus,errorThrown){Mautic.processAjaxError(request,textStatus,errorThrown);}});break;case 3:mQuery.ajax({showLoadingBar:true,url:mauticAjaxUrl+'?action=core:updateExtractPackage',dataType:'json',success:function(response){if(response.redirect){window.location=response.redirect;}else{mQuery('td[id=update-step-extracting-status]').html(''+response.stepStatus+'');if(response.success){mQuery('td[id=update-step-extracting-status]').append(mQuery('').addClass('pull-right fa fa-check text-success'));mQuery('#updateTable tbody').append('
'+response.nextStep+'
'+response.nextStepStatus+'
');Mautic.processUpdate(container,step+1,state);}else{mQuery('td[id=update-step-extracting-status]').append(mQuery('').addClass('pull-right fa fa-warning text-danger'));mQuery('div[id=main-update-panel]').removeClass('panel-default').addClass('panel-danger');mQuery('div#main-update-panel div.panel-body').prepend('
'+response.message+'
');}}},error:function(request,textStatus,errorThrown){Mautic.processAjaxError(request,textStatus,errorThrown);}});break;case 4:mQuery.ajax({showLoadingBar:true,url:baseUrl+'upgrade/upgrade.php?task=moveBundles&updateState='+state,dataType:'json',success:function(response){if(response.redirect){window.location=response.redirect;}else{mQuery('td[id=update-step-moving-status]').html(''+response.stepStatus+'');if(response.error){mQuery('td[id=update-step-moving-status]').append(mQuery('').addClass('pull-right fa fa-warning text-danger'));mQuery('div[id=main-update-panel]').removeClass('panel-default').addClass('panel-danger');mQuery('div#main-update-panel div.panel-body').prepend('
'+response.message+'
');}else if(response.complete){mQuery('td[id=update-step-moving-status]').append(mQuery('').addClass('pull-right fa fa-spinner fa-spin'));Mautic.processUpdate(container,step+1,response.updateState);}else{mQuery('td[id=update-step-moving-status]').append(mQuery('').addClass('pull-right fa fa-spinner fa-spin'));Mautic.processUpdate(container,step,response.updateState);}}},error:function(request,textStatus,errorThrown){Mautic.processAjaxError(request,textStatus,errorThrown);}});break;case 5:mQuery.ajax({showLoadingBar:true,url:baseUrl+'upgrade/upgrade.php?task=moveCore&updateState='+state,dataType:'json',success:function(response){if(response.redirect){window.location=response.redirect;}else{mQuery('td[id=update-step-moving-status]').html(''+response.stepStatus+'');if(response.error){mQuery('td[id=update-step-moving-status]').append(mQuery('').addClass('pull-right fa fa-warning text-danger'));mQuery('div[id=main-update-panel]').removeClass('panel-default').addClass('panel-danger');mQuery('div#main-update-panel div.panel-body').prepend('
'+response.message+'
');}else if(response.complete){mQuery('td[id=update-step-moving-status]').append(mQuery('').addClass('pull-right fa fa-spinner fa-spin'));Mautic.processUpdate(container,step+1,response.updateState);}else{mQuery('td[id=update-step-moving-status]').append(mQuery('').addClass('pull-right fa fa-spinner fa-spin'));Mautic.processUpdate(container,step,response.updateState);}}},error:function(request,textStatus,errorThrown){Mautic.processAjaxError(request,textStatus,errorThrown);}});break;case 6:mQuery.ajax({showLoadingBar:true,url:baseUrl+'upgrade/upgrade.php?task=moveVendors&updateState='+state,dataType:'json',success:function(response){if(response.redirect){window.location=response.redirect;}else{mQuery('td[id=update-step-moving-status]').html(''+response.stepStatus+'');if(response.error){mQuery('td[id=update-step-moving-status]').append(mQuery('').addClass('pull-right fa fa-warning text-danger'));mQuery('div[id=main-update-panel]').removeClass('panel-default').addClass('panel-danger');mQuery('div#main-update-panel div.panel-body').prepend('
'+response.message+'
');}else if(response.complete){mQuery('td[id=update-step-moving-status]').append(mQuery('').addClass('pull-right fa fa-check text-success'));mQuery('#updateTable tbody').append('
'+response.nextStep+'
'+response.nextStepStatus+'
');Mautic.processUpdate(container,step+1,response.updateState);}else{mQuery('td[id=update-step-moving-status]').append(mQuery('').addClass('pull-right fa fa-spinner fa-spin'));Mautic.processUpdate(container,step,response.updateState);}}},error:function(request,textStatus,errorThrown){Mautic.processAjaxError(request,textStatus,errorThrown);}});break;case 7:mQuery.ajax({showLoadingBar:true,url:baseUrl+'upgrade/upgrade.php?task=clearCache&updateState='+state,dataType:'json',success:function(response){if(response.redirect){window.location=response.redirect;}else{mQuery('td[id=update-step-cache-status]').html(''+response.stepStatus+'');if(response.error){mQuery('td[id=update-step-cache-status]').append(mQuery('').addClass('pull-right fa fa-warning text-danger'));mQuery('div[id=main-update-panel]').removeClass('panel-default').addClass('panel-danger');mQuery('div#main-update-panel div.panel-body').prepend('
'+response.message+'
');}else if(response.complete){mQuery('td[id=update-step-cache-status]').append(mQuery('').addClass('pull-right fa fa-check text-success'));mQuery('#updateTable tbody').append('
'+response.nextStep+'
'+response.nextStepStatus+'
');Mautic.processUpdate(container,step+1,response.updateState);}else{mQuery('td[id=update-step-cache-status]').append(mQuery('').addClass('pull-right fa fa-spinner fa-spin'));Mautic.processUpdate(container,step,response.updateState);}}},error:function(request,textStatus,errorThrown){Mautic.processAjaxError(request,textStatus,errorThrown);}});break;case 8:mQuery.ajax({showLoadingBar:true,url:mauticAjaxUrl+'?action=core:updateDatabaseMigration&finalize=1',dataType:'json',success:function(response){if(response.redirect){window.location=response.redirect;}else{mQuery('td[id=update-step-database-status]').html(''+response.stepStatus+'');if(response.success){mQuery('td[id=update-step-database-status]').append(mQuery('').addClass('pull-right fa fa-check text-success'));mQuery('#updateTable tbody').append('
'+response.nextStep+'
'+response.nextStepStatus+'
');Mautic.processUpdate(container,step+1,state);}else{mQuery('td[id=update-step-database-status]').append(mQuery('').addClass('pull-right fa fa-warning text-danger'));mQuery('div[id=main-update-panel]').removeClass('panel-default').addClass('panel-danger');mQuery('div#main-update-panel div.panel-body').prepend('