DuracellTomi's Google Tag Manager for WordPress - Version 1.14.2

Version Description

  • Fixed: undefined google_business_vertical
  • Fixed: missing product price in product impression data
  • Fixed: better compatibility with cache plugins and lazy load functionalities
  • Fixed: Added optional chaining operator to form move tracker code
Download this release

Release Info

Developer duracelltomi
Plugin Icon 128x128 DuracellTomi's Google Tag Manager for WordPress
Version 1.14.2
Comparing to
See all releases

Code changes from version 1.14.1 to 1.14.2

duracelltomi-google-tag-manager-for-wordpress.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
  /*
3
  Plugin Name: Google Tag Manager for Wordpress
4
- Version: 1.14.1
5
  Plugin URI: https://gtm4wp.com/
6
  Description: The first Google Tag Manager plugin for WordPress with business goals in mind
7
  Author: Thomas Geiger
@@ -13,7 +13,7 @@ WC requires at least: 3.2
13
  WC tested up to: 5.9.0
14
  */
15
 
16
- define( 'GTM4WP_VERSION', '1.14.1' );
17
  define( 'GTM4WP_PATH', plugin_dir_path( __FILE__ ) );
18
 
19
  global $gtp4wp_plugin_url, $gtp4wp_plugin_basename;
1
  <?php
2
  /*
3
  Plugin Name: Google Tag Manager for Wordpress
4
+ Version: 1.14.2
5
  Plugin URI: https://gtm4wp.com/
6
  Description: The first Google Tag Manager plugin for WordPress with business goals in mind
7
  Author: Thomas Geiger
13
  WC tested up to: 5.9.0
14
  */
15
 
16
+ define( 'GTM4WP_VERSION', '1.14.2' );
17
  define( 'GTM4WP_PATH', plugin_dir_path( __FILE__ ) );
18
 
19
  global $gtp4wp_plugin_url, $gtp4wp_plugin_basename;
js/admin-tabcreator.js CHANGED
@@ -1,80 +1,82 @@
1
- jQuery( function() {
2
- var admintabs = [];
3
- var adminsubtabsdata = window.adminsubtabs || {};
4
- var adminsubtabs = [];
5
-
6
- jQuery( '#wpbody form h2' ).each(function( i ) {
7
- admintabs.push( '<a class="nav-tab" href="#">' + jQuery(this).text() + '</a>' );
8
-
9
- jQuery(this)
10
- .remove();
11
-
12
- if ( adminsubtabsdata[ i ] ) {
13
- var _subtabs = [];
14
- var _startrow = 0;
15
-
16
- for (var j in adminsubtabsdata[ i ] ) {
17
- _subtabs.push( '<a href="#" data-formtableid="' + i + '" data-startrow="' + _startrow + '" data-endrow="' + (_startrow + adminsubtabsdata[ i ][ j ].numitems) + '">' + adminsubtabsdata[ i ][ j ].tabtext + '</a>' );
18
- _startrow += adminsubtabsdata[ i ][ j ].numitems;
19
- }
20
-
21
- if ( _subtabs.length > 0 ) {
22
- adminsubtabs.push( '<ul class="adminsubtabs" id="adminsubtabs' + i + '"><li>' + _subtabs.join('</li><li>') + '</li></ul>' );
23
- }
24
- }
25
- });
26
-
27
- jQuery("#wpbody form[action='options.php']")
28
- .prepend( adminsubtabs.join('') )
29
- .prepend( '<h2 class="nav-tab-wrapper">' + admintabs.join('') + '</h2>' );
30
-
31
- jQuery( '.adminsubtabs li a' )
32
- .on( 'click', function() {
33
- var jqthis = jQuery(this);
34
-
35
- jqthis
36
- .parent()
37
- .parent()
38
- .find( 'a' )
39
- .removeClass( 'subtab-active' );
40
-
41
- jqthis
42
- .addClass( 'subtab-active' );
43
-
44
- jQuery( '.form-table:eq(' + jqthis.data( 'formtableid' ) + ') tr' )
45
- .hide()
46
- .slice( jqthis.data('startrow'), jqthis.data('endrow') )
47
- .show();
48
-
49
- return false;
50
- });
51
-
52
- jQuery( '.nav-tab-wrapper a' )
53
- .on( "click", function() {
54
- jQuery( '.nav-tab-wrapper a.nav-tab-active' )
55
- .removeClass( "nav-tab-active" );
56
-
57
- jQuery( '#wpbody form .tabinfo,#wpbody form .form-table,.adminsubtabs' )
58
- .hide();
59
-
60
- var tabindex = jQuery(this)
61
- .addClass( 'nav-tab-active' )
62
- .index();
63
-
64
- jQuery( '#wpbody form .tabinfo:eq(' + tabindex + '),#wpbody form .form-table:eq(' + tabindex + ')' )
65
- .show();
66
-
67
- jQuery( '#wpbody #adminsubtabs' + tabindex + ':not(.subtab-activated)' )
68
- .find( 'a:first' )
69
- .trigger( 'click' );
70
-
71
- jQuery( '#wpbody #adminsubtabs' + tabindex )
72
- .addClass( 'subtab-activated' )
73
- .show();
74
-
75
- return false;
76
- });
77
-
78
- jQuery( '.nav-tab-wrapper a:first' )
79
- .trigger( 'click' );
80
- });
 
 
1
+ jQuery( function() {
2
+ var admintabs = [];
3
+ var adminsubtabsdata = window.adminsubtabs || {};
4
+ var adminsubtabs = [];
5
+
6
+ jQuery( '#wpbody form h2' ).each(function( i ) {
7
+ admintabs.push( '<a class="nav-tab" href="#">' + jQuery(this).text() + '</a>' );
8
+
9
+ jQuery(this)
10
+ .remove();
11
+
12
+ if ( adminsubtabsdata[ i ] ) {
13
+ var _subtabs = [];
14
+ var _startrow = 0;
15
+
16
+ for (var j in adminsubtabsdata[ i ] ) {
17
+ _subtabs.push( '<a href="#" data-formtableid="' + i + '" data-startrow="' + _startrow + '" data-endrow="' + (_startrow + adminsubtabsdata[ i ][ j ].numitems) + '">' + adminsubtabsdata[ i ][ j ].tabtext + '</a>' );
18
+ _startrow += adminsubtabsdata[ i ][ j ].numitems;
19
+ }
20
+
21
+ if ( _subtabs.length > 0 ) {
22
+ adminsubtabs.push( '<ul class="adminsubtabs" id="adminsubtabs' + i + '"><li>' + _subtabs.join('</li><li>') + '</li></ul>' );
23
+ }
24
+ }
25
+ });
26
+
27
+ jQuery("#wpbody form[action='options.php']")
28
+ .prepend( adminsubtabs.join('') )
29
+ .prepend( '<h2 class="nav-tab-wrapper">' + admintabs.join('') + '</h2>' );
30
+
31
+ jQuery( '.adminsubtabs li a' )
32
+ .on( 'click', function() {
33
+ var jqthis = jQuery(this);
34
+
35
+ jqthis
36
+ .parent()
37
+ .parent()
38
+ .find( 'a' )
39
+ .removeClass( 'subtab-active' );
40
+
41
+ jqthis
42
+ .addClass( 'subtab-active' );
43
+
44
+ jQuery( '.form-table:eq(' + jqthis.data( 'formtableid' ) + ') tr' )
45
+ .hide()
46
+ .slice( jqthis.data('startrow'), jqthis.data('endrow') )
47
+ .show();
48
+
49
+ return false;
50
+ });
51
+
52
+ jQuery( '.nav-tab-wrapper a' )
53
+ .on( "click", function() {
54
+ jQuery( '.nav-tab-wrapper a.nav-tab-active' )
55
+ .removeClass( "nav-tab-active" );
56
+
57
+ jQuery( '#wpbody form .tabinfo,#wpbody form .form-table,.adminsubtabs' )
58
+ .hide();
59
+
60
+ var tabindex = jQuery(this)
61
+ .addClass( 'nav-tab-active' )
62
+ .index();
63
+
64
+ jQuery( '#wpbody form .tabinfo:eq(' + tabindex + '),#wpbody form .form-table:eq(' + tabindex + ')' )
65
+ .show();
66
+
67
+ jQuery( '#wpbody #adminsubtabs' + tabindex + ':not(.subtab-activated)' )
68
+ .find( 'a' )
69
+ .first()
70
+ .trigger( 'click' );
71
+
72
+ jQuery( '#wpbody #adminsubtabs' + tabindex )
73
+ .addClass( 'subtab-activated' )
74
+ .show();
75
+
76
+ return false;
77
+ });
78
+
79
+ jQuery( '.nav-tab-wrapper a' )
80
+ .first()
81
+ .trigger( 'click' );
82
+ });
js/froogaloop.js DELETED
@@ -1,259 +0,0 @@
1
- // Init style shamelessly stolen from jQuery http://jquery.com
2
- var Froogaloop = (function() {
3
- // Define a local copy of Froogaloop
4
- function Froogaloop(iframe) {
5
- // The Froogaloop object is actually just the init constructor
6
- return new Froogaloop.fn.init(iframe);
7
- }
8
-
9
- var eventCallbacks = {},
10
- hasWindowEvent = false,
11
- isReady = false,
12
- slice = Array.prototype.slice,
13
- playerOrigin = '*';
14
-
15
- Froogaloop.fn = Froogaloop.prototype = {
16
- element: null,
17
-
18
- init: function(iframe) {
19
- if (typeof iframe === "string") {
20
- iframe = document.getElementById(iframe);
21
- }
22
-
23
- this.element = iframe;
24
-
25
- return this;
26
- },
27
-
28
- /*
29
- * Calls a function to act upon the player.
30
- *
31
- * @param {string} method The name of the Javascript API method to call. Eg: 'play'.
32
- * @param {Array|Function} valueOrCallback params Array of parameters to pass when calling an API method
33
- * or callback function when the method returns a value.
34
- */
35
- api: function(method, valueOrCallback) {
36
- if (!this.element || !method) {
37
- return false;
38
- }
39
-
40
- var self = this,
41
- element = self.element,
42
- target_id = element.id !== '' ? element.id : null,
43
- params = !isFunction(valueOrCallback) ? valueOrCallback : null,
44
- callback = isFunction(valueOrCallback) ? valueOrCallback : null;
45
-
46
- // Store the callback for get functions
47
- if (callback) {
48
- storeCallback(method, callback, target_id);
49
- }
50
-
51
- postMessage(method, params, element);
52
- return self;
53
- },
54
-
55
- /*
56
- * Registers an event listener and a callback function that gets called when the event fires.
57
- *
58
- * @param eventName (String): Name of the event to listen for.
59
- * @param callback (Function): Function that should be called when the event fires.
60
- */
61
- addEvent: function(eventName, callback) {
62
- if (!this.element) {
63
- return false;
64
- }
65
-
66
- var self = this,
67
- element = self.element,
68
- target_id = element.id !== '' ? element.id : null;
69
-
70
-
71
- storeCallback(eventName, callback, target_id);
72
-
73
- // The ready event is not registered via postMessage. It fires regardless.
74
- if (eventName != 'ready') {
75
- postMessage('addEventListener', eventName, element);
76
- } else if (eventName == 'ready' && isReady) {
77
- callback.call(null, target_id);
78
- }
79
-
80
- return self;
81
- },
82
-
83
- /*
84
- * Unregisters an event listener that gets called when the event fires.
85
- *
86
- * @param eventName (String): Name of the event to stop listening for.
87
- */
88
- removeEvent: function(eventName) {
89
- if (!this.element) {
90
- return false;
91
- }
92
-
93
- var self = this,
94
- element = self.element,
95
- target_id = element.id !== '' ? element.id : null,
96
- removed = removeCallback(eventName, target_id);
97
-
98
- // The ready event is not registered
99
- if (eventName != 'ready' && removed) {
100
- postMessage('removeEventListener', eventName, element);
101
- }
102
- }
103
- };
104
-
105
- /**
106
- * Handles posting a message to the parent window.
107
- *
108
- * @param method (String): name of the method to call inside the player. For api calls
109
- * this is the name of the api method (api_play or api_pause) while for events this method
110
- * is api_addEventListener.
111
- * @param params (Object or Array): List of parameters to submit to the method. Can be either
112
- * a single param or an array list of parameters.
113
- * @param target (HTMLElement): Target iframe to post the message to.
114
- */
115
-
116
- function postMessage(method, params, target) {
117
- if (!target.contentWindow.postMessage) {
118
- return false;
119
- }
120
-
121
- var data = JSON.stringify({
122
- method: method,
123
- value: params
124
- });
125
-
126
- target.contentWindow.postMessage(data, playerOrigin);
127
- }
128
-
129
- /**
130
- * Event that fires whenever the window receives a message from its parent
131
- * via window.postMessage.
132
- */
133
-
134
- function onMessageReceived(event) {
135
- var data, method;
136
-
137
- try {
138
- data = JSON.parse(event.data);
139
- method = data.event || data.method;
140
- } catch (e) {
141
- //fail silently... like a ninja!
142
- }
143
-
144
- if (method == 'ready' && !isReady) {
145
- isReady = true;
146
- }
147
-
148
- // Handles messages from the vimeo player only
149
- if (!(/^https?:\/\/player.vimeo.com/).test(event.origin)) {
150
- return false;
151
- }
152
-
153
- if (playerOrigin === '*') {
154
- playerOrigin = event.origin;
155
- }
156
-
157
- var value = data.value,
158
- eventData = data.data,
159
- target_id = target_id === '' ? null : data.player_id,
160
-
161
- callback = getCallback(method, target_id),
162
- params = [];
163
-
164
- if (!callback) {
165
- return false;
166
- }
167
-
168
- if (value !== undefined) {
169
- params.push(value);
170
- }
171
-
172
- if (eventData) {
173
- params.push(eventData);
174
- }
175
-
176
- if (target_id) {
177
- params.push(target_id);
178
- }
179
-
180
- return params.length > 0 ? callback.apply(null, params) : callback.call();
181
- }
182
-
183
-
184
- /**
185
- * Stores submitted callbacks for each iframe being tracked and each
186
- * event for that iframe.
187
- *
188
- * @param eventName (String): Name of the event. Eg. api_onPlay
189
- * @param callback (Function): Function that should get executed when the
190
- * event is fired.
191
- * @param target_id (String) [Optional]: If handling more than one iframe then
192
- * it stores the different callbacks for different iframes based on the iframe's
193
- * id.
194
- */
195
-
196
- function storeCallback(eventName, callback, target_id) {
197
- if (target_id) {
198
- if (!eventCallbacks[target_id]) {
199
- eventCallbacks[target_id] = {};
200
- }
201
- eventCallbacks[target_id][eventName] = callback;
202
- } else {
203
- eventCallbacks[eventName] = callback;
204
- }
205
- }
206
-
207
- /**
208
- * Retrieves stored callbacks.
209
- */
210
-
211
- function getCallback(eventName, target_id) {
212
- if (target_id) {
213
- return eventCallbacks[target_id][eventName];
214
- } else {
215
- return eventCallbacks[eventName];
216
- }
217
- }
218
-
219
- function removeCallback(eventName, target_id) {
220
- if (target_id && eventCallbacks[target_id]) {
221
- if (!eventCallbacks[target_id][eventName]) {
222
- return false;
223
- }
224
- eventCallbacks[target_id][eventName] = null;
225
- } else {
226
- if (!eventCallbacks[eventName]) {
227
- return false;
228
- }
229
- eventCallbacks[eventName] = null;
230
- }
231
-
232
- return true;
233
- }
234
-
235
- function isFunction(obj) {
236
- return !!(obj && obj.constructor && obj.call && obj.apply);
237
- }
238
-
239
- function isArray(obj) {
240
- return toString.call(obj) === '[object Array]';
241
- }
242
-
243
- // Give the init function the Froogaloop prototype for later instantiation
244
- Froogaloop.fn.init.prototype = Froogaloop.fn;
245
-
246
- // Listens for the message event.
247
- // W3C
248
- if (window.addEventListener) {
249
- window.addEventListener('message', onMessageReceived, false);
250
- }
251
- // IE
252
- else {
253
- window.attachEvent('onmessage', onMessageReceived);
254
- }
255
-
256
- // Expose froogaloop to the global object
257
- return (window.Froogaloop = window.$f = Froogaloop);
258
-
259
- })();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/gtm4wp-form-move-tracker.js CHANGED
@@ -1,33 +1,33 @@
1
- document.addEventListener("focusin", function(event) {
2
- const elem = event.target.closest("input,select,textarea,button,meter,progress");
3
- if ( elem ) {
4
- window[ gtm4wp_datalayer_name ].push({
5
- 'event' : 'gtm4wp.formElementEnter',
6
-
7
- 'inputID' : elem.getAttribute("id") || "(no input ID)",
8
- 'inputName' : elem.getAttribute("name") || "(no input name)",
9
- 'inputClass': elem.getAttribute("class") || "(no input class)",
10
-
11
- 'formID' : elem.form.getAttribute("id") || "(no form ID)",
12
- 'formName' : elem.form.getAttribute("name") || "(no form name)",
13
- 'formClass': elem.form.getAttribute("class") || "(no form class)"
14
- });
15
- }
16
- }, false);
17
-
18
- document.addEventListener("focusout", function(event) {
19
- const elem = event.target.closest("input,select,textarea,button,meter,progress");
20
- if ( elem ) {
21
- window[ gtm4wp_datalayer_name ].push({
22
- 'event' : 'gtm4wp.formElementLeave',
23
-
24
- 'inputID' : elem.getAttribute("id") || "(no input ID)",
25
- 'inputName' : elem.getAttribute("name") || "(no input name)",
26
- 'inputClass': elem.getAttribute("class") || "(no input class)",
27
-
28
- 'formID' : elem.form.getAttribute("id") || "(no form ID)",
29
- 'formName' : elem.form.getAttribute("name") || "(no form name)",
30
- 'formClass': elem.form.getAttribute("class") || "(no form class)"
31
- });
32
- };
33
- }, false);
1
+ document.addEventListener("focusin", function(event) {
2
+ const elem = event.target?.closest("input,select,textarea,button,meter,progress");
3
+ if ( elem ) {
4
+ window[ gtm4wp_datalayer_name ].push({
5
+ 'event' : 'gtm4wp.formElementEnter',
6
+
7
+ 'inputID' : elem.getAttribute("id") || "(no input ID)",
8
+ 'inputName' : elem.getAttribute("name") || "(no input name)",
9
+ 'inputClass': elem.getAttribute("class") || "(no input class)",
10
+
11
+ 'formID' : elem.form?.getAttribute("id") || "(no form ID)",
12
+ 'formName' : elem.form?.getAttribute("name") || "(no form name)",
13
+ 'formClass': elem.form?.getAttribute("class") || "(no form class)"
14
+ });
15
+ }
16
+ }, false);
17
+
18
+ document.addEventListener("focusout", function(event) {
19
+ const elem = event.target.closest("input,select,textarea,button,meter,progress");
20
+ if ( elem ) {
21
+ window[ gtm4wp_datalayer_name ].push({
22
+ 'event' : 'gtm4wp.formElementLeave',
23
+
24
+ 'inputID' : elem.getAttribute("id") || "(no input ID)",
25
+ 'inputName' : elem.getAttribute("name") || "(no input name)",
26
+ 'inputClass': elem.getAttribute("class") || "(no input class)",
27
+
28
+ 'formID' : elem.form?.getAttribute("id") || "(no form ID)",
29
+ 'formName' : elem.form?.getAttribute("name") || "(no form name)",
30
+ 'formClass': elem.form?.getAttribute("class") || "(no form class)"
31
+ });
32
+ };
33
+ }, false);
js/gtm4wp-woocommerce-enhanced.js CHANGED
@@ -51,8 +51,8 @@ function gtm4wp_map_eec_to_ga4( productdata ) {
51
  ga4_product.coupon = productdata.coupon;
52
  }
53
 
54
- ga4_product.google_business_vertical = window.gtm4wp_business_vertical;
55
- ga4_product[ window.gtm4wp_business_vertical_id ] = gtm4wp_id_prefix + ga4_product[ "item_id" ];
56
 
57
  return ga4_product;
58
  }
@@ -252,7 +252,7 @@ function gtm4wp_handle_shipping_method_change() {
252
  gtm4wp_checkout_step_fired.push( 'shipping_method' );
253
  }
254
 
255
- document.addEventListener( 'DOMContentLoaded', function() {
256
  window.gtm4wp_is_cart = document.querySelector( 'body' )?.classList?.contains( 'woocommerce-cart' );
257
  window.gtm4wp_is_checkout = document.querySelector( 'body' )?.classList?.contains( 'woocommerce-checkout' );
258
 
@@ -322,7 +322,7 @@ document.addEventListener( 'DOMContentLoaded', function() {
322
  let product_data;
323
 
324
  document.querySelectorAll( '.gtm4wp_productdata,.widget-product-item' ).forEach( function( dom_productdata ) {
325
- productprice = dom_productdata.getAttribute( 'gtm4wp_product_price' );
326
 
327
  if ( typeof productprice == "string" ) {
328
  productprice = parseFloat( productprice );
@@ -1188,4 +1188,19 @@ document.addEventListener( 'DOMContentLoaded', function() {
1188
  }
1189
  });
1190
  }
1191
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  ga4_product.coupon = productdata.coupon;
52
  }
53
 
54
+ ga4_product.google_business_vertical = gtm4wp_business_vertical;
55
+ ga4_product[ gtm4wp_business_vertical_id ] = gtm4wp_id_prefix + ga4_product[ "item_id" ];
56
 
57
  return ga4_product;
58
  }
252
  gtm4wp_checkout_step_fired.push( 'shipping_method' );
253
  }
254
 
255
+ function gtm4wp_process_woocommerce_pages() {
256
  window.gtm4wp_is_cart = document.querySelector( 'body' )?.classList?.contains( 'woocommerce-cart' );
257
  window.gtm4wp_is_checkout = document.querySelector( 'body' )?.classList?.contains( 'woocommerce-checkout' );
258
 
322
  let product_data;
323
 
324
  document.querySelectorAll( '.gtm4wp_productdata,.widget-product-item' ).forEach( function( dom_productdata ) {
325
+ productprice = dom_productdata.getAttribute( 'data-gtm4wp_product_price' );
326
 
327
  if ( typeof productprice == "string" ) {
328
  productprice = parseFloat( productprice );
1188
  }
1189
  });
1190
  }
1191
+ };
1192
+
1193
+ function gtm4wp_page_loading_completed() {
1194
+ document.removeEventListener( "DOMContentLoaded", gtm4wp_page_loading_completed );
1195
+ window.removeEventListener( "load", gtm4wp_page_loading_completed );
1196
+ gtm4wp_process_woocommerce_pages();
1197
+ }
1198
+
1199
+ // code and idea borrowed from jQuery:
1200
+ // https://github.com/jquery/jquery/blob/main/src/core/ready.js
1201
+ if ( document.readyState !== "loading" ) {
1202
+ window.setTimeout( gtm4wp_process_woocommerce_pages );
1203
+ } else {
1204
+ document.addEventListener( "DOMContentLoaded", gtm4wp_page_loading_completed );
1205
+ window.addEventListener( "load", gtm4wp_page_loading_completed );
1206
+ }
readme.txt CHANGED
@@ -5,7 +5,7 @@ Tags: google tag manager, tag manager, gtm, google, adwords, google adwords, goo
5
  Requires at least: 3.4.0
6
  Requires PHP: 5.6
7
  Tested up to: 5.8.2
8
- Stable tag: 1.14.1
9
  License: GPLv3
10
  License URI: http://www.gnu.org/licenses/gpl.html
11
 
@@ -233,6 +233,13 @@ https://gtm4wp.com/how-to-articles/how-to-exclude-admin-users-from-being-tracked
233
 
234
  == Changelog ==
235
 
 
 
 
 
 
 
 
236
  = 1.14.1 =
237
 
238
  * Bugfixes
@@ -742,6 +749,10 @@ Please report all bugs found in my plugin using the [contact form on my website]
742
 
743
  == Upgrade Notice ==
744
 
 
 
 
 
745
  = 1.14.1 =
746
 
747
  Bugfix release
5
  Requires at least: 3.4.0
6
  Requires PHP: 5.6
7
  Tested up to: 5.8.2
8
+ Stable tag: 1.14.2
9
  License: GPLv3
10
  License URI: http://www.gnu.org/licenses/gpl.html
11
 
233
 
234
  == Changelog ==
235
 
236
+ = 1.14.2 =
237
+
238
+ * Fixed: undefined google_business_vertical
239
+ * Fixed: missing product price in product impression data
240
+ * Fixed: better compatibility with cache plugins and lazy load functionalities
241
+ * Fixed: Added optional chaining operator to form move tracker code
242
+
243
  = 1.14.1 =
244
 
245
  * Bugfixes
749
 
750
  == Upgrade Notice ==
751
 
752
+ = 1.14.2 =
753
+
754
+ Bugfix release
755
+
756
  = 1.14.1 =
757
 
758
  Bugfix release