From 94fabdb532d971880c99f74ea7ff5f3d33ed3f01 Mon Sep 17 00:00:00 2001 From: Jordan Ryan Date: Sun, 28 May 2017 13:54:32 -0700 Subject: [PATCH 1/9] Forked Form prefill JS and applied to Mautic use case. Thanks to @moreonion for work on webform_prefill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now, you can prepopulate an embedded Mautic form in Drupal page by passing form keys as Hash params in the URL e.g. /your-form-url#p:email=jordan@facetinteractive.com&city=manhattan beach # Use Case Our use case was we needed to prepopulate fields when progressive profiling for users when they submit an initial newsletter subscription form. # Redirecting from Other Forms If you’re passing inputs to the url via another Mautic form , you will need to HTML encode the param inputs in the URL Redirect Link, e.g. /your-form-url#p:email={formfield=email|true}&city={formfield=city|true}, where |true is the boolean for turning on HTML encoding. Included code from the fork may be excessive. I made the smallest number of changes possible in order to get this working. @TODO - Look at LocalStorage instead of Session Storage @TODO - Perhaps consider incorporating this in Mautic Core instead of Drupal module so that prefill functionality is consistent across all embeds. --- js/mauticform-prefill.js | 263 +++++++++++++++++++++++++++++++++++++++ mautic.info | 3 +- mautic.module | 3 + 3 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 js/mauticform-prefill.js diff --git a/js/mauticform-prefill.js b/js/mauticform-prefill.js new file mode 100644 index 0000000..30a951b --- /dev/null +++ b/js/mauticform-prefill.js @@ -0,0 +1,263 @@ +/* + * Credit to + * Original JS work forked from drupal.org/webform_prefill module to mautic use case + */ + +(function ($) { + + var SessionStorage = function (pfx) { + this.pfx = pfx; + }; + + SessionStorage.prototype.browserSupport = function () { + // this is taken from modernizr. + var mod = 'modernizr'; + try { + localStorage.setItem(mod, mod); + localStorage.removeItem(mod); + return true; + } catch (e) { + return false; + } + }; + + SessionStorage.prototype.setItem = function (key, value) { + return sessionStorage.setItem(this.pfx + ':' + key, JSON.stringify(value)); + }; + + SessionStorage.prototype.getItem = function (key) { + try { + var v = sessionStorage.getItem(this.pfx + ':' + key); + if (v !== null) { + v = JSON.parse(v); + } + return v; + } + catch (e) { + return null; + } + }; + + SessionStorage.prototype.getFirst = function (keys) { + // Get value from all possible keys. + var value = null; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + value = prefillStore.getItem(key); + if (value) { + return value; + } + } + return null; + }; + + var prefillStore = new SessionStorage('mauticform_prefill') + + + var FormValList = function ($e, name_attr) { + this.$e = $e; + this.name_attr = name_attr || 'name'; + this.name = $e.attr(this.name_attr); + this.cache_key = this.pfxMap(this.name); + }; + + FormValList.prototype.getVal = function () { + var $e = this.$e; + var type = $e.attr('type'); + if (type == 'checkbox' || type == 'radio') { + $e = $e.closest('form').find('input:' + type + '[' + this.name_attr + '="' + this.name + '"]:checked'); + } + var val = $e.val() || []; + return (val.constructor === Array) ? val : [val]; + }; + + FormValList.prototype.getAllByName = function () { + return this.$e.closest('form') + .find('[' + this.name_attr + '="' + this.name + '"]') + .filter('input:checkbox, input:radio, select[multiple]'); + }; + + FormValList.prototype.pfxMap = function (x) { + return 'l:' + x; + } + + var FormValSingle = function ($e, name_attr) { + this.$e = $e; + this.name_attr = name_attr || 'name'; + this.name = $e.attr(this.name_attr); + this.cache_key = this.pfxMap(this.name); + }; + + FormValSingle.prototype.getVal = function () { + return this.$e.val(); + }; + + FormValSingle.prototype.getAllByName = function () { + return this.$e.closest('form') + .find('[' + this.name_attr + '="' + this.name + '"]') + .not('input:checkbox, input:radio, select[multiple]'); + }; + + FormValSingle.prototype.pfxMap = function (x) { + return 's:' + x; + } + + Drupal.behaviors.mautic = {}; + + Drupal.behaviors.mautic.elementFactory = function ($e, name_attr) { + name_attr = name_attr || 'data-form-key'; + var type = $e.attr('type'); + if (type === 'checkbox' || type === 'radio' || $e.is('select[multiple]')) { + return new FormValList($e, name_attr); + } + return new FormValSingle($e, name_attr); + }; + + Drupal.behaviors.mautic.formKey = function ($e) { + var name = $e.attr('name'); + if (!name) { + return; + } + if ($e.attr('type') === 'checkbox') { + name = name.slice(0, -(2 + $e.attr('value').length)); + } + return name.slice(name.lastIndexOf('[') + 1, -1); + }; + + Drupal.behaviors.mautic._keys = function (name) { + if (name in this.settings.map) { + return this.settings.map[name]; + } + return [name]; + }; + + Drupal.behaviors.mautic.keys = function (val) { + return $.map(this._keys(val.name), val.pfxMap); + }; + + Drupal.behaviors.mautic.attachToInputs = function ($wrapper) { + var self = this; + var $inputs = $wrapper.find('input, select, textarea').not(function (i, element) { + // Exclude file elements. We can't prefill those. + if ($(element).attr('type') === 'file') { + return true; + } + // Check nearest include and exclude-wrapper. + var $exclude = $(element).closest('.mauticform-prefill-exclude'); + var $include = $(element).closest('.mauticform-prefill-include'); + if ($exclude.length > 0) { + // Exclude unless there is an include-wrapper inside the exclude wrapper. + return $include.length <= 0 || $.contains($include.get(), $exclude.get()); + } + return false; + }); + + $inputs.not('[data-form-key]').each(function () { + var $e = $(this); + var fk = self.formKey($e); + if (fk) { + $e.attr('data-form-key', fk); + } + }); + + var done = {}; + $inputs.each(function () { + var e = self.elementFactory($(this)); + if (!(e.cache_key in done)) { + done[e.cache_key] = true; + + // Get value from all possible keys. + var value = prefillStore.getFirst(self.keys(e)); + if (value !== null) { + e.getAllByName().val(value); + } + } + }); + + $inputs.on('change', function () { + var e = self.elementFactory($(this)); + if (!e.name) { + return; + } + prefillStore.setItem(e.cache_key, e.getVal()); + }); + }; + + Drupal.behaviors.mautic.attach = function (context, settings) { + if (!prefillStore.browserSupport()) { + return; + } + + if (typeof this.settings === 'undefined') { + var hash = window.location.hash.substr(1); + if (hash) { + var new_hash = this.readUrlVars(hash); + if (new_hash !== hash) { + window.location.hash = '#' + new_hash; + } + } + if ('mauticform_prefill' in Drupal.settings) { + this.settings = Drupal.settings.mautic; + } + else { + this.settings = {map: {}}; + } + } + + this.attachToInputs($('.mauticform_wrapper', context)); + }; + + /** + * Parse the hash from the hash string and clean them from the string. + * + * The hash string is first split into parts using a semi-colon";" as a + * separator. Each part that contains prefill variables (with the "p:"-prefix) + * is then removed. + * + * All prefill-values are stored into the session store. + */ + Drupal.behaviors.mautic.readUrlVars = function (hash, store) { + hash = hash || window.location.hash.substr(1); + if (!hash) { + return ''; + } + store = store || prefillStore; + var vars = {}, key, value, p, parts, new_parts = []; + parts = hash.split(';'); + for (var j = 0; j < parts.length; j++) { + var part_has_prefill_vars = false; + var part = parts[j]; + // Parts starting with p: are used for pre-filling. + if (part.substr(0, 2) === 'p:') { + var hashes = part.substr(2).split('&'); + for (var i = 0; i < hashes.length; i++) { + p = hashes[i].indexOf('='); + key = hashes[i].substring(0, p); + // Backwards compatibility strip p: prefixes from keys. + if (key.substr(0, 2) === 'p:') { + key = key.substr(2); + } + value = hashes[i].substring(p + 1); + // Prepare values to be set as list values. + if (!(key in vars)) { + vars[key] = []; + } + vars[key].push(value); + // Set string values directly. + store.setItem('s:' + key, value); + } + } + else { + new_parts.push(part); + } + } + + // Finally set all list values. + $.each(vars, function (key, value) { + store.setItem('l:' + key, value); + }); + + return new_parts.join(';'); + }; + +}(jQuery)); diff --git a/mautic.info b/mautic.info index e765e61..57ccd00 100644 --- a/mautic.info +++ b/mautic.info @@ -4,4 +4,5 @@ package = Mautic version = 7.x-1.0 core = 7.x files[] = mautic.module -configure = admin/config/mautic \ No newline at end of file +dependencies[] = jquery_update +configure = admin/config/mautic diff --git a/mautic.module b/mautic.module index c673ee1..64193cb 100644 --- a/mautic.module +++ b/mautic.module @@ -53,6 +53,9 @@ function mautic_page_alter(&$page) { $script .= '})(window,document,"script","' . $mautic_base_url . '/mtc.js","mt");'; $script .= 'mt("send", "pageview");'; drupal_add_js($script, array('scope' => 'header', 'type' => 'inline', 'requires_jquery' => FALSE)); + + // Prefill Helper + drupal_add_js(drupal_get_path('module', 'mautic') . '/js/mauticform-prefill.js', array('scope' => 'footer', 'requires_jquery' => FALSE)); } } From aae5372cca0f9ff07ad755621db0fab797cc62c6 Mon Sep 17 00:00:00 2001 From: Jordan Ryan Date: Sun, 28 May 2017 14:01:31 -0700 Subject: [PATCH 2/9] Standardizing Location of Mautic Admin Page against standards for other third party tools in Drupal --- mautic.info | 2 +- mautic.module | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mautic.info b/mautic.info index e765e61..70a7400 100644 --- a/mautic.info +++ b/mautic.info @@ -4,4 +4,4 @@ package = Mautic version = 7.x-1.0 core = 7.x files[] = mautic.module -configure = admin/config/mautic \ No newline at end of file +configure = admin/config/system/mautic diff --git a/mautic.module b/mautic.module index c673ee1..4d616d0 100644 --- a/mautic.module +++ b/mautic.module @@ -102,8 +102,8 @@ function _mautic_dynamic_content_replace($matches) { function mautic_menu() { $items = array(); - $items['admin/config/mautic'] = array( - 'title' => 'Mautic URL', + $items['admin/config/system/mautic'] = array( + 'title' => 'Mautic', 'description' => 'Configuration for Current posts module', 'page callback' => 'drupal_get_form', 'page arguments' => array('mautic_form'), From e512922a8380adb84c2aeb395eb3f7f22939c4c0 Mon Sep 17 00:00:00 2001 From: Jordan Ryan Date: Sun, 28 May 2017 14:11:59 -0700 Subject: [PATCH 3/9] Updates to documentation --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 92789c7..25db8a6 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,8 @@ To add dynamic web content, insert this shortcode: `[mautic type="content" slot="slot_name"] [/mautic]` where `slot_name` is the dynamic content slot token name you gave in the campaign. + +### Prefill Mautic forms + +You can prefill an embedded Mautic form in Drupal page by passing form keys as Hash params in the URL. +e.g. /your-form-url#p:email=email@somedomain.com&firstname=John&lastname=Doe From 45d08d647c345892e3ca5d8bc4790d856d2e982f Mon Sep 17 00:00:00 2001 From: Jordan Ryan Date: Sun, 28 May 2017 15:05:54 -0700 Subject: [PATCH 4/9] Help Text line return needs to be encapsulated in double quote string. --- mautic.module | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mautic.module b/mautic.module index c673ee1..5ea30bb 100644 --- a/mautic.module +++ b/mautic.module @@ -77,7 +77,8 @@ function mautic_filter_shortcodes_process($text, $filter, $format, $langcode, $c } function mautic_filter_shortcodes_tips($filter, $format, $long) { - return t('[mauticform id=1] - Insert a Mautic form with given ID.\n[mautic type="content" slot="slot_name"]Default Content[/mautic] - Insert dynamic web content.'); + return t("[mauticform id=1] - Insert a Mautic form with given ID.\n + [mautic type=\"content\" slot=\"slot_name\"]Default Content[/mautic] - Insert dynamic web content."); } function _mautic_form_replace($matches) { From fde783050bc38f188c88b9cc87ed477a86923aa3 Mon Sep 17 00:00:00 2001 From: Jordan Ryan Date: Sun, 28 May 2017 15:23:09 -0700 Subject: [PATCH 5/9] HTML break instead of \n --- mautic.module | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mautic.module b/mautic.module index 5ea30bb..e2c5e60 100644 --- a/mautic.module +++ b/mautic.module @@ -77,7 +77,7 @@ function mautic_filter_shortcodes_process($text, $filter, $format, $langcode, $c } function mautic_filter_shortcodes_tips($filter, $format, $long) { - return t("[mauticform id=1] - Insert a Mautic form with given ID.\n + return t("[mauticform id=1] - Insert a Mautic form with given ID.
[mautic type=\"content\" slot=\"slot_name\"]Default Content[/mautic] - Insert dynamic web content."); } From 400481bd2595180df1143f0d7a536fab7cfb1a32 Mon Sep 17 00:00:00 2001 From: Jordan Ryan Date: Sun, 28 May 2017 15:51:19 -0700 Subject: [PATCH 6/9] Focus Items should be shortcode embeddable --- mautic.module | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/mautic.module b/mautic.module index e2c5e60..0d012aa 100644 --- a/mautic.module +++ b/mautic.module @@ -73,12 +73,14 @@ function mautic_filter_info() { function mautic_filter_shortcodes_process($text, $filter, $format, $langcode, $cache, $cache_id) { $text = preg_replace_callback('/\[mauticform id=(.*)\]/', '_mautic_form_replace', $text); $text = preg_replace_callback('/\[mautic type="content" slot="(.*)"\](.*)\[\/mautic\]/', '_mautic_dynamic_content_replace', $text); + $text = preg_replace_callback('/\[mauticfocusitem id=(.*)\]/', '_mautic_focus_item_replace', $text); return $text; } function mautic_filter_shortcodes_tips($filter, $format, $long) { return t("[mauticform id=1] - Insert a Mautic form with given ID.
- [mautic type=\"content\" slot=\"slot_name\"]Default Content[/mautic] - Insert dynamic web content."); + [mautic type=\"content\" slot=\"slot_name\"]Default Content[/mautic] - Insert dynamic web content.
+ [mauticfocusitem id=1] - Insert Mautic Focus Item with given ID."); } function _mautic_form_replace($matches) { @@ -93,10 +95,18 @@ function _mautic_dynamic_content_replace($matches) { $dc_slot_name = $matches[1]; $internal_dom = $matches[2]; $args = array('@slot_name' => $dc_slot_name, '@dom' => $internal_dom); - $dynamic_web_content = format_string('
@dom
', $args); + $dynamic_web_content = format_string('
@dom
', $args); return $dynamic_web_content; } +function _mautic_focus_item_replace($matches) { + $focus_id = $matches[1]; + $mautic_base_url = variable_get('mautic_base_url', ''); + $args = array('@mautic_base_url' => $mautic_base_url, '@focus_id' => $focus_id); + $script = format_string('', $args); + return $script; +} + /** * Implements hook_menu(). */ From 942c03c2ac8f1960831811239e1039f9b23e83a1 Mon Sep 17 00:00:00 2001 From: Jordan Ryan Date: Sun, 28 May 2017 15:54:38 -0700 Subject: [PATCH 7/9] Documentation update for Focus Items --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 92789c7..a8ee97e 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,11 @@ To add dynamic web content, insert this shortcode: `[mautic type="content" slot="slot_name"] [/mautic]` where `slot_name` is the dynamic content slot token name you gave in the campaign. + +To add Focus Item, insert this shortcode + +`[mauticfocusitem id=ID]` + +ID is the identifier of the Focus item you want to embed. + +To control Focus Item visibility, use Blocks to render your Focus item on specific paths. From ffebe742d9518ce3825da6f96d286ebb555a647c Mon Sep 17 00:00:00 2001 From: Jordan Ryan Date: Mon, 19 Jun 2017 22:53:31 -0700 Subject: [PATCH 8/9] Appropriate fix to trim trailing slash on domain. --- mautic.module | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mautic.module b/mautic.module index c673ee1..692769c 100644 --- a/mautic.module +++ b/mautic.module @@ -46,7 +46,7 @@ function mautic_help($path, $arg) { */ function mautic_page_alter(&$page) { if( _mautic_visibility_pages()) { - $mautic_base_url = variable_get('mautic_base_url', ''); + $mautic_base_url = trim(variable_get('mautic_base_url', ''), " \t\n\r\0\x0B/"); $script = '(function(w,d,t,u,n,a,m){w["MauticTrackingObject"]=n;'; $script .= 'w[n]=w[n]||function(){(w[n].q=w[n].q||[]).push(arguments)},a=d.createElement(t),'; $script .= 'm=d.getElementsByTagName(t)[0];a.async=1;a.src=u;m.parentNode.insertBefore(a,m)'; @@ -82,7 +82,7 @@ function mautic_filter_shortcodes_tips($filter, $format, $long) { function _mautic_form_replace($matches) { $form_id = $matches[1]; - $mautic_base_url = variable_get('mautic_base_url', ''); + $mautic_base_url = trim(variable_get('mautic_base_url', ''), " \t\n\r\0\x0B/"); $args = array('@mautic_base_url' => $mautic_base_url, '@form_id' => $form_id); $script = format_string('', $args); return $script; @@ -122,7 +122,7 @@ function mautic_form() { '#title' => t('Mautic URL'), '#default_value' => variable_get('mautic_base_url', ''), '#size' => 60, - '#description' => t("Your mautic base url."), + '#description' => t("Your mautic base url, e.g. https://mydomain.com. No trailing slash required."), '#required' => TRUE, ); From 53f3415725489df920247f80d72689c5c65414c2 Mon Sep 17 00:00:00 2001 From: Jordan Ryan Date: Mon, 19 Jun 2017 23:03:28 -0700 Subject: [PATCH 9/9] Fix Help Text per recent changes --- mautic.module | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mautic.module b/mautic.module index c673ee1..6b6c66e 100644 --- a/mautic.module +++ b/mautic.module @@ -30,9 +30,9 @@ function mautic_help($path, $arg) { $output .= '

' . t('Features') . '

'; $output .= '
'; $output .= '
' . t('Mautic Tracking') . '
'; - $output .= '
' . t('Tracking image works right after you enable the module, insert Base URL and save the plugin. That means it will insert 1 px gif image loaded from your Mautic instance. You can check HTML source code (CTRL + U) of your Drupal website to make sure the plugin works. You should be able to find something like this:
<img src="http://yourmautic.com/mtracking.gif" />
There will be probably longer URL query string at the end of the tracking image URL. It is encoded additional data about the page (title, url, referrer, language).') . '
'; + $output .= '
' . t('Tracking script works right after you enable the module, insert Base URL and save the plugin. That means it will insert a Javascript tracking script from your Mautic instance. You can check HTML source code (CTRL + U) of your Drupal website to make sure the plugin works. You should be able to find the script by searching for:
"http://yourmautic.com/mtc.js"
There will be probably longer URL query string at the end of the tracking script URL. It is encoded additional data about the page (title, url, referrer, language).') . '
'; $output .= '
' . t('Form embed') . '
'; - $output .= '
' . t("To embed a Mautic form into Drupal content, insert this code snippet:
{mauticform id=ID width=300px height=300px}
ID is the identifier of the Mautic form you want to embed. You can see the ID of the form in the URL of the form detail. For example for www.yourmautic.com/forms/view/1, ID = 1.") . '
'; + $output .= '
' . t("To embed a Mautic form into Drupal content, insert this code snippet:
[mauticform id=ID]
ID is the identifier of the Mautic form you want to embed. You can see the ID of the form in the URL of the form detail. For example for www.yourmautic.com/forms/view/1, ID = 1.") . '
'; $output .= '
'; $output .= '

' . t('Mautic-Drupal module documentation, issue reporting', array('@doc' => 'https://github.com/mautic/mautic-drupal/tree/7.x#readme', '@issues' => 'https://github.com/mautic/mautic-drupal/issues')) . '

';