WordPress Landing Pages - Version 1.9.0

Version Description

  • New preview views in landing page edit screens
  • Temporarily disabling geolocation services
Download this release

Release Info

Developer DavidWells
Plugin Icon 128x128 WordPress Landing Pages
Version 1.9.0
Comparing to
See all releases

Code changes from version 1.8.8 to 1.9.0

Files changed (51) hide show
  1. classes/class.activation.php +53 -4
  2. css/admin-style.css +15 -0
  3. js/admin/admin.post-edit.js +5 -2
  4. js/libraries/easyXDM.debug.js +1 -1
  5. js/libraries/jquery.zoomer.js +436 -0
  6. landing-pages.php +46 -48
  7. libraries/class-tgm-plugin-activation.php +3497 -2048
  8. libraries/shareme/sharrre/jquery.sharrre-1.3.3.min.js +1 -7
  9. modules/module.ab-testing.php +72 -146
  10. modules/module.admin-menus.php +5 -2
  11. modules/module.ajax-setup.php +1 -1
  12. modules/module.customizer.php +1 -1
  13. modules/module.install.php +12 -12
  14. modules/module.javascript-admin.php +1 -1
  15. modules/module.javascript-frontend.php +15 -6
  16. modules/module.metaboxes.php +64 -36
  17. modules/module.post-type.php +0 -10
  18. modules/module.store.php +1 -1
  19. modules/module.welcome.php +28 -9
  20. package.json +45 -0
  21. phpunit.xml.dist +8 -0
  22. readme.txt +5 -1
  23. shared/assets/assets.loader.class.php +1 -0
  24. shared/assets/js/frontend/analytics-src/analytics.forms.js +2 -2
  25. shared/assets/js/frontend/analytics-src/analytics.page.js +1 -0
  26. shared/assets/js/frontend/analytics/inboundAnalytics.js +3 -2
  27. shared/assets/js/frontend/analytics/inboundAnalytics.min.js +2 -2
  28. shared/classes/class.feedback.php +1 -1
  29. shared/classes/class.form.php +188 -50
  30. shared/classes/class.inbound-forms.akismet.php +0 -2
  31. shared/classes/class.lead-storage.php +713 -714
  32. shared/classes/class.licensing.php +11 -10
  33. shared/classes/class.load-shared.php +1 -1
  34. shared/classes/class.magic.php +2 -2
  35. shared/classes/class.master-license.php +4 -4
  36. shared/classes/class.menu.php +5 -7
  37. shared/classes/class.menus.adminbar.php +2 -4
  38. shared/classes/class.welcome.php +3 -3
  39. shared/shortcodes/css/form-cpt.css +9 -4
  40. shared/shortcodes/css/select2.png +0 -0
  41. shared/shortcodes/css/shortcodes.css +4 -3
  42. shared/shortcodes/inbound-shortcodes.php +28 -95
  43. shared/shortcodes/js/form-cpt.js +105 -74
  44. shared/shortcodes/shortcodes/forms.php +85 -77
  45. templates/countdown-lander/config.php +1 -1
  46. templates/countdown-lander/index.php +6 -6
  47. tests/codeception/_bootstrap.php +5 -1
  48. tests/codeception/acceptance.suite.yml +2 -2
  49. tests/codeception/acceptance/LoginCept.php +28 -0
  50. tests/codeception/acceptance/StatisticsCept.php +23 -31
  51. tests/phpunit/bootstrap.php +0 -15
classes/class.activation.php CHANGED
@@ -10,6 +10,31 @@ class Landing_Pages_Activation {
10
  static $version_leads;
11
  static $version_lpah;
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  public static function activate() {
14
  self::load_static_vars();
15
  self::run_version_checks();
@@ -41,7 +66,7 @@ class Landing_Pages_Activation {
41
 
42
  /* Activate shared components */
43
  self::activate_shared();
44
-
45
  /* Run additional actions */
46
  do_action( 'activate_landing_pages' );
47
 
@@ -230,14 +255,38 @@ class Landing_Pages_Activation {
230
  }
231
 
232
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  }
234
 
235
  /* Add Activation Hook */
236
  register_activation_hook( LANDINGPAGES_FILE , array( 'Landing_Pages_Activation' , 'activate' ) );
237
  register_deactivation_hook( LANDINGPAGES_FILE , array( 'Landing_Pages_Activation' , 'deactivate' ) );
238
 
239
-
240
- /* Add listener for uncompleted upgrade routines */
241
- add_action( 'admin_init' , array( 'Landing_Pages_Activation' , 'run_upgrade_routine_checks' ) );
242
 
243
  }
10
  static $version_leads;
11
  static $version_lpah;
12
 
13
+ /**
14
+ * Initiate class
15
+ */
16
+ public function __construct() {
17
+ self::load_hooks();
18
+ }
19
+
20
+ /**
21
+ * load supporting hooks and filters
22
+ */
23
+ public static function load_hooks() {
24
+ if (!is_admin()) {
25
+ return;
26
+ }
27
+
28
+ /* Add listener for unset permalinks */
29
+ add_action('admin_notices', array( __CLASS__ , 'permastruct_check' ) );
30
+
31
+ /** add listener for permlaink flush command */
32
+ add_action('admin_init', array( __CLASS__ , 'flush_permalinks' ) , 11 );
33
+
34
+ /* Add listener for uncompleted upgrade routines */
35
+ add_action( 'admin_init' , array( 'Landing_Pages_Activation' , 'run_upgrade_routine_checks' ) );
36
+ }
37
+
38
  public static function activate() {
39
  self::load_static_vars();
40
  self::run_version_checks();
66
 
67
  /* Activate shared components */
68
  self::activate_shared();
69
+
70
  /* Run additional actions */
71
  do_action( 'activate_landing_pages' );
72
 
255
  }
256
 
257
  }
258
+
259
+ /**
260
+ * flush permalinks
261
+ */
262
+ public static function flush_permalinks() {
263
+
264
+ if ( !get_option( 'lp_activate_rewrite_check' ) ) {
265
+ return;
266
+ }
267
+
268
+ flush_rewrite_rules( true );
269
+ delete_option( 'lp_activate_rewrite_check' );
270
+ }
271
+
272
+ /**
273
+ * check for 'default' permalinks and warn
274
+ */
275
+ public static function permastruct_check() {
276
+ if ( '' == get_option( 'permalink_structure' ) ) {
277
+ ?>
278
+ <div class="error">
279
+ <p><?php _e( 'Landing Pages plugin requires you to use a non default permlaink structure. Please head into your pemalink settings and choose an option besides \'default\'.' , 'landing-pages'); ?></p>
280
+ </div>
281
+ <?php
282
+ }
283
+ }
284
  }
285
 
286
  /* Add Activation Hook */
287
  register_activation_hook( LANDINGPAGES_FILE , array( 'Landing_Pages_Activation' , 'activate' ) );
288
  register_deactivation_hook( LANDINGPAGES_FILE , array( 'Landing_Pages_Activation' , 'deactivate' ) );
289
 
290
+ new Landing_Pages_Activation;
 
 
291
 
292
  }
css/admin-style.css CHANGED
@@ -13,6 +13,9 @@
13
  margin-top:30px;
14
  z-index:999999;
15
  }
 
 
 
16
 
17
  tr#leads {
18
  display: table-row !important;
@@ -61,11 +64,23 @@ display: none;
61
  #setting-error-tgmpa p {
62
  text-decoration: bold;
63
  }
 
 
 
64
  #setting-error-tgmpa p:nth-last-child(2), #setting-error-tgmpa a, #setting-error-tgmpa strong {
65
  text-decoration: normal;
66
  }
 
 
 
 
 
 
 
 
67
  #setting-error-tgmpa p:nth-last-child(2) a:first-child{
68
  padding-left: 0px;
 
69
  }
70
  #setting-error-tgmpa p:nth-last-child(2) a {
71
  padding-right: 10px;
13
  margin-top:30px;
14
  z-index:999999;
15
  }
16
+ #setting-error-tgmpa a:-webkit-any-link {
17
+ text-decoration: none;
18
+ }
19
 
20
  tr#leads {
21
  display: table-row !important;
64
  #setting-error-tgmpa p {
65
  text-decoration: bold;
66
  }
67
+ .appearance_page_install-inbound-plugins .inbound-install-notice {
68
+ display: none;
69
+ }
70
  #setting-error-tgmpa p:nth-last-child(2), #setting-error-tgmpa a, #setting-error-tgmpa strong {
71
  text-decoration: normal;
72
  }
73
+ #setting-error-tgmpa em:last-of-type {
74
+ display: inline-block;
75
+ }
76
+ .inbound-and {
77
+ display: block;
78
+ margin-top: 10px;
79
+ font-size: 15px;
80
+ }
81
  #setting-error-tgmpa p:nth-last-child(2) a:first-child{
82
  padding-left: 0px;
83
+ text-decoration: none;
84
  }
85
  #setting-error-tgmpa p:nth-last-child(2) a {
86
  padding-right: 10px;
js/admin/admin.post-edit.js CHANGED
@@ -13,6 +13,9 @@ jQuery(document).ready(function($) {
13
  }
14
  });
15
 
 
 
 
16
  // Filter Styling
17
  jQuery('#template-filter li').first().addClass('button-primary');
18
  // filter items when filter link is clicked
@@ -323,8 +326,8 @@ jQuery(document).ready(function($) {
323
  //jQuery(this).attr('rel', all.hex);
324
 
325
  jQuery(this).parent().find(".lp-success-message").remove();
326
- jQuery(this).parent().find(".new-save-lp").show();
327
- jQuery(this).parent().find(".new-save-lp-frontend").show();
328
  //jQuery(this).attr('value', all.hex);
329
  });
330
  });
13
  }
14
  });
15
 
16
+ var width = jQuery("#lp-thumbnail-sidebar-preview").width();
17
+ jQuery('#zoomer').zoomer({ width: width, height: 225, zoom: 0.3, tranformOrigin: '0 43px', });
18
+
19
  // Filter Styling
20
  jQuery('#template-filter li').first().addClass('button-primary');
21
  // filter items when filter link is clicked
326
  //jQuery(this).attr('rel', all.hex);
327
 
328
  jQuery(this).parent().find(".lp-success-message").remove();
329
+ //jQuery(this).parent().find(".new-save-lp").show();
330
+ //jQuery(this).parent().find(".new-save-lp-frontend").show();
331
  //jQuery(this).attr('value', all.hex);
332
  });
333
  });
js/libraries/easyXDM.debug.js CHANGED
@@ -1 +1 @@
1
- /**
2
  * easyXDM
3
  * http://easyxdm.net/
4
  * Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
5
  *
6
  * Permission is hereby granted, free of charge, to any person obtaining a copy
7
  * of this software and associated documentation files (the "Software"), to deal
8
  * in the Software without restriction, including without limitation the rights
9
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
  * copies of the Software, and to permit persons to whom the Software is
11
  * furnished to do so, subject to the following conditions:
12
  *
13
  * The above copyright notice and this permission notice shall be included in
14
  * all copies or substantial portions of the Software.
15
  *
16
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
  * THE SOFTWARE.
23
  */
24
  var t = typeof object[property];
25
  return t == 'function' ||
26
  (!!(t == 'object' && object[property])) ||
27
  t == 'unknown';
28
  return !!(typeof(object[property]) == 'object' && object[property]);
29
  return Object.prototype.toString.call(o) === '[object Array]';
30
  try {
31
  var activeX = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
32
  flashVersion = Array.prototype.slice.call(activeX.GetVariable("$version").match(/(\d+),(\d+),(\d+),(\d+)/), 1);
33
  HAS_FLASH_THROTTLED_BUG = parseInt(flashVersion[0], 10) > 9 && parseInt(flashVersion[1], 10) > 0;
34
  activeX = null;
35
  return true;
36
  }
37
  catch (notSupportedException) {
38
  return false;
39
  }
40
  * Cross Browser implementation for adding and removing event listeners.
41
  */
42
  on = function(target, type, listener){
43
  _trace("adding listener " + type);
44
  target.addEventListener(type, listener, false);
45
  };
46
  un = function(target, type, listener){
47
  _trace("removing listener " + type);
48
  target.removeEventListener(type, listener, false);
49
  };
50
  on = function(object, sEvent, fpNotify){
51
  _trace("adding listener " + sEvent);
52
  object.attachEvent("on" + sEvent, fpNotify);
53
  };
54
  un = function(object, sEvent, fpNotify){
55
  _trace("removing listener " + sEvent);
56
  object.detachEvent("on" + sEvent, fpNotify);
57
  };
58
  throw new Error("Browser not supported");
59
  * Cross Browser implementation of DOMContentLoaded.
60
  */
61
  // If browser is WebKit-powered, check for both 'loaded' (legacy browsers) and
62
  // 'interactive' (HTML5 specs, recent WebKit builds) states.
63
  // https://bugs.webkit.org/show_bug.cgi?id=45119
64
  readyState = document.readyState;
65
  domIsReady = readyState == "complete" || (~ navigator.userAgent.indexOf('AppleWebKit/') && (readyState == "loaded" || readyState == "interactive"));
66
  // If readyState is not supported in the browser, then in order to be able to fire whenReady functions apropriately
67
  // when added dynamically _after_ DOM load, we have to deduce wether the DOM is ready or not.
68
  // We only need a body to add elements to, so the existence of document.body is enough for us.
69
  domIsReady = !!document.body;
70
  if (domIsReady) {
71
  return;
72
  }
73
  domIsReady = true;
74
  _trace("firing dom_onReady");
75
  for (var i = 0; i < domReadyQueue.length; i++) {
76
  domReadyQueue[i]();
77
  }
78
  domReadyQueue.length = 0;
79
  if (isHostMethod(window, "addEventListener")) {
80
  on(document, "DOMContentLoaded", dom_onReady);
81
  }
82
  else {
83
  on(document, "readystatechange", function(){
84
  if (document.readyState == "complete") {
85
  dom_onReady();
86
  }
87
  });
88
  if (document.documentElement.doScroll && window === top) {
89
  var doScrollCheck = function(){
90
  if (domIsReady) {
91
  return;
92
  }
93
  // http://javascript.nwbox.com/IEContentLoaded/
94
  try {
95
  document.documentElement.doScroll("left");
96
  }
97
  catch (e) {
98
  setTimeout(doScrollCheck, 1);
99
  return;
100
  }
101
  dom_onReady();
102
  };
103
  doScrollCheck();
104
  }
105
  }
106
 
107
  // A fallback to window.onload, that will always work
108
  on(window, "load", dom_onReady);
109
  * This will add a function to the queue of functions to be run once the DOM reaches a ready state.
110
  * If functions are added after this event then they will be executed immediately.
111
  * @param {function} fn The function to add
112
  * @param {Object} scope An optional scope for the function to be called with.
113
  */
114
  if (domIsReady) {
115
  fn.call(scope);
116
  return;
117
  }
118
  domReadyQueue.push(function(){
119
  fn.call(scope);
120
  });
121
  * Returns an instance of easyXDM from the parent window with
122
  * respect to the namespace.
123
  *
124
  * @return An instance of easyXDM (in the parent window)
125
  */
126
  var obj = parent;
127
  if (namespace !== "") {
128
  for (var i = 0, ii = namespace.split("."); i < ii.length; i++) {
129
  if (!obj) {
130
  throw new Error(ii.slice(0, i + 1).join('.') + ' is not an object');
131
  }
132
  obj = obj[ii[i]];
133
  }
134
  }
135
  if (!obj || !obj.easyXDM) {
136
  throw new Error('Could not find easyXDM in parent.' + namespace);
137
  }
138
  return obj.easyXDM;
139
  * Removes easyXDM variable from the global scope. It also returns control
140
  * of the easyXDM variable to whatever code used it before.
141
  *
142
  * @param {String} ns A string representation of an object that will hold
143
  * an instance of easyXDM.
144
  * @return An instance of easyXDM
145
  */
146
  if (typeof ns != "string" || !ns) {
147
  throw new Error('namespace must be a non-empty string');
148
  }
149
  _trace("Settings namespace to '" + ns + "'");
150
 
151
  window.easyXDM = _easyXDM;
152
  namespace = ns;
153
  if (namespace) {
154
  IFRAME_PREFIX = "easyXDM_" + namespace.replace(".", "_") + "_";
155
  }
156
  return easyXDM;
157
  * Methods for working with URLs
158
  */
159
  * Get the domain name from a url.
160
  * @param {String} url The url to extract the domain from.
161
  * @return The domain part of the url.
162
  * @type {String}
163
  */
164
  if (!url) {
165
  throw new Error("url is undefined or empty");
166
  }
167
  return url.match(reURI)[3];
168
  * Get the port for a given URL, or "" if none
169
  * @param {String} url The url to extract the port from.
170
  * @return The port part of the url.
171
  * @type {String}
172
  */
173
  if (!url) {
174
  throw new Error("url is undefined or empty");
175
  }
176
  return url.match(reURI)[4] || "";
177
  * Returns a string containing the schema, domain and if present the port
178
  * @param {String} url The url to extract the location from
179
  * @return {String} The location part of the url
180
  */
181
  if (!url) {
182
  throw new Error("url is undefined or empty");
183
  }
184
  if (/^file/.test(url)) {
185
  throw new Error("The file:// protocol is not supported");
186
  }
187
  var m = url.toLowerCase().match(reURI);
188
  var proto = m[2], domain = m[3], port = m[4] || "";
189
  if ((proto == "http:" && port == ":80") || (proto == "https:" && port == ":443")) {
190
  port = "";
191
  }
192
  return proto + "//" + domain + port;
193
  * Resolves a relative url into an absolute one.
194
  * @param {String} url The path to resolve.
195
  * @return {String} The resolved url.
196
  */
197
  if (!url) {
198
  throw new Error("url is undefined or empty");
199
  }
200
 
201
  // replace all // except the one in proto with /
202
  url = url.replace(reDoubleSlash, "$1/");
203
 
204
  // If the url is a valid url we do nothing
205
  if (!url.match(/^(http||https):\/\//)) {
206
  // If this is a relative path
207
  var path = (url.substring(0, 1) === "/") ? "" : location.pathname;
208
  if (path.substring(path.length - 1) !== "/") {
209
  path = path.substring(0, path.lastIndexOf("/") + 1);
210
  }
211
 
212
  url = location.protocol + "//" + location.host + path + url;
213
  }
214
 
215
  // reduce all 'xyz/../' to just ''
216
  while (reParent.test(url)) {
217
  url = url.replace(reParent, "");
218
  }
219
 
220
  _trace("resolved url '" + url + "'");
221
  return url;
222
  * Appends the parameters to the given url.<br/>
223
  * The base url can contain existing query parameters.
224
  * @param {String} url The base url.
225
  * @param {Object} parameters The parameters to add.
226
  * @return {String} A new valid url with the parameters appended.
227
  */
228
  if (!parameters) {
229
  throw new Error("parameters is undefined or null");
230
  }
231
 
232
  var hash = "", indexOf = url.indexOf("#");
233
  if (indexOf !== -1) {
234
  hash = url.substring(indexOf);
235
  url = url.substring(0, indexOf);
236
  }
237
  var q = [];
238
  for (var key in parameters) {
239
  if (parameters.hasOwnProperty(key)) {
240
  q.push(key + "=" + encodeURIComponent(parameters[key]));
241
  }
242
  }
243
  return url + (useHash ? "#" : (url.indexOf("?") == -1 ? "?" : "&")) + q.join("&") + hash;
244
  input = input.substring(1).split("&");
245
  var data = {}, pair, i = input.length;
246
  while (i--) {
247
  pair = input[i].split("=");
248
  data[pair[0]] = decodeURIComponent(pair[1]);
249
  }
250
  return data;
251
  * Helper methods
252
  */
253
  * Helper for checking if a variable/property is undefined
254
  * @param {Object} v The variable to test
255
  * @return {Boolean} True if the passed variable is undefined
256
  */
257
  return typeof v === "undefined";
258
  * A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works.
259
  * @return {JSON} A valid JSON conforming object, or null if not found.
260
  */
261
  var cached = {};
262
  var obj = {
263
  a: [1, 2, 3]
264
  }, json = "{\"a\":[1,2,3]}";
265
 
266
  if (typeof JSON != "undefined" && typeof JSON.stringify === "function" && JSON.stringify(obj).replace((/\s/g), "") === json) {
267
  // this is a working JSON instance
268
  return JSON;
269
  }
270
  if (Object.toJSON) {
271
  if (Object.toJSON(obj).replace((/\s/g), "") === json) {
272
  // this is a working stringify method
273
  cached.stringify = Object.toJSON;
274
  }
275
  }
276
 
277
  if (typeof String.prototype.evalJSON === "function") {
278
  obj = json.evalJSON();
279
  if (obj.a && obj.a.length === 3 && obj.a[2] === 3) {
280
  // this is a working parse method
281
  cached.parse = function(str){
282
  return str.evalJSON();
283
  };
284
  }
285
  }
286
 
287
  if (cached.stringify && cached.parse) {
288
  // Only memoize the result if we have valid instance
289
  getJSON = function(){
290
  return cached;
291
  };
292
  return cached;
293
  }
294
  return null;
295
  * Applies properties from the source object to the target object.<br/>
296
  * @param {Object} target The target of the properties.
297
  * @param {Object} source The source of the properties.
298
  * @param {Boolean} noOverwrite Set to True to only set non-existing properties.
299
  */
300
  var member;
301
  for (var prop in source) {
302
  if (source.hasOwnProperty(prop)) {
303
  if (prop in destination) {
304
  member = source[prop];
305
  if (typeof member === "object") {
306
  apply(destination[prop], member, noOverwrite);
307
  }
308
  else if (!noOverwrite) {
309
  destination[prop] = source[prop];
310
  }
311
  }
312
  else {
313
  destination[prop] = source[prop];
314
  }
315
  }
316
  }
317
  return destination;
318
  var form = document.body.appendChild(document.createElement("form")), input = form.appendChild(document.createElement("input"));
319
  input.name = IFRAME_PREFIX + "TEST" + channelId; // append channelId in order to avoid caching issues
320
  HAS_NAME_PROPERTY_BUG = input !== form.elements[input.name];
321
  document.body.removeChild(form);
322
  _trace("HAS_NAME_PROPERTY_BUG: " + HAS_NAME_PROPERTY_BUG);
323
  * Creates a frame and appends it to the DOM.
324
  * @param config {object} This object can have the following properties
325
  * <ul>
326
  * <li> {object} prop The properties that should be set on the frame. This should include the 'src' property.</li>
327
  * <li> {object} attr The attributes that should be set on the frame.</li>
328
  * <li> {DOMElement} container Its parent element (Optional).</li>
329
  * <li> {function} onLoad A method that should be called with the frames contentWindow as argument when the frame is fully loaded. (Optional)</li>
330
  * </ul>
331
  * @return The frames DOMElement
332
  * @type DOMElement
333
  */
334
  _trace("creating frame: " + config.props.src);
335
  if (undef(HAS_NAME_PROPERTY_BUG)) {
336
  testForNamePropertyBug();
337
  }
338
  var frame;
339
  // This is to work around the problems in IE6/7 with setting the name property.
340
  // Internally this is set as 'submitName' instead when using 'iframe.name = ...'
341
  // This is not required by easyXDM itself, but is to facilitate other use cases
342
  if (HAS_NAME_PROPERTY_BUG) {
343
  frame = document.createElement("<iframe name=\"" + config.props.name + "\"/>");
344
  }
345
  else {
346
  frame = document.createElement("IFRAME");
347
  frame.name = config.props.name;
348
  }
349
 
350
  frame.id = frame.name = config.props.name;
351
  delete config.props.name;
352
 
353
  if (config.onLoad) {
354
  on(frame, "load", config.onLoad);
355
  }
356
 
357
  if (typeof config.container == "string") {
358
  config.container = document.getElementById(config.container);
359
  }
360
 
361
  if (!config.container) {
362
  // This needs to be hidden like this, simply setting display:none and the like will cause failures in some browsers.
363
  apply(frame.style, {
364
  position: "absolute",
365
  top: "-2000px"
366
  });
367
  config.container = document.body;
368
  }
369
 
370
  // HACK for some reason, IE needs the source set
371
  // after the frame has been appended into the DOM
372
  // so remove the src, and set it afterwards
373
  var src = config.props.src;
374
  delete config.props.src;
375
 
376
  // transfer properties to the frame
377
  apply(frame, config.props);
378
 
379
  frame.border = frame.frameBorder = 0;
380
  frame.allowTransparency = true;
381
  config.container.appendChild(frame);
382
 
383
  // HACK see above
384
  frame.src = src;
385
  config.props.src = src;
386
 
387
  return frame;
388
  * Check whether a domain is allowed using an Access Control List.
389
  * The ACL can contain * and ? as wildcards, or can be regular expressions.
390
  * If regular expressions they need to begin with ^ and end with $.
391
  * @param {Array/String} acl The list of allowed domains
392
  * @param {String} domain The domain to test.
393
  * @return {Boolean} True if the domain is allowed, false if not.
394
  */
395
  // normalize into an array
396
  if (typeof acl == "string") {
397
  acl = [acl];
398
  }
399
  var re, i = acl.length;
400
  while (i--) {
401
  re = acl[i];
402
  re = new RegExp(re.substr(0, 1) == "^" ? re : ("^" + re.replace(/(\*)/g, ".$1").replace(/\?/g, ".") + "$"));
403
  if (re.test(domain)) {
404
  return true;
405
  }
406
  }
407
  return false;
408
  * Functions related to stacks
409
  */
410
  * Prepares an array of stack-elements suitable for the current configuration
411
  * @param {Object} config The Transports configuration. See easyXDM.Socket for more.
412
  * @return {Array} An array of stack-elements with the TransportElement at index 0.
413
  */
414
  var protocol = config.protocol, stackEls;
415
  config.isHost = config.isHost || undef(query.xdm_p);
416
  useHash = config.hash || false;
417
  _trace("preparing transport stack");
418
 
419
  if (!config.props) {
420
  config.props = {};
421
  }
422
  if (!config.isHost) {
423
  _trace("using parameters from query");
424
  config.channel = query.xdm_c;
425
  config.secret = query.xdm_s;
426
  config.remote = query.xdm_e;
427
  protocol = query.xdm_p;
428
  if (config.acl && !checkAcl(config.acl, config.remote)) {
429
  throw new Error("Access denied for " + config.remote);
430
  }
431
  }
432
  else {
433
  config.remote = resolveUrl(config.remote);
434
  config.channel = config.channel || "default" + channelId++;
435
  config.secret = Math.random().toString(16).substring(2);
436
  if (undef(protocol)) {
437
  if (getLocation(location.href) == getLocation(config.remote)) {
438
  /*
439
  * Both documents has the same origin, lets use direct access.
440
  */
441
  protocol = "4";
442
  }
443
  else if (isHostMethod(window, "postMessage") || isHostMethod(document, "postMessage")) {
444
  /*
445
  * This is supported in IE8+, Firefox 3+, Opera 9+, Chrome 2+ and Safari 4+
446
  */
447
  protocol = "1";
448
  }
449
  else if (config.swf && isHostMethod(window, "ActiveXObject") && hasFlash()) {
450
  /*
451
  * The Flash transport superseedes the NixTransport as the NixTransport has been blocked by MS
452
  */
453
  protocol = "6";
454
  }
455
  else if (navigator.product === "Gecko" && "frameElement" in window && navigator.userAgent.indexOf('WebKit') == -1) {
456
  /*
457
  * This is supported in Gecko (Firefox 1+)
458
  */
459
  protocol = "5";
460
  }
461
  else if (config.remoteHelper) {
462
  /*
463
  * This is supported in all browsers that retains the value of window.name when
464
  * navigating from one domain to another, and where parent.frames[foo] can be used
465
  * to get access to a frame from the same domain
466
  */
467
  config.remoteHelper = resolveUrl(config.remoteHelper);
468
  protocol = "2";
469
  }
470
  else {
471
  /*
472
  * This is supported in all browsers where [window].location is writable for all
473
  * The resize event will be used if resize is supported and the iframe is not put
474
  * into a container, else polling will be used.
475
  */
476
  protocol = "0";
477
  }
478
  _trace("selecting protocol: " + protocol);
479
  }
480
  else {
481
  _trace("using protocol: " + protocol);
482
  }
483
  }
484
  config.protocol = protocol; // for conditional branching
485
  switch (protocol) {
486
  case "0":// 0 = HashTransport
487
  apply(config, {
488
  interval: 100,
489
  delay: 2000,
490
  useResize: true,
491
  useParent: false,
492
  usePolling: false
493
  }, true);
494
  if (config.isHost) {
495
  if (!config.local) {
496
  _trace("looking for image to use as local");
497
  // If no local is set then we need to find an image hosted on the current domain
498
  var domain = location.protocol + "//" + location.host, images = document.body.getElementsByTagName("img"), image;
499
  var i = images.length;
500
  while (i--) {
501
  image = images[i];
502
  if (image.src.substring(0, domain.length) === domain) {
503
  config.local = image.src;
504
  break;
505
  }
506
  }
507
  if (!config.local) {
508
  _trace("no image found, defaulting to using the window");
509
  // If no local was set, and we are unable to find a suitable file, then we resort to using the current window
510
  config.local = window;
511
  }
512
  }
513
 
514
  var parameters = {
515
  xdm_c: config.channel,
516
  xdm_p: 0
517
  };
518
 
519
  if (config.local === window) {
520
  // We are using the current window to listen to
521
  config.usePolling = true;
522
  config.useParent = true;
523
  config.local = location.protocol + "//" + location.host + location.pathname + location.search;
524
  parameters.xdm_e = config.local;
525
  parameters.xdm_pa = 1; // use parent
526
  }
527
  else {
528
  parameters.xdm_e = resolveUrl(config.local);
529
  }
530
 
531
  if (config.container) {
532
  config.useResize = false;
533
  parameters.xdm_po = 1; // use polling
534
  }
535
  config.remote = appendQueryParameters(config.remote, parameters);
536
  }
537
  else {
538
  apply(config, {
539
  channel: query.xdm_c,
540
  remote: query.xdm_e,
541
  useParent: !undef(query.xdm_pa),
542
  usePolling: !undef(query.xdm_po),
543
  useResize: config.useParent ? false : config.useResize
544
  });
545
  }
546
  stackEls = [new easyXDM.stack.HashTransport(config), new easyXDM.stack.ReliableBehavior({}), new easyXDM.stack.QueueBehavior({
547
  encode: true,
548
  maxLength: 4000 - config.remote.length
549
  }), new easyXDM.stack.VerifyBehavior({
550
  initiate: config.isHost
551
  })];
552
  break;
553
  case "1":
554
  stackEls = [new easyXDM.stack.PostMessageTransport(config)];
555
  break;
556
  case "2":
557
  stackEls = [new easyXDM.stack.NameTransport(config), new easyXDM.stack.QueueBehavior(), new easyXDM.stack.VerifyBehavior({
558
  initiate: config.isHost
559
  })];
560
  break;
561
  case "3":
562
  stackEls = [new easyXDM.stack.NixTransport(config)];
563
  break;
564
  case "4":
565
  stackEls = [new easyXDM.stack.SameOriginTransport(config)];
566
  break;
567
  case "5":
568
  stackEls = [new easyXDM.stack.FrameElementTransport(config)];
569
  break;
570
  case "6":
571
  if (!flashVersion) {
572
  hasFlash();
573
  }
574
  stackEls = [new easyXDM.stack.FlashTransport(config)];
575
  break;
576
  }
577
  // this behavior is responsible for buffering outgoing messages, and for performing lazy initialization
578
  stackEls.push(new easyXDM.stack.QueueBehavior({
579
  lazy: config.lazy,
580
  remove: true
581
  }));
582
  return stackEls;
583
  * Chains all the separate stack elements into a single usable stack.<br/>
584
  * If an element is missing a necessary method then it will have a pass-through method applied.
585
  * @param {Array} stackElements An array of stack elements to be linked.
586
  * @return {easyXDM.stack.StackElement} The last element in the chain.
587
  */
588
  var stackEl, defaults = {
589
  incoming: function(message, origin){
590
  this.up.incoming(message, origin);
591
  },
592
  outgoing: function(message, recipient){
593
  this.down.outgoing(message, recipient);
594
  },
595
  callback: function(success){
596
  this.up.callback(success);
597
  },
598
  init: function(){
599
  this.down.init();
600
  },
601
  destroy: function(){
602
  this.down.destroy();
603
  }
604
  };
605
  for (var i = 0, len = stackElements.length; i < len; i++) {
606
  stackEl = stackElements[i];
607
  apply(stackEl, defaults, true);
608
  if (i !== 0) {
609
  stackEl.down = stackElements[i - 1];
610
  }
611
  if (i !== len - 1) {
612
  stackEl.up = stackElements[i + 1];
613
  }
614
  }
615
  return stackEl;
616
  * This will remove a stackelement from its stack while leaving the stack functional.
617
  * @param {Object} element The elment to remove from the stack.
618
  */
619
  element.up.down = element.down;
620
  element.down.up = element.up;
621
  element.up = element.down = null;
622
  * Export the main object and any other methods applicable
623
  */
624
  * @class easyXDM
625
  * A javascript library providing cross-browser, cross-domain messaging/RPC.
626
  * @version 2.4.15.118
627
  * @singleton
628
  */
629
  /**
630
  * The version of the library
631
  * @type {string}
632
  */
633
  version: "2.4.15.118",
634
  /**
635
  * This is a map containing all the query parameters passed to the document.
636
  * All the values has been decoded using decodeURIComponent.
637
  * @type {object}
638
  */
639
  query: query,
640
  /**
641
  * @private
642
  */
643
  stack: {},
644
  /**
645
  * Applies properties from the source object to the target object.<br/>
646
  * @param {object} target The target of the properties.
647
  * @param {object} source The source of the properties.
648
  * @param {boolean} noOverwrite Set to True to only set non-existing properties.
649
  */
650
  apply: apply,
651
 
652
  /**
653
  * A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works.
654
  * @return {JSON} A valid JSON conforming object, or null if not found.
655
  */
656
  getJSONObject: getJSON,
657
  /**
658
  * This will add a function to the queue of functions to be run once the DOM reaches a ready state.
659
  * If functions are added after this event then they will be executed immediately.
660
  * @param {function} fn The function to add
661
  * @param {object} scope An optional scope for the function to be called with.
662
  */
663
  whenReady: whenReady,
664
  /**
665
  * Removes easyXDM variable from the global scope. It also returns control
666
  * of the easyXDM variable to whatever code used it before.
667
  *
668
  * @param {String} ns A string representation of an object that will hold
669
  * an instance of easyXDM.
670
  * @return An instance of easyXDM
671
  */
672
  noConflict: noConflict
673
  checkAcl: checkAcl,
674
  getDomainName: getDomainName,
675
  getLocation: getLocation,
676
  appendQueryParameters: appendQueryParameters
677
  _deferred: [],
678
  flush: function(){
679
  this.trace("... deferred messages ...");
680
  for (var i = 0, len = this._deferred.length; i < len; i++) {
681
  this.trace(this._deferred[i]);
682
  }
683
  this._deferred.length = 0;
684
  this.trace("... end of deferred messages ...");
685
  },
686
  getTime: function(){
687
  var d = new Date(), h = d.getHours() + "", m = d.getMinutes() + "", s = d.getSeconds() + "", ms = d.getMilliseconds() + "", zeros = "000";
688
  if (h.length == 1) {
689
  h = "0" + h;
690
  }
691
  if (m.length == 1) {
692
  m = "0" + m;
693
  }
694
  if (s.length == 1) {
695
  s = "0" + s;
696
  }
697
  ms = zeros.substring(ms.length) + ms;
698
  return h + ":" + m + ":" + s + "." + ms;
699
  },
700
  /**
701
  * Logs the message to console.log if available
702
  * @param {String} msg The message to log
703
  */
704
  log: function(msg){
705
  // Uses memoizing to cache the implementation
706
  if (!isHostObject(window, "console") || undef(console.log)) {
707
  /**
708
  * Sets log to be an empty function since we have no output available
709
  * @ignore
710
  */
711
  this.log = emptyFn;
712
  }
713
  else {
714
  /**
715
  * Sets log to be a wrapper around console.log
716
  * @ignore
717
  * @param {String} msg
718
  */
719
  this.log = function(msg){
720
  console.log(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ": " + msg);
721
  };
722
  }
723
  this.log(msg);
724
  },
725
  /**
726
  * Will try to trace the given message either to a DOMElement with the id "log",
727
  * or by using console.info.
728
  * @param {String} msg The message to trace
729
  */
730
  trace: function(msg){
731
  // Uses memoizing to cache the implementation
732
  if (!domIsReady) {
733
  if (this._deferred.length === 0) {
734
  easyXDM.whenReady(debug.flush, debug);
735
  }
736
  this._deferred.push(msg);
737
  this.log(msg);
738
  }
739
  else {
740
  var el = document.getElementById("log");
741
  // is there a log element present?
742
  if (el) {
743
  /**
744
  * Sets trace to be a function that outputs the messages to the DOMElement with id "log"
745
  * @ignore
746
  * @param {String} msg
747
  */
748
  this.trace = function(msg){
749
  try {
750
  el.appendChild(document.createElement("div")).appendChild(document.createTextNode(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg));
751
  el.scrollTop = el.scrollHeight;
752
  }
753
  catch (e) {
754
  //In case we are unloading
755
  }
756
  };
757
  }
758
  else if (isHostObject(window, "console") && !undef(console.info)) {
759
  /**
760
  * Sets trace to be a wrapper around console.info
761
  * @ignore
762
  * @param {String} msg
763
  */
764
  this.trace = function(msg){
765
  console.info(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg);
766
  };
767
  }
768
  else {
769
  /**
770
  * Create log window
771
  * @ignore
772
  */
773
  var domain = location.host, windowname = domain.replace(/\[-.:]/g, "") + "easyxdm_log", logWin;
774
  try {
775
  logWin = window.open("", windowname, "width=800,height=200,status=0,navigation=0,scrollbars=1");
776
  }
777
  catch (e) {
778
  }
779
  if (logWin) {
780
  var doc = logWin.document;
781
  el = doc.getElementById("log");
782
  if (!el) {
783
  doc.write("<html><head><title>easyXDM log " + domain + "</title></head>");
784
  doc.write("<body><div id=\"log\"></div></body></html>");
785
  doc.close();
786
  el = doc.getElementById("log");
787
  }
788
  this.trace = function(msg){
789
  try {
790
  el.appendChild(doc.createElement("div")).appendChild(doc.createTextNode(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg));
791
  el.scrollTop = el.scrollHeight;
792
  }
793
  catch (e) {
794
  //In case we are unloading
795
  }
796
  };
797
  this.trace("---- new logger at " + location.href);
798
  }
799
 
800
  if (!el) {
801
  // We are unable to use any logging
802
  this.trace = emptyFn;
803
  }
804
  }
805
  this.trace(msg);
806
  }
807
  },
808
  /**
809
  * Creates a method usable for tracing.
810
  * @param {String} name The name the messages should be marked with
811
  * @return {Function} A function that accepts a single string as argument.
812
  */
813
  getTracer: function(name){
814
  return function(msg){
815
  debug.trace(name + ": " + msg);
816
  };
817
  }
818
  * @class easyXDM.DomHelper
819
  * Contains methods for dealing with the DOM
820
  * @singleton
821
  */
822
  /**
823
  * Provides a consistent interface for adding eventhandlers
824
  * @param {Object} target The target to add the event to
825
  * @param {String} type The name of the event
826
  * @param {Function} listener The listener
827
  */
828
  on: on,
829
  /**
830
  * Provides a consistent interface for removing eventhandlers
831
  * @param {Object} target The target to remove the event from
832
  * @param {String} type The name of the event
833
  * @param {Function} listener The listener
834
  */
835
  un: un,
836
  /**
837
  * Checks for the presence of the JSON object.
838
  * If it is not present it will use the supplied path to load the JSON2 library.
839
  * This should be called in the documents head right after the easyXDM script tag.
840
  * http://json.org/json2.js
841
  * @param {String} path A valid path to json2.js
842
  */
843
  requiresJSON: function(path){
844
  if (!isHostObject(window, "JSON")) {
845
  debug.log("loading external JSON");
846
  // we need to encode the < in order to avoid an illegal token error
847
  // when the script is inlined in a document.
848
  document.write('<' + 'script type="text/javascript" src="' + path + '"><' + '/script>');
849
  }
850
  else {
851
  debug.log("native JSON found");
852
  }
853
  }
854
  // The map containing the stored functions
855
  var _map = {};
856
 
857
  /**
858
  * @class easyXDM.Fn
859
  * This contains methods related to function handling, such as storing callbacks.
860
  * @singleton
861
  * @namespace easyXDM
862
  */
863
  easyXDM.Fn = {
864
  /**
865
  * Stores a function using the given name for reference
866
  * @param {String} name The name that the function should be referred by
867
  * @param {Function} fn The function to store
868
  * @namespace easyXDM.fn
869
  */
870
  set: function(name, fn){
871
  this._trace("storing function " + name);
872
  _map[name] = fn;
873
  },
874
  /**
875
  * Retrieves the function referred to by the given name
876
  * @param {String} name The name of the function to retrieve
877
  * @param {Boolean} del If the function should be deleted after retrieval
878
  * @return {Function} The stored function
879
  * @namespace easyXDM.fn
880
  */
881
  get: function(name, del){
882
  this._trace("retrieving function " + name);
883
  var fn = _map[name];
884
  if (!fn) {
885
  this._trace(name + " not found");
886
  }
887
 
888
  if (del) {
889
  delete _map[name];
890
  }
891
  return fn;
892
  }
893
  };
894
 
895
  easyXDM.Fn._trace = debug.getTracer("easyXDM.Fn");
896
  * @class easyXDM.Socket
897
  * This class creates a transport channel between two domains that is usable for sending and receiving string-based messages.<br/>
898
  * The channel is reliable, supports queueing, and ensures that the message originates from the expected domain.<br/>
899
  * Internally different stacks will be used depending on the browsers features and the available parameters.
900
  * <h2>How to set up</h2>
901
  * Setting up the provider:
902
  * <pre><code>
903
  * var socket = new easyXDM.Socket({
904
  * &nbsp; local: "name.html",
905
  * &nbsp; onReady: function(){
906
  * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
907
  * &nbsp; &nbsp; socket.postMessage("foo-message");
908
  * &nbsp; },
909
  * &nbsp; onMessage: function(message, origin) {
910
  * &nbsp;&nbsp; alert("received " + message + " from " + origin);
911
  * &nbsp; }
912
  * });
913
  * </code></pre>
914
  * Setting up the consumer:
915
  * <pre><code>
916
  * var socket = new easyXDM.Socket({
917
  * &nbsp; remote: "http:&#47;&#47;remotedomain/page.html",
918
  * &nbsp; remoteHelper: "http:&#47;&#47;remotedomain/name.html",
919
  * &nbsp; onReady: function(){
920
  * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
921
  * &nbsp; &nbsp; socket.postMessage("foo-message");
922
  * &nbsp; },
923
  * &nbsp; onMessage: function(message, origin) {
924
  * &nbsp;&nbsp; alert("received " + message + " from " + origin);
925
  * &nbsp; }
926
  * });
927
  * </code></pre>
928
  * If you are unable to upload the <code>name.html</code> file to the consumers domain then remove the <code>remoteHelper</code> property
929
  * and easyXDM will fall back to using the HashTransport instead of the NameTransport when not able to use any of the primary transports.
930
  * @namespace easyXDM
931
  * @constructor
932
  * @cfg {String/Window} local The url to the local name.html document, a local static file, or a reference to the local window.
933
  * @cfg {Boolean} lazy (Consumer only) Set this to true if you want easyXDM to defer creating the transport until really needed.
934
  * @cfg {String} remote (Consumer only) The url to the providers document.
935
  * @cfg {String} remoteHelper (Consumer only) The url to the remote name.html file. This is to support NameTransport as a fallback. Optional.
936
  * @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window. Optional, defaults to 2000.
937
  * @cfg {Number} interval The interval used when polling for messages. Optional, defaults to 300.
938
  * @cfg {String} channel (Consumer only) The name of the channel to use. Can be used to set consistent iframe names. Must be unique. Optional.
939
  * @cfg {Function} onMessage The method that should handle incoming messages.<br/> This method should accept two arguments, the message as a string, and the origin as a string. Optional.
940
  * @cfg {Function} onReady A method that should be called when the transport is ready. Optional.
941
  * @cfg {DOMElement|String} container (Consumer only) The element, or the id of the element that the primary iframe should be inserted into. If not set then the iframe will be positioned off-screen. Optional.
942
  * @cfg {Array/String} acl (Provider only) Here you can specify which '[protocol]://[domain]' patterns that should be allowed to act as the consumer towards this provider.<br/>
943
  * This can contain the wildcards ? and *. Examples are 'http://example.com', '*.foo.com' and '*dom?.com'. If you want to use reqular expressions then you pattern needs to start with ^ and end with $.
944
  * If none of the patterns match an Error will be thrown.
945
  * @cfg {Object} props (Consumer only) Additional properties that should be applied to the iframe. This can also contain nested objects e.g: <code>{style:{width:"100px", height:"100px"}}</code>.
946
  * Properties such as 'name' and 'src' will be overrided. Optional.
947
  */
948
  var trace = debug.getTracer("easyXDM.Socket");
949
  trace("constructor");
950
 
951
  // create the stack
952
  var stack = chainStack(prepareTransportStack(config).concat([{
953
  incoming: function(message, origin){
954
  config.onMessage(message, origin);
955
  },
956
  callback: function(success){
957
  if (config.onReady) {
958
  config.onReady(success);
959
  }
960
  }
961
  }])), recipient = getLocation(config.remote);
962
 
963
  // set the origin
964
  this.origin = getLocation(config.remote);
965
  /**
966
  * Initiates the destruction of the stack.
967
  */
968
  this.destroy = function(){
969
  stack.destroy();
970
  };
971
 
972
  /**
973
  * Posts a message to the remote end of the channel
974
  * @param {String} message The message to send
975
  */
976
  this.postMessage = function(message){
977
  stack.outgoing(message, recipient);
978
  };
979
 
980
  stack.init();
981
  * @class easyXDM.Rpc
982
  * Creates a proxy object that can be used to call methods implemented on the remote end of the channel, and also to provide the implementation
983
  * of methods to be called from the remote end.<br/>
984
  * The instantiated object will have methods matching those specified in <code>config.remote</code>.<br/>
985
  * This requires the JSON object present in the document, either natively, using json.org's json2 or as a wrapper around library spesific methods.
986
  * <h2>How to set up</h2>
987
  * <pre><code>
988
  * var rpc = new easyXDM.Rpc({
989
  * &nbsp; &#47;&#47; this configuration is equal to that used by the Socket.
990
  * &nbsp; remote: "http:&#47;&#47;remotedomain/...",
991
  * &nbsp; onReady: function(){
992
  * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the proxy
993
  * &nbsp; &nbsp; rpc.foo(...
994
  * &nbsp; }
995
  * },{
996
  * &nbsp; local: {..},
997
  * &nbsp; remote: {..}
998
  * });
999
  * </code></pre>
1000
  *
1001
  * <h2>Exposing functions (procedures)</h2>
1002
  * <pre><code>
1003
  * var rpc = new easyXDM.Rpc({
1004
  * &nbsp; ...
1005
  * },{
1006
  * &nbsp; local: {
1007
  * &nbsp; &nbsp; nameOfMethod: {
1008
  * &nbsp; &nbsp; &nbsp; method: function(arg1, arg2, success, error){
1009
  * &nbsp; &nbsp; &nbsp; &nbsp; ...
1010
  * &nbsp; &nbsp; &nbsp; }
1011
  * &nbsp; &nbsp; },
1012
  * &nbsp; &nbsp; &#47;&#47; with shorthand notation
1013
  * &nbsp; &nbsp; nameOfAnotherMethod: function(arg1, arg2, success, error){
1014
  * &nbsp; &nbsp; }
1015
  * &nbsp; },
1016
  * &nbsp; remote: {...}
1017
  * });
1018
  * </code></pre>
1019
  * The function referenced by [method] will receive the passed arguments followed by the callback functions <code>success</code> and <code>error</code>.<br/>
1020
  * To send a successfull result back you can use
1021
  * <pre><code>
1022
  * return foo;
1023
  * </pre></code>
1024
  * or
1025
  * <pre><code>
1026
  * success(foo);
1027
  * </pre></code>
1028
  * To return an error you can use
1029
  * <pre><code>
1030
  * throw new Error("foo error");
1031
  * </code></pre>
1032
  * or
1033
  * <pre><code>
1034
  * error("foo error");
1035
  * </code></pre>
1036
  *
1037
  * <h2>Defining remotely exposed methods (procedures/notifications)</h2>
1038
  * The definition of the remote end is quite similar:
1039
  * <pre><code>
1040
  * var rpc = new easyXDM.Rpc({
1041
  * &nbsp; ...
1042
  * },{
1043
  * &nbsp; local: {...},
1044
  * &nbsp; remote: {
1045
  * &nbsp; &nbsp; nameOfMethod: {}
1046
  * &nbsp; }
1047
  * });
1048
  * </code></pre>
1049
  * To call a remote method use
1050
  * <pre><code>
1051
  * rpc.nameOfMethod("arg1", "arg2", function(value) {
1052
  * &nbsp; alert("success: " + value);
1053
  * }, function(message) {
1054
  * &nbsp; alert("error: " + message + );
1055
  * });
1056
  * </code></pre>
1057
  * Both the <code>success</code> and <code>errror</code> callbacks are optional.<br/>
1058
  * When called with no callback a JSON-RPC 2.0 notification will be executed.
1059
  * Be aware that you will not be notified of any errors with this method.
1060
  * <br/>
1061
  * <h2>Specifying a custom serializer</h2>
1062
  * If you do not want to use the JSON2 library for non-native JSON support, but instead capabilities provided by some other library
1063
  * then you can specify a custom serializer using <code>serializer: foo</code>
1064
  * <pre><code>
1065
  * var rpc = new easyXDM.Rpc({
1066
  * &nbsp; ...
1067
  * },{
1068
  * &nbsp; local: {...},
1069
  * &nbsp; remote: {...},
1070
  * &nbsp; serializer : {
1071
  * &nbsp; &nbsp; parse: function(string){ ... },
1072
  * &nbsp; &nbsp; stringify: function(object) {...}
1073
  * &nbsp; }
1074
  * });
1075
  * </code></pre>
1076
  * If <code>serializer</code> is set then the class will not attempt to use the native implementation.
1077
  * @namespace easyXDM
1078
  * @constructor
1079
  * @param {Object} config The underlying transports configuration. See easyXDM.Socket for available parameters.
1080
  * @param {Object} jsonRpcConfig The description of the interface to implement.
1081
  */
1082
  var trace = debug.getTracer("easyXDM.Rpc");
1083
  trace("constructor");
1084
 
1085
  // expand shorthand notation
1086
  if (jsonRpcConfig.local) {
1087
  for (var method in jsonRpcConfig.local) {
1088
  if (jsonRpcConfig.local.hasOwnProperty(method)) {
1089
  var member = jsonRpcConfig.local[method];
1090
  if (typeof member === "function") {
1091
  jsonRpcConfig.local[method] = {
1092
  method: member
1093
  };
1094
  }
1095
  }
1096
  }
1097
  }
1098
  // create the stack
1099
  var stack = chainStack(prepareTransportStack(config).concat([new easyXDM.stack.RpcBehavior(this, jsonRpcConfig), {
1100
  callback: function(success){
1101
  if (config.onReady) {
1102
  config.onReady(success);
1103
  }
1104
  }
1105
  }]));
1106
  // set the origin
1107
  this.origin = getLocation(config.remote);
1108
 
1109
  /**
1110
  * Initiates the destruction of the stack.
1111
  */
1112
  this.destroy = function(){
1113
  stack.destroy();
1114
  };
1115
 
1116
  stack.init();
1117
  * @class easyXDM.stack.SameOriginTransport
1118
  * SameOriginTransport is a transport class that can be used when both domains have the same origin.<br/>
1119
  * This can be useful for testing and for when the main application supports both internal and external sources.
1120
  * @namespace easyXDM.stack
1121
  * @constructor
1122
  * @param {Object} config The transports configuration.
1123
  * @cfg {String} remote The remote document to communicate with.
1124
  */
1125
  var trace = debug.getTracer("easyXDM.stack.SameOriginTransport");
1126
  trace("constructor");
1127
  var pub, frame, send, targetOrigin;
1128
 
1129
  return (pub = {
1130
  outgoing: function(message, domain, fn){
1131
  send(message);
1132
  if (fn) {
1133
  fn();
1134
  }
1135
  },
1136
  destroy: function(){
1137
  trace("destroy");
1138
  if (frame) {
1139
  frame.parentNode.removeChild(frame);
1140
  frame = null;
1141
  }
1142
  },
1143
  onDOMReady: function(){
1144
  trace("init");
1145
  targetOrigin = getLocation(config.remote);
1146
 
1147
  if (config.isHost) {
1148
  // set up the iframe
1149
  apply(config.props, {
1150
  src: appendQueryParameters(config.remote, {
1151
  xdm_e: location.protocol + "//" + location.host + location.pathname,
1152
  xdm_c: config.channel,
1153
  xdm_p: 4 // 4 = SameOriginTransport
1154
  }),
1155
  name: IFRAME_PREFIX + config.channel + "_provider"
1156
  });
1157
  frame = createFrame(config);
1158
  easyXDM.Fn.set(config.channel, function(sendFn){
1159
  send = sendFn;
1160
  setTimeout(function(){
1161
  pub.up.callback(true);
1162
  }, 0);
1163
  return function(msg){
1164
  pub.up.incoming(msg, targetOrigin);
1165
  };
1166
  });
1167
  }
1168
  else {
1169
  send = getParentObject().Fn.get(config.channel, true)(function(msg){
1170
  pub.up.incoming(msg, targetOrigin);
1171
  });
1172
  setTimeout(function(){
1173
  pub.up.callback(true);
1174
  }, 0);
1175
  }
1176
  },
1177
  init: function(){
1178
  whenReady(pub.onDOMReady, pub);
1179
  }
1180
  });
1181
  * @class easyXDM.stack.FlashTransport
1182
  * FlashTransport is a transport class that uses an SWF with LocalConnection to pass messages back and forth.
1183
  * @namespace easyXDM.stack
1184
  * @constructor
1185
  * @param {Object} config The transports configuration.
1186
  * @cfg {String} remote The remote domain to communicate with.
1187
  * @cfg {String} secret the pre-shared secret used to secure the communication.
1188
  * @cfg {String} swf The path to the swf file
1189
  * @cfg {Boolean} swfNoThrottle Set this to true if you want to take steps to avoid beeing throttled when hidden.
1190
  * @cfg {String || DOMElement} swfContainer Set this if you want to control where the swf is placed
1191
  */
1192
  var trace = debug.getTracer("easyXDM.stack.FlashTransport");
1193
  trace("constructor");
1194
  if (!config.swf) {
1195
  throw new Error("Path to easyxdm.swf is missing");
1196
  }
1197
  var pub, // the public interface
1198
  frame, send, targetOrigin, swf, swfContainer;
1199
 
1200
  function onMessage(message, origin){
1201
  setTimeout(function(){
1202
  trace("received message");
1203
  pub.up.incoming(message, targetOrigin);
1204
  }, 0);
1205
  }
1206
 
1207
  /**
1208
  * This method adds the SWF to the DOM and prepares the initialization of the channel
1209
  */
1210
  function addSwf(domain){
1211
  trace("creating factory with SWF from " + domain);
1212
  // the differentiating query argument is needed in Flash9 to avoid a caching issue where LocalConnection would throw an error.
1213
  var url = config.swf + "?host=" + config.isHost;
1214
  var id = "easyXDM_swf_" + Math.floor(Math.random() * 10000);
1215
 
1216
  // prepare the init function that will fire once the swf is ready
1217
  easyXDM.Fn.set("flash_loaded" + domain.replace(/[\-.]/g, "_"), function(){
1218
  easyXDM.stack.FlashTransport[domain].swf = swf = swfContainer.firstChild;
1219
  var queue = easyXDM.stack.FlashTransport[domain].queue;
1220
  for (var i = 0; i < queue.length; i++) {
1221
  queue[i]();
1222
  }
1223
  queue.length = 0;
1224
  });
1225
 
1226
  if (config.swfContainer) {
1227
  swfContainer = (typeof config.swfContainer == "string") ? document.getElementById(config.swfContainer) : config.swfContainer;
1228
  }
1229
  else {
1230
  // create the container that will hold the swf
1231
  swfContainer = document.createElement('div');
1232
 
1233
  // http://bugs.adobe.com/jira/browse/FP-4796
1234
  // http://tech.groups.yahoo.com/group/flexcoders/message/162365
1235
  // https://groups.google.com/forum/#!topic/easyxdm/mJZJhWagoLc
1236
  apply(swfContainer.style, HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle ? {
1237
  height: "20px",
1238
  width: "20px",
1239
  position: "fixed",
1240
  right: 0,
1241
  top: 0
1242
  } : {
1243
  height: "1px",
1244
  width: "1px",
1245
  position: "absolute",
1246
  overflow: "hidden",
1247
  right: 0,
1248
  top: 0
1249
  });
1250
  document.body.appendChild(swfContainer);
1251
  }
1252
 
1253
  // create the object/embed
1254
  var flashVars = "callback=flash_loaded" + domain.replace(/[\-.]/g, "_") + "&proto=" + global.location.protocol + "&domain=" + getDomainName(global.location.href) + "&port=" + getPort(global.location.href) + "&ns=" + namespace;
1255
  flashVars += "&log=true";
1256
  swfContainer.innerHTML = "<object height='20' width='20' type='application/x-shockwave-flash' id='" + id + "' data='" + url + "'>" +
1257
  "<param name='allowScriptAccess' value='always'></param>" +
1258
  "<param name='wmode' value='transparent'>" +
1259
  "<param name='movie' value='" +
1260
  url +
1261
  "'></param>" +
1262
  "<param name='flashvars' value='" +
1263
  flashVars +
1264
  "'></param>" +
1265
  "<embed type='application/x-shockwave-flash' FlashVars='" +
1266
  flashVars +
1267
  "' allowScriptAccess='always' wmode='transparent' src='" +
1268
  url +
1269
  "' height='1' width='1'></embed>" +
1270
  "</object>";
1271
  }
1272
 
1273
  return (pub = {
1274
  outgoing: function(message, domain, fn){
1275
  swf.postMessage(config.channel, message.toString());
1276
  if (fn) {
1277
  fn();
1278
  }
1279
  },
1280
  destroy: function(){
1281
  trace("destroy");
1282
  try {
1283
  swf.destroyChannel(config.channel);
1284
  }
1285
  catch (e) {
1286
  }
1287
  swf = null;
1288
  if (frame) {
1289
  frame.parentNode.removeChild(frame);
1290
  frame = null;
1291
  }
1292
  },
1293
  onDOMReady: function(){
1294
  trace("init");
1295
 
1296
  targetOrigin = config.remote;
1297
 
1298
  // Prepare the code that will be run after the swf has been intialized
1299
  easyXDM.Fn.set("flash_" + config.channel + "_init", function(){
1300
  setTimeout(function(){
1301
  trace("firing onReady");
1302
  pub.up.callback(true);
1303
  });
1304
  });
1305
 
1306
  // set up the omMessage handler
1307
  easyXDM.Fn.set("flash_" + config.channel + "_onMessage", onMessage);
1308
 
1309
  config.swf = resolveUrl(config.swf); // reports have been made of requests gone rogue when using relative paths
1310
  var swfdomain = getDomainName(config.swf);
1311
  var fn = function(){
1312
  // set init to true in case the fn was called was invoked from a separate instance
1313
  easyXDM.stack.FlashTransport[swfdomain].init = true;
1314
  swf = easyXDM.stack.FlashTransport[swfdomain].swf;
1315
  // create the channel
1316
  swf.createChannel(config.channel, config.secret, getLocation(config.remote), config.isHost);
1317
 
1318
  if (config.isHost) {
1319
  // if Flash is going to be throttled and we want to avoid this
1320
  if (HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle) {
1321
  apply(config.props, {
1322
  position: "fixed",
1323
  right: 0,
1324
  top: 0,
1325
  height: "20px",
1326
  width: "20px"
1327
  });
1328
  }
1329
  // set up the iframe
1330
  apply(config.props, {
1331
  src: appendQueryParameters(config.remote, {
1332
  xdm_e: getLocation(location.href),
1333
  xdm_c: config.channel,
1334
  xdm_p: 6, // 6 = FlashTransport
1335
  xdm_s: config.secret
1336
  }),
1337
  name: IFRAME_PREFIX + config.channel + "_provider"
1338
  });
1339
  frame = createFrame(config);
1340
  }
1341
  };
1342
 
1343
  if (easyXDM.stack.FlashTransport[swfdomain] && easyXDM.stack.FlashTransport[swfdomain].init) {
1344
  // if the swf is in place and we are the consumer
1345
  fn();
1346
  }
1347
  else {
1348
  // if the swf does not yet exist
1349
  if (!easyXDM.stack.FlashTransport[swfdomain]) {
1350
  // add the queue to hold the init fn's
1351
  easyXDM.stack.FlashTransport[swfdomain] = {
1352
  queue: [fn]
1353
  };
1354
  addSwf(swfdomain);
1355
  }
1356
  else {
1357
  easyXDM.stack.FlashTransport[swfdomain].queue.push(fn);
1358
  }
1359
  }
1360
  },
1361
  init: function(){
1362
  whenReady(pub.onDOMReady, pub);
1363
  }
1364
  });
1365
  * @class easyXDM.stack.PostMessageTransport
1366
  * PostMessageTransport is a transport class that uses HTML5 postMessage for communication.<br/>
1367
  * <a href="http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx">http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx</a><br/>
1368
  * <a href="https://developer.mozilla.org/en/DOM/window.postMessage">https://developer.mozilla.org/en/DOM/window.postMessage</a>
1369
  * @namespace easyXDM.stack
1370
  * @constructor
1371
  * @param {Object} config The transports configuration.
1372
  * @cfg {String} remote The remote domain to communicate with.
1373
  */
1374
  var trace = debug.getTracer("easyXDM.stack.PostMessageTransport");
1375
  trace("constructor");
1376
  var pub, // the public interface
1377
  frame, // the remote frame, if any
1378
  callerWindow, // the window that we will call with
1379
  targetOrigin; // the domain to communicate with
1380
  /**
1381
  * Resolves the origin from the event object
1382
  * @private
1383
  * @param {Object} event The messageevent
1384
  * @return {String} The scheme, host and port of the origin
1385
  */
1386
  function _getOrigin(event){
1387
  if (event.origin) {
1388
  // This is the HTML5 property
1389
  return getLocation(event.origin);
1390
  }
1391
  if (event.uri) {
1392
  // From earlier implementations
1393
  return getLocation(event.uri);
1394
  }
1395
  if (event.domain) {
1396
  // This is the last option and will fail if the
1397
  // origin is not using the same schema as we are
1398
  return location.protocol + "//" + event.domain;
1399
  }
1400
  throw "Unable to retrieve the origin of the event";
1401
  }
1402
 
1403
  /**
1404
  * This is the main implementation for the onMessage event.<br/>
1405
  * It checks the validity of the origin and passes the message on if appropriate.
1406
  * @private
1407
  * @param {Object} event The messageevent
1408
  */
1409
  function _window_onMessage(event){
1410
  var origin = _getOrigin(event);
1411
  trace("received message '" + event.data + "' from " + origin);
1412
  if (origin == targetOrigin && event.data.substring(0, config.channel.length + 1) == config.channel + " ") {
1413
  pub.up.incoming(event.data.substring(config.channel.length + 1), origin);
1414
  }
1415
  }
1416
 
1417
  return (pub = {
1418
  outgoing: function(message, domain, fn){
1419
  callerWindow.postMessage(config.channel + " " + message, domain || targetOrigin);
1420
  if (fn) {
1421
  fn();
1422
  }
1423
  },
1424
  destroy: function(){
1425
  trace("destroy");
1426
  un(window, "message", _window_onMessage);
1427
  if (frame) {
1428
  callerWindow = null;
1429
  frame.parentNode.removeChild(frame);
1430
  frame = null;
1431
  }
1432
  },
1433
  onDOMReady: function(){
1434
  trace("init");
1435
  targetOrigin = getLocation(config.remote);
1436
  if (config.isHost) {
1437
  // add the event handler for listening
1438
  var waitForReady = function(event){
1439
  if (event.data == config.channel + "-ready") {
1440
  trace("firing onReady");
1441
  // replace the eventlistener
1442
  callerWindow = ("postMessage" in frame.contentWindow) ? frame.contentWindow : frame.contentWindow.document;
1443
  un(window, "message", waitForReady);
1444
  on(window, "message", _window_onMessage);
1445
  setTimeout(function(){
1446
  pub.up.callback(true);
1447
  }, 0);
1448
  }
1449
  };
1450
  on(window, "message", waitForReady);
1451
 
1452
  // set up the iframe
1453
  apply(config.props, {
1454
  src: appendQueryParameters(config.remote, {
1455
  xdm_e: getLocation(location.href),
1456
  xdm_c: config.channel,
1457
  xdm_p: 1 // 1 = PostMessage
1458
  }),
1459
  name: IFRAME_PREFIX + config.channel + "_provider"
1460
  });
1461
  frame = createFrame(config);
1462
  }
1463
  else {
1464
  // add the event handler for listening
1465
  on(window, "message", _window_onMessage);
1466
  callerWindow = ("postMessage" in window.parent) ? window.parent : window.parent.document;
1467
  callerWindow.postMessage(config.channel + "-ready", targetOrigin);
1468
 
1469
  setTimeout(function(){
1470
  pub.up.callback(true);
1471
  }, 0);
1472
  }
1473
  },
1474
  init: function(){
1475
  whenReady(pub.onDOMReady, pub);
1476
  }
1477
  });
1478
  * @class easyXDM.stack.FrameElementTransport
1479
  * FrameElementTransport is a transport class that can be used with Gecko-browser as these allow passing variables using the frameElement property.<br/>
1480
  * Security is maintained as Gecho uses Lexical Authorization to determine under which scope a function is running.
1481
  * @namespace easyXDM.stack
1482
  * @constructor
1483
  * @param {Object} config The transports configuration.
1484
  * @cfg {String} remote The remote document to communicate with.
1485
  */
1486
  var trace = debug.getTracer("easyXDM.stack.FrameElementTransport");
1487
  trace("constructor");
1488
  var pub, frame, send, targetOrigin;
1489
 
1490
  return (pub = {
1491
  outgoing: function(message, domain, fn){
1492
  send.call(this, message);
1493
  if (fn) {
1494
  fn();
1495
  }
1496
  },
1497
  destroy: function(){
1498
  trace("destroy");
1499
  if (frame) {
1500
  frame.parentNode.removeChild(frame);
1501
  frame = null;
1502
  }
1503
  },
1504
  onDOMReady: function(){
1505
  trace("init");
1506
  targetOrigin = getLocation(config.remote);
1507
 
1508
  if (config.isHost) {
1509
  // set up the iframe
1510
  apply(config.props, {
1511
  src: appendQueryParameters(config.remote, {
1512
  xdm_e: getLocation(location.href),
1513
  xdm_c: config.channel,
1514
  xdm_p: 5 // 5 = FrameElementTransport
1515
  }),
1516
  name: IFRAME_PREFIX + config.channel + "_provider"
1517
  });
1518
  frame = createFrame(config);
1519
  frame.fn = function(sendFn){
1520
  delete frame.fn;
1521
  send = sendFn;
1522
  setTimeout(function(){
1523
  pub.up.callback(true);
1524
  }, 0);
1525
  // remove the function so that it cannot be used to overwrite the send function later on
1526
  return function(msg){
1527
  pub.up.incoming(msg, targetOrigin);
1528
  };
1529
  };
1530
  }
1531
  else {
1532
  // This is to mitigate origin-spoofing
1533
  if (document.referrer && getLocation(document.referrer) != query.xdm_e) {
1534
  window.top.location = query.xdm_e;
1535
  }
1536
  send = window.frameElement.fn(function(msg){
1537
  pub.up.incoming(msg, targetOrigin);
1538
  });
1539
  pub.up.callback(true);
1540
  }
1541
  },
1542
  init: function(){
1543
  whenReady(pub.onDOMReady, pub);
1544
  }
1545
  });
1546
  * @class easyXDM.stack.NameTransport
1547
  * NameTransport uses the window.name property to relay data.
1548
  * The <code>local</code> parameter needs to be set on both the consumer and provider,<br/>
1549
  * and the <code>remoteHelper</code> parameter needs to be set on the consumer.
1550
  * @constructor
1551
  * @param {Object} config The transports configuration.
1552
  * @cfg {String} remoteHelper The url to the remote instance of hash.html - this is only needed for the host.
1553
  * @namespace easyXDM.stack
1554
  */
1555
  var trace = debug.getTracer("easyXDM.stack.NameTransport");
1556
  trace("constructor");
1557
  if (config.isHost && undef(config.remoteHelper)) {
1558
  trace("missing remoteHelper");
1559
  throw new Error("missing remoteHelper");
1560
  }
1561
 
1562
  var pub; // the public interface
1563
  var isHost, callerWindow, remoteWindow, readyCount, callback, remoteOrigin, remoteUrl;
1564
 
1565
  function _sendMessage(message){
1566
  var url = config.remoteHelper + (isHost ? "#_3" : "#_2") + config.channel;
1567
  trace("sending message " + message);
1568
  trace("navigating to '" + url + "'");
1569
  callerWindow.contentWindow.sendMessage(message, url);
1570
  }
1571
 
1572
  function _onReady(){
1573
  if (isHost) {
1574
  if (++readyCount === 2 || !isHost) {
1575
  pub.up.callback(true);
1576
  }
1577
  }
1578
  else {
1579
  _sendMessage("ready");
1580
  trace("calling onReady");
1581
  pub.up.callback(true);
1582
  }
1583
  }
1584
 
1585
  function _onMessage(message){
1586
  trace("received message " + message);
1587
  pub.up.incoming(message, remoteOrigin);
1588
  }
1589
 
1590
  function _onLoad(){
1591
  if (callback) {
1592
  setTimeout(function(){
1593
  callback(true);
1594
  }, 0);
1595
  }
1596
  }
1597
 
1598
  return (pub = {
1599
  outgoing: function(message, domain, fn){
1600
  callback = fn;
1601
  _sendMessage(message);
1602
  },
1603
  destroy: function(){
1604
  trace("destroy");
1605
  callerWindow.parentNode.removeChild(callerWindow);
1606
  callerWindow = null;
1607
  if (isHost) {
1608
  remoteWindow.parentNode.removeChild(remoteWindow);
1609
  remoteWindow = null;
1610
  }
1611
  },
1612
  onDOMReady: function(){
1613
  trace("init");
1614
  isHost = config.isHost;
1615
  readyCount = 0;
1616
  remoteOrigin = getLocation(config.remote);
1617
  config.local = resolveUrl(config.local);
1618
 
1619
  if (isHost) {
1620
  // Register the callback
1621
  easyXDM.Fn.set(config.channel, function(message){
1622
  trace("received initial message " + message);
1623
  if (isHost && message === "ready") {
1624
  // Replace the handler
1625
  easyXDM.Fn.set(config.channel, _onMessage);
1626
  _onReady();
1627
  }
1628
  });
1629
 
1630
  // Set up the frame that points to the remote instance
1631
  remoteUrl = appendQueryParameters(config.remote, {
1632
  xdm_e: config.local,
1633
  xdm_c: config.channel,
1634
  xdm_p: 2
1635
  });
1636
  apply(config.props, {
1637
  src: remoteUrl + '#' + config.channel,
1638
  name: IFRAME_PREFIX + config.channel + "_provider"
1639
  });
1640
  remoteWindow = createFrame(config);
1641
  }
1642
  else {
1643
  config.remoteHelper = config.remote;
1644
  easyXDM.Fn.set(config.channel, _onMessage);
1645
  }
1646
  // Set up the iframe that will be used for the transport
1647
 
1648
  callerWindow = createFrame({
1649
  props: {
1650
  src: config.local + "#_4" + config.channel
1651
  },
1652
  onLoad: function onLoad(){
1653
  // Remove the handler
1654
  var w = callerWindow || this;
1655
  un(w, "load", onLoad);
1656
  easyXDM.Fn.set(config.channel + "_load", _onLoad);
1657
  (function test(){
1658
  if (typeof w.contentWindow.sendMessage == "function") {
1659
  _onReady();
1660
  }
1661
  else {
1662
  setTimeout(test, 50);
1663
  }
1664
  }());
1665
  }
1666
  });
1667
  },
1668
  init: function(){
1669
  whenReady(pub.onDOMReady, pub);
1670
  }
1671
  });
1672
  * @class easyXDM.stack.HashTransport
1673
  * HashTransport is a transport class that uses the IFrame URL Technique for communication.<br/>
1674
  * <a href="http://msdn.microsoft.com/en-us/library/bb735305.aspx">http://msdn.microsoft.com/en-us/library/bb735305.aspx</a><br/>
1675
  * @namespace easyXDM.stack
1676
  * @constructor
1677
  * @param {Object} config The transports configuration.
1678
  * @cfg {String/Window} local The url to the local file used for proxying messages, or the local window.
1679
  * @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window.
1680
  * @cfg {Number} interval The interval used when polling for messages.
1681
  */
1682
  var trace = debug.getTracer("easyXDM.stack.HashTransport");
1683
  trace("constructor");
1684
  var pub;
1685
  var me = this, isHost, _timer, pollInterval, _lastMsg, _msgNr, _listenerWindow, _callerWindow;
1686
  var useParent, _remoteOrigin;
1687
 
1688
  function _sendMessage(message){
1689
  trace("sending message '" + (_msgNr + 1) + " " + message + "' to " + _remoteOrigin);
1690
  if (!_callerWindow) {
1691
  trace("no caller window");
1692
  return;
1693
  }
1694
  var url = config.remote + "#" + (_msgNr++) + "_" + message;
1695
  ((isHost || !useParent) ? _callerWindow.contentWindow : _callerWindow).location = url;
1696
  }
1697
 
1698
  function _handleHash(hash){
1699
  _lastMsg = hash;
1700
  trace("received message '" + _lastMsg + "' from " + _remoteOrigin);
1701
  pub.up.incoming(_lastMsg.substring(_lastMsg.indexOf("_") + 1), _remoteOrigin);
1702
  }
1703
 
1704
  /**
1705
  * Checks location.hash for a new message and relays this to the receiver.
1706
  * @private
1707
  */
1708
  function _pollHash(){
1709
  if (!_listenerWindow) {
1710
  return;
1711
  }
1712
  var href = _listenerWindow.location.href, hash = "", indexOf = href.indexOf("#");
1713
  if (indexOf != -1) {
1714
  hash = href.substring(indexOf);
1715
  }
1716
  if (hash && hash != _lastMsg) {
1717
  trace("poll: new message");
1718
  _handleHash(hash);
1719
  }
1720
  }
1721
 
1722
  function _attachListeners(){
1723
  trace("starting polling");
1724
  _timer = setInterval(_pollHash, pollInterval);
1725
  }
1726
 
1727
  return (pub = {
1728
  outgoing: function(message, domain){
1729
  _sendMessage(message);
1730
  },
1731
  destroy: function(){
1732
  window.clearInterval(_timer);
1733
  if (isHost || !useParent) {
1734
  _callerWindow.parentNode.removeChild(_callerWindow);
1735
  }
1736
  _callerWindow = null;
1737
  },
1738
  onDOMReady: function(){
1739
  isHost = config.isHost;
1740
  pollInterval = config.interval;
1741
  _lastMsg = "#" + config.channel;
1742
  _msgNr = 0;
1743
  useParent = config.useParent;
1744
  _remoteOrigin = getLocation(config.remote);
1745
  if (isHost) {
1746
  config.props = {
1747
  src: config.remote,
1748
  name: IFRAME_PREFIX + config.channel + "_provider"
1749
  };
1750
  if (useParent) {
1751
  config.onLoad = function(){
1752
  _listenerWindow = window;
1753
  _attachListeners();
1754
  pub.up.callback(true);
1755
  };
1756
  }
1757
  else {
1758
  var tries = 0, max = config.delay / 50;
1759
  (function getRef(){
1760
  if (++tries > max) {
1761
  trace("unable to get reference to _listenerWindow, giving up");
1762
  throw new Error("Unable to reference listenerwindow");
1763
  }
1764
  try {
1765
  _listenerWindow = _callerWindow.contentWindow.frames[IFRAME_PREFIX + config.channel + "_consumer"];
1766
  }
1767
  catch (ex) {
1768
  }
1769
  if (_listenerWindow) {
1770
  _attachListeners();
1771
  trace("got a reference to _listenerWindow");
1772
  pub.up.callback(true);
1773
  }
1774
  else {
1775
  setTimeout(getRef, 50);
1776
  }
1777
  }());
1778
  }
1779
  _callerWindow = createFrame(config);
1780
  }
1781
  else {
1782
  _listenerWindow = window;
1783
  _attachListeners();
1784
  if (useParent) {
1785
  _callerWindow = parent;
1786
  pub.up.callback(true);
1787
  }
1788
  else {
1789
  apply(config, {
1790
  props: {
1791
  src: config.remote + "#" + config.channel + new Date(),
1792
  name: IFRAME_PREFIX + config.channel + "_consumer"
1793
  },
1794
  onLoad: function(){
1795
  pub.up.callback(true);
1796
  }
1797
  });
1798
  _callerWindow = createFrame(config);
1799
  }
1800
  }
1801
  },
1802
  init: function(){
1803
  whenReady(pub.onDOMReady, pub);
1804
  }
1805
  });
1806
  * @class easyXDM.stack.ReliableBehavior
1807
  * This is a behavior that tries to make the underlying transport reliable by using acknowledgements.
1808
  * @namespace easyXDM.stack
1809
  * @constructor
1810
  * @param {Object} config The behaviors configuration.
1811
  */
1812
  var trace = debug.getTracer("easyXDM.stack.ReliableBehavior");
1813
  trace("constructor");
1814
  var pub, // the public interface
1815
  callback; // the callback to execute when we have a confirmed success/failure
1816
  var idOut = 0, idIn = 0, currentMessage = "";
1817
 
1818
  return (pub = {
1819
  incoming: function(message, origin){
1820
  trace("incoming: " + message);
1821
  var indexOf = message.indexOf("_"), ack = message.substring(0, indexOf).split(",");
1822
  message = message.substring(indexOf + 1);
1823
 
1824
  if (ack[0] == idOut) {
1825
  trace("message delivered");
1826
  currentMessage = "";
1827
  if (callback) {
1828
  callback(true);
1829
  }
1830
  }
1831
  if (message.length > 0) {
1832
  trace("sending ack, and passing on " + message);
1833
  pub.down.outgoing(ack[1] + "," + idOut + "_" + currentMessage, origin);
1834
  if (idIn != ack[1]) {
1835
  idIn = ack[1];
1836
  pub.up.incoming(message, origin);
1837
  }
1838
  }
1839
 
1840
  },
1841
  outgoing: function(message, origin, fn){
1842
  currentMessage = message;
1843
  callback = fn;
1844
  pub.down.outgoing(idIn + "," + (++idOut) + "_" + message, origin);
1845
  }
1846
  });
1847
  * @class easyXDM.stack.QueueBehavior
1848
  * This is a behavior that enables queueing of messages. <br/>
1849
  * It will buffer incoming messages and dispach these as fast as the underlying transport allows.
1850
  * This will also fragment/defragment messages so that the outgoing message is never bigger than the
1851
  * set length.
1852
  * @namespace easyXDM.stack
1853
  * @constructor
1854
  * @param {Object} config The behaviors configuration. Optional.
1855
  * @cfg {Number} maxLength The maximum length of each outgoing message. Set this to enable fragmentation.
1856
  */
1857
  var trace = debug.getTracer("easyXDM.stack.QueueBehavior");
1858
  trace("constructor");
1859
  var pub, queue = [], waiting = true, incoming = "", destroying, maxLength = 0, lazy = false, doFragment = false;
1860
 
1861
  function dispatch(){
1862
  if (config.remove && queue.length === 0) {
1863
  trace("removing myself from the stack");
1864
  removeFromStack(pub);
1865
  return;
1866
  }
1867
  if (waiting || queue.length === 0 || destroying) {
1868
  return;
1869
  }
1870
  trace("dispatching from queue");
1871
  waiting = true;
1872
  var message = queue.shift();
1873
 
1874
  pub.down.outgoing(message.data, message.origin, function(success){
1875
  waiting = false;
1876
  if (message.callback) {
1877
  setTimeout(function(){
1878
  message.callback(success);
1879
  }, 0);
1880
  }
1881
  dispatch();
1882
  });
1883
  }
1884
  return (pub = {
1885
  init: function(){
1886
  if (undef(config)) {
1887
  config = {};
1888
  }
1889
  if (config.maxLength) {
1890
  maxLength = config.maxLength;
1891
  doFragment = true;
1892
  }
1893
  if (config.lazy) {
1894
  lazy = true;
1895
  }
1896
  else {
1897
  pub.down.init();
1898
  }
1899
  },
1900
  callback: function(success){
1901
  waiting = false;
1902
  var up = pub.up; // in case dispatch calls removeFromStack
1903
  dispatch();
1904
  up.callback(success);
1905
  },
1906
  incoming: function(message, origin){
1907
  if (doFragment) {
1908
  var indexOf = message.indexOf("_"), seq = parseInt(message.substring(0, indexOf), 10);
1909
  incoming += message.substring(indexOf + 1);
1910
  if (seq === 0) {
1911
  trace("received the last fragment");
1912
  if (config.encode) {
1913
  incoming = decodeURIComponent(incoming);
1914
  }
1915
  pub.up.incoming(incoming, origin);
1916
  incoming = "";
1917
  }
1918
  else {
1919
  trace("waiting for more fragments, seq=" + message);
1920
  }
1921
  }
1922
  else {
1923
  pub.up.incoming(message, origin);
1924
  }
1925
  },
1926
  outgoing: function(message, origin, fn){
1927
  if (config.encode) {
1928
  message = encodeURIComponent(message);
1929
  }
1930
  var fragments = [], fragment;
1931
  if (doFragment) {
1932
  // fragment into chunks
1933
  while (message.length !== 0) {
1934
  fragment = message.substring(0, maxLength);
1935
  message = message.substring(fragment.length);
1936
  fragments.push(fragment);
1937
  }
1938
  // enqueue the chunks
1939
  while ((fragment = fragments.shift())) {
1940
  trace("enqueuing");
1941
  queue.push({
1942
  data: fragments.length + "_" + fragment,
1943
  origin: origin,
1944
  callback: fragments.length === 0 ? fn : null
1945
  });
1946
  }
1947
  }
1948
  else {
1949
  queue.push({
1950
  data: message,
1951
  origin: origin,
1952
  callback: fn
1953
  });
1954
  }
1955
  if (lazy) {
1956
  pub.down.init();
1957
  }
1958
  else {
1959
  dispatch();
1960
  }
1961
  },
1962
  destroy: function(){
1963
  trace("destroy");
1964
  destroying = true;
1965
  pub.down.destroy();
1966
  }
1967
  });
1968
  * @class easyXDM.stack.VerifyBehavior
1969
  * This behavior will verify that communication with the remote end is possible, and will also sign all outgoing,
1970
  * and verify all incoming messages. This removes the risk of someone hijacking the iframe to send malicious messages.
1971
  * @namespace easyXDM.stack
1972
  * @constructor
1973
  * @param {Object} config The behaviors configuration.
1974
  * @cfg {Boolean} initiate If the verification should be initiated from this end.
1975
  */
1976
  var trace = debug.getTracer("easyXDM.stack.VerifyBehavior");
1977
  trace("constructor");
1978
  if (undef(config.initiate)) {
1979
  throw new Error("settings.initiate is not set");
1980
  }
1981
  var pub, mySecret, theirSecret, verified = false;
1982
 
1983
  function startVerification(){
1984
  trace("requesting verification");
1985
  mySecret = Math.random().toString(16).substring(2);
1986
  pub.down.outgoing(mySecret);
1987
  }
1988
 
1989
  return (pub = {
1990
  incoming: function(message, origin){
1991
  var indexOf = message.indexOf("_");
1992
  if (indexOf === -1) {
1993
  if (message === mySecret) {
1994
  trace("verified, calling callback");
1995
  pub.up.callback(true);
1996
  }
1997
  else if (!theirSecret) {
1998
  trace("returning secret");
1999
  theirSecret = message;
2000
  if (!config.initiate) {
2001
  startVerification();
2002
  }
2003
  pub.down.outgoing(message);
2004
  }
2005
  }
2006
  else {
2007
  if (message.substring(0, indexOf) === theirSecret) {
2008
  pub.up.incoming(message.substring(indexOf + 1), origin);
2009
  }
2010
  }
2011
  },
2012
  outgoing: function(message, origin, fn){
2013
  pub.down.outgoing(mySecret + "_" + message, origin, fn);
2014
  },
2015
  callback: function(success){
2016
  if (config.initiate) {
2017
  startVerification();
2018
  }
2019
  }
2020
  });
2021
  * @class easyXDM.stack.RpcBehavior
2022
  * This uses JSON-RPC 2.0 to expose local methods and to invoke remote methods and have responses returned over the the string based transport stack.<br/>
2023
  * Exposed methods can return values synchronous, asyncronous, or bet set up to not return anything.
2024
  * @namespace easyXDM.stack
2025
  * @constructor
2026
  * @param {Object} proxy The object to apply the methods to.
2027
  * @param {Object} config The definition of the local and remote interface to implement.
2028
  * @cfg {Object} local The local interface to expose.
2029
  * @cfg {Object} remote The remote methods to expose through the proxy.
2030
  * @cfg {Object} serializer The serializer to use for serializing and deserializing the JSON. Should be compatible with the HTML5 JSON object. Optional, will default to JSON.
2031
  */
2032
  var trace = debug.getTracer("easyXDM.stack.RpcBehavior");
2033
  var pub, serializer = config.serializer || getJSON();
2034
  var _callbackCounter = 0, _callbacks = {};
2035
 
2036
  /**
2037
  * Serializes and sends the message
2038
  * @private
2039
  * @param {Object} data The JSON-RPC message to be sent. The jsonrpc property will be added.
2040
  */
2041
  function _send(data){
2042
  data.jsonrpc = "2.0";
2043
  pub.down.outgoing(serializer.stringify(data));
2044
  }
2045
 
2046
  /**
2047
  * Creates a method that implements the given definition
2048
  * @private
2049
  * @param {Object} The method configuration
2050
  * @param {String} method The name of the method
2051
  * @return {Function} A stub capable of proxying the requested method call
2052
  */
2053
  function _createMethod(definition, method){
2054
  var slice = Array.prototype.slice;
2055
 
2056
  trace("creating method " + method);
2057
  return function(){
2058
  trace("executing method " + method);
2059
  var l = arguments.length, callback, message = {
2060
  method: method
2061
  };
2062
 
2063
  if (l > 0 && typeof arguments[l - 1] === "function") {
2064
  //with callback, procedure
2065
  if (l > 1 && typeof arguments[l - 2] === "function") {
2066
  // two callbacks, success and error
2067
  callback = {
2068
  success: arguments[l - 2],
2069
  error: arguments[l - 1]
2070
  };
2071
  message.params = slice.call(arguments, 0, l - 2);
2072
  }
2073
  else {
2074
  // single callback, success
2075
  callback = {
2076
  success: arguments[l - 1]
2077
  };
2078
  message.params = slice.call(arguments, 0, l - 1);
2079
  }
2080
  _callbacks["" + (++_callbackCounter)] = callback;
2081
  message.id = _callbackCounter;
2082
  }
2083
  else {
2084
  // no callbacks, a notification
2085
  message.params = slice.call(arguments, 0);
2086
  }
2087
  if (definition.namedParams && message.params.length === 1) {
2088
  message.params = message.params[0];
2089
  }
2090
  // Send the method request
2091
  _send(message);
2092
  };
2093
  }
2094
 
2095
  /**
2096
  * Executes the exposed method
2097
  * @private
2098
  * @param {String} method The name of the method
2099
  * @param {Number} id The callback id to use
2100
  * @param {Function} method The exposed implementation
2101
  * @param {Array} params The parameters supplied by the remote end
2102
  */
2103
  function _executeMethod(method, id, fn, params){
2104
  if (!fn) {
2105
  trace("requested to execute non-existent procedure " + method);
2106
  if (id) {
2107
  _send({
2108
  id: id,
2109
  error: {
2110
  code: -32601,
2111
  message: "Procedure not found."
2112
  }
2113
  });
2114
  }
2115
  return;
2116
  }
2117
 
2118
  trace("requested to execute procedure " + method);
2119
  var success, error;
2120
  if (id) {
2121
  success = function(result){
2122
  success = emptyFn;
2123
  _send({
2124
  id: id,
2125
  result: result
2126
  });
2127
  };
2128
  error = function(message, data){
2129
  error = emptyFn;
2130
  var msg = {
2131
  id: id,
2132
  error: {
2133
  code: -32099,
2134
  message: message
2135
  }
2136
  };
2137
  if (data) {
2138
  msg.error.data = data;
2139
  }
2140
  _send(msg);
2141
  };
2142
  }
2143
  else {
2144
  success = error = emptyFn;
2145
  }
2146
  // Call local method
2147
  if (!isArray(params)) {
2148
  params = [params];
2149
  }
2150
  try {
2151
  var result = fn.method.apply(fn.scope, params.concat([success, error]));
2152
  if (!undef(result)) {
2153
  success(result);
2154
  }
2155
  }
2156
  catch (ex1) {
2157
  error(ex1.message);
2158
  }
2159
  }
2160
 
2161
  return (pub = {
2162
  incoming: function(message, origin){
2163
  var data = serializer.parse(message);
2164
  if (data.method) {
2165
  trace("received request to execute method " + data.method + (data.id ? (" using callback id " + data.id) : ""));
2166
  // A method call from the remote end
2167
  if (config.handle) {
2168
  config.handle(data, _send);
2169
  }
2170
  else {
2171
  _executeMethod(data.method, data.id, config.local[data.method], data.params);
2172
  }
2173
  }
2174
  else {
2175
  trace("received return value destined to callback with id " + data.id);
2176
  // A method response from the other end
2177
  var callback = _callbacks[data.id];
2178
  if (data.error) {
2179
  if (callback.error) {
2180
  callback.error(data.error);
2181
  }
2182
  else {
2183
  trace("unhandled error returned.");
2184
  }
2185
  }
2186
  else if (callback.success) {
2187
  callback.success(data.result);
2188
  }
2189
  delete _callbacks[data.id];
2190
  }
2191
  },
2192
  init: function(){
2193
  trace("init");
2194
  if (config.remote) {
2195
  trace("creating stubs");
2196
  // Implement the remote sides exposed methods
2197
  for (var method in config.remote) {
2198
  if (config.remote.hasOwnProperty(method)) {
2199
  proxy[method] = _createMethod(config.remote[method], method);
2200
  }
2201
  }
2202
  }
2203
  pub.down.init();
2204
  },
2205
  destroy: function(){
2206
  trace("destroy");
2207
  for (var method in config.remote) {
2208
  if (config.remote.hasOwnProperty(method) && proxy.hasOwnProperty(method)) {
2209
  delete proxy[method];
2210
  }
2211
  }
2212
  pub.down.destroy();
2213
  }
2214
  });
 
2215
  var t = typeof object[property];
2216
  return t == 'function' ||
2217
  (!!(t == 'object' && object[property])) ||
2218
  t == 'unknown';
2219
  return !!(typeof(object[property]) == 'object' && object[property]);
2220
  return Object.prototype.toString.call(o) === '[object Array]';
2221
  var name = "Shockwave Flash", mimeType = "application/x-shockwave-flash";
2222
  if (!undef(navigator.plugins) && typeof navigator.plugins[name] == "object") {
2223
  // adapted from the swfobject code
2224
  var description = navigator.plugins[name].description;
2225
  if (description && !undef(navigator.mimeTypes) && navigator.mimeTypes[mimeType] && navigator.mimeTypes[mimeType].enabledPlugin) {
2226
  flashVersion = description.match(/\d+/g);
2227
  }
2228
  }
2229
  if (!flashVersion) {
2230
  var flash;
2231
  try {
2232
  flash = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
2233
  flashVersion = Array.prototype.slice.call(flash.GetVariable("$version").match(/(\d+),(\d+),(\d+),(\d+)/), 1);
2234
  flash = null;
2235
  }
2236
  catch (notSupportedException) {
2237
  }
2238
  }
2239
  if (!flashVersion) {
2240
  return false;
2241
  }
2242
  var major = parseInt(flashVersion[0], 10), minor = parseInt(flashVersion[1], 10);
2243
  HAS_FLASH_THROTTLED_BUG = major > 9 && minor > 0;
2244
  return true;
2245
  * Cross Browser implementation for adding and removing event listeners.
2246
  */
2247
  on = function(target, type, listener){
2248
  target.addEventListener(type, listener, false);
2249
  };
2250
  un = function(target, type, listener){
2251
  target.removeEventListener(type, listener, false);
2252
  };
2253
  on = function(object, sEvent, fpNotify){
2254
  object.attachEvent("on" + sEvent, fpNotify);
2255
  };
2256
  un = function(object, sEvent, fpNotify){
2257
  object.detachEvent("on" + sEvent, fpNotify);
2258
  };
2259
  throw new Error("Browser not supported");
2260
  * Cross Browser implementation of DOMContentLoaded.
2261
  */
2262
  // If browser is WebKit-powered, check for both 'loaded' (legacy browsers) and
2263
  // 'interactive' (HTML5 specs, recent WebKit builds) states.
2264
  // https://bugs.webkit.org/show_bug.cgi?id=45119
2265
  readyState = document.readyState;
2266
  domIsReady = readyState == "complete" || (~ navigator.userAgent.indexOf('AppleWebKit/') && (readyState == "loaded" || readyState == "interactive"));
2267
  // If readyState is not supported in the browser, then in order to be able to fire whenReady functions apropriately
2268
  // when added dynamically _after_ DOM load, we have to deduce wether the DOM is ready or not.
2269
  // We only need a body to add elements to, so the existence of document.body is enough for us.
2270
  domIsReady = !!document.body;
2271
  if (domIsReady) {
2272
  return;
2273
  }
2274
  domIsReady = true;
2275
  for (var i = 0; i < domReadyQueue.length; i++) {
2276
  domReadyQueue[i]();
2277
  }
2278
  domReadyQueue.length = 0;
2279
  if (isHostMethod(window, "addEventListener")) {
2280
  on(document, "DOMContentLoaded", dom_onReady);
2281
  }
2282
  else {
2283
  on(document, "readystatechange", function(){
2284
  if (document.readyState == "complete") {
2285
  dom_onReady();
2286
  }
2287
  });
2288
  if (document.documentElement.doScroll && window === top) {
2289
  var doScrollCheck = function(){
2290
  if (domIsReady) {
2291
  return;
2292
  }
2293
  // http://javascript.nwbox.com/IEContentLoaded/
2294
  try {
2295
  document.documentElement.doScroll("left");
2296
  }
2297
  catch (e) {
2298
  setTimeout(doScrollCheck, 1);
2299
  return;
2300
  }
2301
  dom_onReady();
2302
  };
2303
  doScrollCheck();
2304
  }
2305
  }
2306
  // A fallback to window.onload, that will always work
2307
  on(window, "load", dom_onReady);
2308
  * This will add a function to the queue of functions to be run once the DOM reaches a ready state.
2309
  * If functions are added after this event then they will be executed immediately.
2310
  * @param {function} fn The function to add
2311
  * @param {Object} scope An optional scope for the function to be called with.
2312
  */
2313
  if (domIsReady) {
2314
  fn.call(scope);
2315
  return;
2316
  }
2317
  domReadyQueue.push(function(){
2318
  fn.call(scope);
2319
  });
2320
  * Returns an instance of easyXDM from the parent window with
2321
  * respect to the namespace.
2322
  *
2323
  * @return An instance of easyXDM (in the parent window)
2324
  */
2325
  var obj = parent;
2326
  if (namespace !== "") {
2327
  for (var i = 0, ii = namespace.split("."); i < ii.length; i++) {
2328
  obj = obj[ii[i]];
2329
  }
2330
  }
2331
  return obj.easyXDM;
2332
  * Removes easyXDM variable from the global scope. It also returns control
2333
  * of the easyXDM variable to whatever code used it before.
2334
  *
2335
  * @param {String} ns A string representation of an object that will hold
2336
  * an instance of easyXDM.
2337
  * @return An instance of easyXDM
2338
  */
2339
  window.easyXDM = _easyXDM;
2340
  namespace = ns;
2341
  if (namespace) {
2342
  IFRAME_PREFIX = "easyXDM_" + namespace.replace(".", "_") + "_";
2343
  }
2344
  return easyXDM;
2345
  * Methods for working with URLs
2346
  */
2347
  * Get the domain name from a url.
2348
  * @param {String} url The url to extract the domain from.
2349
  * @return The domain part of the url.
2350
  * @type {String}
2351
  */
2352
  return url.match(reURI)[3];
2353
  * Get the port for a given URL, or "" if none
2354
  * @param {String} url The url to extract the port from.
2355
  * @return The port part of the url.
2356
  * @type {String}
2357
  */
2358
  return url.match(reURI)[4] || "";
2359
  * Returns a string containing the schema, domain and if present the port
2360
  * @param {String} url The url to extract the location from
2361
  * @return {String} The location part of the url
2362
  */
2363
  var m = url.toLowerCase().match(reURI);
2364
  var proto = m[2], domain = m[3], port = m[4] || "";
2365
  if ((proto == "http:" && port == ":80") || (proto == "https:" && port == ":443")) {
2366
  port = "";
2367
  }
2368
  return proto + "//" + domain + port;
2369
  * Resolves a relative url into an absolute one.
2370
  * @param {String} url The path to resolve.
2371
  * @return {String} The resolved url.
2372
  */
2373
  // replace all // except the one in proto with /
2374
  url = url.replace(reDoubleSlash, "$1/");
2375
  // If the url is a valid url we do nothing
2376
  if (!url.match(/^(http||https):\/\//)) {
2377
  // If this is a relative path
2378
  var path = (url.substring(0, 1) === "/") ? "" : location.pathname;
2379
  if (path.substring(path.length - 1) !== "/") {
2380
  path = path.substring(0, path.lastIndexOf("/") + 1);
2381
  }
2382
  url = location.protocol + "//" + location.host + path + url;
2383
  }
2384
  // reduce all 'xyz/../' to just ''
2385
  while (reParent.test(url)) {
2386
  url = url.replace(reParent, "");
2387
  }
2388
  return url;
2389
  * Appends the parameters to the given url.<br/>
2390
  * The base url can contain existing query parameters.
2391
  * @param {String} url The base url.
2392
  * @param {Object} parameters The parameters to add.
2393
  * @return {String} A new valid url with the parameters appended.
2394
  */
2395
  var hash = "", indexOf = url.indexOf("#");
2396
  if (indexOf !== -1) {
2397
  hash = url.substring(indexOf);
2398
  url = url.substring(0, indexOf);
2399
  }
2400
  var q = [];
2401
  for (var key in parameters) {
2402
  if (parameters.hasOwnProperty(key)) {
2403
  q.push(key + "=" + encodeURIComponent(parameters[key]));
2404
  }
2405
  }
2406
  return url + (useHash ? "#" : (url.indexOf("?") == -1 ? "?" : "&")) + q.join("&") + hash;
2407
  input = input.substring(1).split("&");
2408
  var data = {}, pair, i = input.length;
2409
  while (i--) {
2410
  pair = input[i].split("=");
2411
  data[pair[0]] = decodeURIComponent(pair[1]);
2412
  }
2413
  return data;
2414
  * Helper methods
2415
  */
2416
  * Helper for checking if a variable/property is undefined
2417
  * @param {Object} v The variable to test
2418
  * @return {Boolean} True if the passed variable is undefined
2419
  */
2420
  return typeof v === "undefined";
2421
  * A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works.
2422
  * @return {JSON} A valid JSON conforming object, or null if not found.
2423
  */
2424
  var cached = {};
2425
  var obj = {
2426
  a: [1, 2, 3]
2427
  }, json = "{\"a\":[1,2,3]}";
2428
  if (typeof JSON != "undefined" && typeof JSON.stringify === "function" && JSON.stringify(obj).replace((/\s/g), "") === json) {
2429
  // this is a working JSON instance
2430
  return JSON;
2431
  }
2432
  if (Object.toJSON) {
2433
  if (Object.toJSON(obj).replace((/\s/g), "") === json) {
2434
  // this is a working stringify method
2435
  cached.stringify = Object.toJSON;
2436
  }
2437
  }
2438
  if (typeof String.prototype.evalJSON === "function") {
2439
  obj = json.evalJSON();
2440
  if (obj.a && obj.a.length === 3 && obj.a[2] === 3) {
2441
  // this is a working parse method
2442
  cached.parse = function(str){
2443
  return str.evalJSON();
2444
  };
2445
  }
2446
  }
2447
  if (cached.stringify && cached.parse) {
2448
  // Only memoize the result if we have valid instance
2449
  getJSON = function(){
2450
  return cached;
2451
  };
2452
  return cached;
2453
  }
2454
  return null;
2455
  * Applies properties from the source object to the target object.<br/>
2456
  * @param {Object} target The target of the properties.
2457
  * @param {Object} source The source of the properties.
2458
  * @param {Boolean} noOverwrite Set to True to only set non-existing properties.
2459
  */
2460
  var member;
2461
  for (var prop in source) {
2462
  if (source.hasOwnProperty(prop)) {
2463
  if (prop in destination) {
2464
  member = source[prop];
2465
  if (typeof member === "object") {
2466
  apply(destination[prop], member, noOverwrite);
2467
  }
2468
  else if (!noOverwrite) {
2469
  destination[prop] = source[prop];
2470
  }
2471
  }
2472
  else {
2473
  destination[prop] = source[prop];
2474
  }
2475
  }
2476
  }
2477
  return destination;
2478
  var form = document.body.appendChild(document.createElement("form")), input = form.appendChild(document.createElement("input"));
2479
  input.name = IFRAME_PREFIX + "TEST" + channelId; // append channelId in order to avoid caching issues
2480
  HAS_NAME_PROPERTY_BUG = input !== form.elements[input.name];
2481
  document.body.removeChild(form);
2482
  * Creates a frame and appends it to the DOM.
2483
  * @param config {object} This object can have the following properties
2484
  * <ul>
2485
  * <li> {object} prop The properties that should be set on the frame. This should include the 'src' property.</li>
2486
  * <li> {object} attr The attributes that should be set on the frame.</li>
2487
  * <li> {DOMElement} container Its parent element (Optional).</li>
2488
  * <li> {function} onLoad A method that should be called with the frames contentWindow as argument when the frame is fully loaded. (Optional)</li>
2489
  * </ul>
2490
  * @return The frames DOMElement
2491
  * @type DOMElement
2492
  */
2493
  if (undef(HAS_NAME_PROPERTY_BUG)) {
2494
  testForNamePropertyBug();
2495
  }
2496
  var frame;
2497
  // This is to work around the problems in IE6/7 with setting the name property.
2498
  // Internally this is set as 'submitName' instead when using 'iframe.name = ...'
2499
  // This is not required by easyXDM itself, but is to facilitate other use cases
2500
  if (HAS_NAME_PROPERTY_BUG) {
2501
  frame = document.createElement("<iframe name=\"" + config.props.name + "\"/>");
2502
  }
2503
  else {
2504
  frame = document.createElement("IFRAME");
2505
  frame.name = config.props.name;
2506
  }
2507
  frame.id = frame.name = config.props.name;
2508
  delete config.props.name;
2509
  if (typeof config.container == "string") {
2510
  config.container = document.getElementById(config.container);
2511
  }
2512
  if (!config.container) {
2513
  // This needs to be hidden like this, simply setting display:none and the like will cause failures in some browsers.
2514
  apply(frame.style, {
2515
  position: "absolute",
2516
  top: "-2000px",
2517
  // Avoid potential horizontal scrollbar
2518
  left: "0px"
2519
  });
2520
  config.container = document.body;
2521
  }
2522
  // HACK: IE cannot have the src attribute set when the frame is appended
2523
  // into the container, so we set it to "javascript:false" as a
2524
  // placeholder for now. If we left the src undefined, it would
2525
  // instead default to "about:blank", which causes SSL mixed-content
2526
  // warnings in IE6 when on an SSL parent page.
2527
  var src = config.props.src;
2528
  config.props.src = "javascript:false";
2529
  // transfer properties to the frame
2530
  apply(frame, config.props);
2531
  frame.border = frame.frameBorder = 0;
2532
  frame.allowTransparency = true;
2533
  config.container.appendChild(frame);
2534
  if (config.onLoad) {
2535
  on(frame, "load", config.onLoad);
2536
  }
2537
  // set the frame URL to the proper value (we previously set it to
2538
  // "javascript:false" to work around the IE issue mentioned above)
2539
  if(config.usePost) {
2540
  var form = config.container.appendChild(document.createElement('form')), input;
2541
  form.target = frame.name;
2542
  form.action = src;
2543
  form.method = 'POST';
2544
  if (typeof(config.usePost) === 'object') {
2545
  for (var i in config.usePost) {
2546
  if (config.usePost.hasOwnProperty(i)) {
2547
  if (HAS_NAME_PROPERTY_BUG) {
2548
  input = document.createElement('<input name="' + i + '"/>');
2549
  } else {
2550
  input = document.createElement("INPUT");
2551
  input.name = i;
2552
  }
2553
  input.value = config.usePost[i];
2554
  form.appendChild(input);
2555
  }
2556
  }
2557
  }
2558
  form.submit();
2559
  form.parentNode.removeChild(form);
2560
  } else {
2561
  frame.src = src;
2562
  }
2563
  config.props.src = src;
2564
  return frame;
2565
  * Check whether a domain is allowed using an Access Control List.
2566
  * The ACL can contain * and ? as wildcards, or can be regular expressions.
2567
  * If regular expressions they need to begin with ^ and end with $.
2568
  * @param {Array/String} acl The list of allowed domains
2569
  * @param {String} domain The domain to test.
2570
  * @return {Boolean} True if the domain is allowed, false if not.
2571
  */
2572
  // normalize into an array
2573
  if (typeof acl == "string") {
2574
  acl = [acl];
2575
  }
2576
  var re, i = acl.length;
2577
  while (i--) {
2578
  re = acl[i];
2579
  re = new RegExp(re.substr(0, 1) == "^" ? re : ("^" + re.replace(/(\*)/g, ".$1").replace(/\?/g, ".") + "$"));
2580
  if (re.test(domain)) {
2581
  return true;
2582
  }
2583
  }
2584
  return false;
2585
  * Functions related to stacks
2586
  */
2587
  * Prepares an array of stack-elements suitable for the current configuration
2588
  * @param {Object} config The Transports configuration. See easyXDM.Socket for more.
2589
  * @return {Array} An array of stack-elements with the TransportElement at index 0.
2590
  */
2591
  var protocol = config.protocol, stackEls;
2592
  config.isHost = config.isHost || undef(query.xdm_p);
2593
  useHash = config.hash || false;
2594
  if (!config.props) {
2595
  config.props = {};
2596
  }
2597
  if (!config.isHost) {
2598
  config.channel = query.xdm_c.replace(/["'<>\\]/g, "");
2599
  config.secret = query.xdm_s;
2600
  config.remote = query.xdm_e.replace(/["'<>\\]/g, "");
2601
  ;
2602
  protocol = query.xdm_p;
2603
  if (config.acl && !checkAcl(config.acl, config.remote)) {
2604
  throw new Error("Access denied for " + config.remote);
2605
  }
2606
  }
2607
  else {
2608
  config.remote = resolveUrl(config.remote);
2609
  config.channel = config.channel || "default" + channelId++;
2610
  config.secret = Math.random().toString(16).substring(2);
2611
  if (undef(protocol)) {
2612
  if (getLocation(location.href) == getLocation(config.remote)) {
2613
  /*
2614
  * Both documents has the same origin, lets use direct access.
2615
  */
2616
  protocol = "4";
2617
  }
2618
  else if (isHostMethod(window, "postMessage") || isHostMethod(document, "postMessage")) {
2619
  /*
2620
  * This is supported in IE8+, Firefox 3+, Opera 9+, Chrome 2+ and Safari 4+
2621
  */
2622
  protocol = "1";
2623
  }
2624
  else if (config.swf && isHostMethod(window, "ActiveXObject") && hasFlash()) {
2625
  /*
2626
  * The Flash transport superseedes the NixTransport as the NixTransport has been blocked by MS
2627
  */
2628
  protocol = "6";
2629
  }
2630
  else if (navigator.product === "Gecko" && "frameElement" in window && navigator.userAgent.indexOf('WebKit') == -1) {
2631
  /*
2632
  * This is supported in Gecko (Firefox 1+)
2633
  */
2634
  protocol = "5";
2635
  }
2636
  else if (config.remoteHelper) {
2637
  /*
2638
  * This is supported in all browsers that retains the value of window.name when
2639
  * navigating from one domain to another, and where parent.frames[foo] can be used
2640
  * to get access to a frame from the same domain
2641
  */
2642
  protocol = "2";
2643
  }
2644
  else {
2645
  /*
2646
  * This is supported in all browsers where [window].location is writable for all
2647
  * The resize event will be used if resize is supported and the iframe is not put
2648
  * into a container, else polling will be used.
2649
  */
2650
  protocol = "0";
2651
  }
2652
  }
2653
  }
2654
  config.protocol = protocol; // for conditional branching
2655
  switch (protocol) {
2656
  case "0":// 0 = HashTransport
2657
  apply(config, {
2658
  interval: 100,
2659
  delay: 2000,
2660
  useResize: true,
2661
  useParent: false,
2662
  usePolling: false
2663
  }, true);
2664
  if (config.isHost) {
2665
  if (!config.local) {
2666
  // If no local is set then we need to find an image hosted on the current domain
2667
  var domain = location.protocol + "//" + location.host, images = document.body.getElementsByTagName("img"), image;
2668
  var i = images.length;
2669
  while (i--) {
2670
  image = images[i];
2671
  if (image.src.substring(0, domain.length) === domain) {
2672
  config.local = image.src;
2673
  break;
2674
  }
2675
  }
2676
  if (!config.local) {
2677
  // If no local was set, and we are unable to find a suitable file, then we resort to using the current window
2678
  config.local = window;
2679
  }
2680
  }
2681
  var parameters = {
2682
  xdm_c: config.channel,
2683
  xdm_p: 0
2684
  };
2685
  if (config.local === window) {
2686
  // We are using the current window to listen to
2687
  config.usePolling = true;
2688
  config.useParent = true;
2689
  config.local = location.protocol + "//" + location.host + location.pathname + location.search;
2690
  parameters.xdm_e = config.local;
2691
  parameters.xdm_pa = 1; // use parent
2692
  }
2693
  else {
2694
  parameters.xdm_e = resolveUrl(config.local);
2695
  }
2696
  if (config.container) {
2697
  config.useResize = false;
2698
  parameters.xdm_po = 1; // use polling
2699
  }
2700
  config.remote = appendQueryParameters(config.remote, parameters);
2701
  }
2702
  else {
2703
  apply(config, {
2704
  channel: query.xdm_c,
2705
  remote: query.xdm_e,
2706
  useParent: !undef(query.xdm_pa),
2707
  usePolling: !undef(query.xdm_po),
2708
  useResize: config.useParent ? false : config.useResize
2709
  });
2710
  }
2711
  stackEls = [new easyXDM.stack.HashTransport(config), new easyXDM.stack.ReliableBehavior({}), new easyXDM.stack.QueueBehavior({
2712
  encode: true,
2713
  maxLength: 4000 - config.remote.length
2714
  }), new easyXDM.stack.VerifyBehavior({
2715
  initiate: config.isHost
2716
  })];
2717
  break;
2718
  case "1":
2719
  stackEls = [new easyXDM.stack.PostMessageTransport(config)];
2720
  break;
2721
  case "2":
2722
  if (config.isHost) {
2723
  config.remoteHelper = resolveUrl(config.remoteHelper);
2724
  }
2725
  stackEls = [new easyXDM.stack.NameTransport(config), new easyXDM.stack.QueueBehavior(), new easyXDM.stack.VerifyBehavior({
2726
  initiate: config.isHost
2727
  })];
2728
  break;
2729
  case "3":
2730
  stackEls = [new easyXDM.stack.NixTransport(config)];
2731
  break;
2732
  case "4":
2733
  stackEls = [new easyXDM.stack.SameOriginTransport(config)];
2734
  break;
2735
  case "5":
2736
  stackEls = [new easyXDM.stack.FrameElementTransport(config)];
2737
  break;
2738
  case "6":
2739
  if (!flashVersion) {
2740
  hasFlash();
2741
  }
2742
  stackEls = [new easyXDM.stack.FlashTransport(config)];
2743
  break;
2744
  }
2745
  // this behavior is responsible for buffering outgoing messages, and for performing lazy initialization
2746
  stackEls.push(new easyXDM.stack.QueueBehavior({
2747
  lazy: config.lazy,
2748
  remove: true
2749
  }));
2750
  return stackEls;
2751
  * Chains all the separate stack elements into a single usable stack.<br/>
2752
  * If an element is missing a necessary method then it will have a pass-through method applied.
2753
  * @param {Array} stackElements An array of stack elements to be linked.
2754
  * @return {easyXDM.stack.StackElement} The last element in the chain.
2755
  */
2756
  var stackEl, defaults = {
2757
  incoming: function(message, origin){
2758
  this.up.incoming(message, origin);
2759
  },
2760
  outgoing: function(message, recipient){
2761
  this.down.outgoing(message, recipient);
2762
  },
2763
  callback: function(success){
2764
  this.up.callback(success);
2765
  },
2766
  init: function(){
2767
  this.down.init();
2768
  },
2769
  destroy: function(){
2770
  this.down.destroy();
2771
  }
2772
  };
2773
  for (var i = 0, len = stackElements.length; i < len; i++) {
2774
  stackEl = stackElements[i];
2775
  apply(stackEl, defaults, true);
2776
  if (i !== 0) {
2777
  stackEl.down = stackElements[i - 1];
2778
  }
2779
  if (i !== len - 1) {
2780
  stackEl.up = stackElements[i + 1];
2781
  }
2782
  }
2783
  return stackEl;
2784
  * This will remove a stackelement from its stack while leaving the stack functional.
2785
  * @param {Object} element The elment to remove from the stack.
2786
  */
2787
  element.up.down = element.down;
2788
  element.down.up = element.up;
2789
  element.up = element.down = null;
2790
  * Export the main object and any other methods applicable
2791
  */
2792
  * @class easyXDM
2793
  * A javascript library providing cross-browser, cross-domain messaging/RPC.
2794
  * @version 2.4.19.0
2795
  * @singleton
2796
  */
2797
  /**
2798
  * The version of the library
2799
  * @type {string}
2800
  */
2801
  version: "2.4.19.0",
2802
  /**
2803
  * This is a map containing all the query parameters passed to the document.
2804
  * All the values has been decoded using decodeURIComponent.
2805
  * @type {object}
2806
  */
2807
  query: query,
2808
  /**
2809
  * @private
2810
  */
2811
  stack: {},
2812
  /**
2813
  * Applies properties from the source object to the target object.<br/>
2814
  * @param {object} target The target of the properties.
2815
  * @param {object} source The source of the properties.
2816
  * @param {boolean} noOverwrite Set to True to only set non-existing properties.
2817
  */
2818
  apply: apply,
2819
  /**
2820
  * A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works.
2821
  * @return {JSON} A valid JSON conforming object, or null if not found.
2822
  */
2823
  getJSONObject: getJSON,
2824
  /**
2825
  * This will add a function to the queue of functions to be run once the DOM reaches a ready state.
2826
  * If functions are added after this event then they will be executed immediately.
2827
  * @param {function} fn The function to add
2828
  * @param {object} scope An optional scope for the function to be called with.
2829
  */
2830
  whenReady: whenReady,
2831
  /**
2832
  * Removes easyXDM variable from the global scope. It also returns control
2833
  * of the easyXDM variable to whatever code used it before.
2834
  *
2835
  * @param {String} ns A string representation of an object that will hold
2836
  * an instance of easyXDM.
2837
  * @return An instance of easyXDM
2838
  */
2839
  noConflict: noConflict
2840
  * @class easyXDM.DomHelper
2841
  * Contains methods for dealing with the DOM
2842
  * @singleton
2843
  */
2844
  /**
2845
  * Provides a consistent interface for adding eventhandlers
2846
  * @param {Object} target The target to add the event to
2847
  * @param {String} type The name of the event
2848
  * @param {Function} listener The listener
2849
  */
2850
  on: on,
2851
  /**
2852
  * Provides a consistent interface for removing eventhandlers
2853
  * @param {Object} target The target to remove the event from
2854
  * @param {String} type The name of the event
2855
  * @param {Function} listener The listener
2856
  */
2857
  un: un,
2858
  /**
2859
  * Checks for the presence of the JSON object.
2860
  * If it is not present it will use the supplied path to load the JSON2 library.
2861
  * This should be called in the documents head right after the easyXDM script tag.
2862
  * http://json.org/json2.js
2863
  * @param {String} path A valid path to json2.js
2864
  */
2865
  requiresJSON: function(path){
2866
  if (!isHostObject(window, "JSON")) {
2867
  // we need to encode the < in order to avoid an illegal token error
2868
  // when the script is inlined in a document.
2869
  document.write('<' + 'script type="text/javascript" src="' + path + '"><' + '/script>');
2870
  }
2871
  }
2872
  // The map containing the stored functions
2873
  var _map = {};
2874
  /**
2875
  * @class easyXDM.Fn
2876
  * This contains methods related to function handling, such as storing callbacks.
2877
  * @singleton
2878
  * @namespace easyXDM
2879
  */
2880
  easyXDM.Fn = {
2881
  /**
2882
  * Stores a function using the given name for reference
2883
  * @param {String} name The name that the function should be referred by
2884
  * @param {Function} fn The function to store
2885
  * @namespace easyXDM.fn
2886
  */
2887
  set: function(name, fn){
2888
  _map[name] = fn;
2889
  },
2890
  /**
2891
  * Retrieves the function referred to by the given name
2892
  * @param {String} name The name of the function to retrieve
2893
  * @param {Boolean} del If the function should be deleted after retrieval
2894
  * @return {Function} The stored function
2895
  * @namespace easyXDM.fn
2896
  */
2897
  get: function(name, del){
2898
  if (!_map.hasOwnProperty(name)) {
2899
  return;
2900
  }
2901
  var fn = _map[name];
2902
  if (del) {
2903
  delete _map[name];
2904
  }
2905
  return fn;
2906
  }
2907
  };
2908
  * @class easyXDM.Socket
2909
  * This class creates a transport channel between two domains that is usable for sending and receiving string-based messages.<br/>
2910
  * The channel is reliable, supports queueing, and ensures that the message originates from the expected domain.<br/>
2911
  * Internally different stacks will be used depending on the browsers features and the available parameters.
2912
  * <h2>How to set up</h2>
2913
  * Setting up the provider:
2914
  * <pre><code>
2915
  * var socket = new easyXDM.Socket({
2916
  * &nbsp; local: "name.html",
2917
  * &nbsp; onReady: function(){
2918
  * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
2919
  * &nbsp; &nbsp; socket.postMessage("foo-message");
2920
  * &nbsp; },
2921
  * &nbsp; onMessage: function(message, origin) {
2922
  * &nbsp;&nbsp; alert("received " + message + " from " + origin);
2923
  * &nbsp; }
2924
  * });
2925
  * </code></pre>
2926
  * Setting up the consumer:
2927
  * <pre><code>
2928
  * var socket = new easyXDM.Socket({
2929
  * &nbsp; remote: "http:&#47;&#47;remotedomain/page.html",
2930
  * &nbsp; remoteHelper: "http:&#47;&#47;remotedomain/name.html",
2931
  * &nbsp; onReady: function(){
2932
  * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
2933
  * &nbsp; &nbsp; socket.postMessage("foo-message");
2934
  * &nbsp; },
2935
  * &nbsp; onMessage: function(message, origin) {
2936
  * &nbsp;&nbsp; alert("received " + message + " from " + origin);
2937
  * &nbsp; }
2938
  * });
2939
  * </code></pre>
2940
  * If you are unable to upload the <code>name.html</code> file to the consumers domain then remove the <code>remoteHelper</code> property
2941
  * and easyXDM will fall back to using the HashTransport instead of the NameTransport when not able to use any of the primary transports.
2942
  * @namespace easyXDM
2943
  * @constructor
2944
  * @cfg {String/Window} local The url to the local name.html document, a local static file, or a reference to the local window.
2945
  * @cfg {Boolean} lazy (Consumer only) Set this to true if you want easyXDM to defer creating the transport until really needed.
2946
  * @cfg {String} remote (Consumer only) The url to the providers document.
2947
  * @cfg {String} remoteHelper (Consumer only) The url to the remote name.html file. This is to support NameTransport as a fallback. Optional.
2948
  * @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window. Optional, defaults to 2000.
2949
  * @cfg {Number} interval The interval used when polling for messages. Optional, defaults to 300.
2950
  * @cfg {String} channel (Consumer only) The name of the channel to use. Can be used to set consistent iframe names. Must be unique. Optional.
2951
  * @cfg {Function} onMessage The method that should handle incoming messages.<br/> This method should accept two arguments, the message as a string, and the origin as a string. Optional.
2952
  * @cfg {Function} onReady A method that should be called when the transport is ready. Optional.
2953
  * @cfg {DOMElement|String} container (Consumer only) The element, or the id of the element that the primary iframe should be inserted into. If not set then the iframe will be positioned off-screen. Optional.
2954
  * @cfg {Array/String} acl (Provider only) Here you can specify which '[protocol]://[domain]' patterns that should be allowed to act as the consumer towards this provider.<br/>
2955
  * This can contain the wildcards ? and *. Examples are 'http://example.com', '*.foo.com' and '*dom?.com'. If you want to use reqular expressions then you pattern needs to start with ^ and end with $.
2956
  * If none of the patterns match an Error will be thrown.
2957
  * @cfg {Object} props (Consumer only) Additional properties that should be applied to the iframe. This can also contain nested objects e.g: <code>{style:{width:"100px", height:"100px"}}</code>.
2958
  * Properties such as 'name' and 'src' will be overrided. Optional.
2959
  */
2960
  // create the stack
2961
  var stack = chainStack(prepareTransportStack(config).concat([{
2962
  incoming: function(message, origin){
2963
  config.onMessage(message, origin);
2964
  },
2965
  callback: function(success){
2966
  if (config.onReady) {
2967
  config.onReady(success);
2968
  }
2969
  }
2970
  }])), recipient = getLocation(config.remote);
2971
  // set the origin
2972
  this.origin = getLocation(config.remote);
2973
  /**
2974
  * Initiates the destruction of the stack.
2975
  */
2976
  this.destroy = function(){
2977
  stack.destroy();
2978
  };
2979
  /**
2980
  * Posts a message to the remote end of the channel
2981
  * @param {String} message The message to send
2982
  */
2983
  this.postMessage = function(message){
2984
  stack.outgoing(message, recipient);
2985
  };
2986
  stack.init();
2987
  * @class easyXDM.Rpc
2988
  * Creates a proxy object that can be used to call methods implemented on the remote end of the channel, and also to provide the implementation
2989
  * of methods to be called from the remote end.<br/>
2990
  * The instantiated object will have methods matching those specified in <code>config.remote</code>.<br/>
2991
  * This requires the JSON object present in the document, either natively, using json.org's json2 or as a wrapper around library spesific methods.
2992
  * <h2>How to set up</h2>
2993
  * <pre><code>
2994
  * var rpc = new easyXDM.Rpc({
2995
  * &nbsp; &#47;&#47; this configuration is equal to that used by the Socket.
2996
  * &nbsp; remote: "http:&#47;&#47;remotedomain/...",
2997
  * &nbsp; onReady: function(){
2998
  * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the proxy
2999
  * &nbsp; &nbsp; rpc.foo(...
3000
  * &nbsp; }
3001
  * },{
3002
  * &nbsp; local: {..},
3003
  * &nbsp; remote: {..}
3004
  * });
3005
  * </code></pre>
3006
  *
3007
  * <h2>Exposing functions (procedures)</h2>
3008
  * <pre><code>
3009
  * var rpc = new easyXDM.Rpc({
3010
  * &nbsp; ...
3011
  * },{
3012
  * &nbsp; local: {
3013
  * &nbsp; &nbsp; nameOfMethod: {
3014
  * &nbsp; &nbsp; &nbsp; method: function(arg1, arg2, success, error){
3015
  * &nbsp; &nbsp; &nbsp; &nbsp; ...
3016
  * &nbsp; &nbsp; &nbsp; }
3017
  * &nbsp; &nbsp; },
3018
  * &nbsp; &nbsp; &#47;&#47; with shorthand notation
3019
  * &nbsp; &nbsp; nameOfAnotherMethod: function(arg1, arg2, success, error){
3020
  * &nbsp; &nbsp; }
3021
  * &nbsp; },
3022
  * &nbsp; remote: {...}
3023
  * });
3024
  * </code></pre>
3025
  * The function referenced by [method] will receive the passed arguments followed by the callback functions <code>success</code> and <code>error</code>.<br/>
3026
  * To send a successfull result back you can use
3027
  * <pre><code>
3028
  * return foo;
3029
  * </pre></code>
3030
  * or
3031
  * <pre><code>
3032
  * success(foo);
3033
  * </pre></code>
3034
  * To return an error you can use
3035
  * <pre><code>
3036
  * throw new Error("foo error");
3037
  * </code></pre>
3038
  * or
3039
  * <pre><code>
3040
  * error("foo error");
3041
  * </code></pre>
3042
  *
3043
  * <h2>Defining remotely exposed methods (procedures/notifications)</h2>
3044
  * The definition of the remote end is quite similar:
3045
  * <pre><code>
3046
  * var rpc = new easyXDM.Rpc({
3047
  * &nbsp; ...
3048
  * },{
3049
  * &nbsp; local: {...},
3050
  * &nbsp; remote: {
3051
  * &nbsp; &nbsp; nameOfMethod: {}
3052
  * &nbsp; }
3053
  * });
3054
  * </code></pre>
3055
  * To call a remote method use
3056
  * <pre><code>
3057
  * rpc.nameOfMethod("arg1", "arg2", function(value) {
3058
  * &nbsp; alert("success: " + value);
3059
  * }, function(message) {
3060
  * &nbsp; alert("error: " + message + );
3061
  * });
3062
  * </code></pre>
3063
  * Both the <code>success</code> and <code>errror</code> callbacks are optional.<br/>
3064
  * When called with no callback a JSON-RPC 2.0 notification will be executed.
3065
  * Be aware that you will not be notified of any errors with this method.
3066
  * <br/>
3067
  * <h2>Specifying a custom serializer</h2>
3068
  * If you do not want to use the JSON2 library for non-native JSON support, but instead capabilities provided by some other library
3069
  * then you can specify a custom serializer using <code>serializer: foo</code>
3070
  * <pre><code>
3071
  * var rpc = new easyXDM.Rpc({
3072
  * &nbsp; ...
3073
  * },{
3074
  * &nbsp; local: {...},
3075
  * &nbsp; remote: {...},
3076
  * &nbsp; serializer : {
3077
  * &nbsp; &nbsp; parse: function(string){ ... },
3078
  * &nbsp; &nbsp; stringify: function(object) {...}
3079
  * &nbsp; }
3080
  * });
3081
  * </code></pre>
3082
  * If <code>serializer</code> is set then the class will not attempt to use the native implementation.
3083
  * @namespace easyXDM
3084
  * @constructor
3085
  * @param {Object} config The underlying transports configuration. See easyXDM.Socket for available parameters.
3086
  * @param {Object} jsonRpcConfig The description of the interface to implement.
3087
  */
3088
  // expand shorthand notation
3089
  if (jsonRpcConfig.local) {
3090
  for (var method in jsonRpcConfig.local) {
3091
  if (jsonRpcConfig.local.hasOwnProperty(method)) {
3092
  var member = jsonRpcConfig.local[method];
3093
  if (typeof member === "function") {
3094
  jsonRpcConfig.local[method] = {
3095
  method: member
3096
  };
3097
  }
3098
  }
3099
  }
3100
  }
3101
  // create the stack
3102
  var stack = chainStack(prepareTransportStack(config).concat([new easyXDM.stack.RpcBehavior(this, jsonRpcConfig), {
3103
  callback: function(success){
3104
  if (config.onReady) {
3105
  config.onReady(success);
3106
  }
3107
  }
3108
  }]));
3109
  // set the origin
3110
  this.origin = getLocation(config.remote);
3111
  /**
3112
  * Initiates the destruction of the stack.
3113
  */
3114
  this.destroy = function(){
3115
  stack.destroy();
3116
  };
3117
  stack.init();
3118
  * @class easyXDM.stack.SameOriginTransport
3119
  * SameOriginTransport is a transport class that can be used when both domains have the same origin.<br/>
3120
  * This can be useful for testing and for when the main application supports both internal and external sources.
3121
  * @namespace easyXDM.stack
3122
  * @constructor
3123
  * @param {Object} config The transports configuration.
3124
  * @cfg {String} remote The remote document to communicate with.
3125
  */
3126
  var pub, frame, send, targetOrigin;
3127
  return (pub = {
3128
  outgoing: function(message, domain, fn){
3129
  send(message);
3130
  if (fn) {
3131
  fn();
3132
  }
3133
  },
3134
  destroy: function(){
3135
  if (frame) {
3136
  frame.parentNode.removeChild(frame);
3137
  frame = null;
3138
  }
3139
  },
3140
  onDOMReady: function(){
3141
  targetOrigin = getLocation(config.remote);
3142
  if (config.isHost) {
3143
  // set up the iframe
3144
  apply(config.props, {
3145
  src: appendQueryParameters(config.remote, {
3146
  xdm_e: location.protocol + "//" + location.host + location.pathname,
3147
  xdm_c: config.channel,
3148
  xdm_p: 4 // 4 = SameOriginTransport
3149
  }),
3150
  name: IFRAME_PREFIX + config.channel + "_provider"
3151
  });
3152
  frame = createFrame(config);
3153
  easyXDM.Fn.set(config.channel, function(sendFn){
3154
  send = sendFn;
3155
  setTimeout(function(){
3156
  pub.up.callback(true);
3157
  }, 0);
3158
  return function(msg){
3159
  pub.up.incoming(msg, targetOrigin);
3160
  };
3161
  });
3162
  }
3163
  else {
3164
  send = getParentObject().Fn.get(config.channel, true)(function(msg){
3165
  pub.up.incoming(msg, targetOrigin);
3166
  });
3167
  setTimeout(function(){
3168
  pub.up.callback(true);
3169
  }, 0);
3170
  }
3171
  },
3172
  init: function(){
3173
  whenReady(pub.onDOMReady, pub);
3174
  }
3175
  });
3176
  * @class easyXDM.stack.FlashTransport
3177
  * FlashTransport is a transport class that uses an SWF with LocalConnection to pass messages back and forth.
3178
  * @namespace easyXDM.stack
3179
  * @constructor
3180
  * @param {Object} config The transports configuration.
3181
  * @cfg {String} remote The remote domain to communicate with.
3182
  * @cfg {String} secret the pre-shared secret used to secure the communication.
3183
  * @cfg {String} swf The path to the swf file
3184
  * @cfg {Boolean} swfNoThrottle Set this to true if you want to take steps to avoid beeing throttled when hidden.
3185
  * @cfg {String || DOMElement} swfContainer Set this if you want to control where the swf is placed
3186
  */
3187
  var pub, // the public interface
3188
  frame, send, targetOrigin, swf, swfContainer;
3189
  function onMessage(message, origin){
3190
  setTimeout(function(){
3191
  pub.up.incoming(message, targetOrigin);
3192
  }, 0);
3193
  }
3194
  /**
3195
  * This method adds the SWF to the DOM and prepares the initialization of the channel
3196
  */
3197
  function addSwf(domain){
3198
  // the differentiating query argument is needed in Flash9 to avoid a caching issue where LocalConnection would throw an error.
3199
  var url = config.swf + "?host=" + config.isHost;
3200
  var id = "easyXDM_swf_" + Math.floor(Math.random() * 10000);
3201
  // prepare the init function that will fire once the swf is ready
3202
  easyXDM.Fn.set("flash_loaded" + domain.replace(/[\-.]/g, "_"), function(){
3203
  easyXDM.stack.FlashTransport[domain].swf = swf = swfContainer.firstChild;
3204
  var queue = easyXDM.stack.FlashTransport[domain].queue;
3205
  for (var i = 0; i < queue.length; i++) {
3206
  queue[i]();
3207
  }
3208
  queue.length = 0;
3209
  });
3210
  if (config.swfContainer) {
3211
  swfContainer = (typeof config.swfContainer == "string") ? document.getElementById(config.swfContainer) : config.swfContainer;
3212
  }
3213
  else {
3214
  // create the container that will hold the swf
3215
  swfContainer = document.createElement('div');
3216
  // http://bugs.adobe.com/jira/browse/FP-4796
3217
  // http://tech.groups.yahoo.com/group/flexcoders/message/162365
3218
  // https://groups.google.com/forum/#!topic/easyxdm/mJZJhWagoLc
3219
  apply(swfContainer.style, HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle ? {
3220
  height: "20px",
3221
  width: "20px",
3222
  position: "fixed",
3223
  right: 0,
3224
  top: 0
3225
  } : {
3226
  height: "1px",
3227
  width: "1px",
3228
  position: "absolute",
3229
  overflow: "hidden",
3230
  right: 0,
3231
  top: 0
3232
  });
3233
  document.body.appendChild(swfContainer);
3234
  }
3235
  // create the object/embed
3236
  var flashVars = "callback=flash_loaded" + encodeURIComponent(domain.replace(/[\-.]/g, "_"))
3237
  + "&proto=" + global.location.protocol
3238
  + "&domain=" + encodeURIComponent(getDomainName(global.location.href))
3239
  + "&port=" + encodeURIComponent(getPort(global.location.href))
3240
  + "&ns=" + encodeURIComponent(namespace);
3241
  swfContainer.innerHTML = "<object height='20' width='20' type='application/x-shockwave-flash' id='" + id + "' data='" + url + "'>" +
3242
  "<param name='allowScriptAccess' value='always'></param>" +
3243
  "<param name='wmode' value='transparent'>" +
3244
  "<param name='movie' value='" +
3245
  url +
3246
  "'></param>" +
3247
  "<param name='flashvars' value='" +
3248
  flashVars +
3249
  "'></param>" +
3250
  "<embed type='application/x-shockwave-flash' FlashVars='" +
3251
  flashVars +
3252
  "' allowScriptAccess='always' wmode='transparent' src='" +
3253
  url +
3254
  "' height='1' width='1'></embed>" +
3255
  "</object>";
3256
  }
3257
  return (pub = {
3258
  outgoing: function(message, domain, fn){
3259
  swf.postMessage(config.channel, message.toString());
3260
  if (fn) {
3261
  fn();
3262
  }
3263
  },
3264
  destroy: function(){
3265
  try {
3266
  swf.destroyChannel(config.channel);
3267
  }
3268
  catch (e) {
3269
  }
3270
  swf = null;
3271
  if (frame) {
3272
  frame.parentNode.removeChild(frame);
3273
  frame = null;
3274
  }
3275
  },
3276
  onDOMReady: function(){
3277
  targetOrigin = config.remote;
3278
  // Prepare the code that will be run after the swf has been intialized
3279
  easyXDM.Fn.set("flash_" + config.channel + "_init", function(){
3280
  setTimeout(function(){
3281
  pub.up.callback(true);
3282
  });
3283
  });
3284
  // set up the omMessage handler
3285
  easyXDM.Fn.set("flash_" + config.channel + "_onMessage", onMessage);
3286
  config.swf = resolveUrl(config.swf); // reports have been made of requests gone rogue when using relative paths
3287
  var swfdomain = getDomainName(config.swf);
3288
  var fn = function(){
3289
  // set init to true in case the fn was called was invoked from a separate instance
3290
  easyXDM.stack.FlashTransport[swfdomain].init = true;
3291
  swf = easyXDM.stack.FlashTransport[swfdomain].swf;
3292
  // create the channel
3293
  swf.createChannel(config.channel, config.secret, getLocation(config.remote), config.isHost);
3294
  if (config.isHost) {
3295
  // if Flash is going to be throttled and we want to avoid this
3296
  if (HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle) {
3297
  apply(config.props, {
3298
  position: "fixed",
3299
  right: 0,
3300
  top: 0,
3301
  height: "20px",
3302
  width: "20px"
3303
  });
3304
  }
3305
  // set up the iframe
3306
  apply(config.props, {
3307
  src: appendQueryParameters(config.remote, {
3308
  xdm_e: getLocation(location.href),
3309
  xdm_c: config.channel,
3310
  xdm_p: 6, // 6 = FlashTransport
3311
  xdm_s: config.secret
3312
  }),
3313
  name: IFRAME_PREFIX + config.channel + "_provider"
3314
  });
3315
  frame = createFrame(config);
3316
  }
3317
  };
3318
  if (easyXDM.stack.FlashTransport[swfdomain] && easyXDM.stack.FlashTransport[swfdomain].init) {
3319
  // if the swf is in place and we are the consumer
3320
  fn();
3321
  }
3322
  else {
3323
  // if the swf does not yet exist
3324
  if (!easyXDM.stack.FlashTransport[swfdomain]) {
3325
  // add the queue to hold the init fn's
3326
  easyXDM.stack.FlashTransport[swfdomain] = {
3327
  queue: [fn]
3328
  };
3329
  addSwf(swfdomain);
3330
  }
3331
  else {
3332
  easyXDM.stack.FlashTransport[swfdomain].queue.push(fn);
3333
  }
3334
  }
3335
  },
3336
  init: function(){
3337
  whenReady(pub.onDOMReady, pub);
3338
  }
3339
  });
3340
  * @class easyXDM.stack.PostMessageTransport
3341
  * PostMessageTransport is a transport class that uses HTML5 postMessage for communication.<br/>
3342
  * <a href="http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx">http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx</a><br/>
3343
  * <a href="https://developer.mozilla.org/en/DOM/window.postMessage">https://developer.mozilla.org/en/DOM/window.postMessage</a>
3344
  * @namespace easyXDM.stack
3345
  * @constructor
3346
  * @param {Object} config The transports configuration.
3347
  * @cfg {String} remote The remote domain to communicate with.
3348
  */
3349
  var pub, // the public interface
3350
  frame, // the remote frame, if any
3351
  callerWindow, // the window that we will call with
3352
  targetOrigin; // the domain to communicate with
3353
  /**
3354
  * Resolves the origin from the event object
3355
  * @private
3356
  * @param {Object} event The messageevent
3357
  * @return {String} The scheme, host and port of the origin
3358
  */
3359
  function _getOrigin(event){
3360
  if (event.origin) {
3361
  // This is the HTML5 property
3362
  return getLocation(event.origin);
3363
  }
3364
  if (event.uri) {
3365
  // From earlier implementations
3366
  return getLocation(event.uri);
3367
  }
3368
  if (event.domain) {
3369
  // This is the last option and will fail if the
3370
  // origin is not using the same schema as we are
3371
  return location.protocol + "//" + event.domain;
3372
  }
3373
  throw "Unable to retrieve the origin of the event";
3374
  }
3375
  /**
3376
  * This is the main implementation for the onMessage event.<br/>
3377
  * It checks the validity of the origin and passes the message on if appropriate.
3378
  * @private
3379
  * @param {Object} event The messageevent
3380
  */
3381
  function _window_onMessage(event){
3382
  var origin = _getOrigin(event);
3383
  if (origin == targetOrigin && event.data.substring(0, config.channel.length + 1) == config.channel + " ") {
3384
  pub.up.incoming(event.data.substring(config.channel.length + 1), origin);
3385
  }
3386
  }
3387
  return (pub = {
3388
  outgoing: function(message, domain, fn){
3389
  callerWindow.postMessage(config.channel + " " + message, domain || targetOrigin);
3390
  if (fn) {
3391
  fn();
3392
  }
3393
  },
3394
  destroy: function(){
3395
  un(window, "message", _window_onMessage);
3396
  if (frame) {
3397
  callerWindow = null;
3398
  frame.parentNode.removeChild(frame);
3399
  frame = null;
3400
  }
3401
  },
3402
  onDOMReady: function(){
3403
  targetOrigin = getLocation(config.remote);
3404
  if (config.isHost) {
3405
  // add the event handler for listening
3406
  var waitForReady = function(event){
3407
  if (event.data == config.channel + "-ready") {
3408
  // replace the eventlistener
3409
  callerWindow = ("postMessage" in frame.contentWindow) ? frame.contentWindow : frame.contentWindow.document;
3410
  un(window, "message", waitForReady);
3411
  on(window, "message", _window_onMessage);
3412
  setTimeout(function(){
3413
  pub.up.callback(true);
3414
  }, 0);
3415
  }
3416
  };
3417
  on(window, "message", waitForReady);
3418
  // set up the iframe
3419
  apply(config.props, {
3420
  src: appendQueryParameters(config.remote, {
3421
  xdm_e: getLocation(location.href),
3422
  xdm_c: config.channel,
3423
  xdm_p: 1 // 1 = PostMessage
3424
  }),
3425
  name: IFRAME_PREFIX + config.channel + "_provider"
3426
  });
3427
  frame = createFrame(config);
3428
  }
3429
  else {
3430
  // add the event handler for listening
3431
  on(window, "message", _window_onMessage);
3432
  callerWindow = ("postMessage" in window.parent) ? window.parent : window.parent.document;
3433
  callerWindow.postMessage(config.channel + "-ready", targetOrigin);
3434
  setTimeout(function(){
3435
  pub.up.callback(true);
3436
  }, 0);
3437
  }
3438
  },
3439
  init: function(){
3440
  whenReady(pub.onDOMReady, pub);
3441
  }
3442
  });
3443
  * @class easyXDM.stack.FrameElementTransport
3444
  * FrameElementTransport is a transport class that can be used with Gecko-browser as these allow passing variables using the frameElement property.<br/>
3445
  * Security is maintained as Gecho uses Lexical Authorization to determine under which scope a function is running.
3446
  * @namespace easyXDM.stack
3447
  * @constructor
3448
  * @param {Object} config The transports configuration.
3449
  * @cfg {String} remote The remote document to communicate with.
3450
  */
3451
  var pub, frame, send, targetOrigin;
3452
  return (pub = {
3453
  outgoing: function(message, domain, fn){
3454
  send.call(this, message);
3455
  if (fn) {
3456
  fn();
3457
  }
3458
  },
3459
  destroy: function(){
3460
  if (frame) {
3461
  frame.parentNode.removeChild(frame);
3462
  frame = null;
3463
  }
3464
  },
3465
  onDOMReady: function(){
3466
  targetOrigin = getLocation(config.remote);
3467
  if (config.isHost) {
3468
  // set up the iframe
3469
  apply(config.props, {
3470
  src: appendQueryParameters(config.remote, {
3471
  xdm_e: getLocation(location.href),
3472
  xdm_c: config.channel,
3473
  xdm_p: 5 // 5 = FrameElementTransport
3474
  }),
3475
  name: IFRAME_PREFIX + config.channel + "_provider"
3476
  });
3477
  frame = createFrame(config);
3478
  frame.fn = function(sendFn){
3479
  delete frame.fn;
3480
  send = sendFn;
3481
  setTimeout(function(){
3482
  pub.up.callback(true);
3483
  }, 0);
3484
  // remove the function so that it cannot be used to overwrite the send function later on
3485
  return function(msg){
3486
  pub.up.incoming(msg, targetOrigin);
3487
  };
3488
  };
3489
  }
3490
  else {
3491
  // This is to mitigate origin-spoofing
3492
  if (document.referrer && getLocation(document.referrer) != query.xdm_e) {
3493
  window.top.location = query.xdm_e;
3494
  }
3495
  send = window.frameElement.fn(function(msg){
3496
  pub.up.incoming(msg, targetOrigin);
3497
  });
3498
  pub.up.callback(true);
3499
  }
3500
  },
3501
  init: function(){
3502
  whenReady(pub.onDOMReady, pub);
3503
  }
3504
  });
3505
  * @class easyXDM.stack.NameTransport
3506
  * NameTransport uses the window.name property to relay data.
3507
  * The <code>local</code> parameter needs to be set on both the consumer and provider,<br/>
3508
  * and the <code>remoteHelper</code> parameter needs to be set on the consumer.
3509
  * @constructor
3510
  * @param {Object} config The transports configuration.
3511
  * @cfg {String} remoteHelper The url to the remote instance of hash.html - this is only needed for the host.
3512
  * @namespace easyXDM.stack
3513
  */
3514
  var pub; // the public interface
3515
  var isHost, callerWindow, remoteWindow, readyCount, callback, remoteOrigin, remoteUrl;
3516
  function _sendMessage(message){
3517
  var url = config.remoteHelper + (isHost ? "#_3" : "#_2") + config.channel;
3518
  callerWindow.contentWindow.sendMessage(message, url);
3519
  }
3520
  function _onReady(){
3521
  if (isHost) {
3522
  if (++readyCount === 2 || !isHost) {
3523
  pub.up.callback(true);
3524
  }
3525
  }
3526
  else {
3527
  _sendMessage("ready");
3528
  pub.up.callback(true);
3529
  }
3530
  }
3531
  function _onMessage(message){
3532
  pub.up.incoming(message, remoteOrigin);
3533
  }
3534
  function _onLoad(){
3535
  if (callback) {
3536
  setTimeout(function(){
3537
  callback(true);
3538
  }, 0);
3539
  }
3540
  }
3541
  return (pub = {
3542
  outgoing: function(message, domain, fn){
3543
  callback = fn;
3544
  _sendMessage(message);
3545
  },
3546
  destroy: function(){
3547
  callerWindow.parentNode.removeChild(callerWindow);
3548
  callerWindow = null;
3549
  if (isHost) {
3550
  remoteWindow.parentNode.removeChild(remoteWindow);
3551
  remoteWindow = null;
3552
  }
3553
  },
3554
  onDOMReady: function(){
3555
  isHost = config.isHost;
3556
  readyCount = 0;
3557
  remoteOrigin = getLocation(config.remote);
3558
  config.local = resolveUrl(config.local);
3559
  if (isHost) {
3560
  // Register the callback
3561
  easyXDM.Fn.set(config.channel, function(message){
3562
  if (isHost && message === "ready") {
3563
  // Replace the handler
3564
  easyXDM.Fn.set(config.channel, _onMessage);
3565
  _onReady();
3566
  }
3567
  });
3568
  // Set up the frame that points to the remote instance
3569
  remoteUrl = appendQueryParameters(config.remote, {
3570
  xdm_e: config.local,
3571
  xdm_c: config.channel,
3572
  xdm_p: 2
3573
  });
3574
  apply(config.props, {
3575
  src: remoteUrl + '#' + config.channel,
3576
  name: IFRAME_PREFIX + config.channel + "_provider"
3577
  });
3578
  remoteWindow = createFrame(config);
3579
  }
3580
  else {
3581
  config.remoteHelper = config.remote;
3582
  easyXDM.Fn.set(config.channel, _onMessage);
3583
  }
3584
  // Set up the iframe that will be used for the transport
3585
  var onLoad = function(){
3586
  // Remove the handler
3587
  var w = callerWindow || this;
3588
  un(w, "load", onLoad);
3589
  easyXDM.Fn.set(config.channel + "_load", _onLoad);
3590
  (function test(){
3591
  if (typeof w.contentWindow.sendMessage == "function") {
3592
  _onReady();
3593
  }
3594
  else {
3595
  setTimeout(test, 50);
3596
  }
3597
  }());
3598
  };
3599
  callerWindow = createFrame({
3600
  props: {
3601
  src: config.local + "#_4" + config.channel
3602
  },
3603
  onLoad: onLoad
3604
  });
3605
  },
3606
  init: function(){
3607
  whenReady(pub.onDOMReady, pub);
3608
  }
3609
  });
3610
  * @class easyXDM.stack.HashTransport
3611
  * HashTransport is a transport class that uses the IFrame URL Technique for communication.<br/>
3612
  * <a href="http://msdn.microsoft.com/en-us/library/bb735305.aspx">http://msdn.microsoft.com/en-us/library/bb735305.aspx</a><br/>
3613
  * @namespace easyXDM.stack
3614
  * @constructor
3615
  * @param {Object} config The transports configuration.
3616
  * @cfg {String/Window} local The url to the local file used for proxying messages, or the local window.
3617
  * @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window.
3618
  * @cfg {Number} interval The interval used when polling for messages.
3619
  */
3620
  var pub;
3621
  var me = this, isHost, _timer, pollInterval, _lastMsg, _msgNr, _listenerWindow, _callerWindow;
3622
  var useParent, _remoteOrigin;
3623
  function _sendMessage(message){
3624
  if (!_callerWindow) {
3625
  return;
3626
  }
3627
  var url = config.remote + "#" + (_msgNr++) + "_" + message;
3628
  ((isHost || !useParent) ? _callerWindow.contentWindow : _callerWindow).location = url;
3629
  }
3630
  function _handleHash(hash){
3631
  _lastMsg = hash;
3632
  pub.up.incoming(_lastMsg.substring(_lastMsg.indexOf("_") + 1), _remoteOrigin);
3633
  }
3634
  /**
3635
  * Checks location.hash for a new message and relays this to the receiver.
3636
  * @private
3637
  */
3638
  function _pollHash(){
3639
  if (!_listenerWindow) {
3640
  return;
3641
  }
3642
  var href = _listenerWindow.location.href, hash = "", indexOf = href.indexOf("#");
3643
  if (indexOf != -1) {
3644
  hash = href.substring(indexOf);
3645
  }
3646
  if (hash && hash != _lastMsg) {
3647
  _handleHash(hash);
3648
  }
3649
  }
3650
  function _attachListeners(){
3651
  _timer = setInterval(_pollHash, pollInterval);
3652
  }
3653
  return (pub = {
3654
  outgoing: function(message, domain){
3655
  _sendMessage(message);
3656
  },
3657
  destroy: function(){
3658
  window.clearInterval(_timer);
3659
  if (isHost || !useParent) {
3660
  _callerWindow.parentNode.removeChild(_callerWindow);
3661
  }
3662
  _callerWindow = null;
3663
  },
3664
  onDOMReady: function(){
3665
  isHost = config.isHost;
3666
  pollInterval = config.interval;
3667
  _lastMsg = "#" + config.channel;
3668
  _msgNr = 0;
3669
  useParent = config.useParent;
3670
  _remoteOrigin = getLocation(config.remote);
3671
  if (isHost) {
3672
  apply(config.props, {
3673
  src: config.remote,
3674
  name: IFRAME_PREFIX + config.channel + "_provider"
3675
  });
3676
  if (useParent) {
3677
  config.onLoad = function(){
3678
  _listenerWindow = window;
3679
  _attachListeners();
3680
  pub.up.callback(true);
3681
  };
3682
  }
3683
  else {
3684
  var tries = 0, max = config.delay / 50;
3685
  (function getRef(){
3686
  if (++tries > max) {
3687
  throw new Error("Unable to reference listenerwindow");
3688
  }
3689
  try {
3690
  _listenerWindow = _callerWindow.contentWindow.frames[IFRAME_PREFIX + config.channel + "_consumer"];
3691
  }
3692
  catch (ex) {
3693
  }
3694
  if (_listenerWindow) {
3695
  _attachListeners();
3696
  pub.up.callback(true);
3697
  }
3698
  else {
3699
  setTimeout(getRef, 50);
3700
  }
3701
  }());
3702
  }
3703
  _callerWindow = createFrame(config);
3704
  }
3705
  else {
3706
  _listenerWindow = window;
3707
  _attachListeners();
3708
  if (useParent) {
3709
  _callerWindow = parent;
3710
  pub.up.callback(true);
3711
  }
3712
  else {
3713
  apply(config, {
3714
  props: {
3715
  src: config.remote + "#" + config.channel + new Date(),
3716
  name: IFRAME_PREFIX + config.channel + "_consumer"
3717
  },
3718
  onLoad: function(){
3719
  pub.up.callback(true);
3720
  }
3721
  });
3722
  _callerWindow = createFrame(config);
3723
  }
3724
  }
3725
  },
3726
  init: function(){
3727
  whenReady(pub.onDOMReady, pub);
3728
  }
3729
  });
3730
  * @class easyXDM.stack.ReliableBehavior
3731
  * This is a behavior that tries to make the underlying transport reliable by using acknowledgements.
3732
  * @namespace easyXDM.stack
3733
  * @constructor
3734
  * @param {Object} config The behaviors configuration.
3735
  */
3736
  var pub, // the public interface
3737
  callback; // the callback to execute when we have a confirmed success/failure
3738
  var idOut = 0, idIn = 0, currentMessage = "";
3739
  return (pub = {
3740
  incoming: function(message, origin){
3741
  var indexOf = message.indexOf("_"), ack = message.substring(0, indexOf).split(",");
3742
  message = message.substring(indexOf + 1);
3743
  if (ack[0] == idOut) {
3744
  currentMessage = "";
3745
  if (callback) {
3746
  callback(true);
3747
  }
3748
  }
3749
  if (message.length > 0) {
3750
  pub.down.outgoing(ack[1] + "," + idOut + "_" + currentMessage, origin);
3751
  if (idIn != ack[1]) {
3752
  idIn = ack[1];
3753
  pub.up.incoming(message, origin);
3754
  }
3755
  }
3756
  },
3757
  outgoing: function(message, origin, fn){
3758
  currentMessage = message;
3759
  callback = fn;
3760
  pub.down.outgoing(idIn + "," + (++idOut) + "_" + message, origin);
3761
  }
3762
  });
3763
  * @class easyXDM.stack.QueueBehavior
3764
  * This is a behavior that enables queueing of messages. <br/>
3765
  * It will buffer incoming messages and dispach these as fast as the underlying transport allows.
3766
  * This will also fragment/defragment messages so that the outgoing message is never bigger than the
3767
  * set length.
3768
  * @namespace easyXDM.stack
3769
  * @constructor
3770
  * @param {Object} config The behaviors configuration. Optional.
3771
  * @cfg {Number} maxLength The maximum length of each outgoing message. Set this to enable fragmentation.
3772
  */
3773
  var pub, queue = [], waiting = true, incoming = "", destroying, maxLength = 0, lazy = false, doFragment = false;
3774
  function dispatch(){
3775
  if (config.remove && queue.length === 0) {
3776
  removeFromStack(pub);
3777
  return;
3778
  }
3779
  if (waiting || queue.length === 0 || destroying) {
3780
  return;
3781
  }
3782
  waiting = true;
3783
  var message = queue.shift();
3784
  pub.down.outgoing(message.data, message.origin, function(success){
3785
  waiting = false;
3786
  if (message.callback) {
3787
  setTimeout(function(){
3788
  message.callback(success);
3789
  }, 0);
3790
  }
3791
  dispatch();
3792
  });
3793
  }
3794
  return (pub = {
3795
  init: function(){
3796
  if (undef(config)) {
3797
  config = {};
3798
  }
3799
  if (config.maxLength) {
3800
  maxLength = config.maxLength;
3801
  doFragment = true;
3802
  }
3803
  if (config.lazy) {
3804
  lazy = true;
3805
  }
3806
  else {
3807
  pub.down.init();
3808
  }
3809
  },
3810
  callback: function(success){
3811
  waiting = false;
3812
  var up = pub.up; // in case dispatch calls removeFromStack
3813
  dispatch();
3814
  up.callback(success);
3815
  },
3816
  incoming: function(message, origin){
3817
  if (doFragment) {
3818
  var indexOf = message.indexOf("_"), seq = parseInt(message.substring(0, indexOf), 10);
3819
  incoming += message.substring(indexOf + 1);
3820
  if (seq === 0) {
3821
  if (config.encode) {
3822
  incoming = decodeURIComponent(incoming);
3823
  }
3824
  pub.up.incoming(incoming, origin);
3825
  incoming = "";
3826
  }
3827
  }
3828
  else {
3829
  pub.up.incoming(message, origin);
3830
  }
3831
  },
3832
  outgoing: function(message, origin, fn){
3833
  if (config.encode) {
3834
  message = encodeURIComponent(message);
3835
  }
3836
  var fragments = [], fragment;
3837
  if (doFragment) {
3838
  // fragment into chunks
3839
  while (message.length !== 0) {
3840
  fragment = message.substring(0, maxLength);
3841
  message = message.substring(fragment.length);
3842
  fragments.push(fragment);
3843
  }
3844
  // enqueue the chunks
3845
  while ((fragment = fragments.shift())) {
3846
  queue.push({
3847
  data: fragments.length + "_" + fragment,
3848
  origin: origin,
3849
  callback: fragments.length === 0 ? fn : null
3850
  });
3851
  }
3852
  }
3853
  else {
3854
  queue.push({
3855
  data: message,
3856
  origin: origin,
3857
  callback: fn
3858
  });
3859
  }
3860
  if (lazy) {
3861
  pub.down.init();
3862
  }
3863
  else {
3864
  dispatch();
3865
  }
3866
  },
3867
  destroy: function(){
3868
  destroying = true;
3869
  pub.down.destroy();
3870
  }
3871
  });
3872
  * @class easyXDM.stack.VerifyBehavior
3873
  * This behavior will verify that communication with the remote end is possible, and will also sign all outgoing,
3874
  * and verify all incoming messages. This removes the risk of someone hijacking the iframe to send malicious messages.
3875
  * @namespace easyXDM.stack
3876
  * @constructor
3877
  * @param {Object} config The behaviors configuration.
3878
  * @cfg {Boolean} initiate If the verification should be initiated from this end.
3879
  */
3880
  var pub, mySecret, theirSecret, verified = false;
3881
  function startVerification(){
3882
  mySecret = Math.random().toString(16).substring(2);
3883
  pub.down.outgoing(mySecret);
3884
  }
3885
  return (pub = {
3886
  incoming: function(message, origin){
3887
  var indexOf = message.indexOf("_");
3888
  if (indexOf === -1) {
3889
  if (message === mySecret) {
3890
  pub.up.callback(true);
3891
  }
3892
  else if (!theirSecret) {
3893
  theirSecret = message;
3894
  if (!config.initiate) {
3895
  startVerification();
3896
  }
3897
  pub.down.outgoing(message);
3898
  }
3899
  }
3900
  else {
3901
  if (message.substring(0, indexOf) === theirSecret) {
3902
  pub.up.incoming(message.substring(indexOf + 1), origin);
3903
  }
3904
  }
3905
  },
3906
  outgoing: function(message, origin, fn){
3907
  pub.down.outgoing(mySecret + "_" + message, origin, fn);
3908
  },
3909
  callback: function(success){
3910
  if (config.initiate) {
3911
  startVerification();
3912
  }
3913
  }
3914
  });
3915
  * @class easyXDM.stack.RpcBehavior
3916
  * This uses JSON-RPC 2.0 to expose local methods and to invoke remote methods and have responses returned over the the string based transport stack.<br/>
3917
  * Exposed methods can return values synchronous, asyncronous, or bet set up to not return anything.
3918
  * @namespace easyXDM.stack
3919
  * @constructor
3920
  * @param {Object} proxy The object to apply the methods to.
3921
  * @param {Object} config The definition of the local and remote interface to implement.
3922
  * @cfg {Object} local The local interface to expose.
3923
  * @cfg {Object} remote The remote methods to expose through the proxy.
3924
  * @cfg {Object} serializer The serializer to use for serializing and deserializing the JSON. Should be compatible with the HTML5 JSON object. Optional, will default to JSON.
3925
  */
3926
  var pub, serializer = config.serializer || getJSON();
3927
  var _callbackCounter = 0, _callbacks = {};
3928
  /**
3929
  * Serializes and sends the message
3930
  * @private
3931
  * @param {Object} data The JSON-RPC message to be sent. The jsonrpc property will be added.
3932
  */
3933
  function _send(data){
3934
  data.jsonrpc = "2.0";
3935
  pub.down.outgoing(serializer.stringify(data));
3936
  }
3937
  /**
3938
  * Creates a method that implements the given definition
3939
  * @private
3940
  * @param {Object} The method configuration
3941
  * @param {String} method The name of the method
3942
  * @return {Function} A stub capable of proxying the requested method call
3943
  */
3944
  function _createMethod(definition, method){
3945
  var slice = Array.prototype.slice;
3946
  return function(){
3947
  var l = arguments.length, callback, message = {
3948
  method: method
3949
  };
3950
  if (l > 0 && typeof arguments[l - 1] === "function") {
3951
  //with callback, procedure
3952
  if (l > 1 && typeof arguments[l - 2] === "function") {
3953
  // two callbacks, success and error
3954
  callback = {
3955
  success: arguments[l - 2],
3956
  error: arguments[l - 1]
3957
  };
3958
  message.params = slice.call(arguments, 0, l - 2);
3959
  }
3960
  else {
3961
  // single callback, success
3962
  callback = {
3963
  success: arguments[l - 1]
3964
  };
3965
  message.params = slice.call(arguments, 0, l - 1);
3966
  }
3967
  _callbacks["" + (++_callbackCounter)] = callback;
3968
  message.id = _callbackCounter;
3969
  }
3970
  else {
3971
  // no callbacks, a notification
3972
  message.params = slice.call(arguments, 0);
3973
  }
3974
  if (definition.namedParams && message.params.length === 1) {
3975
  message.params = message.params[0];
3976
  }
3977
  // Send the method request
3978
  _send(message);
3979
  };
3980
  }
3981
  /**
3982
  * Executes the exposed method
3983
  * @private
3984
  * @param {String} method The name of the method
3985
  * @param {Number} id The callback id to use
3986
  * @param {Function} method The exposed implementation
3987
  * @param {Array} params The parameters supplied by the remote end
3988
  */
3989
  function _executeMethod(method, id, fn, params){
3990
  if (!fn) {
3991
  if (id) {
3992
  _send({
3993
  id: id,
3994
  error: {
3995
  code: -32601,
3996
  message: "Procedure not found."
3997
  }
3998
  });
3999
  }
4000
  return;
4001
  }
4002
  var success, error;
4003
  if (id) {
4004
  success = function(result){
4005
  success = emptyFn;
4006
  _send({
4007
  id: id,
4008
  result: result
4009
  });
4010
  };
4011
  error = function(message, data){
4012
  error = emptyFn;
4013
  var msg = {
4014
  id: id,
4015
  error: {
4016
  code: -32099,
4017
  message: message
4018
  }
4019
  };
4020
  if (data) {
4021
  msg.error.data = data;
4022
  }
4023
  _send(msg);
4024
  };
4025
  }
4026
  else {
4027
  success = error = emptyFn;
4028
  }
4029
  // Call local method
4030
  if (!isArray(params)) {
4031
  params = [params];
4032
  }
4033
  try {
4034
  var result = fn.method.apply(fn.scope, params.concat([success, error]));
4035
  if (!undef(result)) {
4036
  success(result);
4037
  }
4038
  }
4039
  catch (ex1) {
4040
  error(ex1.message);
4041
  }
4042
  }
4043
  return (pub = {
4044
  incoming: function(message, origin){
4045
  var data = serializer.parse(message);
4046
  if (data.method) {
4047
  // A method call from the remote end
4048
  if (config.handle) {
4049
  config.handle(data, _send);
4050
  }
4051
  else {
4052
  _executeMethod(data.method, data.id, config.local[data.method], data.params);
4053
  }
4054
  }
4055
  else {
4056
  // A method response from the other end
4057
  var callback = _callbacks[data.id];
4058
  if (data.error) {
4059
  if (callback.error) {
4060
  callback.error(data.error);
4061
  }
4062
  }
4063
  else if (callback.success) {
4064
  callback.success(data.result);
4065
  }
4066
  delete _callbacks[data.id];
4067
  }
4068
  },
4069
  init: function(){
4070
  if (config.remote) {
4071
  // Implement the remote sides exposed methods
4072
  for (var method in config.remote) {
4073
  if (config.remote.hasOwnProperty(method)) {
4074
  proxy[method] = _createMethod(config.remote[method], method);
4075
  }
4076
  }
4077
  }
4078
  pub.down.init();
4079
  },
4080
  destroy: function(){
4081
  for (var method in config.remote) {
4082
  if (config.remote.hasOwnProperty(method) && proxy.hasOwnProperty(method)) {
4083
  delete proxy[method];
4084
  }
4085
  }
4086
  pub.down.destroy();
4087
  }
4088
  });
 
1
  * easyXDM
2
  * http://easyxdm.net/
3
  * Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no.
4
  *
5
  * Permission is hereby granted, free of charge, to any person obtaining a copy
6
  * of this software and associated documentation files (the "Software"), to deal
7
  * in the Software without restriction, including without limitation the rights
8
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
  * copies of the Software, and to permit persons to whom the Software is
10
  * furnished to do so, subject to the following conditions:
11
  *
12
  * The above copyright notice and this permission notice shall be included in
13
  * all copies or substantial portions of the Software.
14
  *
15
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
  * THE SOFTWARE.
22
  */
23
  var t = typeof object[property];
24
  return t == 'function' ||
25
  (!!(t == 'object' && object[property])) ||
26
  t == 'unknown';
27
  return !!(typeof(object[property]) == 'object' && object[property]);
28
  return Object.prototype.toString.call(o) === '[object Array]';
29
  try {
30
  var activeX = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
31
  flashVersion = Array.prototype.slice.call(activeX.GetVariable("$version").match(/(\d+),(\d+),(\d+),(\d+)/), 1);
32
  HAS_FLASH_THROTTLED_BUG = parseInt(flashVersion[0], 10) > 9 && parseInt(flashVersion[1], 10) > 0;
33
  activeX = null;
34
  return true;
35
  }
36
  catch (notSupportedException) {
37
  return false;
38
  }
39
  * Cross Browser implementation for adding and removing event listeners.
40
  */
41
  on = function(target, type, listener){
42
  _trace("adding listener " + type);
43
  target.addEventListener(type, listener, false);
44
  };
45
  un = function(target, type, listener){
46
  _trace("removing listener " + type);
47
  target.removeEventListener(type, listener, false);
48
  };
49
  on = function(object, sEvent, fpNotify){
50
  _trace("adding listener " + sEvent);
51
  object.attachEvent("on" + sEvent, fpNotify);
52
  };
53
  un = function(object, sEvent, fpNotify){
54
  _trace("removing listener " + sEvent);
55
  object.detachEvent("on" + sEvent, fpNotify);
56
  };
57
  throw new Error("Browser not supported");
58
  * Cross Browser implementation of DOMContentLoaded.
59
  */
60
  // If browser is WebKit-powered, check for both 'loaded' (legacy browsers) and
61
  // 'interactive' (HTML5 specs, recent WebKit builds) states.
62
  // https://bugs.webkit.org/show_bug.cgi?id=45119
63
  readyState = document.readyState;
64
  domIsReady = readyState == "complete" || (~ navigator.userAgent.indexOf('AppleWebKit/') && (readyState == "loaded" || readyState == "interactive"));
65
  // If readyState is not supported in the browser, then in order to be able to fire whenReady functions apropriately
66
  // when added dynamically _after_ DOM load, we have to deduce wether the DOM is ready or not.
67
  // We only need a body to add elements to, so the existence of document.body is enough for us.
68
  domIsReady = !!document.body;
69
  if (domIsReady) {
70
  return;
71
  }
72
  domIsReady = true;
73
  _trace("firing dom_onReady");
74
  for (var i = 0; i < domReadyQueue.length; i++) {
75
  domReadyQueue[i]();
76
  }
77
  domReadyQueue.length = 0;
78
  if (isHostMethod(window, "addEventListener")) {
79
  on(document, "DOMContentLoaded", dom_onReady);
80
  }
81
  else {
82
  on(document, "readystatechange", function(){
83
  if (document.readyState == "complete") {
84
  dom_onReady();
85
  }
86
  });
87
  if (document.documentElement.doScroll && window === top) {
88
  var doScrollCheck = function(){
89
  if (domIsReady) {
90
  return;
91
  }
92
  // http://javascript.nwbox.com/IEContentLoaded/
93
  try {
94
  document.documentElement.doScroll("left");
95
  }
96
  catch (e) {
97
  setTimeout(doScrollCheck, 1);
98
  return;
99
  }
100
  dom_onReady();
101
  };
102
  doScrollCheck();
103
  }
104
  }
105
 
106
  // A fallback to window.onload, that will always work
107
  on(window, "load", dom_onReady);
108
  * This will add a function to the queue of functions to be run once the DOM reaches a ready state.
109
  * If functions are added after this event then they will be executed immediately.
110
  * @param {function} fn The function to add
111
  * @param {Object} scope An optional scope for the function to be called with.
112
  */
113
  if (domIsReady) {
114
  fn.call(scope);
115
  return;
116
  }
117
  domReadyQueue.push(function(){
118
  fn.call(scope);
119
  });
120
  * Returns an instance of easyXDM from the parent window with
121
  * respect to the namespace.
122
  *
123
  * @return An instance of easyXDM (in the parent window)
124
  */
125
  var obj = parent;
126
  if (namespace !== "") {
127
  for (var i = 0, ii = namespace.split("."); i < ii.length; i++) {
128
  if (!obj) {
129
  throw new Error(ii.slice(0, i + 1).join('.') + ' is not an object');
130
  }
131
  obj = obj[ii[i]];
132
  }
133
  }
134
  if (!obj || !obj.easyXDM) {
135
  throw new Error('Could not find easyXDM in parent.' + namespace);
136
  }
137
  return obj.easyXDM;
138
  * Removes easyXDM variable from the global scope. It also returns control
139
  * of the easyXDM variable to whatever code used it before.
140
  *
141
  * @param {String} ns A string representation of an object that will hold
142
  * an instance of easyXDM.
143
  * @return An instance of easyXDM
144
  */
145
  if (typeof ns != "string" || !ns) {
146
  throw new Error('namespace must be a non-empty string');
147
  }
148
  _trace("Settings namespace to '" + ns + "'");
149
 
150
  window.easyXDM = _easyXDM;
151
  namespace = ns;
152
  if (namespace) {
153
  IFRAME_PREFIX = "easyXDM_" + namespace.replace(".", "_") + "_";
154
  }
155
  return easyXDM;
156
  * Methods for working with URLs
157
  */
158
  * Get the domain name from a url.
159
  * @param {String} url The url to extract the domain from.
160
  * @return The domain part of the url.
161
  * @type {String}
162
  */
163
  if (!url) {
164
  throw new Error("url is undefined or empty");
165
  }
166
  return url.match(reURI)[3];
167
  * Get the port for a given URL, or "" if none
168
  * @param {String} url The url to extract the port from.
169
  * @return The port part of the url.
170
  * @type {String}
171
  */
172
  if (!url) {
173
  throw new Error("url is undefined or empty");
174
  }
175
  return url.match(reURI)[4] || "";
176
  * Returns a string containing the schema, domain and if present the port
177
  * @param {String} url The url to extract the location from
178
  * @return {String} The location part of the url
179
  */
180
  if (!url) {
181
  throw new Error("url is undefined or empty");
182
  }
183
  if (/^file/.test(url)) {
184
  throw new Error("The file:// protocol is not supported");
185
  }
186
  var m = url.toLowerCase().match(reURI);
187
  var proto = m[2], domain = m[3], port = m[4] || "";
188
  if ((proto == "http:" && port == ":80") || (proto == "https:" && port == ":443")) {
189
  port = "";
190
  }
191
  return proto + "//" + domain + port;
192
  * Resolves a relative url into an absolute one.
193
  * @param {String} url The path to resolve.
194
  * @return {String} The resolved url.
195
  */
196
  if (!url) {
197
  throw new Error("url is undefined or empty");
198
  }
199
 
200
  // replace all // except the one in proto with /
201
  url = url.replace(reDoubleSlash, "$1/");
202
 
203
  // If the url is a valid url we do nothing
204
  if (!url.match(/^(http||https):\/\//)) {
205
  // If this is a relative path
206
  var path = (url.substring(0, 1) === "/") ? "" : location.pathname;
207
  if (path.substring(path.length - 1) !== "/") {
208
  path = path.substring(0, path.lastIndexOf("/") + 1);
209
  }
210
 
211
  url = location.protocol + "//" + location.host + path + url;
212
  }
213
 
214
  // reduce all 'xyz/../' to just ''
215
  while (reParent.test(url)) {
216
  url = url.replace(reParent, "");
217
  }
218
 
219
  _trace("resolved url '" + url + "'");
220
  return url;
221
  * Appends the parameters to the given url.<br/>
222
  * The base url can contain existing query parameters.
223
  * @param {String} url The base url.
224
  * @param {Object} parameters The parameters to add.
225
  * @return {String} A new valid url with the parameters appended.
226
  */
227
  if (!parameters) {
228
  throw new Error("parameters is undefined or null");
229
  }
230
 
231
  var hash = "", indexOf = url.indexOf("#");
232
  if (indexOf !== -1) {
233
  hash = url.substring(indexOf);
234
  url = url.substring(0, indexOf);
235
  }
236
  var q = [];
237
  for (var key in parameters) {
238
  if (parameters.hasOwnProperty(key)) {
239
  q.push(key + "=" + encodeURIComponent(parameters[key]));
240
  }
241
  }
242
  return url + (useHash ? "#" : (url.indexOf("?") == -1 ? "?" : "&")) + q.join("&") + hash;
243
  input = input.substring(1).split("&");
244
  var data = {}, pair, i = input.length;
245
  while (i--) {
246
  pair = input[i].split("=");
247
  data[pair[0]] = decodeURIComponent(pair[1]);
248
  }
249
  return data;
250
  * Helper methods
251
  */
252
  * Helper for checking if a variable/property is undefined
253
  * @param {Object} v The variable to test
254
  * @return {Boolean} True if the passed variable is undefined
255
  */
256
  return typeof v === "undefined";
257
  * A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works.
258
  * @return {JSON} A valid JSON conforming object, or null if not found.
259
  */
260
  var cached = {};
261
  var obj = {
262
  a: [1, 2, 3]
263
  }, json = "{\"a\":[1,2,3]}";
264
 
265
  if (typeof JSON != "undefined" && typeof JSON.stringify === "function" && JSON.stringify(obj).replace((/\s/g), "") === json) {
266
  // this is a working JSON instance
267
  return JSON;
268
  }
269
  if (Object.toJSON) {
270
  if (Object.toJSON(obj).replace((/\s/g), "") === json) {
271
  // this is a working stringify method
272
  cached.stringify = Object.toJSON;
273
  }
274
  }
275
 
276
  if (typeof String.prototype.evalJSON === "function") {
277
  obj = json.evalJSON();
278
  if (obj.a && obj.a.length === 3 && obj.a[2] === 3) {
279
  // this is a working parse method
280
  cached.parse = function(str){
281
  return str.evalJSON();
282
  };
283
  }
284
  }
285
 
286
  if (cached.stringify && cached.parse) {
287
  // Only memoize the result if we have valid instance
288
  getJSON = function(){
289
  return cached;
290
  };
291
  return cached;
292
  }
293
  return null;
294
  * Applies properties from the source object to the target object.<br/>
295
  * @param {Object} target The target of the properties.
296
  * @param {Object} source The source of the properties.
297
  * @param {Boolean} noOverwrite Set to True to only set non-existing properties.
298
  */
299
  var member;
300
  for (var prop in source) {
301
  if (source.hasOwnProperty(prop)) {
302
  if (prop in destination) {
303
  member = source[prop];
304
  if (typeof member === "object") {
305
  apply(destination[prop], member, noOverwrite);
306
  }
307
  else if (!noOverwrite) {
308
  destination[prop] = source[prop];
309
  }
310
  }
311
  else {
312
  destination[prop] = source[prop];
313
  }
314
  }
315
  }
316
  return destination;
317
  var form = document.body.appendChild(document.createElement("form")), input = form.appendChild(document.createElement("input"));
318
  input.name = IFRAME_PREFIX + "TEST" + channelId; // append channelId in order to avoid caching issues
319
  HAS_NAME_PROPERTY_BUG = input !== form.elements[input.name];
320
  document.body.removeChild(form);
321
  _trace("HAS_NAME_PROPERTY_BUG: " + HAS_NAME_PROPERTY_BUG);
322
  * Creates a frame and appends it to the DOM.
323
  * @param config {object} This object can have the following properties
324
  * <ul>
325
  * <li> {object} prop The properties that should be set on the frame. This should include the 'src' property.</li>
326
  * <li> {object} attr The attributes that should be set on the frame.</li>
327
  * <li> {DOMElement} container Its parent element (Optional).</li>
328
  * <li> {function} onLoad A method that should be called with the frames contentWindow as argument when the frame is fully loaded. (Optional)</li>
329
  * </ul>
330
  * @return The frames DOMElement
331
  * @type DOMElement
332
  */
333
  _trace("creating frame: " + config.props.src);
334
  if (undef(HAS_NAME_PROPERTY_BUG)) {
335
  testForNamePropertyBug();
336
  }
337
  var frame;
338
  // This is to work around the problems in IE6/7 with setting the name property.
339
  // Internally this is set as 'submitName' instead when using 'iframe.name = ...'
340
  // This is not required by easyXDM itself, but is to facilitate other use cases
341
  if (HAS_NAME_PROPERTY_BUG) {
342
  frame = document.createElement("<iframe name=\"" + config.props.name + "\"/>");
343
  }
344
  else {
345
  frame = document.createElement("IFRAME");
346
  frame.name = config.props.name;
347
  }
348
 
349
  frame.id = frame.name = config.props.name;
350
  delete config.props.name;
351
 
352
  if (config.onLoad) {
353
  on(frame, "load", config.onLoad);
354
  }
355
 
356
  if (typeof config.container == "string") {
357
  config.container = document.getElementById(config.container);
358
  }
359
 
360
  if (!config.container) {
361
  // This needs to be hidden like this, simply setting display:none and the like will cause failures in some browsers.
362
  apply(frame.style, {
363
  position: "absolute",
364
  top: "-2000px"
365
  });
366
  config.container = document.body;
367
  }
368
 
369
  // HACK for some reason, IE needs the source set
370
  // after the frame has been appended into the DOM
371
  // so remove the src, and set it afterwards
372
  var src = config.props.src;
373
  delete config.props.src;
374
 
375
  // transfer properties to the frame
376
  apply(frame, config.props);
377
 
378
  frame.border = frame.frameBorder = 0;
379
  frame.allowTransparency = true;
380
  config.container.appendChild(frame);
381
 
382
  // HACK see above
383
  frame.src = src;
384
  config.props.src = src;
385
 
386
  return frame;
387
  * Check whether a domain is allowed using an Access Control List.
388
  * The ACL can contain * and ? as wildcards, or can be regular expressions.
389
  * If regular expressions they need to begin with ^ and end with $.
390
  * @param {Array/String} acl The list of allowed domains
391
  * @param {String} domain The domain to test.
392
  * @return {Boolean} True if the domain is allowed, false if not.
393
  */
394
  // normalize into an array
395
  if (typeof acl == "string") {
396
  acl = [acl];
397
  }
398
  var re, i = acl.length;
399
  while (i--) {
400
  re = acl[i];
401
  re = new RegExp(re.substr(0, 1) == "^" ? re : ("^" + re.replace(/(\*)/g, ".$1").replace(/\?/g, ".") + "$"));
402
  if (re.test(domain)) {
403
  return true;
404
  }
405
  }
406
  return false;
407
  * Functions related to stacks
408
  */
409
  * Prepares an array of stack-elements suitable for the current configuration
410
  * @param {Object} config The Transports configuration. See easyXDM.Socket for more.
411
  * @return {Array} An array of stack-elements with the TransportElement at index 0.
412
  */
413
  var protocol = config.protocol, stackEls;
414
  config.isHost = config.isHost || undef(query.xdm_p);
415
  useHash = config.hash || false;
416
  _trace("preparing transport stack");
417
 
418
  if (!config.props) {
419
  config.props = {};
420
  }
421
  if (!config.isHost) {
422
  _trace("using parameters from query");
423
  config.channel = query.xdm_c;
424
  config.secret = query.xdm_s;
425
  config.remote = query.xdm_e;
426
  protocol = query.xdm_p;
427
  if (config.acl && !checkAcl(config.acl, config.remote)) {
428
  throw new Error("Access denied for " + config.remote);
429
  }
430
  }
431
  else {
432
  config.remote = resolveUrl(config.remote);
433
  config.channel = config.channel || "default" + channelId++;
434
  config.secret = Math.random().toString(16).substring(2);
435
  if (undef(protocol)) {
436
  if (getLocation(location.href) == getLocation(config.remote)) {
437
  /*
438
  * Both documents has the same origin, lets use direct access.
439
  */
440
  protocol = "4";
441
  }
442
  else if (isHostMethod(window, "postMessage") || isHostMethod(document, "postMessage")) {
443
  /*
444
  * This is supported in IE8+, Firefox 3+, Opera 9+, Chrome 2+ and Safari 4+
445
  */
446
  protocol = "1";
447
  }
448
  else if (config.swf && isHostMethod(window, "ActiveXObject") && hasFlash()) {
449
  /*
450
  * The Flash transport superseedes the NixTransport as the NixTransport has been blocked by MS
451
  */
452
  protocol = "6";
453
  }
454
  else if (navigator.product === "Gecko" && "frameElement" in window && navigator.userAgent.indexOf('WebKit') == -1) {
455
  /*
456
  * This is supported in Gecko (Firefox 1+)
457
  */
458
  protocol = "5";
459
  }
460
  else if (config.remoteHelper) {
461
  /*
462
  * This is supported in all browsers that retains the value of window.name when
463
  * navigating from one domain to another, and where parent.frames[foo] can be used
464
  * to get access to a frame from the same domain
465
  */
466
  config.remoteHelper = resolveUrl(config.remoteHelper);
467
  protocol = "2";
468
  }
469
  else {
470
  /*
471
  * This is supported in all browsers where [window].location is writable for all
472
  * The resize event will be used if resize is supported and the iframe is not put
473
  * into a container, else polling will be used.
474
  */
475
  protocol = "0";
476
  }
477
  _trace("selecting protocol: " + protocol);
478
  }
479
  else {
480
  _trace("using protocol: " + protocol);
481
  }
482
  }
483
  config.protocol = protocol; // for conditional branching
484
  switch (protocol) {
485
  case "0":// 0 = HashTransport
486
  apply(config, {
487
  interval: 100,
488
  delay: 2000,
489
  useResize: true,
490
  useParent: false,
491
  usePolling: false
492
  }, true);
493
  if (config.isHost) {
494
  if (!config.local) {
495
  _trace("looking for image to use as local");
496
  // If no local is set then we need to find an image hosted on the current domain
497
  var domain = location.protocol + "//" + location.host, images = document.body.getElementsByTagName("img"), image;
498
  var i = images.length;
499
  while (i--) {
500
  image = images[i];
501
  if (image.src.substring(0, domain.length) === domain) {
502
  config.local = image.src;
503
  break;
504
  }
505
  }
506
  if (!config.local) {
507
  _trace("no image found, defaulting to using the window");
508
  // If no local was set, and we are unable to find a suitable file, then we resort to using the current window
509
  config.local = window;
510
  }
511
  }
512
 
513
  var parameters = {
514
  xdm_c: config.channel,
515
  xdm_p: 0
516
  };
517
 
518
  if (config.local === window) {
519
  // We are using the current window to listen to
520
  config.usePolling = true;
521
  config.useParent = true;
522
  config.local = location.protocol + "//" + location.host + location.pathname + location.search;
523
  parameters.xdm_e = config.local;
524
  parameters.xdm_pa = 1; // use parent
525
  }
526
  else {
527
  parameters.xdm_e = resolveUrl(config.local);
528
  }
529
 
530
  if (config.container) {
531
  config.useResize = false;
532
  parameters.xdm_po = 1; // use polling
533
  }
534
  config.remote = appendQueryParameters(config.remote, parameters);
535
  }
536
  else {
537
  apply(config, {
538
  channel: query.xdm_c,
539
  remote: query.xdm_e,
540
  useParent: !undef(query.xdm_pa),
541
  usePolling: !undef(query.xdm_po),
542
  useResize: config.useParent ? false : config.useResize
543
  });
544
  }
545
  stackEls = [new easyXDM.stack.HashTransport(config), new easyXDM.stack.ReliableBehavior({}), new easyXDM.stack.QueueBehavior({
546
  encode: true,
547
  maxLength: 4000 - config.remote.length
548
  }), new easyXDM.stack.VerifyBehavior({
549
  initiate: config.isHost
550
  })];
551
  break;
552
  case "1":
553
  stackEls = [new easyXDM.stack.PostMessageTransport(config)];
554
  break;
555
  case "2":
556
  stackEls = [new easyXDM.stack.NameTransport(config), new easyXDM.stack.QueueBehavior(), new easyXDM.stack.VerifyBehavior({
557
  initiate: config.isHost
558
  })];
559
  break;
560
  case "3":
561
  stackEls = [new easyXDM.stack.NixTransport(config)];
562
  break;
563
  case "4":
564
  stackEls = [new easyXDM.stack.SameOriginTransport(config)];
565
  break;
566
  case "5":
567
  stackEls = [new easyXDM.stack.FrameElementTransport(config)];
568
  break;
569
  case "6":
570
  if (!flashVersion) {
571
  hasFlash();
572
  }
573
  stackEls = [new easyXDM.stack.FlashTransport(config)];
574
  break;
575
  }
576
  // this behavior is responsible for buffering outgoing messages, and for performing lazy initialization
577
  stackEls.push(new easyXDM.stack.QueueBehavior({
578
  lazy: config.lazy,
579
  remove: true
580
  }));
581
  return stackEls;
582
  * Chains all the separate stack elements into a single usable stack.<br/>
583
  * If an element is missing a necessary method then it will have a pass-through method applied.
584
  * @param {Array} stackElements An array of stack elements to be linked.
585
  * @return {easyXDM.stack.StackElement} The last element in the chain.
586
  */
587
  var stackEl, defaults = {
588
  incoming: function(message, origin){
589
  this.up.incoming(message, origin);
590
  },
591
  outgoing: function(message, recipient){
592
  this.down.outgoing(message, recipient);
593
  },
594
  callback: function(success){
595
  this.up.callback(success);
596
  },
597
  init: function(){
598
  this.down.init();
599
  },
600
  destroy: function(){
601
  this.down.destroy();
602
  }
603
  };
604
  for (var i = 0, len = stackElements.length; i < len; i++) {
605
  stackEl = stackElements[i];
606
  apply(stackEl, defaults, true);
607
  if (i !== 0) {
608
  stackEl.down = stackElements[i - 1];
609
  }
610
  if (i !== len - 1) {
611
  stackEl.up = stackElements[i + 1];
612
  }
613
  }
614
  return stackEl;
615
  * This will remove a stackelement from its stack while leaving the stack functional.
616
  * @param {Object} element The elment to remove from the stack.
617
  */
618
  element.up.down = element.down;
619
  element.down.up = element.up;
620
  element.up = element.down = null;
621
  * Export the main object and any other methods applicable
622
  */
623
  * @class easyXDM
624
  * A javascript library providing cross-browser, cross-domain messaging/RPC.
625
  * @version 2.4.15.118
626
  * @singleton
627
  */
628
  /**
629
  * The version of the library
630
  * @type {string}
631
  */
632
  version: "2.4.15.118",
633
  /**
634
  * This is a map containing all the query parameters passed to the document.
635
  * All the values has been decoded using decodeURIComponent.
636
  * @type {object}
637
  */
638
  query: query,
639
  /**
640
  * @private
641
  */
642
  stack: {},
643
  /**
644
  * Applies properties from the source object to the target object.<br/>
645
  * @param {object} target The target of the properties.
646
  * @param {object} source The source of the properties.
647
  * @param {boolean} noOverwrite Set to True to only set non-existing properties.
648
  */
649
  apply: apply,
650
 
651
  /**
652
  * A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works.
653
  * @return {JSON} A valid JSON conforming object, or null if not found.
654
  */
655
  getJSONObject: getJSON,
656
  /**
657
  * This will add a function to the queue of functions to be run once the DOM reaches a ready state.
658
  * If functions are added after this event then they will be executed immediately.
659
  * @param {function} fn The function to add
660
  * @param {object} scope An optional scope for the function to be called with.
661
  */
662
  whenReady: whenReady,
663
  /**
664
  * Removes easyXDM variable from the global scope. It also returns control
665
  * of the easyXDM variable to whatever code used it before.
666
  *
667
  * @param {String} ns A string representation of an object that will hold
668
  * an instance of easyXDM.
669
  * @return An instance of easyXDM
670
  */
671
  noConflict: noConflict
672
  checkAcl: checkAcl,
673
  getDomainName: getDomainName,
674
  getLocation: getLocation,
675
  appendQueryParameters: appendQueryParameters
676
  _deferred: [],
677
  flush: function(){
678
  this.trace("... deferred messages ...");
679
  for (var i = 0, len = this._deferred.length; i < len; i++) {
680
  this.trace(this._deferred[i]);
681
  }
682
  this._deferred.length = 0;
683
  this.trace("... end of deferred messages ...");
684
  },
685
  getTime: function(){
686
  var d = new Date(), h = d.getHours() + "", m = d.getMinutes() + "", s = d.getSeconds() + "", ms = d.getMilliseconds() + "", zeros = "000";
687
  if (h.length == 1) {
688
  h = "0" + h;
689
  }
690
  if (m.length == 1) {
691
  m = "0" + m;
692
  }
693
  if (s.length == 1) {
694
  s = "0" + s;
695
  }
696
  ms = zeros.substring(ms.length) + ms;
697
  return h + ":" + m + ":" + s + "." + ms;
698
  },
699
  /**
700
  * Logs the message to console.log if available
701
  * @param {String} msg The message to log
702
  */
703
  log: function(msg){
704
  // Uses memoizing to cache the implementation
705
  if (!isHostObject(window, "console") || undef(console.log)) {
706
  /**
707
  * Sets log to be an empty function since we have no output available
708
  * @ignore
709
  */
710
  this.log = emptyFn;
711
  }
712
  else {
713
  /**
714
  * Sets log to be a wrapper around console.log
715
  * @ignore
716
  * @param {String} msg
717
  */
718
  this.log = function(msg){
719
  console.log(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ": " + msg);
720
  };
721
  }
722
  this.log(msg);
723
  },
724
  /**
725
  * Will try to trace the given message either to a DOMElement with the id "log",
726
  * or by using console.info.
727
  * @param {String} msg The message to trace
728
  */
729
  trace: function(msg){
730
  // Uses memoizing to cache the implementation
731
  if (!domIsReady) {
732
  if (this._deferred.length === 0) {
733
  easyXDM.whenReady(debug.flush, debug);
734
  }
735
  this._deferred.push(msg);
736
  this.log(msg);
737
  }
738
  else {
739
  var el = document.getElementById("log");
740
  // is there a log element present?
741
  if (el) {
742
  /**
743
  * Sets trace to be a function that outputs the messages to the DOMElement with id "log"
744
  * @ignore
745
  * @param {String} msg
746
  */
747
  this.trace = function(msg){
748
  try {
749
  el.appendChild(document.createElement("div")).appendChild(document.createTextNode(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg));
750
  el.scrollTop = el.scrollHeight;
751
  }
752
  catch (e) {
753
  //In case we are unloading
754
  }
755
  };
756
  }
757
  else if (isHostObject(window, "console") && !undef(console.info)) {
758
  /**
759
  * Sets trace to be a wrapper around console.info
760
  * @ignore
761
  * @param {String} msg
762
  */
763
  this.trace = function(msg){
764
  console.info(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg);
765
  };
766
  }
767
  else {
768
  /**
769
  * Create log window
770
  * @ignore
771
  */
772
  var domain = location.host, windowname = domain.replace(/\[-.:]/g, "") + "easyxdm_log", logWin;
773
  try {
774
  logWin = window.open("", windowname, "width=800,height=200,status=0,navigation=0,scrollbars=1");
775
  }
776
  catch (e) {
777
  }
778
  if (logWin) {
779
  var doc = logWin.document;
780
  el = doc.getElementById("log");
781
  if (!el) {
782
  doc.write("<html><head><title>easyXDM log " + domain + "</title></head>");
783
  doc.write("<body><div id=\"log\"></div></body></html>");
784
  doc.close();
785
  el = doc.getElementById("log");
786
  }
787
  this.trace = function(msg){
788
  try {
789
  el.appendChild(doc.createElement("div")).appendChild(doc.createTextNode(location.host + (namespace ? ":" + namespace : "") + " - " + this.getTime() + ":" + msg));
790
  el.scrollTop = el.scrollHeight;
791
  }
792
  catch (e) {
793
  //In case we are unloading
794
  }
795
  };
796
  this.trace("---- new logger at " + location.href);
797
  }
798
 
799
  if (!el) {
800
  // We are unable to use any logging
801
  this.trace = emptyFn;
802
  }
803
  }
804
  this.trace(msg);
805
  }
806
  },
807
  /**
808
  * Creates a method usable for tracing.
809
  * @param {String} name The name the messages should be marked with
810
  * @return {Function} A function that accepts a single string as argument.
811
  */
812
  getTracer: function(name){
813
  return function(msg){
814
  debug.trace(name + ": " + msg);
815
  };
816
  }
817
  * @class easyXDM.DomHelper
818
  * Contains methods for dealing with the DOM
819
  * @singleton
820
  */
821
  /**
822
  * Provides a consistent interface for adding eventhandlers
823
  * @param {Object} target The target to add the event to
824
  * @param {String} type The name of the event
825
  * @param {Function} listener The listener
826
  */
827
  on: on,
828
  /**
829
  * Provides a consistent interface for removing eventhandlers
830
  * @param {Object} target The target to remove the event from
831
  * @param {String} type The name of the event
832
  * @param {Function} listener The listener
833
  */
834
  un: un,
835
  /**
836
  * Checks for the presence of the JSON object.
837
  * If it is not present it will use the supplied path to load the JSON2 library.
838
  * This should be called in the documents head right after the easyXDM script tag.
839
  * http://json.org/json2.js
840
  * @param {String} path A valid path to json2.js
841
  */
842
  requiresJSON: function(path){
843
  if (!isHostObject(window, "JSON")) {
844
  debug.log("loading external JSON");
845
  // we need to encode the < in order to avoid an illegal token error
846
  // when the script is inlined in a document.
847
  document.write('<' + 'script type="text/javascript" src="' + path + '"><' + '/script>');
848
  }
849
  else {
850
  debug.log("native JSON found");
851
  }
852
  }
853
  // The map containing the stored functions
854
  var _map = {};
855
 
856
  /**
857
  * @class easyXDM.Fn
858
  * This contains methods related to function handling, such as storing callbacks.
859
  * @singleton
860
  * @namespace easyXDM
861
  */
862
  easyXDM.Fn = {
863
  /**
864
  * Stores a function using the given name for reference
865
  * @param {String} name The name that the function should be referred by
866
  * @param {Function} fn The function to store
867
  * @namespace easyXDM.fn
868
  */
869
  set: function(name, fn){
870
  this._trace("storing function " + name);
871
  _map[name] = fn;
872
  },
873
  /**
874
  * Retrieves the function referred to by the given name
875
  * @param {String} name The name of the function to retrieve
876
  * @param {Boolean} del If the function should be deleted after retrieval
877
  * @return {Function} The stored function
878
  * @namespace easyXDM.fn
879
  */
880
  get: function(name, del){
881
  this._trace("retrieving function " + name);
882
  var fn = _map[name];
883
  if (!fn) {
884
  this._trace(name + " not found");
885
  }
886
 
887
  if (del) {
888
  delete _map[name];
889
  }
890
  return fn;
891
  }
892
  };
893
 
894
  easyXDM.Fn._trace = debug.getTracer("easyXDM.Fn");
895
  * @class easyXDM.Socket
896
  * This class creates a transport channel between two domains that is usable for sending and receiving string-based messages.<br/>
897
  * The channel is reliable, supports queueing, and ensures that the message originates from the expected domain.<br/>
898
  * Internally different stacks will be used depending on the browsers features and the available parameters.
899
  * <h2>How to set up</h2>
900
  * Setting up the provider:
901
  * <pre><code>
902
  * var socket = new easyXDM.Socket({
903
  * &nbsp; local: "name.html",
904
  * &nbsp; onReady: function(){
905
  * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
906
  * &nbsp; &nbsp; socket.postMessage("foo-message");
907
  * &nbsp; },
908
  * &nbsp; onMessage: function(message, origin) {
909
  * &nbsp;&nbsp; alert("received " + message + " from " + origin);
910
  * &nbsp; }
911
  * });
912
  * </code></pre>
913
  * Setting up the consumer:
914
  * <pre><code>
915
  * var socket = new easyXDM.Socket({
916
  * &nbsp; remote: "http:&#47;&#47;remotedomain/page.html",
917
  * &nbsp; remoteHelper: "http:&#47;&#47;remotedomain/name.html",
918
  * &nbsp; onReady: function(){
919
  * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
920
  * &nbsp; &nbsp; socket.postMessage("foo-message");
921
  * &nbsp; },
922
  * &nbsp; onMessage: function(message, origin) {
923
  * &nbsp;&nbsp; alert("received " + message + " from " + origin);
924
  * &nbsp; }
925
  * });
926
  * </code></pre>
927
  * If you are unable to upload the <code>name.html</code> file to the consumers domain then remove the <code>remoteHelper</code> property
928
  * and easyXDM will fall back to using the HashTransport instead of the NameTransport when not able to use any of the primary transports.
929
  * @namespace easyXDM
930
  * @constructor
931
  * @cfg {String/Window} local The url to the local name.html document, a local static file, or a reference to the local window.
932
  * @cfg {Boolean} lazy (Consumer only) Set this to true if you want easyXDM to defer creating the transport until really needed.
933
  * @cfg {String} remote (Consumer only) The url to the providers document.
934
  * @cfg {String} remoteHelper (Consumer only) The url to the remote name.html file. This is to support NameTransport as a fallback. Optional.
935
  * @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window. Optional, defaults to 2000.
936
  * @cfg {Number} interval The interval used when polling for messages. Optional, defaults to 300.
937
  * @cfg {String} channel (Consumer only) The name of the channel to use. Can be used to set consistent iframe names. Must be unique. Optional.
938
  * @cfg {Function} onMessage The method that should handle incoming messages.<br/> This method should accept two arguments, the message as a string, and the origin as a string. Optional.
939
  * @cfg {Function} onReady A method that should be called when the transport is ready. Optional.
940
  * @cfg {DOMElement|String} container (Consumer only) The element, or the id of the element that the primary iframe should be inserted into. If not set then the iframe will be positioned off-screen. Optional.
941
  * @cfg {Array/String} acl (Provider only) Here you can specify which '[protocol]://[domain]' patterns that should be allowed to act as the consumer towards this provider.<br/>
942
  * This can contain the wildcards ? and *. Examples are 'http://example.com', '*.foo.com' and '*dom?.com'. If you want to use reqular expressions then you pattern needs to start with ^ and end with $.
943
  * If none of the patterns match an Error will be thrown.
944
  * @cfg {Object} props (Consumer only) Additional properties that should be applied to the iframe. This can also contain nested objects e.g: <code>{style:{width:"100px", height:"100px"}}</code>.
945
  * Properties such as 'name' and 'src' will be overrided. Optional.
946
  */
947
  var trace = debug.getTracer("easyXDM.Socket");
948
  trace("constructor");
949
 
950
  // create the stack
951
  var stack = chainStack(prepareTransportStack(config).concat([{
952
  incoming: function(message, origin){
953
  config.onMessage(message, origin);
954
  },
955
  callback: function(success){
956
  if (config.onReady) {
957
  config.onReady(success);
958
  }
959
  }
960
  }])), recipient = getLocation(config.remote);
961
 
962
  // set the origin
963
  this.origin = getLocation(config.remote);
964
  /**
965
  * Initiates the destruction of the stack.
966
  */
967
  this.destroy = function(){
968
  stack.destroy();
969
  };
970
 
971
  /**
972
  * Posts a message to the remote end of the channel
973
  * @param {String} message The message to send
974
  */
975
  this.postMessage = function(message){
976
  stack.outgoing(message, recipient);
977
  };
978
 
979
  stack.init();
980
  * @class easyXDM.Rpc
981
  * Creates a proxy object that can be used to call methods implemented on the remote end of the channel, and also to provide the implementation
982
  * of methods to be called from the remote end.<br/>
983
  * The instantiated object will have methods matching those specified in <code>config.remote</code>.<br/>
984
  * This requires the JSON object present in the document, either natively, using json.org's json2 or as a wrapper around library spesific methods.
985
  * <h2>How to set up</h2>
986
  * <pre><code>
987
  * var rpc = new easyXDM.Rpc({
988
  * &nbsp; &#47;&#47; this configuration is equal to that used by the Socket.
989
  * &nbsp; remote: "http:&#47;&#47;remotedomain/...",
990
  * &nbsp; onReady: function(){
991
  * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the proxy
992
  * &nbsp; &nbsp; rpc.foo(...
993
  * &nbsp; }
994
  * },{
995
  * &nbsp; local: {..},
996
  * &nbsp; remote: {..}
997
  * });
998
  * </code></pre>
999
  *
1000
  * <h2>Exposing functions (procedures)</h2>
1001
  * <pre><code>
1002
  * var rpc = new easyXDM.Rpc({
1003
  * &nbsp; ...
1004
  * },{
1005
  * &nbsp; local: {
1006
  * &nbsp; &nbsp; nameOfMethod: {
1007
  * &nbsp; &nbsp; &nbsp; method: function(arg1, arg2, success, error){
1008
  * &nbsp; &nbsp; &nbsp; &nbsp; ...
1009
  * &nbsp; &nbsp; &nbsp; }
1010
  * &nbsp; &nbsp; },
1011
  * &nbsp; &nbsp; &#47;&#47; with shorthand notation
1012
  * &nbsp; &nbsp; nameOfAnotherMethod: function(arg1, arg2, success, error){
1013
  * &nbsp; &nbsp; }
1014
  * &nbsp; },
1015
  * &nbsp; remote: {...}
1016
  * });
1017
  * </code></pre>
1018
  * The function referenced by [method] will receive the passed arguments followed by the callback functions <code>success</code> and <code>error</code>.<br/>
1019
  * To send a successfull result back you can use
1020
  * <pre><code>
1021
  * return foo;
1022
  * </pre></code>
1023
  * or
1024
  * <pre><code>
1025
  * success(foo);
1026
  * </pre></code>
1027
  * To return an error you can use
1028
  * <pre><code>
1029
  * throw new Error("foo error");
1030
  * </code></pre>
1031
  * or
1032
  * <pre><code>
1033
  * error("foo error");
1034
  * </code></pre>
1035
  *
1036
  * <h2>Defining remotely exposed methods (procedures/notifications)</h2>
1037
  * The definition of the remote end is quite similar:
1038
  * <pre><code>
1039
  * var rpc = new easyXDM.Rpc({
1040
  * &nbsp; ...
1041
  * },{
1042
  * &nbsp; local: {...},
1043
  * &nbsp; remote: {
1044
  * &nbsp; &nbsp; nameOfMethod: {}
1045
  * &nbsp; }
1046
  * });
1047
  * </code></pre>
1048
  * To call a remote method use
1049
  * <pre><code>
1050
  * rpc.nameOfMethod("arg1", "arg2", function(value) {
1051
  * &nbsp; alert("success: " + value);
1052
  * }, function(message) {
1053
  * &nbsp; alert("error: " + message + );
1054
  * });
1055
  * </code></pre>
1056
  * Both the <code>success</code> and <code>errror</code> callbacks are optional.<br/>
1057
  * When called with no callback a JSON-RPC 2.0 notification will be executed.
1058
  * Be aware that you will not be notified of any errors with this method.
1059
  * <br/>
1060
  * <h2>Specifying a custom serializer</h2>
1061
  * If you do not want to use the JSON2 library for non-native JSON support, but instead capabilities provided by some other library
1062
  * then you can specify a custom serializer using <code>serializer: foo</code>
1063
  * <pre><code>
1064
  * var rpc = new easyXDM.Rpc({
1065
  * &nbsp; ...
1066
  * },{
1067
  * &nbsp; local: {...},
1068
  * &nbsp; remote: {...},
1069
  * &nbsp; serializer : {
1070
  * &nbsp; &nbsp; parse: function(string){ ... },
1071
  * &nbsp; &nbsp; stringify: function(object) {...}
1072
  * &nbsp; }
1073
  * });
1074
  * </code></pre>
1075
  * If <code>serializer</code> is set then the class will not attempt to use the native implementation.
1076
  * @namespace easyXDM
1077
  * @constructor
1078
  * @param {Object} config The underlying transports configuration. See easyXDM.Socket for available parameters.
1079
  * @param {Object} jsonRpcConfig The description of the interface to implement.
1080
  */
1081
  var trace = debug.getTracer("easyXDM.Rpc");
1082
  trace("constructor");
1083
 
1084
  // expand shorthand notation
1085
  if (jsonRpcConfig.local) {
1086
  for (var method in jsonRpcConfig.local) {
1087
  if (jsonRpcConfig.local.hasOwnProperty(method)) {
1088
  var member = jsonRpcConfig.local[method];
1089
  if (typeof member === "function") {
1090
  jsonRpcConfig.local[method] = {
1091
  method: member
1092
  };
1093
  }
1094
  }
1095
  }
1096
  }
1097
  // create the stack
1098
  var stack = chainStack(prepareTransportStack(config).concat([new easyXDM.stack.RpcBehavior(this, jsonRpcConfig), {
1099
  callback: function(success){
1100
  if (config.onReady) {
1101
  config.onReady(success);
1102
  }
1103
  }
1104
  }]));
1105
  // set the origin
1106
  this.origin = getLocation(config.remote);
1107
 
1108
  /**
1109
  * Initiates the destruction of the stack.
1110
  */
1111
  this.destroy = function(){
1112
  stack.destroy();
1113
  };
1114
 
1115
  stack.init();
1116
  * @class easyXDM.stack.SameOriginTransport
1117
  * SameOriginTransport is a transport class that can be used when both domains have the same origin.<br/>
1118
  * This can be useful for testing and for when the main application supports both internal and external sources.
1119
  * @namespace easyXDM.stack
1120
  * @constructor
1121
  * @param {Object} config The transports configuration.
1122
  * @cfg {String} remote The remote document to communicate with.
1123
  */
1124
  var trace = debug.getTracer("easyXDM.stack.SameOriginTransport");
1125
  trace("constructor");
1126
  var pub, frame, send, targetOrigin;
1127
 
1128
  return (pub = {
1129
  outgoing: function(message, domain, fn){
1130
  send(message);
1131
  if (fn) {
1132
  fn();
1133
  }
1134
  },
1135
  destroy: function(){
1136
  trace("destroy");
1137
  if (frame) {
1138
  frame.parentNode.removeChild(frame);
1139
  frame = null;
1140
  }
1141
  },
1142
  onDOMReady: function(){
1143
  trace("init");
1144
  targetOrigin = getLocation(config.remote);
1145
 
1146
  if (config.isHost) {
1147
  // set up the iframe
1148
  apply(config.props, {
1149
  src: appendQueryParameters(config.remote, {
1150
  xdm_e: location.protocol + "//" + location.host + location.pathname,
1151
  xdm_c: config.channel,
1152
  xdm_p: 4 // 4 = SameOriginTransport
1153
  }),
1154
  name: IFRAME_PREFIX + config.channel + "_provider"
1155
  });
1156
  frame = createFrame(config);
1157
  easyXDM.Fn.set(config.channel, function(sendFn){
1158
  send = sendFn;
1159
  setTimeout(function(){
1160
  pub.up.callback(true);
1161
  }, 0);
1162
  return function(msg){
1163
  pub.up.incoming(msg, targetOrigin);
1164
  };
1165
  });
1166
  }
1167
  else {
1168
  send = getParentObject().Fn.get(config.channel, true)(function(msg){
1169
  pub.up.incoming(msg, targetOrigin);
1170
  });
1171
  setTimeout(function(){
1172
  pub.up.callback(true);
1173
  }, 0);
1174
  }
1175
  },
1176
  init: function(){
1177
  whenReady(pub.onDOMReady, pub);
1178
  }
1179
  });
1180
  * @class easyXDM.stack.FlashTransport
1181
  * FlashTransport is a transport class that uses an SWF with LocalConnection to pass messages back and forth.
1182
  * @namespace easyXDM.stack
1183
  * @constructor
1184
  * @param {Object} config The transports configuration.
1185
  * @cfg {String} remote The remote domain to communicate with.
1186
  * @cfg {String} secret the pre-shared secret used to secure the communication.
1187
  * @cfg {String} swf The path to the swf file
1188
  * @cfg {Boolean} swfNoThrottle Set this to true if you want to take steps to avoid beeing throttled when hidden.
1189
  * @cfg {String || DOMElement} swfContainer Set this if you want to control where the swf is placed
1190
  */
1191
  var trace = debug.getTracer("easyXDM.stack.FlashTransport");
1192
  trace("constructor");
1193
  if (!config.swf) {
1194
  throw new Error("Path to easyxdm.swf is missing");
1195
  }
1196
  var pub, // the public interface
1197
  frame, send, targetOrigin, swf, swfContainer;
1198
 
1199
  function onMessage(message, origin){
1200
  setTimeout(function(){
1201
  trace("received message");
1202
  pub.up.incoming(message, targetOrigin);
1203
  }, 0);
1204
  }
1205
 
1206
  /**
1207
  * This method adds the SWF to the DOM and prepares the initialization of the channel
1208
  */
1209
  function addSwf(domain){
1210
  trace("creating factory with SWF from " + domain);
1211
  // the differentiating query argument is needed in Flash9 to avoid a caching issue where LocalConnection would throw an error.
1212
  var url = config.swf + "?host=" + config.isHost;
1213
  var id = "easyXDM_swf_" + Math.floor(Math.random() * 10000);
1214
 
1215
  // prepare the init function that will fire once the swf is ready
1216
  easyXDM.Fn.set("flash_loaded" + domain.replace(/[\-.]/g, "_"), function(){
1217
  easyXDM.stack.FlashTransport[domain].swf = swf = swfContainer.firstChild;
1218
  var queue = easyXDM.stack.FlashTransport[domain].queue;
1219
  for (var i = 0; i < queue.length; i++) {
1220
  queue[i]();
1221
  }
1222
  queue.length = 0;
1223
  });
1224
 
1225
  if (config.swfContainer) {
1226
  swfContainer = (typeof config.swfContainer == "string") ? document.getElementById(config.swfContainer) : config.swfContainer;
1227
  }
1228
  else {
1229
  // create the container that will hold the swf
1230
  swfContainer = document.createElement('div');
1231
 
1232
  // http://bugs.adobe.com/jira/browse/FP-4796
1233
  // http://tech.groups.yahoo.com/group/flexcoders/message/162365
1234
  // https://groups.google.com/forum/#!topic/easyxdm/mJZJhWagoLc
1235
  apply(swfContainer.style, HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle ? {
1236
  height: "20px",
1237
  width: "20px",
1238
  position: "fixed",
1239
  right: 0,
1240
  top: 0
1241
  } : {
1242
  height: "1px",
1243
  width: "1px",
1244
  position: "absolute",
1245
  overflow: "hidden",
1246
  right: 0,
1247
  top: 0
1248
  });
1249
  document.body.appendChild(swfContainer);
1250
  }
1251
 
1252
  // create the object/embed
1253
  var flashVars = "callback=flash_loaded" + domain.replace(/[\-.]/g, "_") + "&proto=" + global.location.protocol + "&domain=" + getDomainName(global.location.href) + "&port=" + getPort(global.location.href) + "&ns=" + namespace;
1254
  flashVars += "&log=true";
1255
  swfContainer.innerHTML = "<object height='20' width='20' type='application/x-shockwave-flash' id='" + id + "' data='" + url + "'>" +
1256
  "<param name='allowScriptAccess' value='always'></param>" +
1257
  "<param name='wmode' value='transparent'>" +
1258
  "<param name='movie' value='" +
1259
  url +
1260
  "'></param>" +
1261
  "<param name='flashvars' value='" +
1262
  flashVars +
1263
  "'></param>" +
1264
  "<embed type='application/x-shockwave-flash' FlashVars='" +
1265
  flashVars +
1266
  "' allowScriptAccess='always' wmode='transparent' src='" +
1267
  url +
1268
  "' height='1' width='1'></embed>" +
1269
  "</object>";
1270
  }
1271
 
1272
  return (pub = {
1273
  outgoing: function(message, domain, fn){
1274
  swf.postMessage(config.channel, message.toString());
1275
  if (fn) {
1276
  fn();
1277
  }
1278
  },
1279
  destroy: function(){
1280
  trace("destroy");
1281
  try {
1282
  swf.destroyChannel(config.channel);
1283
  }
1284
  catch (e) {
1285
  }
1286
  swf = null;
1287
  if (frame) {
1288
  frame.parentNode.removeChild(frame);
1289
  frame = null;
1290
  }
1291
  },
1292
  onDOMReady: function(){
1293
  trace("init");
1294
 
1295
  targetOrigin = config.remote;
1296
 
1297
  // Prepare the code that will be run after the swf has been intialized
1298
  easyXDM.Fn.set("flash_" + config.channel + "_init", function(){
1299
  setTimeout(function(){
1300
  trace("firing onReady");
1301
  pub.up.callback(true);
1302
  });
1303
  });
1304
 
1305
  // set up the omMessage handler
1306
  easyXDM.Fn.set("flash_" + config.channel + "_onMessage", onMessage);
1307
 
1308
  config.swf = resolveUrl(config.swf); // reports have been made of requests gone rogue when using relative paths
1309
  var swfdomain = getDomainName(config.swf);
1310
  var fn = function(){
1311
  // set init to true in case the fn was called was invoked from a separate instance
1312
  easyXDM.stack.FlashTransport[swfdomain].init = true;
1313
  swf = easyXDM.stack.FlashTransport[swfdomain].swf;
1314
  // create the channel
1315
  swf.createChannel(config.channel, config.secret, getLocation(config.remote), config.isHost);
1316
 
1317
  if (config.isHost) {
1318
  // if Flash is going to be throttled and we want to avoid this
1319
  if (HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle) {
1320
  apply(config.props, {
1321
  position: "fixed",
1322
  right: 0,
1323
  top: 0,
1324
  height: "20px",
1325
  width: "20px"
1326
  });
1327
  }
1328
  // set up the iframe
1329
  apply(config.props, {
1330
  src: appendQueryParameters(config.remote, {
1331
  xdm_e: getLocation(location.href),
1332
  xdm_c: config.channel,
1333
  xdm_p: 6, // 6 = FlashTransport
1334
  xdm_s: config.secret
1335
  }),
1336
  name: IFRAME_PREFIX + config.channel + "_provider"
1337
  });
1338
  frame = createFrame(config);
1339
  }
1340
  };
1341
 
1342
  if (easyXDM.stack.FlashTransport[swfdomain] && easyXDM.stack.FlashTransport[swfdomain].init) {
1343
  // if the swf is in place and we are the consumer
1344
  fn();
1345
  }
1346
  else {
1347
  // if the swf does not yet exist
1348
  if (!easyXDM.stack.FlashTransport[swfdomain]) {
1349
  // add the queue to hold the init fn's
1350
  easyXDM.stack.FlashTransport[swfdomain] = {
1351
  queue: [fn]
1352
  };
1353
  addSwf(swfdomain);
1354
  }
1355
  else {
1356
  easyXDM.stack.FlashTransport[swfdomain].queue.push(fn);
1357
  }
1358
  }
1359
  },
1360
  init: function(){
1361
  whenReady(pub.onDOMReady, pub);
1362
  }
1363
  });
1364
  * @class easyXDM.stack.PostMessageTransport
1365
  * PostMessageTransport is a transport class that uses HTML5 postMessage for communication.<br/>
1366
  * <a href="http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx">http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx</a><br/>
1367
  * <a href="https://developer.mozilla.org/en/DOM/window.postMessage">https://developer.mozilla.org/en/DOM/window.postMessage</a>
1368
  * @namespace easyXDM.stack
1369
  * @constructor
1370
  * @param {Object} config The transports configuration.
1371
  * @cfg {String} remote The remote domain to communicate with.
1372
  */
1373
  var trace = debug.getTracer("easyXDM.stack.PostMessageTransport");
1374
  trace("constructor");
1375
  var pub, // the public interface
1376
  frame, // the remote frame, if any
1377
  callerWindow, // the window that we will call with
1378
  targetOrigin; // the domain to communicate with
1379
  /**
1380
  * Resolves the origin from the event object
1381
  * @private
1382
  * @param {Object} event The messageevent
1383
  * @return {String} The scheme, host and port of the origin
1384
  */
1385
  function _getOrigin(event){
1386
  if (event.origin) {
1387
  // This is the HTML5 property
1388
  return getLocation(event.origin);
1389
  }
1390
  if (event.uri) {
1391
  // From earlier implementations
1392
  return getLocation(event.uri);
1393
  }
1394
  if (event.domain) {
1395
  // This is the last option and will fail if the
1396
  // origin is not using the same schema as we are
1397
  return location.protocol + "//" + event.domain;
1398
  }
1399
  throw "Unable to retrieve the origin of the event";
1400
  }
1401
 
1402
  /**
1403
  * This is the main implementation for the onMessage event.<br/>
1404
  * It checks the validity of the origin and passes the message on if appropriate.
1405
  * @private
1406
  * @param {Object} event The messageevent
1407
  */
1408
  function _window_onMessage(event){
1409
  var origin = _getOrigin(event);
1410
  trace("received message '" + event.data + "' from " + origin);
1411
  if (origin == targetOrigin && event.data.substring(0, config.channel.length + 1) == config.channel + " ") {
1412
  pub.up.incoming(event.data.substring(config.channel.length + 1), origin);
1413
  }
1414
  }
1415
 
1416
  return (pub = {
1417
  outgoing: function(message, domain, fn){
1418
  callerWindow.postMessage(config.channel + " " + message, domain || targetOrigin);
1419
  if (fn) {
1420
  fn();
1421
  }
1422
  },
1423
  destroy: function(){
1424
  trace("destroy");
1425
  un(window, "message", _window_onMessage);
1426
  if (frame) {
1427
  callerWindow = null;
1428
  frame.parentNode.removeChild(frame);
1429
  frame = null;
1430
  }
1431
  },
1432
  onDOMReady: function(){
1433
  trace("init");
1434
  targetOrigin = getLocation(config.remote);
1435
  if (config.isHost) {
1436
  // add the event handler for listening
1437
  var waitForReady = function(event){
1438
  if (event.data == config.channel + "-ready") {
1439
  trace("firing onReady");
1440
  // replace the eventlistener
1441
  callerWindow = ("postMessage" in frame.contentWindow) ? frame.contentWindow : frame.contentWindow.document;
1442
  un(window, "message", waitForReady);
1443
  on(window, "message", _window_onMessage);
1444
  setTimeout(function(){
1445
  pub.up.callback(true);
1446
  }, 0);
1447
  }
1448
  };
1449
  on(window, "message", waitForReady);
1450
 
1451
  // set up the iframe
1452
  apply(config.props, {
1453
  src: appendQueryParameters(config.remote, {
1454
  xdm_e: getLocation(location.href),
1455
  xdm_c: config.channel,
1456
  xdm_p: 1 // 1 = PostMessage
1457
  }),
1458
  name: IFRAME_PREFIX + config.channel + "_provider"
1459
  });
1460
  frame = createFrame(config);
1461
  }
1462
  else {
1463
  // add the event handler for listening
1464
  on(window, "message", _window_onMessage);
1465
  callerWindow = ("postMessage" in window.parent) ? window.parent : window.parent.document;
1466
  callerWindow.postMessage(config.channel + "-ready", targetOrigin);
1467
 
1468
  setTimeout(function(){
1469
  pub.up.callback(true);
1470
  }, 0);
1471
  }
1472
  },
1473
  init: function(){
1474
  whenReady(pub.onDOMReady, pub);
1475
  }
1476
  });
1477
  * @class easyXDM.stack.FrameElementTransport
1478
  * FrameElementTransport is a transport class that can be used with Gecko-browser as these allow passing variables using the frameElement property.<br/>
1479
  * Security is maintained as Gecho uses Lexical Authorization to determine under which scope a function is running.
1480
  * @namespace easyXDM.stack
1481
  * @constructor
1482
  * @param {Object} config The transports configuration.
1483
  * @cfg {String} remote The remote document to communicate with.
1484
  */
1485
  var trace = debug.getTracer("easyXDM.stack.FrameElementTransport");
1486
  trace("constructor");
1487
  var pub, frame, send, targetOrigin;
1488
 
1489
  return (pub = {
1490
  outgoing: function(message, domain, fn){
1491
  send.call(this, message);
1492
  if (fn) {
1493
  fn();
1494
  }
1495
  },
1496
  destroy: function(){
1497
  trace("destroy");
1498
  if (frame) {
1499
  frame.parentNode.removeChild(frame);
1500
  frame = null;
1501
  }
1502
  },
1503
  onDOMReady: function(){
1504
  trace("init");
1505
  targetOrigin = getLocation(config.remote);
1506
 
1507
  if (config.isHost) {
1508
  // set up the iframe
1509
  apply(config.props, {
1510
  src: appendQueryParameters(config.remote, {
1511
  xdm_e: getLocation(location.href),
1512
  xdm_c: config.channel,
1513
  xdm_p: 5 // 5 = FrameElementTransport
1514
  }),
1515
  name: IFRAME_PREFIX + config.channel + "_provider"
1516
  });
1517
  frame = createFrame(config);
1518
  frame.fn = function(sendFn){
1519
  delete frame.fn;
1520
  send = sendFn;
1521
  setTimeout(function(){
1522
  pub.up.callback(true);
1523
  }, 0);
1524
  // remove the function so that it cannot be used to overwrite the send function later on
1525
  return function(msg){
1526
  pub.up.incoming(msg, targetOrigin);
1527
  };
1528
  };
1529
  }
1530
  else {
1531
  // This is to mitigate origin-spoofing
1532
  if (document.referrer && getLocation(document.referrer) != query.xdm_e) {
1533
  window.top.location = query.xdm_e;
1534
  }
1535
  send = window.frameElement.fn(function(msg){
1536
  pub.up.incoming(msg, targetOrigin);
1537
  });
1538
  pub.up.callback(true);
1539
  }
1540
  },
1541
  init: function(){
1542
  whenReady(pub.onDOMReady, pub);
1543
  }
1544
  });
1545
  * @class easyXDM.stack.NameTransport
1546
  * NameTransport uses the window.name property to relay data.
1547
  * The <code>local</code> parameter needs to be set on both the consumer and provider,<br/>
1548
  * and the <code>remoteHelper</code> parameter needs to be set on the consumer.
1549
  * @constructor
1550
  * @param {Object} config The transports configuration.
1551
  * @cfg {String} remoteHelper The url to the remote instance of hash.html - this is only needed for the host.
1552
  * @namespace easyXDM.stack
1553
  */
1554
  var trace = debug.getTracer("easyXDM.stack.NameTransport");
1555
  trace("constructor");
1556
  if (config.isHost && undef(config.remoteHelper)) {
1557
  trace("missing remoteHelper");
1558
  throw new Error("missing remoteHelper");
1559
  }
1560
 
1561
  var pub; // the public interface
1562
  var isHost, callerWindow, remoteWindow, readyCount, callback, remoteOrigin, remoteUrl;
1563
 
1564
  function _sendMessage(message){
1565
  var url = config.remoteHelper + (isHost ? "#_3" : "#_2") + config.channel;
1566
  trace("sending message " + message);
1567
  trace("navigating to '" + url + "'");
1568
  callerWindow.contentWindow.sendMessage(message, url);
1569
  }
1570
 
1571
  function _onReady(){
1572
  if (isHost) {
1573
  if (++readyCount === 2 || !isHost) {
1574
  pub.up.callback(true);
1575
  }
1576
  }
1577
  else {
1578
  _sendMessage("ready");
1579
  trace("calling onReady");
1580
  pub.up.callback(true);
1581
  }
1582
  }
1583
 
1584
  function _onMessage(message){
1585
  trace("received message " + message);
1586
  pub.up.incoming(message, remoteOrigin);
1587
  }
1588
 
1589
  function _onLoad(){
1590
  if (callback) {
1591
  setTimeout(function(){
1592
  callback(true);
1593
  }, 0);
1594
  }
1595
  }
1596
 
1597
  return (pub = {
1598
  outgoing: function(message, domain, fn){
1599
  callback = fn;
1600
  _sendMessage(message);
1601
  },
1602
  destroy: function(){
1603
  trace("destroy");
1604
  callerWindow.parentNode.removeChild(callerWindow);
1605
  callerWindow = null;
1606
  if (isHost) {
1607
  remoteWindow.parentNode.removeChild(remoteWindow);
1608
  remoteWindow = null;
1609
  }
1610
  },
1611
  onDOMReady: function(){
1612
  trace("init");
1613
  isHost = config.isHost;
1614
  readyCount = 0;
1615
  remoteOrigin = getLocation(config.remote);
1616
  config.local = resolveUrl(config.local);
1617
 
1618
  if (isHost) {
1619
  // Register the callback
1620
  easyXDM.Fn.set(config.channel, function(message){
1621
  trace("received initial message " + message);
1622
  if (isHost && message === "ready") {
1623
  // Replace the handler
1624
  easyXDM.Fn.set(config.channel, _onMessage);
1625
  _onReady();
1626
  }
1627
  });
1628
 
1629
  // Set up the frame that points to the remote instance
1630
  remoteUrl = appendQueryParameters(config.remote, {
1631
  xdm_e: config.local,
1632
  xdm_c: config.channel,
1633
  xdm_p: 2
1634
  });
1635
  apply(config.props, {
1636
  src: remoteUrl + '#' + config.channel,
1637
  name: IFRAME_PREFIX + config.channel + "_provider"
1638
  });
1639
  remoteWindow = createFrame(config);
1640
  }
1641
  else {
1642
  config.remoteHelper = config.remote;
1643
  easyXDM.Fn.set(config.channel, _onMessage);
1644
  }
1645
  // Set up the iframe that will be used for the transport
1646
 
1647
  callerWindow = createFrame({
1648
  props: {
1649
  src: config.local + "#_4" + config.channel
1650
  },
1651
  onLoad: function onLoad(){
1652
  // Remove the handler
1653
  var w = callerWindow || this;
1654
  un(w, "load", onLoad);
1655
  easyXDM.Fn.set(config.channel + "_load", _onLoad);
1656
  (function test(){
1657
  if (typeof w.contentWindow.sendMessage == "function") {
1658
  _onReady();
1659
  }
1660
  else {
1661
  setTimeout(test, 50);
1662
  }
1663
  }());
1664
  }
1665
  });
1666
  },
1667
  init: function(){
1668
  whenReady(pub.onDOMReady, pub);
1669
  }
1670
  });
1671
  * @class easyXDM.stack.HashTransport
1672
  * HashTransport is a transport class that uses the IFrame URL Technique for communication.<br/>
1673
  * <a href="http://msdn.microsoft.com/en-us/library/bb735305.aspx">http://msdn.microsoft.com/en-us/library/bb735305.aspx</a><br/>
1674
  * @namespace easyXDM.stack
1675
  * @constructor
1676
  * @param {Object} config The transports configuration.
1677
  * @cfg {String/Window} local The url to the local file used for proxying messages, or the local window.
1678
  * @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window.
1679
  * @cfg {Number} interval The interval used when polling for messages.
1680
  */
1681
  var trace = debug.getTracer("easyXDM.stack.HashTransport");
1682
  trace("constructor");
1683
  var pub;
1684
  var me = this, isHost, _timer, pollInterval, _lastMsg, _msgNr, _listenerWindow, _callerWindow;
1685
  var useParent, _remoteOrigin;
1686
 
1687
  function _sendMessage(message){
1688
  trace("sending message '" + (_msgNr + 1) + " " + message + "' to " + _remoteOrigin);
1689
  if (!_callerWindow) {
1690
  trace("no caller window");
1691
  return;
1692
  }
1693
  var url = config.remote + "#" + (_msgNr++) + "_" + message;
1694
  ((isHost || !useParent) ? _callerWindow.contentWindow : _callerWindow).location = url;
1695
  }
1696
 
1697
  function _handleHash(hash){
1698
  _lastMsg = hash;
1699
  trace("received message '" + _lastMsg + "' from " + _remoteOrigin);
1700
  pub.up.incoming(_lastMsg.substring(_lastMsg.indexOf("_") + 1), _remoteOrigin);
1701
  }
1702
 
1703
  /**
1704
  * Checks location.hash for a new message and relays this to the receiver.
1705
  * @private
1706
  */
1707
  function _pollHash(){
1708
  if (!_listenerWindow) {
1709
  return;
1710
  }
1711
  var href = _listenerWindow.location.href, hash = "", indexOf = href.indexOf("#");
1712
  if (indexOf != -1) {
1713
  hash = href.substring(indexOf);
1714
  }
1715
  if (hash && hash != _lastMsg) {
1716
  trace("poll: new message");
1717
  _handleHash(hash);
1718
  }
1719
  }
1720
 
1721
  function _attachListeners(){
1722
  trace("starting polling");
1723
  _timer = setInterval(_pollHash, pollInterval);
1724
  }
1725
 
1726
  return (pub = {
1727
  outgoing: function(message, domain){
1728
  _sendMessage(message);
1729
  },
1730
  destroy: function(){
1731
  window.clearInterval(_timer);
1732
  if (isHost || !useParent) {
1733
  _callerWindow.parentNode.removeChild(_callerWindow);
1734
  }
1735
  _callerWindow = null;
1736
  },
1737
  onDOMReady: function(){
1738
  isHost = config.isHost;
1739
  pollInterval = config.interval;
1740
  _lastMsg = "#" + config.channel;
1741
  _msgNr = 0;
1742
  useParent = config.useParent;
1743
  _remoteOrigin = getLocation(config.remote);
1744
  if (isHost) {
1745
  config.props = {
1746
  src: config.remote,
1747
  name: IFRAME_PREFIX + config.channel + "_provider"
1748
  };
1749
  if (useParent) {
1750
  config.onLoad = function(){
1751
  _listenerWindow = window;
1752
  _attachListeners();
1753
  pub.up.callback(true);
1754
  };
1755
  }
1756
  else {
1757
  var tries = 0, max = config.delay / 50;
1758
  (function getRef(){
1759
  if (++tries > max) {
1760
  trace("unable to get reference to _listenerWindow, giving up");
1761
  throw new Error("Unable to reference listenerwindow");
1762
  }
1763
  try {
1764
  _listenerWindow = _callerWindow.contentWindow.frames[IFRAME_PREFIX + config.channel + "_consumer"];
1765
  }
1766
  catch (ex) {
1767
  }
1768
  if (_listenerWindow) {
1769
  _attachListeners();
1770
  trace("got a reference to _listenerWindow");
1771
  pub.up.callback(true);
1772
  }
1773
  else {
1774
  setTimeout(getRef, 50);
1775
  }
1776
  }());
1777
  }
1778
  _callerWindow = createFrame(config);
1779
  }
1780
  else {
1781
  _listenerWindow = window;
1782
  _attachListeners();
1783
  if (useParent) {
1784
  _callerWindow = parent;
1785
  pub.up.callback(true);
1786
  }
1787
  else {
1788
  apply(config, {
1789
  props: {
1790
  src: config.remote + "#" + config.channel + new Date(),
1791
  name: IFRAME_PREFIX + config.channel + "_consumer"
1792
  },
1793
  onLoad: function(){
1794
  pub.up.callback(true);
1795
  }
1796
  });
1797
  _callerWindow = createFrame(config);
1798
  }
1799
  }
1800
  },
1801
  init: function(){
1802
  whenReady(pub.onDOMReady, pub);
1803
  }
1804
  });
1805
  * @class easyXDM.stack.ReliableBehavior
1806
  * This is a behavior that tries to make the underlying transport reliable by using acknowledgements.
1807
  * @namespace easyXDM.stack
1808
  * @constructor
1809
  * @param {Object} config The behaviors configuration.
1810
  */
1811
  var trace = debug.getTracer("easyXDM.stack.ReliableBehavior");
1812
  trace("constructor");
1813
  var pub, // the public interface
1814
  callback; // the callback to execute when we have a confirmed success/failure
1815
  var idOut = 0, idIn = 0, currentMessage = "";
1816
 
1817
  return (pub = {
1818
  incoming: function(message, origin){
1819
  trace("incoming: " + message);
1820
  var indexOf = message.indexOf("_"), ack = message.substring(0, indexOf).split(",");
1821
  message = message.substring(indexOf + 1);
1822
 
1823
  if (ack[0] == idOut) {
1824
  trace("message delivered");
1825
  currentMessage = "";
1826
  if (callback) {
1827
  callback(true);
1828
  }
1829
  }
1830
  if (message.length > 0) {
1831
  trace("sending ack, and passing on " + message);
1832
  pub.down.outgoing(ack[1] + "," + idOut + "_" + currentMessage, origin);
1833
  if (idIn != ack[1]) {
1834
  idIn = ack[1];
1835
  pub.up.incoming(message, origin);
1836
  }
1837
  }
1838
 
1839
  },
1840
  outgoing: function(message, origin, fn){
1841
  currentMessage = message;
1842
  callback = fn;
1843
  pub.down.outgoing(idIn + "," + (++idOut) + "_" + message, origin);
1844
  }
1845
  });
1846
  * @class easyXDM.stack.QueueBehavior
1847
  * This is a behavior that enables queueing of messages. <br/>
1848
  * It will buffer incoming messages and dispach these as fast as the underlying transport allows.
1849
  * This will also fragment/defragment messages so that the outgoing message is never bigger than the
1850
  * set length.
1851
  * @namespace easyXDM.stack
1852
  * @constructor
1853
  * @param {Object} config The behaviors configuration. Optional.
1854
  * @cfg {Number} maxLength The maximum length of each outgoing message. Set this to enable fragmentation.
1855
  */
1856
  var trace = debug.getTracer("easyXDM.stack.QueueBehavior");
1857
  trace("constructor");
1858
  var pub, queue = [], waiting = true, incoming = "", destroying, maxLength = 0, lazy = false, doFragment = false;
1859
 
1860
  function dispatch(){
1861
  if (config.remove && queue.length === 0) {
1862
  trace("removing myself from the stack");
1863
  removeFromStack(pub);
1864
  return;
1865
  }
1866
  if (waiting || queue.length === 0 || destroying) {
1867
  return;
1868
  }
1869
  trace("dispatching from queue");
1870
  waiting = true;
1871
  var message = queue.shift();
1872
 
1873
  pub.down.outgoing(message.data, message.origin, function(success){
1874
  waiting = false;
1875
  if (message.callback) {
1876
  setTimeout(function(){
1877
  message.callback(success);
1878
  }, 0);
1879
  }
1880
  dispatch();
1881
  });
1882
  }
1883
  return (pub = {
1884
  init: function(){
1885
  if (undef(config)) {
1886
  config = {};
1887
  }
1888
  if (config.maxLength) {
1889
  maxLength = config.maxLength;
1890
  doFragment = true;
1891
  }
1892
  if (config.lazy) {
1893
  lazy = true;
1894
  }
1895
  else {
1896
  pub.down.init();
1897
  }
1898
  },
1899
  callback: function(success){
1900
  waiting = false;
1901
  var up = pub.up; // in case dispatch calls removeFromStack
1902
  dispatch();
1903
  up.callback(success);
1904
  },
1905
  incoming: function(message, origin){
1906
  if (doFragment) {
1907
  var indexOf = message.indexOf("_"), seq = parseInt(message.substring(0, indexOf), 10);
1908
  incoming += message.substring(indexOf + 1);
1909
  if (seq === 0) {
1910
  trace("received the last fragment");
1911
  if (config.encode) {
1912
  incoming = decodeURIComponent(incoming);
1913
  }
1914
  pub.up.incoming(incoming, origin);
1915
  incoming = "";
1916
  }
1917
  else {
1918
  trace("waiting for more fragments, seq=" + message);
1919
  }
1920
  }
1921
  else {
1922
  pub.up.incoming(message, origin);
1923
  }
1924
  },
1925
  outgoing: function(message, origin, fn){
1926
  if (config.encode) {
1927
  message = encodeURIComponent(message);
1928
  }
1929
  var fragments = [], fragment;
1930
  if (doFragment) {
1931
  // fragment into chunks
1932
  while (message.length !== 0) {
1933
  fragment = message.substring(0, maxLength);
1934
  message = message.substring(fragment.length);
1935
  fragments.push(fragment);
1936
  }
1937
  // enqueue the chunks
1938
  while ((fragment = fragments.shift())) {
1939
  trace("enqueuing");
1940
  queue.push({
1941
  data: fragments.length + "_" + fragment,
1942
  origin: origin,
1943
  callback: fragments.length === 0 ? fn : null
1944
  });
1945
  }
1946
  }
1947
  else {
1948
  queue.push({
1949
  data: message,
1950
  origin: origin,
1951
  callback: fn
1952
  });
1953
  }
1954
  if (lazy) {
1955
  pub.down.init();
1956
  }
1957
  else {
1958
  dispatch();
1959
  }
1960
  },
1961
  destroy: function(){
1962
  trace("destroy");
1963
  destroying = true;
1964
  pub.down.destroy();
1965
  }
1966
  });
1967
  * @class easyXDM.stack.VerifyBehavior
1968
  * This behavior will verify that communication with the remote end is possible, and will also sign all outgoing,
1969
  * and verify all incoming messages. This removes the risk of someone hijacking the iframe to send malicious messages.
1970
  * @namespace easyXDM.stack
1971
  * @constructor
1972
  * @param {Object} config The behaviors configuration.
1973
  * @cfg {Boolean} initiate If the verification should be initiated from this end.
1974
  */
1975
  var trace = debug.getTracer("easyXDM.stack.VerifyBehavior");
1976
  trace("constructor");
1977
  if (undef(config.initiate)) {
1978
  throw new Error("settings.initiate is not set");
1979
  }
1980
  var pub, mySecret, theirSecret, verified = false;
1981
 
1982
  function startVerification(){
1983
  trace("requesting verification");
1984
  mySecret = Math.random().toString(16).substring(2);
1985
  pub.down.outgoing(mySecret);
1986
  }
1987
 
1988
  return (pub = {
1989
  incoming: function(message, origin){
1990
  var indexOf = message.indexOf("_");
1991
  if (indexOf === -1) {
1992
  if (message === mySecret) {
1993
  trace("verified, calling callback");
1994
  pub.up.callback(true);
1995
  }
1996
  else if (!theirSecret) {
1997
  trace("returning secret");
1998
  theirSecret = message;
1999
  if (!config.initiate) {
2000
  startVerification();
2001
  }
2002
  pub.down.outgoing(message);
2003
  }
2004
  }
2005
  else {
2006
  if (message.substring(0, indexOf) === theirSecret) {
2007
  pub.up.incoming(message.substring(indexOf + 1), origin);
2008
  }
2009
  }
2010
  },
2011
  outgoing: function(message, origin, fn){
2012
  pub.down.outgoing(mySecret + "_" + message, origin, fn);
2013
  },
2014
  callback: function(success){
2015
  if (config.initiate) {
2016
  startVerification();
2017
  }
2018
  }
2019
  });
2020
  * @class easyXDM.stack.RpcBehavior
2021
  * This uses JSON-RPC 2.0 to expose local methods and to invoke remote methods and have responses returned over the the string based transport stack.<br/>
2022
  * Exposed methods can return values synchronous, asyncronous, or bet set up to not return anything.
2023
  * @namespace easyXDM.stack
2024
  * @constructor
2025
  * @param {Object} proxy The object to apply the methods to.
2026
  * @param {Object} config The definition of the local and remote interface to implement.
2027
  * @cfg {Object} local The local interface to expose.
2028
  * @cfg {Object} remote The remote methods to expose through the proxy.
2029
  * @cfg {Object} serializer The serializer to use for serializing and deserializing the JSON. Should be compatible with the HTML5 JSON object. Optional, will default to JSON.
2030
  */
2031
  var trace = debug.getTracer("easyXDM.stack.RpcBehavior");
2032
  var pub, serializer = config.serializer || getJSON();
2033
  var _callbackCounter = 0, _callbacks = {};
2034
 
2035
  /**
2036
  * Serializes and sends the message
2037
  * @private
2038
  * @param {Object} data The JSON-RPC message to be sent. The jsonrpc property will be added.
2039
  */
2040
  function _send(data){
2041
  data.jsonrpc = "2.0";
2042
  pub.down.outgoing(serializer.stringify(data));
2043
  }
2044
 
2045
  /**
2046
  * Creates a method that implements the given definition
2047
  * @private
2048
  * @param {Object} The method configuration
2049
  * @param {String} method The name of the method
2050
  * @return {Function} A stub capable of proxying the requested method call
2051
  */
2052
  function _createMethod(definition, method){
2053
  var slice = Array.prototype.slice;
2054
 
2055
  trace("creating method " + method);
2056
  return function(){
2057
  trace("executing method " + method);
2058
  var l = arguments.length, callback, message = {
2059
  method: method
2060
  };
2061
 
2062
  if (l > 0 && typeof arguments[l - 1] === "function") {
2063
  //with callback, procedure
2064
  if (l > 1 && typeof arguments[l - 2] === "function") {
2065
  // two callbacks, success and error
2066
  callback = {
2067
  success: arguments[l - 2],
2068
  error: arguments[l - 1]
2069
  };
2070
  message.params = slice.call(arguments, 0, l - 2);
2071
  }
2072
  else {
2073
  // single callback, success
2074
  callback = {
2075
  success: arguments[l - 1]
2076
  };
2077
  message.params = slice.call(arguments, 0, l - 1);
2078
  }
2079
  _callbacks["" + (++_callbackCounter)] = callback;
2080
  message.id = _callbackCounter;
2081
  }
2082
  else {
2083
  // no callbacks, a notification
2084
  message.params = slice.call(arguments, 0);
2085
  }
2086
  if (definition.namedParams && message.params.length === 1) {
2087
  message.params = message.params[0];
2088
  }
2089
  // Send the method request
2090
  _send(message);
2091
  };
2092
  }
2093
 
2094
  /**
2095
  * Executes the exposed method
2096
  * @private
2097
  * @param {String} method The name of the method
2098
  * @param {Number} id The callback id to use
2099
  * @param {Function} method The exposed implementation
2100
  * @param {Array} params The parameters supplied by the remote end
2101
  */
2102
  function _executeMethod(method, id, fn, params){
2103
  if (!fn) {
2104
  trace("requested to execute non-existent procedure " + method);
2105
  if (id) {
2106
  _send({
2107
  id: id,
2108
  error: {
2109
  code: -32601,
2110
  message: "Procedure not found."
2111
  }
2112
  });
2113
  }
2114
  return;
2115
  }
2116
 
2117
  trace("requested to execute procedure " + method);
2118
  var success, error;
2119
  if (id) {
2120
  success = function(result){
2121
  success = emptyFn;
2122
  _send({
2123
  id: id,
2124
  result: result
2125
  });
2126
  };
2127
  error = function(message, data){
2128
  error = emptyFn;
2129
  var msg = {
2130
  id: id,
2131
  error: {
2132
  code: -32099,
2133
  message: message
2134
  }
2135
  };
2136
  if (data) {
2137
  msg.error.data = data;
2138
  }
2139
  _send(msg);
2140
  };
2141
  }
2142
  else {
2143
  success = error = emptyFn;
2144
  }
2145
  // Call local method
2146
  if (!isArray(params)) {
2147
  params = [params];
2148
  }
2149
  try {
2150
  var result = fn.method.apply(fn.scope, params.concat([success, error]));
2151
  if (!undef(result)) {
2152
  success(result);
2153
  }
2154
  }
2155
  catch (ex1) {
2156
  error(ex1.message);
2157
  }
2158
  }
2159
 
2160
  return (pub = {
2161
  incoming: function(message, origin){
2162
  var data = serializer.parse(message);
2163
  if (data.method) {
2164
  trace("received request to execute method " + data.method + (data.id ? (" using callback id " + data.id) : ""));
2165
  // A method call from the remote end
2166
  if (config.handle) {
2167
  config.handle(data, _send);
2168
  }
2169
  else {
2170
  _executeMethod(data.method, data.id, config.local[data.method], data.params);
2171
  }
2172
  }
2173
  else {
2174
  trace("received return value destined to callback with id " + data.id);
2175
  // A method response from the other end
2176
  var callback = _callbacks[data.id];
2177
  if (data.error) {
2178
  if (callback.error) {
2179
  callback.error(data.error);
2180
  }
2181
  else {
2182
  trace("unhandled error returned.");
2183
  }
2184
  }
2185
  else if (callback.success) {
2186
  callback.success(data.result);
2187
  }
2188
  delete _callbacks[data.id];
2189
  }
2190
  },
2191
  init: function(){
2192
  trace("init");
2193
  if (config.remote) {
2194
  trace("creating stubs");
2195
  // Implement the remote sides exposed methods
2196
  for (var method in config.remote) {
2197
  if (config.remote.hasOwnProperty(method)) {
2198
  proxy[method] = _createMethod(config.remote[method], method);
2199
  }
2200
  }
2201
  }
2202
  pub.down.init();
2203
  },
2204
  destroy: function(){
2205
  trace("destroy");
2206
  for (var method in config.remote) {
2207
  if (config.remote.hasOwnProperty(method) && proxy.hasOwnProperty(method)) {
2208
  delete proxy[method];
2209
  }
2210
  }
2211
  pub.down.destroy();
2212
  }
2213
  });
2214
+ (function (window, document, location, setTimeout, decodeURIComponent, encodeURIComponent) {
2215
  var t = typeof object[property];
2216
  return t == 'function' ||
2217
  (!!(t == 'object' && object[property])) ||
2218
  t == 'unknown';
2219
  return !!(typeof(object[property]) == 'object' && object[property]);
2220
  return Object.prototype.toString.call(o) === '[object Array]';
2221
  var name = "Shockwave Flash", mimeType = "application/x-shockwave-flash";
2222
  if (!undef(navigator.plugins) && typeof navigator.plugins[name] == "object") {
2223
  // adapted from the swfobject code
2224
  var description = navigator.plugins[name].description;
2225
  if (description && !undef(navigator.mimeTypes) && navigator.mimeTypes[mimeType] && navigator.mimeTypes[mimeType].enabledPlugin) {
2226
  flashVersion = description.match(/\d+/g);
2227
  }
2228
  }
2229
  if (!flashVersion) {
2230
  var flash;
2231
  try {
2232
  flash = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
2233
  flashVersion = Array.prototype.slice.call(flash.GetVariable("$version").match(/(\d+),(\d+),(\d+),(\d+)/), 1);
2234
  flash = null;
2235
  }
2236
  catch (notSupportedException) {
2237
  }
2238
  }
2239
  if (!flashVersion) {
2240
  return false;
2241
  }
2242
  var major = parseInt(flashVersion[0], 10), minor = parseInt(flashVersion[1], 10);
2243
  HAS_FLASH_THROTTLED_BUG = major > 9 && minor > 0;
2244
  return true;
2245
  * Cross Browser implementation for adding and removing event listeners.
2246
  */
2247
  on = function(target, type, listener){
2248
  target.addEventListener(type, listener, false);
2249
  };
2250
  un = function(target, type, listener){
2251
  target.removeEventListener(type, listener, false);
2252
  };
2253
  on = function(object, sEvent, fpNotify){
2254
  object.attachEvent("on" + sEvent, fpNotify);
2255
  };
2256
  un = function(object, sEvent, fpNotify){
2257
  object.detachEvent("on" + sEvent, fpNotify);
2258
  };
2259
  throw new Error("Browser not supported");
2260
  * Cross Browser implementation of DOMContentLoaded.
2261
  */
2262
  // If browser is WebKit-powered, check for both 'loaded' (legacy browsers) and
2263
  // 'interactive' (HTML5 specs, recent WebKit builds) states.
2264
  // https://bugs.webkit.org/show_bug.cgi?id=45119
2265
  readyState = document.readyState;
2266
  domIsReady = readyState == "complete" || (~ navigator.userAgent.indexOf('AppleWebKit/') && (readyState == "loaded" || readyState == "interactive"));
2267
  // If readyState is not supported in the browser, then in order to be able to fire whenReady functions apropriately
2268
  // when added dynamically _after_ DOM load, we have to deduce wether the DOM is ready or not.
2269
  // We only need a body to add elements to, so the existence of document.body is enough for us.
2270
  domIsReady = !!document.body;
2271
  if (domIsReady) {
2272
  return;
2273
  }
2274
  domIsReady = true;
2275
  for (var i = 0; i < domReadyQueue.length; i++) {
2276
  domReadyQueue[i]();
2277
  }
2278
  domReadyQueue.length = 0;
2279
  if (isHostMethod(window, "addEventListener")) {
2280
  on(document, "DOMContentLoaded", dom_onReady);
2281
  }
2282
  else {
2283
  on(document, "readystatechange", function(){
2284
  if (document.readyState == "complete") {
2285
  dom_onReady();
2286
  }
2287
  });
2288
  if (document.documentElement.doScroll && window === top) {
2289
  var doScrollCheck = function(){
2290
  if (domIsReady) {
2291
  return;
2292
  }
2293
  // http://javascript.nwbox.com/IEContentLoaded/
2294
  try {
2295
  document.documentElement.doScroll("left");
2296
  }
2297
  catch (e) {
2298
  setTimeout(doScrollCheck, 1);
2299
  return;
2300
  }
2301
  dom_onReady();
2302
  };
2303
  doScrollCheck();
2304
  }
2305
  }
2306
  // A fallback to window.onload, that will always work
2307
  on(window, "load", dom_onReady);
2308
  * This will add a function to the queue of functions to be run once the DOM reaches a ready state.
2309
  * If functions are added after this event then they will be executed immediately.
2310
  * @param {function} fn The function to add
2311
  * @param {Object} scope An optional scope for the function to be called with.
2312
  */
2313
  if (domIsReady) {
2314
  fn.call(scope);
2315
  return;
2316
  }
2317
  domReadyQueue.push(function(){
2318
  fn.call(scope);
2319
  });
2320
  * Returns an instance of easyXDM from the parent window with
2321
  * respect to the namespace.
2322
  *
2323
  * @return An instance of easyXDM (in the parent window)
2324
  */
2325
  var obj = parent;
2326
  if (namespace !== "") {
2327
  for (var i = 0, ii = namespace.split("."); i < ii.length; i++) {
2328
  obj = obj[ii[i]];
2329
  }
2330
  }
2331
  return obj.easyXDM;
2332
  * Removes easyXDM variable from the global scope. It also returns control
2333
  * of the easyXDM variable to whatever code used it before.
2334
  *
2335
  * @param {String} ns A string representation of an object that will hold
2336
  * an instance of easyXDM.
2337
  * @return An instance of easyXDM
2338
  */
2339
  window.easyXDM = _easyXDM;
2340
  namespace = ns;
2341
  if (namespace) {
2342
  IFRAME_PREFIX = "easyXDM_" + namespace.replace(".", "_") + "_";
2343
  }
2344
  return easyXDM;
2345
  * Methods for working with URLs
2346
  */
2347
  * Get the domain name from a url.
2348
  * @param {String} url The url to extract the domain from.
2349
  * @return The domain part of the url.
2350
  * @type {String}
2351
  */
2352
  return url.match(reURI)[3];
2353
  * Get the port for a given URL, or "" if none
2354
  * @param {String} url The url to extract the port from.
2355
  * @return The port part of the url.
2356
  * @type {String}
2357
  */
2358
  return url.match(reURI)[4] || "";
2359
  * Returns a string containing the schema, domain and if present the port
2360
  * @param {String} url The url to extract the location from
2361
  * @return {String} The location part of the url
2362
  */
2363
  var m = url.toLowerCase().match(reURI);
2364
  var proto = m[2], domain = m[3], port = m[4] || "";
2365
  if ((proto == "http:" && port == ":80") || (proto == "https:" && port == ":443")) {
2366
  port = "";
2367
  }
2368
  return proto + "//" + domain + port;
2369
  * Resolves a relative url into an absolute one.
2370
  * @param {String} url The path to resolve.
2371
  * @return {String} The resolved url.
2372
  */
2373
  // replace all // except the one in proto with /
2374
  url = url.replace(reDoubleSlash, "$1/");
2375
  // If the url is a valid url we do nothing
2376
  if (!url.match(/^(http||https):\/\//)) {
2377
  // If this is a relative path
2378
  var path = (url.substring(0, 1) === "/") ? "" : location.pathname;
2379
  if (path.substring(path.length - 1) !== "/") {
2380
  path = path.substring(0, path.lastIndexOf("/") + 1);
2381
  }
2382
  url = location.protocol + "//" + location.host + path + url;
2383
  }
2384
  // reduce all 'xyz/../' to just ''
2385
  while (reParent.test(url)) {
2386
  url = url.replace(reParent, "");
2387
  }
2388
  return url;
2389
  * Appends the parameters to the given url.<br/>
2390
  * The base url can contain existing query parameters.
2391
  * @param {String} url The base url.
2392
  * @param {Object} parameters The parameters to add.
2393
  * @return {String} A new valid url with the parameters appended.
2394
  */
2395
  var hash = "", indexOf = url.indexOf("#");
2396
  if (indexOf !== -1) {
2397
  hash = url.substring(indexOf);
2398
  url = url.substring(0, indexOf);
2399
  }
2400
  var q = [];
2401
  for (var key in parameters) {
2402
  if (parameters.hasOwnProperty(key)) {
2403
  q.push(key + "=" + encodeURIComponent(parameters[key]));
2404
  }
2405
  }
2406
  return url + (useHash ? "#" : (url.indexOf("?") == -1 ? "?" : "&")) + q.join("&") + hash;
2407
  input = input.substring(1).split("&");
2408
  var data = {}, pair, i = input.length;
2409
  while (i--) {
2410
  pair = input[i].split("=");
2411
  data[pair[0]] = decodeURIComponent(pair[1]);
2412
  }
2413
  return data;
2414
  * Helper methods
2415
  */
2416
  * Helper for checking if a variable/property is undefined
2417
  * @param {Object} v The variable to test
2418
  * @return {Boolean} True if the passed variable is undefined
2419
  */
2420
  return typeof v === "undefined";
2421
  * A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works.
2422
  * @return {JSON} A valid JSON conforming object, or null if not found.
2423
  */
2424
  var cached = {};
2425
  var obj = {
2426
  a: [1, 2, 3]
2427
  }, json = "{\"a\":[1,2,3]}";
2428
  if (typeof JSON != "undefined" && typeof JSON.stringify === "function" && JSON.stringify(obj).replace((/\s/g), "") === json) {
2429
  // this is a working JSON instance
2430
  return JSON;
2431
  }
2432
  if (Object.toJSON) {
2433
  if (Object.toJSON(obj).replace((/\s/g), "") === json) {
2434
  // this is a working stringify method
2435
  cached.stringify = Object.toJSON;
2436
  }
2437
  }
2438
  if (typeof String.prototype.evalJSON === "function") {
2439
  obj = json.evalJSON();
2440
  if (obj.a && obj.a.length === 3 && obj.a[2] === 3) {
2441
  // this is a working parse method
2442
  cached.parse = function(str){
2443
  return str.evalJSON();
2444
  };
2445
  }
2446
  }
2447
  if (cached.stringify && cached.parse) {
2448
  // Only memoize the result if we have valid instance
2449
  getJSON = function(){
2450
  return cached;
2451
  };
2452
  return cached;
2453
  }
2454
  return null;
2455
  * Applies properties from the source object to the target object.<br/>
2456
  * @param {Object} target The target of the properties.
2457
  * @param {Object} source The source of the properties.
2458
  * @param {Boolean} noOverwrite Set to True to only set non-existing properties.
2459
  */
2460
  var member;
2461
  for (var prop in source) {
2462
  if (source.hasOwnProperty(prop)) {
2463
  if (prop in destination) {
2464
  member = source[prop];
2465
  if (typeof member === "object") {
2466
  apply(destination[prop], member, noOverwrite);
2467
  }
2468
  else if (!noOverwrite) {
2469
  destination[prop] = source[prop];
2470
  }
2471
  }
2472
  else {
2473
  destination[prop] = source[prop];
2474
  }
2475
  }
2476
  }
2477
  return destination;
2478
  var form = document.body.appendChild(document.createElement("form")), input = form.appendChild(document.createElement("input"));
2479
  input.name = IFRAME_PREFIX + "TEST" + channelId; // append channelId in order to avoid caching issues
2480
  HAS_NAME_PROPERTY_BUG = input !== form.elements[input.name];
2481
  document.body.removeChild(form);
2482
  * Creates a frame and appends it to the DOM.
2483
  * @param config {object} This object can have the following properties
2484
  * <ul>
2485
  * <li> {object} prop The properties that should be set on the frame. This should include the 'src' property.</li>
2486
  * <li> {object} attr The attributes that should be set on the frame.</li>
2487
  * <li> {DOMElement} container Its parent element (Optional).</li>
2488
  * <li> {function} onLoad A method that should be called with the frames contentWindow as argument when the frame is fully loaded. (Optional)</li>
2489
  * </ul>
2490
  * @return The frames DOMElement
2491
  * @type DOMElement
2492
  */
2493
  if (undef(HAS_NAME_PROPERTY_BUG)) {
2494
  testForNamePropertyBug();
2495
  }
2496
  var frame;
2497
  // This is to work around the problems in IE6/7 with setting the name property.
2498
  // Internally this is set as 'submitName' instead when using 'iframe.name = ...'
2499
  // This is not required by easyXDM itself, but is to facilitate other use cases
2500
  if (HAS_NAME_PROPERTY_BUG) {
2501
  frame = document.createElement("<iframe name=\"" + config.props.name + "\"/>");
2502
  }
2503
  else {
2504
  frame = document.createElement("IFRAME");
2505
  frame.name = config.props.name;
2506
  }
2507
  frame.id = frame.name = config.props.name;
2508
  delete config.props.name;
2509
  if (typeof config.container == "string") {
2510
  config.container = document.getElementById(config.container);
2511
  }
2512
  if (!config.container) {
2513
  // This needs to be hidden like this, simply setting display:none and the like will cause failures in some browsers.
2514
  apply(frame.style, {
2515
  position: "absolute",
2516
  top: "-2000px",
2517
  // Avoid potential horizontal scrollbar
2518
  left: "0px"
2519
  });
2520
  config.container = document.body;
2521
  }
2522
  // HACK: IE cannot have the src attribute set when the frame is appended
2523
  // into the container, so we set it to "javascript:false" as a
2524
  // placeholder for now. If we left the src undefined, it would
2525
  // instead default to "about:blank", which causes SSL mixed-content
2526
  // warnings in IE6 when on an SSL parent page.
2527
  var src = config.props.src;
2528
  config.props.src = "javascript:false";
2529
  // transfer properties to the frame
2530
  apply(frame, config.props);
2531
  frame.border = frame.frameBorder = 0;
2532
  frame.allowTransparency = true;
2533
  config.container.appendChild(frame);
2534
  if (config.onLoad) {
2535
  on(frame, "load", config.onLoad);
2536
  }
2537
  // set the frame URL to the proper value (we previously set it to
2538
  // "javascript:false" to work around the IE issue mentioned above)
2539
  if(config.usePost) {
2540
  var form = config.container.appendChild(document.createElement('form')), input;
2541
  form.target = frame.name;
2542
  form.action = src;
2543
  form.method = 'POST';
2544
  if (typeof(config.usePost) === 'object') {
2545
  for (var i in config.usePost) {
2546
  if (config.usePost.hasOwnProperty(i)) {
2547
  if (HAS_NAME_PROPERTY_BUG) {
2548
  input = document.createElement('<input name="' + i + '"/>');
2549
  } else {
2550
  input = document.createElement("INPUT");
2551
  input.name = i;
2552
  }
2553
  input.value = config.usePost[i];
2554
  form.appendChild(input);
2555
  }
2556
  }
2557
  }
2558
  form.submit();
2559
  form.parentNode.removeChild(form);
2560
  } else {
2561
  frame.src = src;
2562
  }
2563
  config.props.src = src;
2564
  return frame;
2565
  * Check whether a domain is allowed using an Access Control List.
2566
  * The ACL can contain * and ? as wildcards, or can be regular expressions.
2567
  * If regular expressions they need to begin with ^ and end with $.
2568
  * @param {Array/String} acl The list of allowed domains
2569
  * @param {String} domain The domain to test.
2570
  * @return {Boolean} True if the domain is allowed, false if not.
2571
  */
2572
  // normalize into an array
2573
  if (typeof acl == "string") {
2574
  acl = [acl];
2575
  }
2576
  var re, i = acl.length;
2577
  while (i--) {
2578
  re = acl[i];
2579
  re = new RegExp(re.substr(0, 1) == "^" ? re : ("^" + re.replace(/(\*)/g, ".$1").replace(/\?/g, ".") + "$"));
2580
  if (re.test(domain)) {
2581
  return true;
2582
  }
2583
  }
2584
  return false;
2585
  * Functions related to stacks
2586
  */
2587
  * Prepares an array of stack-elements suitable for the current configuration
2588
  * @param {Object} config The Transports configuration. See easyXDM.Socket for more.
2589
  * @return {Array} An array of stack-elements with the TransportElement at index 0.
2590
  */
2591
  var protocol = config.protocol, stackEls;
2592
  config.isHost = config.isHost || undef(query.xdm_p);
2593
  useHash = config.hash || false;
2594
  if (!config.props) {
2595
  config.props = {};
2596
  }
2597
  if (!config.isHost) {
2598
  config.channel = query.xdm_c.replace(/["'<>\\]/g, "");
2599
  config.secret = query.xdm_s;
2600
  config.remote = query.xdm_e.replace(/["'<>\\]/g, "");
2601
  ;
2602
  protocol = query.xdm_p;
2603
  if (config.acl && !checkAcl(config.acl, config.remote)) {
2604
  throw new Error("Access denied for " + config.remote);
2605
  }
2606
  }
2607
  else {
2608
  config.remote = resolveUrl(config.remote);
2609
  config.channel = config.channel || "default" + channelId++;
2610
  config.secret = Math.random().toString(16).substring(2);
2611
  if (undef(protocol)) {
2612
  if (getLocation(location.href) == getLocation(config.remote)) {
2613
  /*
2614
  * Both documents has the same origin, lets use direct access.
2615
  */
2616
  protocol = "4";
2617
  }
2618
  else if (isHostMethod(window, "postMessage") || isHostMethod(document, "postMessage")) {
2619
  /*
2620
  * This is supported in IE8+, Firefox 3+, Opera 9+, Chrome 2+ and Safari 4+
2621
  */
2622
  protocol = "1";
2623
  }
2624
  else if (config.swf && isHostMethod(window, "ActiveXObject") && hasFlash()) {
2625
  /*
2626
  * The Flash transport superseedes the NixTransport as the NixTransport has been blocked by MS
2627
  */
2628
  protocol = "6";
2629
  }
2630
  else if (navigator.product === "Gecko" && "frameElement" in window && navigator.userAgent.indexOf('WebKit') == -1) {
2631
  /*
2632
  * This is supported in Gecko (Firefox 1+)
2633
  */
2634
  protocol = "5";
2635
  }
2636
  else if (config.remoteHelper) {
2637
  /*
2638
  * This is supported in all browsers that retains the value of window.name when
2639
  * navigating from one domain to another, and where parent.frames[foo] can be used
2640
  * to get access to a frame from the same domain
2641
  */
2642
  protocol = "2";
2643
  }
2644
  else {
2645
  /*
2646
  * This is supported in all browsers where [window].location is writable for all
2647
  * The resize event will be used if resize is supported and the iframe is not put
2648
  * into a container, else polling will be used.
2649
  */
2650
  protocol = "0";
2651
  }
2652
  }
2653
  }
2654
  config.protocol = protocol; // for conditional branching
2655
  switch (protocol) {
2656
  case "0":// 0 = HashTransport
2657
  apply(config, {
2658
  interval: 100,
2659
  delay: 2000,
2660
  useResize: true,
2661
  useParent: false,
2662
  usePolling: false
2663
  }, true);
2664
  if (config.isHost) {
2665
  if (!config.local) {
2666
  // If no local is set then we need to find an image hosted on the current domain
2667
  var domain = location.protocol + "//" + location.host, images = document.body.getElementsByTagName("img"), image;
2668
  var i = images.length;
2669
  while (i--) {
2670
  image = images[i];
2671
  if (image.src.substring(0, domain.length) === domain) {
2672
  config.local = image.src;
2673
  break;
2674
  }
2675
  }
2676
  if (!config.local) {
2677
  // If no local was set, and we are unable to find a suitable file, then we resort to using the current window
2678
  config.local = window;
2679
  }
2680
  }
2681
  var parameters = {
2682
  xdm_c: config.channel,
2683
  xdm_p: 0
2684
  };
2685
  if (config.local === window) {
2686
  // We are using the current window to listen to
2687
  config.usePolling = true;
2688
  config.useParent = true;
2689
  config.local = location.protocol + "//" + location.host + location.pathname + location.search;
2690
  parameters.xdm_e = config.local;
2691
  parameters.xdm_pa = 1; // use parent
2692
  }
2693
  else {
2694
  parameters.xdm_e = resolveUrl(config.local);
2695
  }
2696
  if (config.container) {
2697
  config.useResize = false;
2698
  parameters.xdm_po = 1; // use polling
2699
  }
2700
  config.remote = appendQueryParameters(config.remote, parameters);
2701
  }
2702
  else {
2703
  apply(config, {
2704
  channel: query.xdm_c,
2705
  remote: query.xdm_e,
2706
  useParent: !undef(query.xdm_pa),
2707
  usePolling: !undef(query.xdm_po),
2708
  useResize: config.useParent ? false : config.useResize
2709
  });
2710
  }
2711
  stackEls = [new easyXDM.stack.HashTransport(config), new easyXDM.stack.ReliableBehavior({}), new easyXDM.stack.QueueBehavior({
2712
  encode: true,
2713
  maxLength: 4000 - config.remote.length
2714
  }), new easyXDM.stack.VerifyBehavior({
2715
  initiate: config.isHost
2716
  })];
2717
  break;
2718
  case "1":
2719
  stackEls = [new easyXDM.stack.PostMessageTransport(config)];
2720
  break;
2721
  case "2":
2722
  if (config.isHost) {
2723
  config.remoteHelper = resolveUrl(config.remoteHelper);
2724
  }
2725
  stackEls = [new easyXDM.stack.NameTransport(config), new easyXDM.stack.QueueBehavior(), new easyXDM.stack.VerifyBehavior({
2726
  initiate: config.isHost
2727
  })];
2728
  break;
2729
  case "3":
2730
  stackEls = [new easyXDM.stack.NixTransport(config)];
2731
  break;
2732
  case "4":
2733
  stackEls = [new easyXDM.stack.SameOriginTransport(config)];
2734
  break;
2735
  case "5":
2736
  stackEls = [new easyXDM.stack.FrameElementTransport(config)];
2737
  break;
2738
  case "6":
2739
  if (!flashVersion) {
2740
  hasFlash();
2741
  }
2742
  stackEls = [new easyXDM.stack.FlashTransport(config)];
2743
  break;
2744
  }
2745
  // this behavior is responsible for buffering outgoing messages, and for performing lazy initialization
2746
  stackEls.push(new easyXDM.stack.QueueBehavior({
2747
  lazy: config.lazy,
2748
  remove: true
2749
  }));
2750
  return stackEls;
2751
  * Chains all the separate stack elements into a single usable stack.<br/>
2752
  * If an element is missing a necessary method then it will have a pass-through method applied.
2753
  * @param {Array} stackElements An array of stack elements to be linked.
2754
  * @return {easyXDM.stack.StackElement} The last element in the chain.
2755
  */
2756
  var stackEl, defaults = {
2757
  incoming: function(message, origin){
2758
  this.up.incoming(message, origin);
2759
  },
2760
  outgoing: function(message, recipient){
2761
  this.down.outgoing(message, recipient);
2762
  },
2763
  callback: function(success){
2764
  this.up.callback(success);
2765
  },
2766
  init: function(){
2767
  this.down.init();
2768
  },
2769
  destroy: function(){
2770
  this.down.destroy();
2771
  }
2772
  };
2773
  for (var i = 0, len = stackElements.length; i < len; i++) {
2774
  stackEl = stackElements[i];
2775
  apply(stackEl, defaults, true);
2776
  if (i !== 0) {
2777
  stackEl.down = stackElements[i - 1];
2778
  }
2779
  if (i !== len - 1) {
2780
  stackEl.up = stackElements[i + 1];
2781
  }
2782
  }
2783
  return stackEl;
2784
  * This will remove a stackelement from its stack while leaving the stack functional.
2785
  * @param {Object} element The elment to remove from the stack.
2786
  */
2787
  element.up.down = element.down;
2788
  element.down.up = element.up;
2789
  element.up = element.down = null;
2790
  * Export the main object and any other methods applicable
2791
  */
2792
  * @class easyXDM
2793
  * A javascript library providing cross-browser, cross-domain messaging/RPC.
2794
  * @version 2.4.19.0
2795
  * @singleton
2796
  */
2797
  /**
2798
  * The version of the library
2799
  * @type {string}
2800
  */
2801
  version: "2.4.19.0",
2802
  /**
2803
  * This is a map containing all the query parameters passed to the document.
2804
  * All the values has been decoded using decodeURIComponent.
2805
  * @type {object}
2806
  */
2807
  query: query,
2808
  /**
2809
  * @private
2810
  */
2811
  stack: {},
2812
  /**
2813
  * Applies properties from the source object to the target object.<br/>
2814
  * @param {object} target The target of the properties.
2815
  * @param {object} source The source of the properties.
2816
  * @param {boolean} noOverwrite Set to True to only set non-existing properties.
2817
  */
2818
  apply: apply,
2819
  /**
2820
  * A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works.
2821
  * @return {JSON} A valid JSON conforming object, or null if not found.
2822
  */
2823
  getJSONObject: getJSON,
2824
  /**
2825
  * This will add a function to the queue of functions to be run once the DOM reaches a ready state.
2826
  * If functions are added after this event then they will be executed immediately.
2827
  * @param {function} fn The function to add
2828
  * @param {object} scope An optional scope for the function to be called with.
2829
  */
2830
  whenReady: whenReady,
2831
  /**
2832
  * Removes easyXDM variable from the global scope. It also returns control
2833
  * of the easyXDM variable to whatever code used it before.
2834
  *
2835
  * @param {String} ns A string representation of an object that will hold
2836
  * an instance of easyXDM.
2837
  * @return An instance of easyXDM
2838
  */
2839
  noConflict: noConflict
2840
  * @class easyXDM.DomHelper
2841
  * Contains methods for dealing with the DOM
2842
  * @singleton
2843
  */
2844
  /**
2845
  * Provides a consistent interface for adding eventhandlers
2846
  * @param {Object} target The target to add the event to
2847
  * @param {String} type The name of the event
2848
  * @param {Function} listener The listener
2849
  */
2850
  on: on,
2851
  /**
2852
  * Provides a consistent interface for removing eventhandlers
2853
  * @param {Object} target The target to remove the event from
2854
  * @param {String} type The name of the event
2855
  * @param {Function} listener The listener
2856
  */
2857
  un: un,
2858
  /**
2859
  * Checks for the presence of the JSON object.
2860
  * If it is not present it will use the supplied path to load the JSON2 library.
2861
  * This should be called in the documents head right after the easyXDM script tag.
2862
  * http://json.org/json2.js
2863
  * @param {String} path A valid path to json2.js
2864
  */
2865
  requiresJSON: function(path){
2866
  if (!isHostObject(window, "JSON")) {
2867
  // we need to encode the < in order to avoid an illegal token error
2868
  // when the script is inlined in a document.
2869
  document.write('<' + 'script type="text/javascript" src="' + path + '"><' + '/script>');
2870
  }
2871
  }
2872
  // The map containing the stored functions
2873
  var _map = {};
2874
  /**
2875
  * @class easyXDM.Fn
2876
  * This contains methods related to function handling, such as storing callbacks.
2877
  * @singleton
2878
  * @namespace easyXDM
2879
  */
2880
  easyXDM.Fn = {
2881
  /**
2882
  * Stores a function using the given name for reference
2883
  * @param {String} name The name that the function should be referred by
2884
  * @param {Function} fn The function to store
2885
  * @namespace easyXDM.fn
2886
  */
2887
  set: function(name, fn){
2888
  _map[name] = fn;
2889
  },
2890
  /**
2891
  * Retrieves the function referred to by the given name
2892
  * @param {String} name The name of the function to retrieve
2893
  * @param {Boolean} del If the function should be deleted after retrieval
2894
  * @return {Function} The stored function
2895
  * @namespace easyXDM.fn
2896
  */
2897
  get: function(name, del){
2898
  if (!_map.hasOwnProperty(name)) {
2899
  return;
2900
  }
2901
  var fn = _map[name];
2902
  if (del) {
2903
  delete _map[name];
2904
  }
2905
  return fn;
2906
  }
2907
  };
2908
  * @class easyXDM.Socket
2909
  * This class creates a transport channel between two domains that is usable for sending and receiving string-based messages.<br/>
2910
  * The channel is reliable, supports queueing, and ensures that the message originates from the expected domain.<br/>
2911
  * Internally different stacks will be used depending on the browsers features and the available parameters.
2912
  * <h2>How to set up</h2>
2913
  * Setting up the provider:
2914
  * <pre><code>
2915
  * var socket = new easyXDM.Socket({
2916
  * &nbsp; local: "name.html",
2917
  * &nbsp; onReady: function(){
2918
  * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
2919
  * &nbsp; &nbsp; socket.postMessage("foo-message");
2920
  * &nbsp; },
2921
  * &nbsp; onMessage: function(message, origin) {
2922
  * &nbsp;&nbsp; alert("received " + message + " from " + origin);
2923
  * &nbsp; }
2924
  * });
2925
  * </code></pre>
2926
  * Setting up the consumer:
2927
  * <pre><code>
2928
  * var socket = new easyXDM.Socket({
2929
  * &nbsp; remote: "http:&#47;&#47;remotedomain/page.html",
2930
  * &nbsp; remoteHelper: "http:&#47;&#47;remotedomain/name.html",
2931
  * &nbsp; onReady: function(){
2932
  * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the socket
2933
  * &nbsp; &nbsp; socket.postMessage("foo-message");
2934
  * &nbsp; },
2935
  * &nbsp; onMessage: function(message, origin) {
2936
  * &nbsp;&nbsp; alert("received " + message + " from " + origin);
2937
  * &nbsp; }
2938
  * });
2939
  * </code></pre>
2940
  * If you are unable to upload the <code>name.html</code> file to the consumers domain then remove the <code>remoteHelper</code> property
2941
  * and easyXDM will fall back to using the HashTransport instead of the NameTransport when not able to use any of the primary transports.
2942
  * @namespace easyXDM
2943
  * @constructor
2944
  * @cfg {String/Window} local The url to the local name.html document, a local static file, or a reference to the local window.
2945
  * @cfg {Boolean} lazy (Consumer only) Set this to true if you want easyXDM to defer creating the transport until really needed.
2946
  * @cfg {String} remote (Consumer only) The url to the providers document.
2947
  * @cfg {String} remoteHelper (Consumer only) The url to the remote name.html file. This is to support NameTransport as a fallback. Optional.
2948
  * @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window. Optional, defaults to 2000.
2949
  * @cfg {Number} interval The interval used when polling for messages. Optional, defaults to 300.
2950
  * @cfg {String} channel (Consumer only) The name of the channel to use. Can be used to set consistent iframe names. Must be unique. Optional.
2951
  * @cfg {Function} onMessage The method that should handle incoming messages.<br/> This method should accept two arguments, the message as a string, and the origin as a string. Optional.
2952
  * @cfg {Function} onReady A method that should be called when the transport is ready. Optional.
2953
  * @cfg {DOMElement|String} container (Consumer only) The element, or the id of the element that the primary iframe should be inserted into. If not set then the iframe will be positioned off-screen. Optional.
2954
  * @cfg {Array/String} acl (Provider only) Here you can specify which '[protocol]://[domain]' patterns that should be allowed to act as the consumer towards this provider.<br/>
2955
  * This can contain the wildcards ? and *. Examples are 'http://example.com', '*.foo.com' and '*dom?.com'. If you want to use reqular expressions then you pattern needs to start with ^ and end with $.
2956
  * If none of the patterns match an Error will be thrown.
2957
  * @cfg {Object} props (Consumer only) Additional properties that should be applied to the iframe. This can also contain nested objects e.g: <code>{style:{width:"100px", height:"100px"}}</code>.
2958
  * Properties such as 'name' and 'src' will be overrided. Optional.
2959
  */
2960
  // create the stack
2961
  var stack = chainStack(prepareTransportStack(config).concat([{
2962
  incoming: function(message, origin){
2963
  config.onMessage(message, origin);
2964
  },
2965
  callback: function(success){
2966
  if (config.onReady) {
2967
  config.onReady(success);
2968
  }
2969
  }
2970
  }])), recipient = getLocation(config.remote);
2971
  // set the origin
2972
  this.origin = getLocation(config.remote);
2973
  /**
2974
  * Initiates the destruction of the stack.
2975
  */
2976
  this.destroy = function(){
2977
  stack.destroy();
2978
  };
2979
  /**
2980
  * Posts a message to the remote end of the channel
2981
  * @param {String} message The message to send
2982
  */
2983
  this.postMessage = function(message){
2984
  stack.outgoing(message, recipient);
2985
  };
2986
  stack.init();
2987
  * @class easyXDM.Rpc
2988
  * Creates a proxy object that can be used to call methods implemented on the remote end of the channel, and also to provide the implementation
2989
  * of methods to be called from the remote end.<br/>
2990
  * The instantiated object will have methods matching those specified in <code>config.remote</code>.<br/>
2991
  * This requires the JSON object present in the document, either natively, using json.org's json2 or as a wrapper around library spesific methods.
2992
  * <h2>How to set up</h2>
2993
  * <pre><code>
2994
  * var rpc = new easyXDM.Rpc({
2995
  * &nbsp; &#47;&#47; this configuration is equal to that used by the Socket.
2996
  * &nbsp; remote: "http:&#47;&#47;remotedomain/...",
2997
  * &nbsp; onReady: function(){
2998
  * &nbsp; &nbsp; &#47;&#47; you need to wait for the onReady callback before using the proxy
2999
  * &nbsp; &nbsp; rpc.foo(...
3000
  * &nbsp; }
3001
  * },{
3002
  * &nbsp; local: {..},
3003
  * &nbsp; remote: {..}
3004
  * });
3005
  * </code></pre>
3006
  *
3007
  * <h2>Exposing functions (procedures)</h2>
3008
  * <pre><code>
3009
  * var rpc = new easyXDM.Rpc({
3010
  * &nbsp; ...
3011
  * },{
3012
  * &nbsp; local: {
3013
  * &nbsp; &nbsp; nameOfMethod: {
3014
  * &nbsp; &nbsp; &nbsp; method: function(arg1, arg2, success, error){
3015
  * &nbsp; &nbsp; &nbsp; &nbsp; ...
3016
  * &nbsp; &nbsp; &nbsp; }
3017
  * &nbsp; &nbsp; },
3018
  * &nbsp; &nbsp; &#47;&#47; with shorthand notation
3019
  * &nbsp; &nbsp; nameOfAnotherMethod: function(arg1, arg2, success, error){
3020
  * &nbsp; &nbsp; }
3021
  * &nbsp; },
3022
  * &nbsp; remote: {...}
3023
  * });
3024
  * </code></pre>
3025
  * The function referenced by [method] will receive the passed arguments followed by the callback functions <code>success</code> and <code>error</code>.<br/>
3026
  * To send a successfull result back you can use
3027
  * <pre><code>
3028
  * return foo;
3029
  * </pre></code>
3030
  * or
3031
  * <pre><code>
3032
  * success(foo);
3033
  * </pre></code>
3034
  * To return an error you can use
3035
  * <pre><code>
3036
  * throw new Error("foo error");
3037
  * </code></pre>
3038
  * or
3039
  * <pre><code>
3040
  * error("foo error");
3041
  * </code></pre>
3042
  *
3043
  * <h2>Defining remotely exposed methods (procedures/notifications)</h2>
3044
  * The definition of the remote end is quite similar:
3045
  * <pre><code>
3046
  * var rpc = new easyXDM.Rpc({
3047
  * &nbsp; ...
3048
  * },{
3049
  * &nbsp; local: {...},
3050
  * &nbsp; remote: {
3051
  * &nbsp; &nbsp; nameOfMethod: {}
3052
  * &nbsp; }
3053
  * });
3054
  * </code></pre>
3055
  * To call a remote method use
3056
  * <pre><code>
3057
  * rpc.nameOfMethod("arg1", "arg2", function(value) {
3058
  * &nbsp; alert("success: " + value);
3059
  * }, function(message) {
3060
  * &nbsp; alert("error: " + message + );
3061
  * });
3062
  * </code></pre>
3063
  * Both the <code>success</code> and <code>errror</code> callbacks are optional.<br/>
3064
  * When called with no callback a JSON-RPC 2.0 notification will be executed.
3065
  * Be aware that you will not be notified of any errors with this method.
3066
  * <br/>
3067
  * <h2>Specifying a custom serializer</h2>
3068
  * If you do not want to use the JSON2 library for non-native JSON support, but instead capabilities provided by some other library
3069
  * then you can specify a custom serializer using <code>serializer: foo</code>
3070
  * <pre><code>
3071
  * var rpc = new easyXDM.Rpc({
3072
  * &nbsp; ...
3073
  * },{
3074
  * &nbsp; local: {...},
3075
  * &nbsp; remote: {...},
3076
  * &nbsp; serializer : {
3077
  * &nbsp; &nbsp; parse: function(string){ ... },
3078
  * &nbsp; &nbsp; stringify: function(object) {...}
3079
  * &nbsp; }
3080
  * });
3081
  * </code></pre>
3082
  * If <code>serializer</code> is set then the class will not attempt to use the native implementation.
3083
  * @namespace easyXDM
3084
  * @constructor
3085
  * @param {Object} config The underlying transports configuration. See easyXDM.Socket for available parameters.
3086
  * @param {Object} jsonRpcConfig The description of the interface to implement.
3087
  */
3088
  // expand shorthand notation
3089
  if (jsonRpcConfig.local) {
3090
  for (var method in jsonRpcConfig.local) {
3091
  if (jsonRpcConfig.local.hasOwnProperty(method)) {
3092
  var member = jsonRpcConfig.local[method];
3093
  if (typeof member === "function") {
3094
  jsonRpcConfig.local[method] = {
3095
  method: member
3096
  };
3097
  }
3098
  }
3099
  }
3100
  }
3101
  // create the stack
3102
  var stack = chainStack(prepareTransportStack(config).concat([new easyXDM.stack.RpcBehavior(this, jsonRpcConfig), {
3103
  callback: function(success){
3104
  if (config.onReady) {
3105
  config.onReady(success);
3106
  }
3107
  }
3108
  }]));
3109
  // set the origin
3110
  this.origin = getLocation(config.remote);
3111
  /**
3112
  * Initiates the destruction of the stack.
3113
  */
3114
  this.destroy = function(){
3115
  stack.destroy();
3116
  };
3117
  stack.init();
3118
  * @class easyXDM.stack.SameOriginTransport
3119
  * SameOriginTransport is a transport class that can be used when both domains have the same origin.<br/>
3120
  * This can be useful for testing and for when the main application supports both internal and external sources.
3121
  * @namespace easyXDM.stack
3122
  * @constructor
3123
  * @param {Object} config The transports configuration.
3124
  * @cfg {String} remote The remote document to communicate with.
3125
  */
3126
  var pub, frame, send, targetOrigin;
3127
  return (pub = {
3128
  outgoing: function(message, domain, fn){
3129
  send(message);
3130
  if (fn) {
3131
  fn();
3132
  }
3133
  },
3134
  destroy: function(){
3135
  if (frame) {
3136
  frame.parentNode.removeChild(frame);
3137
  frame = null;
3138
  }
3139
  },
3140
  onDOMReady: function(){
3141
  targetOrigin = getLocation(config.remote);
3142
  if (config.isHost) {
3143
  // set up the iframe
3144
  apply(config.props, {
3145
  src: appendQueryParameters(config.remote, {
3146
  xdm_e: location.protocol + "//" + location.host + location.pathname,
3147
  xdm_c: config.channel,
3148
  xdm_p: 4 // 4 = SameOriginTransport
3149
  }),
3150
  name: IFRAME_PREFIX + config.channel + "_provider"
3151
  });
3152
  frame = createFrame(config);
3153
  easyXDM.Fn.set(config.channel, function(sendFn){
3154
  send = sendFn;
3155
  setTimeout(function(){
3156
  pub.up.callback(true);
3157
  }, 0);
3158
  return function(msg){
3159
  pub.up.incoming(msg, targetOrigin);
3160
  };
3161
  });
3162
  }
3163
  else {
3164
  send = getParentObject().Fn.get(config.channel, true)(function(msg){
3165
  pub.up.incoming(msg, targetOrigin);
3166
  });
3167
  setTimeout(function(){
3168
  pub.up.callback(true);
3169
  }, 0);
3170
  }
3171
  },
3172
  init: function(){
3173
  whenReady(pub.onDOMReady, pub);
3174
  }
3175
  });
3176
  * @class easyXDM.stack.FlashTransport
3177
  * FlashTransport is a transport class that uses an SWF with LocalConnection to pass messages back and forth.
3178
  * @namespace easyXDM.stack
3179
  * @constructor
3180
  * @param {Object} config The transports configuration.
3181
  * @cfg {String} remote The remote domain to communicate with.
3182
  * @cfg {String} secret the pre-shared secret used to secure the communication.
3183
  * @cfg {String} swf The path to the swf file
3184
  * @cfg {Boolean} swfNoThrottle Set this to true if you want to take steps to avoid beeing throttled when hidden.
3185
  * @cfg {String || DOMElement} swfContainer Set this if you want to control where the swf is placed
3186
  */
3187
  var pub, // the public interface
3188
  frame, send, targetOrigin, swf, swfContainer;
3189
  function onMessage(message, origin){
3190
  setTimeout(function(){
3191
  pub.up.incoming(message, targetOrigin);
3192
  }, 0);
3193
  }
3194
  /**
3195
  * This method adds the SWF to the DOM and prepares the initialization of the channel
3196
  */
3197
  function addSwf(domain){
3198
  // the differentiating query argument is needed in Flash9 to avoid a caching issue where LocalConnection would throw an error.
3199
  var url = config.swf + "?host=" + config.isHost;
3200
  var id = "easyXDM_swf_" + Math.floor(Math.random() * 10000);
3201
  // prepare the init function that will fire once the swf is ready
3202
  easyXDM.Fn.set("flash_loaded" + domain.replace(/[\-.]/g, "_"), function(){
3203
  easyXDM.stack.FlashTransport[domain].swf = swf = swfContainer.firstChild;
3204
  var queue = easyXDM.stack.FlashTransport[domain].queue;
3205
  for (var i = 0; i < queue.length; i++) {
3206
  queue[i]();
3207
  }
3208
  queue.length = 0;
3209
  });
3210
  if (config.swfContainer) {
3211
  swfContainer = (typeof config.swfContainer == "string") ? document.getElementById(config.swfContainer) : config.swfContainer;
3212
  }
3213
  else {
3214
  // create the container that will hold the swf
3215
  swfContainer = document.createElement('div');
3216
  // http://bugs.adobe.com/jira/browse/FP-4796
3217
  // http://tech.groups.yahoo.com/group/flexcoders/message/162365
3218
  // https://groups.google.com/forum/#!topic/easyxdm/mJZJhWagoLc
3219
  apply(swfContainer.style, HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle ? {
3220
  height: "20px",
3221
  width: "20px",
3222
  position: "fixed",
3223
  right: 0,
3224
  top: 0
3225
  } : {
3226
  height: "1px",
3227
  width: "1px",
3228
  position: "absolute",
3229
  overflow: "hidden",
3230
  right: 0,
3231
  top: 0
3232
  });
3233
  document.body.appendChild(swfContainer);
3234
  }
3235
  // create the object/embed
3236
  var flashVars = "callback=flash_loaded" + encodeURIComponent(domain.replace(/[\-.]/g, "_"))
3237
  + "&proto=" + global.location.protocol
3238
  + "&domain=" + encodeURIComponent(getDomainName(global.location.href))
3239
  + "&port=" + encodeURIComponent(getPort(global.location.href))
3240
  + "&ns=" + encodeURIComponent(namespace);
3241
  swfContainer.innerHTML = "<object height='20' width='20' type='application/x-shockwave-flash' id='" + id + "' data='" + url + "'>" +
3242
  "<param name='allowScriptAccess' value='always'></param>" +
3243
  "<param name='wmode' value='transparent'>" +
3244
  "<param name='movie' value='" +
3245
  url +
3246
  "'></param>" +
3247
  "<param name='flashvars' value='" +
3248
  flashVars +
3249
  "'></param>" +
3250
  "<embed type='application/x-shockwave-flash' FlashVars='" +
3251
  flashVars +
3252
  "' allowScriptAccess='always' wmode='transparent' src='" +
3253
  url +
3254
  "' height='1' width='1'></embed>" +
3255
  "</object>";
3256
  }
3257
  return (pub = {
3258
  outgoing: function(message, domain, fn){
3259
  swf.postMessage(config.channel, message.toString());
3260
  if (fn) {
3261
  fn();
3262
  }
3263
  },
3264
  destroy: function(){
3265
  try {
3266
  swf.destroyChannel(config.channel);
3267
  }
3268
  catch (e) {
3269
  }
3270
  swf = null;
3271
  if (frame) {
3272
  frame.parentNode.removeChild(frame);
3273
  frame = null;
3274
  }
3275
  },
3276
  onDOMReady: function(){
3277
  targetOrigin = config.remote;
3278
  // Prepare the code that will be run after the swf has been intialized
3279
  easyXDM.Fn.set("flash_" + config.channel + "_init", function(){
3280
  setTimeout(function(){
3281
  pub.up.callback(true);
3282
  });
3283
  });
3284
  // set up the omMessage handler
3285
  easyXDM.Fn.set("flash_" + config.channel + "_onMessage", onMessage);
3286
  config.swf = resolveUrl(config.swf); // reports have been made of requests gone rogue when using relative paths
3287
  var swfdomain = getDomainName(config.swf);
3288
  var fn = function(){
3289
  // set init to true in case the fn was called was invoked from a separate instance
3290
  easyXDM.stack.FlashTransport[swfdomain].init = true;
3291
  swf = easyXDM.stack.FlashTransport[swfdomain].swf;
3292
  // create the channel
3293
  swf.createChannel(config.channel, config.secret, getLocation(config.remote), config.isHost);
3294
  if (config.isHost) {
3295
  // if Flash is going to be throttled and we want to avoid this
3296
  if (HAS_FLASH_THROTTLED_BUG && config.swfNoThrottle) {
3297
  apply(config.props, {
3298
  position: "fixed",
3299
  right: 0,
3300
  top: 0,
3301
  height: "20px",
3302
  width: "20px"
3303
  });
3304
  }
3305
  // set up the iframe
3306
  apply(config.props, {
3307
  src: appendQueryParameters(config.remote, {
3308
  xdm_e: getLocation(location.href),
3309
  xdm_c: config.channel,
3310
  xdm_p: 6, // 6 = FlashTransport
3311
  xdm_s: config.secret
3312
  }),
3313
  name: IFRAME_PREFIX + config.channel + "_provider"
3314
  });
3315
  frame = createFrame(config);
3316
  }
3317
  };
3318
  if (easyXDM.stack.FlashTransport[swfdomain] && easyXDM.stack.FlashTransport[swfdomain].init) {
3319
  // if the swf is in place and we are the consumer
3320
  fn();
3321
  }
3322
  else {
3323
  // if the swf does not yet exist
3324
  if (!easyXDM.stack.FlashTransport[swfdomain]) {
3325
  // add the queue to hold the init fn's
3326
  easyXDM.stack.FlashTransport[swfdomain] = {
3327
  queue: [fn]
3328
  };
3329
  addSwf(swfdomain);
3330
  }
3331
  else {
3332
  easyXDM.stack.FlashTransport[swfdomain].queue.push(fn);
3333
  }
3334
  }
3335
  },
3336
  init: function(){
3337
  whenReady(pub.onDOMReady, pub);
3338
  }
3339
  });
3340
  * @class easyXDM.stack.PostMessageTransport
3341
  * PostMessageTransport is a transport class that uses HTML5 postMessage for communication.<br/>
3342
  * <a href="http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx">http://msdn.microsoft.com/en-us/library/ms644944(VS.85).aspx</a><br/>
3343
  * <a href="https://developer.mozilla.org/en/DOM/window.postMessage">https://developer.mozilla.org/en/DOM/window.postMessage</a>
3344
  * @namespace easyXDM.stack
3345
  * @constructor
3346
  * @param {Object} config The transports configuration.
3347
  * @cfg {String} remote The remote domain to communicate with.
3348
  */
3349
  var pub, // the public interface
3350
  frame, // the remote frame, if any
3351
  callerWindow, // the window that we will call with
3352
  targetOrigin; // the domain to communicate with
3353
  /**
3354
  * Resolves the origin from the event object
3355
  * @private
3356
  * @param {Object} event The messageevent
3357
  * @return {String} The scheme, host and port of the origin
3358
  */
3359
  function _getOrigin(event){
3360
  if (event.origin) {
3361
  // This is the HTML5 property
3362
  return getLocation(event.origin);
3363
  }
3364
  if (event.uri) {
3365
  // From earlier implementations
3366
  return getLocation(event.uri);
3367
  }
3368
  if (event.domain) {
3369
  // This is the last option and will fail if the
3370
  // origin is not using the same schema as we are
3371
  return location.protocol + "//" + event.domain;
3372
  }
3373
  throw "Unable to retrieve the origin of the event";
3374
  }
3375
  /**
3376
  * This is the main implementation for the onMessage event.<br/>
3377
  * It checks the validity of the origin and passes the message on if appropriate.
3378
  * @private
3379
  * @param {Object} event The messageevent
3380
  */
3381
  function _window_onMessage(event){
3382
  var origin = _getOrigin(event);
3383
  if (origin == targetOrigin && event.data.substring(0, config.channel.length + 1) == config.channel + " ") {
3384
  pub.up.incoming(event.data.substring(config.channel.length + 1), origin);
3385
  }
3386
  }
3387
  return (pub = {
3388
  outgoing: function(message, domain, fn){
3389
  callerWindow.postMessage(config.channel + " " + message, domain || targetOrigin);
3390
  if (fn) {
3391
  fn();
3392
  }
3393
  },
3394
  destroy: function(){
3395
  un(window, "message", _window_onMessage);
3396
  if (frame) {
3397
  callerWindow = null;
3398
  frame.parentNode.removeChild(frame);
3399
  frame = null;
3400
  }
3401
  },
3402
  onDOMReady: function(){
3403
  targetOrigin = getLocation(config.remote);
3404
  if (config.isHost) {
3405
  // add the event handler for listening
3406
  var waitForReady = function(event){
3407
  if (event.data == config.channel + "-ready") {
3408
  // replace the eventlistener
3409
  callerWindow = ("postMessage" in frame.contentWindow) ? frame.contentWindow : frame.contentWindow.document;
3410
  un(window, "message", waitForReady);
3411
  on(window, "message", _window_onMessage);
3412
  setTimeout(function(){
3413
  pub.up.callback(true);
3414
  }, 0);
3415
  }
3416
  };
3417
  on(window, "message", waitForReady);
3418
  // set up the iframe
3419
  apply(config.props, {
3420
  src: appendQueryParameters(config.remote, {
3421
  xdm_e: getLocation(location.href),
3422
  xdm_c: config.channel,
3423
  xdm_p: 1 // 1 = PostMessage
3424
  }),
3425
  name: IFRAME_PREFIX + config.channel + "_provider"
3426
  });
3427
  frame = createFrame(config);
3428
  }
3429
  else {
3430
  // add the event handler for listening
3431
  on(window, "message", _window_onMessage);
3432
  callerWindow = ("postMessage" in window.parent) ? window.parent : window.parent.document;
3433
  callerWindow.postMessage(config.channel + "-ready", targetOrigin);
3434
  setTimeout(function(){
3435
  pub.up.callback(true);
3436
  }, 0);
3437
  }
3438
  },
3439
  init: function(){
3440
  whenReady(pub.onDOMReady, pub);
3441
  }
3442
  });
3443
  * @class easyXDM.stack.FrameElementTransport
3444
  * FrameElementTransport is a transport class that can be used with Gecko-browser as these allow passing variables using the frameElement property.<br/>
3445
  * Security is maintained as Gecho uses Lexical Authorization to determine under which scope a function is running.
3446
  * @namespace easyXDM.stack
3447
  * @constructor
3448
  * @param {Object} config The transports configuration.
3449
  * @cfg {String} remote The remote document to communicate with.
3450
  */
3451
  var pub, frame, send, targetOrigin;
3452
  return (pub = {
3453
  outgoing: function(message, domain, fn){
3454
  send.call(this, message);
3455
  if (fn) {
3456
  fn();
3457
  }
3458
  },
3459
  destroy: function(){
3460
  if (frame) {
3461
  frame.parentNode.removeChild(frame);
3462
  frame = null;
3463
  }
3464
  },
3465
  onDOMReady: function(){
3466
  targetOrigin = getLocation(config.remote);
3467
  if (config.isHost) {
3468
  // set up the iframe
3469
  apply(config.props, {
3470
  src: appendQueryParameters(config.remote, {
3471
  xdm_e: getLocation(location.href),
3472
  xdm_c: config.channel,
3473
  xdm_p: 5 // 5 = FrameElementTransport
3474
  }),
3475
  name: IFRAME_PREFIX + config.channel + "_provider"
3476
  });
3477
  frame = createFrame(config);
3478
  frame.fn = function(sendFn){
3479
  delete frame.fn;
3480
  send = sendFn;
3481
  setTimeout(function(){
3482
  pub.up.callback(true);
3483
  }, 0);
3484
  // remove the function so that it cannot be used to overwrite the send function later on
3485
  return function(msg){
3486
  pub.up.incoming(msg, targetOrigin);
3487
  };
3488
  };
3489
  }
3490
  else {
3491
  // This is to mitigate origin-spoofing
3492
  if (document.referrer && getLocation(document.referrer) != query.xdm_e) {
3493
  window.top.location = query.xdm_e;
3494
  }
3495
  send = window.frameElement.fn(function(msg){
3496
  pub.up.incoming(msg, targetOrigin);
3497
  });
3498
  pub.up.callback(true);
3499
  }
3500
  },
3501
  init: function(){
3502
  whenReady(pub.onDOMReady, pub);
3503
  }
3504
  });
3505
  * @class easyXDM.stack.NameTransport
3506
  * NameTransport uses the window.name property to relay data.
3507
  * The <code>local</code> parameter needs to be set on both the consumer and provider,<br/>
3508
  * and the <code>remoteHelper</code> parameter needs to be set on the consumer.
3509
  * @constructor
3510
  * @param {Object} config The transports configuration.
3511
  * @cfg {String} remoteHelper The url to the remote instance of hash.html - this is only needed for the host.
3512
  * @namespace easyXDM.stack
3513
  */
3514
  var pub; // the public interface
3515
  var isHost, callerWindow, remoteWindow, readyCount, callback, remoteOrigin, remoteUrl;
3516
  function _sendMessage(message){
3517
  var url = config.remoteHelper + (isHost ? "#_3" : "#_2") + config.channel;
3518
  callerWindow.contentWindow.sendMessage(message, url);
3519
  }
3520
  function _onReady(){
3521
  if (isHost) {
3522
  if (++readyCount === 2 || !isHost) {
3523
  pub.up.callback(true);
3524
  }
3525
  }
3526
  else {
3527
  _sendMessage("ready");
3528
  pub.up.callback(true);
3529
  }
3530
  }
3531
  function _onMessage(message){
3532
  pub.up.incoming(message, remoteOrigin);
3533
  }
3534
  function _onLoad(){
3535
  if (callback) {
3536
  setTimeout(function(){
3537
  callback(true);
3538
  }, 0);
3539
  }
3540
  }
3541
  return (pub = {
3542
  outgoing: function(message, domain, fn){
3543
  callback = fn;
3544
  _sendMessage(message);
3545
  },
3546
  destroy: function(){
3547
  callerWindow.parentNode.removeChild(callerWindow);
3548
  callerWindow = null;
3549
  if (isHost) {
3550
  remoteWindow.parentNode.removeChild(remoteWindow);
3551
  remoteWindow = null;
3552
  }
3553
  },
3554
  onDOMReady: function(){
3555
  isHost = config.isHost;
3556
  readyCount = 0;
3557
  remoteOrigin = getLocation(config.remote);
3558
  config.local = resolveUrl(config.local);
3559
  if (isHost) {
3560
  // Register the callback
3561
  easyXDM.Fn.set(config.channel, function(message){
3562
  if (isHost && message === "ready") {
3563
  // Replace the handler
3564
  easyXDM.Fn.set(config.channel, _onMessage);
3565
  _onReady();
3566
  }
3567
  });
3568
  // Set up the frame that points to the remote instance
3569
  remoteUrl = appendQueryParameters(config.remote, {
3570
  xdm_e: config.local,
3571
  xdm_c: config.channel,
3572
  xdm_p: 2
3573
  });
3574
  apply(config.props, {
3575
  src: remoteUrl + '#' + config.channel,
3576
  name: IFRAME_PREFIX + config.channel + "_provider"
3577
  });
3578
  remoteWindow = createFrame(config);
3579
  }
3580
  else {
3581
  config.remoteHelper = config.remote;
3582
  easyXDM.Fn.set(config.channel, _onMessage);
3583
  }
3584
  // Set up the iframe that will be used for the transport
3585
  var onLoad = function(){
3586
  // Remove the handler
3587
  var w = callerWindow || this;
3588
  un(w, "load", onLoad);
3589
  easyXDM.Fn.set(config.channel + "_load", _onLoad);
3590
  (function test(){
3591
  if (typeof w.contentWindow.sendMessage == "function") {
3592
  _onReady();
3593
  }
3594
  else {
3595
  setTimeout(test, 50);
3596
  }
3597
  }());
3598
  };
3599
  callerWindow = createFrame({
3600
  props: {
3601
  src: config.local + "#_4" + config.channel
3602
  },
3603
  onLoad: onLoad
3604
  });
3605
  },
3606
  init: function(){
3607
  whenReady(pub.onDOMReady, pub);
3608
  }
3609
  });
3610
  * @class easyXDM.stack.HashTransport
3611
  * HashTransport is a transport class that uses the IFrame URL Technique for communication.<br/>
3612
  * <a href="http://msdn.microsoft.com/en-us/library/bb735305.aspx">http://msdn.microsoft.com/en-us/library/bb735305.aspx</a><br/>
3613
  * @namespace easyXDM.stack
3614
  * @constructor
3615
  * @param {Object} config The transports configuration.
3616
  * @cfg {String/Window} local The url to the local file used for proxying messages, or the local window.
3617
  * @cfg {Number} delay The number of milliseconds easyXDM should try to get a reference to the local window.
3618
  * @cfg {Number} interval The interval used when polling for messages.
3619
  */
3620
  var pub;
3621
  var me = this, isHost, _timer, pollInterval, _lastMsg, _msgNr, _listenerWindow, _callerWindow;
3622
  var useParent, _remoteOrigin;
3623
  function _sendMessage(message){
3624
  if (!_callerWindow) {
3625
  return;
3626
  }
3627
  var url = config.remote + "#" + (_msgNr++) + "_" + message;
3628
  ((isHost || !useParent) ? _callerWindow.contentWindow : _callerWindow).location = url;
3629
  }
3630
  function _handleHash(hash){
3631
  _lastMsg = hash;
3632
  pub.up.incoming(_lastMsg.substring(_lastMsg.indexOf("_") + 1), _remoteOrigin);
3633
  }
3634
  /**
3635
  * Checks location.hash for a new message and relays this to the receiver.
3636
  * @private
3637
  */
3638
  function _pollHash(){
3639
  if (!_listenerWindow) {
3640
  return;
3641
  }
3642
  var href = _listenerWindow.location.href, hash = "", indexOf = href.indexOf("#");
3643
  if (indexOf != -1) {
3644
  hash = href.substring(indexOf);
3645
  }
3646
  if (hash && hash != _lastMsg) {
3647
  _handleHash(hash);
3648
  }
3649
  }
3650
  function _attachListeners(){
3651
  _timer = setInterval(_pollHash, pollInterval);
3652
  }
3653
  return (pub = {
3654
  outgoing: function(message, domain){
3655
  _sendMessage(message);
3656
  },
3657
  destroy: function(){
3658
  window.clearInterval(_timer);
3659
  if (isHost || !useParent) {
3660
  _callerWindow.parentNode.removeChild(_callerWindow);
3661
  }
3662
  _callerWindow = null;
3663
  },
3664
  onDOMReady: function(){
3665
  isHost = config.isHost;
3666
  pollInterval = config.interval;
3667
  _lastMsg = "#" + config.channel;
3668
  _msgNr = 0;
3669
  useParent = config.useParent;
3670
  _remoteOrigin = getLocation(config.remote);
3671
  if (isHost) {
3672
  apply(config.props, {
3673
  src: config.remote,
3674
  name: IFRAME_PREFIX + config.channel + "_provider"
3675
  });
3676
  if (useParent) {
3677
  config.onLoad = function(){
3678
  _listenerWindow = window;
3679
  _attachListeners();
3680
  pub.up.callback(true);
3681
  };
3682
  }
3683
  else {
3684
  var tries = 0, max = config.delay / 50;
3685
  (function getRef(){
3686
  if (++tries > max) {
3687
  throw new Error("Unable to reference listenerwindow");
3688
  }
3689
  try {
3690
  _listenerWindow = _callerWindow.contentWindow.frames[IFRAME_PREFIX + config.channel + "_consumer"];
3691
  }
3692
  catch (ex) {
3693
  }
3694
  if (_listenerWindow) {
3695
  _attachListeners();
3696
  pub.up.callback(true);
3697
  }
3698
  else {
3699
  setTimeout(getRef, 50);
3700
  }
3701
  }());
3702
  }
3703
  _callerWindow = createFrame(config);
3704
  }
3705
  else {
3706
  _listenerWindow = window;
3707
  _attachListeners();
3708
  if (useParent) {
3709
  _callerWindow = parent;
3710
  pub.up.callback(true);
3711
  }
3712
  else {
3713
  apply(config, {
3714
  props: {
3715
  src: config.remote + "#" + config.channel + new Date(),
3716
  name: IFRAME_PREFIX + config.channel + "_consumer"
3717
  },
3718
  onLoad: function(){
3719
  pub.up.callback(true);
3720
  }
3721
  });
3722
  _callerWindow = createFrame(config);
3723
  }
3724
  }
3725
  },
3726
  init: function(){
3727
  whenReady(pub.onDOMReady, pub);
3728
  }
3729
  });
3730
  * @class easyXDM.stack.ReliableBehavior
3731
  * This is a behavior that tries to make the underlying transport reliable by using acknowledgements.
3732
  * @namespace easyXDM.stack
3733
  * @constructor
3734
  * @param {Object} config The behaviors configuration.
3735
  */
3736
  var pub, // the public interface
3737
  callback; // the callback to execute when we have a confirmed success/failure
3738
  var idOut = 0, idIn = 0, currentMessage = "";
3739
  return (pub = {
3740
  incoming: function(message, origin){
3741
  var indexOf = message.indexOf("_"), ack = message.substring(0, indexOf).split(",");
3742
  message = message.substring(indexOf + 1);
3743
  if (ack[0] == idOut) {
3744
  currentMessage = "";
3745
  if (callback) {
3746
  callback(true);
3747
  }
3748
  }
3749
  if (message.length > 0) {
3750
  pub.down.outgoing(ack[1] + "," + idOut + "_" + currentMessage, origin);
3751
  if (idIn != ack[1]) {
3752
  idIn = ack[1];
3753
  pub.up.incoming(message, origin);
3754
  }
3755
  }
3756
  },
3757
  outgoing: function(message, origin, fn){
3758
  currentMessage = message;
3759
  callback = fn;
3760
  pub.down.outgoing(idIn + "," + (++idOut) + "_" + message, origin);
3761
  }
3762
  });
3763
  * @class easyXDM.stack.QueueBehavior
3764
  * This is a behavior that enables queueing of messages. <br/>
3765
  * It will buffer incoming messages and dispach these as fast as the underlying transport allows.
3766
  * This will also fragment/defragment messages so that the outgoing message is never bigger than the
3767
  * set length.
3768
  * @namespace easyXDM.stack
3769
  * @constructor
3770
  * @param {Object} config The behaviors configuration. Optional.
3771
  * @cfg {Number} maxLength The maximum length of each outgoing message. Set this to enable fragmentation.
3772
  */
3773
  var pub, queue = [], waiting = true, incoming = "", destroying, maxLength = 0, lazy = false, doFragment = false;
3774
  function dispatch(){
3775
  if (config.remove && queue.length === 0) {
3776
  removeFromStack(pub);
3777
  return;
3778
  }
3779
  if (waiting || queue.length === 0 || destroying) {
3780
  return;
3781
  }
3782
  waiting = true;
3783
  var message = queue.shift();
3784
  pub.down.outgoing(message.data, message.origin, function(success){
3785
  waiting = false;
3786
  if (message.callback) {
3787
  setTimeout(function(){
3788
  message.callback(success);
3789
  }, 0);
3790
  }
3791
  dispatch();
3792
  });
3793
  }
3794
  return (pub = {
3795
  init: function(){
3796
  if (undef(config)) {
3797
  config = {};
3798
  }
3799
  if (config.maxLength) {
3800
  maxLength = config.maxLength;
3801
  doFragment = true;
3802
  }
3803
  if (config.lazy) {
3804
  lazy = true;
3805
  }
3806
  else {
3807
  pub.down.init();
3808
  }
3809
  },
3810
  callback: function(success){
3811
  waiting = false;
3812
  var up = pub.up; // in case dispatch calls removeFromStack
3813
  dispatch();
3814
  up.callback(success);
3815
  },
3816
  incoming: function(message, origin){
3817
  if (doFragment) {
3818
  var indexOf = message.indexOf("_"), seq = parseInt(message.substring(0, indexOf), 10);
3819
  incoming += message.substring(indexOf + 1);
3820
  if (seq === 0) {
3821
  if (config.encode) {
3822
  incoming = decodeURIComponent(incoming);
3823
  }
3824
  pub.up.incoming(incoming, origin);
3825
  incoming = "";
3826
  }
3827
  }
3828
  else {
3829
  pub.up.incoming(message, origin);
3830
  }
3831
  },
3832
  outgoing: function(message, origin, fn){
3833
  if (config.encode) {
3834
  message = encodeURIComponent(message);
3835
  }
3836
  var fragments = [], fragment;
3837
  if (doFragment) {
3838
  // fragment into chunks
3839
  while (message.length !== 0) {
3840
  fragment = message.substring(0, maxLength);
3841
  message = message.substring(fragment.length);
3842
  fragments.push(fragment);
3843
  }
3844
  // enqueue the chunks
3845
  while ((fragment = fragments.shift())) {
3846
  queue.push({
3847
  data: fragments.length + "_" + fragment,
3848
  origin: origin,
3849
  callback: fragments.length === 0 ? fn : null
3850
  });
3851
  }
3852
  }
3853
  else {
3854
  queue.push({
3855
  data: message,
3856
  origin: origin,
3857
  callback: fn
3858
  });
3859
  }
3860
  if (lazy) {
3861
  pub.down.init();
3862
  }
3863
  else {
3864
  dispatch();
3865
  }
3866
  },
3867
  destroy: function(){
3868
  destroying = true;
3869
  pub.down.destroy();
3870
  }
3871
  });
3872
  * @class easyXDM.stack.VerifyBehavior
3873
  * This behavior will verify that communication with the remote end is possible, and will also sign all outgoing,
3874
  * and verify all incoming messages. This removes the risk of someone hijacking the iframe to send malicious messages.
3875
  * @namespace easyXDM.stack
3876
  * @constructor
3877
  * @param {Object} config The behaviors configuration.
3878
  * @cfg {Boolean} initiate If the verification should be initiated from this end.
3879
  */
3880
  var pub, mySecret, theirSecret, verified = false;
3881
  function startVerification(){
3882
  mySecret = Math.random().toString(16).substring(2);
3883
  pub.down.outgoing(mySecret);
3884
  }
3885
  return (pub = {
3886
  incoming: function(message, origin){
3887
  var indexOf = message.indexOf("_");
3888
  if (indexOf === -1) {
3889
  if (message === mySecret) {
3890
  pub.up.callback(true);
3891
  }
3892
  else if (!theirSecret) {
3893
  theirSecret = message;
3894
  if (!config.initiate) {
3895
  startVerification();
3896
  }
3897
  pub.down.outgoing(message);
3898
  }
3899
  }
3900
  else {
3901
  if (message.substring(0, indexOf) === theirSecret) {
3902
  pub.up.incoming(message.substring(indexOf + 1), origin);
3903
  }
3904
  }
3905
  },
3906
  outgoing: function(message, origin, fn){
3907
  pub.down.outgoing(mySecret + "_" + message, origin, fn);
3908
  },
3909
  callback: function(success){
3910
  if (config.initiate) {
3911
  startVerification();
3912
  }
3913
  }
3914
  });
3915
  * @class easyXDM.stack.RpcBehavior
3916
  * This uses JSON-RPC 2.0 to expose local methods and to invoke remote methods and have responses returned over the the string based transport stack.<br/>
3917
  * Exposed methods can return values synchronous, asyncronous, or bet set up to not return anything.
3918
  * @namespace easyXDM.stack
3919
  * @constructor
3920
  * @param {Object} proxy The object to apply the methods to.
3921
  * @param {Object} config The definition of the local and remote interface to implement.
3922
  * @cfg {Object} local The local interface to expose.
3923
  * @cfg {Object} remote The remote methods to expose through the proxy.
3924
  * @cfg {Object} serializer The serializer to use for serializing and deserializing the JSON. Should be compatible with the HTML5 JSON object. Optional, will default to JSON.
3925
  */
3926
  var pub, serializer = config.serializer || getJSON();
3927
  var _callbackCounter = 0, _callbacks = {};
3928
  /**
3929
  * Serializes and sends the message
3930
  * @private
3931
  * @param {Object} data The JSON-RPC message to be sent. The jsonrpc property will be added.
3932
  */
3933
  function _send(data){
3934
  data.jsonrpc = "2.0";
3935
  pub.down.outgoing(serializer.stringify(data));
3936
  }
3937
  /**
3938
  * Creates a method that implements the given definition
3939
  * @private
3940
  * @param {Object} The method configuration
3941
  * @param {String} method The name of the method
3942
  * @return {Function} A stub capable of proxying the requested method call
3943
  */
3944
  function _createMethod(definition, method){
3945
  var slice = Array.prototype.slice;
3946
  return function(){
3947
  var l = arguments.length, callback, message = {
3948
  method: method
3949
  };
3950
  if (l > 0 && typeof arguments[l - 1] === "function") {
3951
  //with callback, procedure
3952
  if (l > 1 && typeof arguments[l - 2] === "function") {
3953
  // two callbacks, success and error
3954
  callback = {
3955
  success: arguments[l - 2],
3956
  error: arguments[l - 1]
3957
  };
3958
  message.params = slice.call(arguments, 0, l - 2);
3959
  }
3960
  else {
3961
  // single callback, success
3962
  callback = {
3963
  success: arguments[l - 1]
3964
  };
3965
  message.params = slice.call(arguments, 0, l - 1);
3966
  }
3967
  _callbacks["" + (++_callbackCounter)] = callback;
3968
  message.id = _callbackCounter;
3969
  }
3970
  else {
3971
  // no callbacks, a notification
3972
  message.params = slice.call(arguments, 0);
3973
  }
3974
  if (definition.namedParams && message.params.length === 1) {
3975
  message.params = message.params[0];
3976
  }
3977
  // Send the method request
3978
  _send(message);
3979
  };
3980
  }
3981
  /**
3982
  * Executes the exposed method
3983
  * @private
3984
  * @param {String} method The name of the method
3985
  * @param {Number} id The callback id to use
3986
  * @param {Function} method The exposed implementation
3987
  * @param {Array} params The parameters supplied by the remote end
3988
  */
3989
  function _executeMethod(method, id, fn, params){
3990
  if (!fn) {
3991
  if (id) {
3992
  _send({
3993
  id: id,
3994
  error: {
3995
  code: -32601,
3996
  message: "Procedure not found."
3997
  }
3998
  });
3999
  }
4000
  return;
4001
  }
4002
  var success, error;
4003
  if (id) {
4004
  success = function(result){
4005
  success = emptyFn;
4006
  _send({
4007
  id: id,
4008
  result: result
4009
  });
4010
  };
4011
  error = function(message, data){
4012
  error = emptyFn;
4013
  var msg = {
4014
  id: id,
4015
  error: {
4016
  code: -32099,
4017
  message: message
4018
  }
4019
  };
4020
  if (data) {
4021
  msg.error.data = data;
4022
  }
4023
  _send(msg);
4024
  };
4025
  }
4026
  else {
4027
  success = error = emptyFn;
4028
  }
4029
  // Call local method
4030
  if (!isArray(params)) {
4031
  params = [params];
4032
  }
4033
  try {
4034
  var result = fn.method.apply(fn.scope, params.concat([success, error]));
4035
  if (!undef(result)) {
4036
  success(result);
4037
  }
4038
  }
4039
  catch (ex1) {
4040
  error(ex1.message);
4041
  }
4042
  }
4043
  return (pub = {
4044
  incoming: function(message, origin){
4045
  var data = serializer.parse(message);
4046
  if (data.method) {
4047
  // A method call from the remote end
4048
  if (config.handle) {
4049
  config.handle(data, _send);
4050
  }
4051
  else {
4052
  _executeMethod(data.method, data.id, config.local[data.method], data.params);
4053
  }
4054
  }
4055
  else {
4056
  // A method response from the other end
4057
  var callback = _callbacks[data.id];
4058
  if (data.error) {
4059
  if (callback.error) {
4060
  callback.error(data.error);
4061
  }
4062
  }
4063
  else if (callback.success) {
4064
  callback.success(data.result);
4065
  }
4066
  delete _callbacks[data.id];
4067
  }
4068
  },
4069
  init: function(){
4070
  if (config.remote) {
4071
  // Implement the remote sides exposed methods
4072
  for (var method in config.remote) {
4073
  if (config.remote.hasOwnProperty(method)) {
4074
  proxy[method] = _createMethod(config.remote[method], method);
4075
  }
4076
  }
4077
  }
4078
  pub.down.init();
4079
  },
4080
  destroy: function(){
4081
  for (var method in config.remote) {
4082
  if (config.remote.hasOwnProperty(method) && proxy.hasOwnProperty(method)) {
4083
  delete proxy[method];
4084
  }
4085
  }
4086
  pub.down.destroy();
4087
  }
4088
  });
js/libraries/jquery.zoomer.js ADDED
@@ -0,0 +1,436 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * jQuery Zoomer v1.1
3
+ *
4
+ * By HubSpot >('_')<
5
+ *
6
+ * Licensed under the MIT license:
7
+ * http://www.opensource.org/licenses/mit-license.php
8
+ *
9
+ * Example usage:
10
+
11
+ $('iframe').zoomer({ width: 200, zoom: 0.5 });
12
+
13
+ *
14
+ */
15
+
16
+ (function($){
17
+
18
+ var methods,
19
+ pluginName = 'zoomer',
20
+ defaults = {
21
+ width: 'auto',
22
+ height: 'auto',
23
+ zoom: 0.4,
24
+ tranformOrigin: '0 0',
25
+ //loading
26
+ loadingType: 'message', // other type: 'spinner'
27
+ loadingMessage: 'Generating preview...',
28
+ spinnerURL: 'http://oi46.tinypic.com/6y375z.jpg', // requires loadingType: 'spinner'
29
+ //hover
30
+ message: 'Open Page',
31
+ ieMessageButtonClass: 'btn btn-secondary', // used in IE only
32
+ messageURL: false,
33
+ onComplete: function() {}
34
+ },
35
+ visible = {
36
+ visibility: 'visible'
37
+ },
38
+ invisible = {
39
+ visibility: 'hidden'
40
+ },
41
+ unselectable = {
42
+ '-webkit-user-select': 'none',
43
+ '-khtml-user-select': 'none',
44
+ '-moz-user-select': 'none',
45
+ '-o-user-select': 'none',
46
+ 'user-select': 'none',
47
+ 'overflow': 'hidden'
48
+ },
49
+ absolute = {
50
+ top: 0,
51
+ position: 'absolute'
52
+ },
53
+ relative = {
54
+ position: 'relative'
55
+ },
56
+ isMSIE = navigator.userAgent.match(/MSIE/),
57
+ MSIEVersion = navigator.userAgent.match(/MSIE (\d\.\d+)/) ? parseInt(RegExp.$1, 10) : null
58
+ ;
59
+
60
+ methods = {
61
+
62
+ init: function(opts) {
63
+ return this.each(function(){
64
+ var $el = $(this),
65
+ options = $.extend({}, defaults, opts)
66
+ ;
67
+
68
+ options.src = $el.attr('src');
69
+
70
+ $el.data(pluginName, options);
71
+
72
+ $el[pluginName]('zoomer');
73
+ });
74
+ },
75
+
76
+ zoomer: function() {
77
+ var $el = $(this), options = $el.data(pluginName);
78
+
79
+ $el
80
+ .css(invisible)
81
+ .css(unselectable)
82
+ ;
83
+
84
+ if (options.zoom === 'auto') {
85
+ if (options.width === 'auto' && options.height === 'auto') {
86
+ $.error('jQuery.zoomer: You must set either zoom or height and width.');
87
+ return;
88
+ }
89
+ options.zoom = options.width / $(window).width();
90
+ }
91
+
92
+ if (options.width === 'auto') {
93
+ options.width = $(window).height() * options.zoom;
94
+ }
95
+
96
+ if (options.height === 'auto') {
97
+ options.height = $(window).height() * options.zoom;
98
+ }
99
+
100
+ if (options.loadingType === 'spinner') {
101
+ options.loadingMessage = '<img style="padding: ' + parseInt((options.height - 17) / 2, 10) + 'px 0" src="' + options.spinnerURL + '" />';
102
+ }
103
+
104
+ //fix bug in older version of chrome:
105
+ //http://stackoverflow.com/questions/5159713/
106
+ if (navigator.userAgent.indexOf('Chrome/10.0.648') > -1) {
107
+ options.zoom = Math.sqrt(1 / options.zoom);
108
+ }
109
+
110
+ options.externalSrc = true;
111
+
112
+ try {
113
+ if ($el.get(0).contentWindow.document) {
114
+ options.externalSrc = false;
115
+ }
116
+ } catch (e) {}
117
+
118
+ $el[pluginName]('setUpWrapper');
119
+
120
+ $el[pluginName]('zoom');
121
+
122
+ return $el;
123
+ },
124
+
125
+ setUpWrapper: function() {
126
+ var $el = $(this), options = $el.data(pluginName);
127
+
128
+ if (!$el.parents('.zoomer-wrapper').length) {
129
+ $el
130
+ .wrap(
131
+ $('<div/>')
132
+ .addClass('zoomer-wrapper')
133
+ .css(unselectable)
134
+ .css(relative)
135
+ )
136
+ .wrap(
137
+ $('<div/>')
138
+ .addClass('zoomer-small')
139
+ .css(invisible)
140
+ .css(unselectable)
141
+ )
142
+ ;
143
+ }
144
+
145
+ options.zoomerWrapper = $el.parents('.zoomer-wrapper');
146
+
147
+ options.zoomerSmall = $el.parents('.zoomer-small');
148
+
149
+ options.zoomerCover = $('<div/>')
150
+ .addClass('zoomer-cover')
151
+ .css(unselectable)
152
+ .css(absolute)
153
+ .css({
154
+ textAlign: 'center',
155
+ fontSize: '15px'
156
+ })
157
+ ;
158
+
159
+ options.zoomerLink = $('<a/>')
160
+ .attr('target', '_blank')
161
+ .html(options.message)
162
+ .css({
163
+ height: options.height,
164
+ width: options.width,
165
+ color: '#444',
166
+ display: 'block',
167
+ lineHeight: (parseInt(options.height, 10) - parseInt((options.height - 80) / 10, 10)) + 'px',
168
+ textDecoration: 'none'
169
+ })
170
+ .css('background', '-moz-radial-gradient(center center, circle farthest-corner, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.4) 100%) repeat scroll 0 0 transparent')
171
+ .css('background-image', '-webkit-gradient(radial, center center, 0, center center, ' + parseInt(options.width, 10) + ', from(rgba(255, 255, 255, 0.95)), to(rgba(255, 255, 255, 0.4)))')
172
+ .mousedown(function(){
173
+ $(this).css('box-shadow', 'inset 0px 2px 8px rgba(100, 100, 100, 0.4)');
174
+ })
175
+ .bind('mouseout mouseup', function(){
176
+ $(this).css('box-shadow', 'none');
177
+ })
178
+ .hide()
179
+ ;
180
+
181
+ if (isMSIE) {
182
+ options.zoomerLink.css({
183
+ backgroundColor: 'rgba(255, 255, 255, 0.5)'
184
+ });
185
+ }
186
+
187
+ if (options.click) {
188
+ options.zoomerLink
189
+ .attr('href', options.messageURL || options.src || '#')
190
+ .unbind('click').bind('click', options.click)
191
+ ;
192
+ } else {
193
+ options.zoomerLink.attr('href', options.messageURL || options.src);
194
+ }
195
+
196
+ options.zoomerCover
197
+ .append(options.zoomerLink)
198
+ .hover(function(){
199
+ options.zoomerLink.show();
200
+ $(this).css('box-shadow', 'inset 2px 2px ' + (parseInt(options.width, 10) * 2) + 'px rgba(255, 255, 255, 0.2)');
201
+ }, function(){
202
+ options.zoomerLink.hide();
203
+ $(this).css('box-shadow', 'none');
204
+ })
205
+ .mousedown(function(){
206
+ $(this).css('box-shadow', 'inset 2px 2px ' + (parseInt(options.width, 10) * 2) + 'px rgba(200, 200, 200, 0.8)');
207
+ })
208
+ .bind('mouseout mouseup', function(){
209
+ $(this).css('box-shadow', 'none');
210
+ })
211
+ ;
212
+
213
+ options.zoomerLoader = $('<div/>')
214
+ .addClass('zoomer-loader')
215
+ .css(invisible)
216
+ .css(unselectable)
217
+ .css(absolute)
218
+ .css({
219
+ textAlign: 'center',
220
+ fontSize: '15px',
221
+ lineHeight: (parseInt(options.height, 10) - parseInt((options.height - 80) / 10, 10)) + 'px',
222
+ background: '#fff'
223
+ })
224
+ .html(options.loadingMessage)
225
+ ;
226
+
227
+ options.zoomerWrapper
228
+ .append(options.zoomerCover)
229
+ .append(options.zoomerLoader)
230
+ ;
231
+
232
+ if (isMSIE) { options.zoomerLoader.css(invisible); }
233
+
234
+ return $el[pluginName]('updateWrapper')[pluginName]('fadeOut');
235
+ },
236
+
237
+ updateWrapper: function() {
238
+ var $el = $(this), options = $el.data(pluginName);
239
+
240
+ $.each([options.zoomerWrapper.get(0), options.zoomerCover.get(0), options.zoomerLoader.get(0), options.zoomerSmall.get(0)], function(){
241
+ $(this).css({
242
+ height: options.height,
243
+ width: options.width
244
+ });
245
+ });
246
+
247
+ return $el;
248
+ },
249
+
250
+ fadeIn: function() {
251
+ var $el = $(this), options = $el.data(pluginName);
252
+
253
+ if (isMSIE) { return $el; }
254
+
255
+ $el.css(invisible);
256
+
257
+ options.zoomerSmall
258
+ .stop()
259
+ .css('opacity', 0)
260
+ .css(visible)
261
+ .animate({ 'opacity': 1 }, 150, function(){
262
+ $el
263
+ .css(visible)
264
+ .css('opacity', 0)
265
+ .animate({ 'opacity': 1 }, 500)
266
+ ;
267
+ })
268
+ ;
269
+
270
+ options.zoomerLoader
271
+ .show()
272
+ .animate({ 'opacity': 0 }, 300, function(){
273
+ $(this).hide();
274
+ })
275
+ ;
276
+
277
+ return $el;
278
+ },
279
+
280
+ fadeOut: function() {
281
+ var $el = $(this), options = $el.data(pluginName);
282
+
283
+ if (isMSIE) { return $el; }
284
+
285
+ options.zoomerSmall
286
+ .stop()
287
+ .animate({ 'opacity': 0 }, 300, function(){
288
+ $(this).css('visibility', 'hidden');
289
+ })
290
+ ;
291
+
292
+ options.zoomerLoader
293
+ .css('opacity', 0)
294
+ .css(visible)
295
+ .show()
296
+ .animate({ opacity: 1 }, 100)
297
+ ;
298
+
299
+ return $el;
300
+ },
301
+
302
+ zoom: function() {
303
+ var $el = $(this), options = $el.data(pluginName);
304
+
305
+ if (isMSIE) {
306
+ setTimeout(function(){
307
+ $el
308
+ .css({
309
+ zoom: options.zoom,
310
+ height: parseInt((options.height / options.zoom) * (1 / (MSIEVersion >= 9 ? 1 : options.zoom)), 10),
311
+ width: parseInt((options.width / options.zoom) * (1 / (MSIEVersion >= 9 ? 1 : options.zoom)), 10)
312
+ })
313
+ .css(visible)
314
+ ;
315
+
316
+ options.zoomerLink.remove();
317
+
318
+ options.zoomerCover
319
+ .unbind('hover mouseover mouseout')
320
+ .addClass(options.ieMessageButtonClass)
321
+ .html(options.message)
322
+ .css({
323
+ width: 94,
324
+ height: 14,
325
+ fontSize: 12,
326
+ padding: '6px 18px 6px 18px',
327
+ top: parseInt(options.height - (12 + (2 * 6) + 2 + 10), 10),
328
+ left: parseInt((options.width - (94 + (2 * 18))) / 2, 10)
329
+ })
330
+ .show()
331
+ ;
332
+
333
+ if (!options.click) {
334
+ options.click = function() {
335
+ location.href = options.messageURL || options.src;
336
+ };
337
+ }
338
+
339
+ options.zoomerCover.unbind('click').bind('click', options.click);
340
+
341
+ options.onComplete($el);
342
+ }, 1000);
343
+
344
+ return $el;
345
+ }
346
+
347
+ if (options.externalSrc) {
348
+ $el
349
+ .css({
350
+ height: options.height / options.zoom,
351
+ width: options.width / options.zoom,
352
+ 'transform-origin': options.tranformOrigin,
353
+ '-webkit-transform-origin': options.tranformOrigin,
354
+ '-moz-transform-origin': options.tranformOrigin,
355
+ '-o-transform-origin': options.tranformOrigin,
356
+ 'transform': 'scale(' + options.zoom + ')',
357
+ '-webkit-transform': 'scale(' + options.zoom + ')',
358
+ '-moz-transform': 'scale(' + options.zoom + ')',
359
+ '-o-transform': 'scale(' + options.zoom + ')'
360
+ })
361
+ .css(visible)
362
+ ;
363
+
364
+ $el[pluginName]('fadeIn');
365
+
366
+ options.onComplete($el);
367
+
368
+ return $el;
369
+ }
370
+
371
+ $el
372
+ .css({
373
+ height: options.height / options.zoom,
374
+ width: options.width / options.zoom
375
+ })
376
+ .load(function(){
377
+ $el.contents().find('html').css({
378
+ 'transform-origin': options.tranformOrigin,
379
+ '-webkit-transform-origin': options.tranformOrigin,
380
+ '-moz-transform-origin': options.tranformOrigin,
381
+ '-o-transform-origin': options.tranformOrigin,
382
+ 'transform': 'scale(' + options.zoom + ')',
383
+ '-webkit-transform': 'scale(' + options.zoom + ')',
384
+ '-moz-transform': 'scale(' + options.zoom + ')',
385
+ '-o-transform': 'scale(' + options.zoom + ')'
386
+ });
387
+
388
+ $el[pluginName]('fadeIn');
389
+
390
+ options.onComplete($el);
391
+ })
392
+ ;
393
+
394
+ return $el;
395
+ },
396
+
397
+ src: function(src) {
398
+ var $el = $(this),
399
+ options = $el.data(pluginName)
400
+ ;
401
+
402
+ options.src = src;
403
+
404
+ $el[pluginName]('fadeOut').attr('src', src);
405
+
406
+ return $el;
407
+ },
408
+
409
+ refresh: function() {
410
+ var $el = $(this), options = $el.data(pluginName);
411
+
412
+ return $el[pluginName]('src', options.src);
413
+ },
414
+
415
+ zoomedBodyHeight: function() {
416
+ var $el = $(this), options = $el.data(pluginName);
417
+
418
+ if (options.externalSrc) {
419
+ return $.error('jQuery.zoomer: cannot access bodyHeight of an external iFrame');
420
+ }
421
+
422
+ return options.zoom * $($el.get(0).contentWindow.document).height();
423
+ }
424
+ };
425
+
426
+ $.fn[pluginName] = function(options) {
427
+ if (methods[options]) {
428
+ return methods[options].apply(this, Array.prototype.slice.call(arguments, 1));
429
+ } else if (typeof options === 'object' || ! options) {
430
+ return methods.init.apply(this, arguments);
431
+ } else {
432
+ $.error('jQuery.' + pluginName + ': Method ' + options + ' does not exist');
433
+ }
434
+ };
435
+
436
+ })(jQuery);
landing-pages.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Landing Pages
4
  Plugin URI: http://www.inboundnow.com/landing-pages/
5
  Description: The first true all-in-one Landing Page solution for WordPress, including ongoing conversion metrics, a/b split testing, unlimited design options and so much more!
6
- Version: 1.8.8
7
  Author: Inbound Now
8
  Author URI: http://www.inboundnow.com/
9
  Text Domain: landing-pages
@@ -38,75 +38,73 @@ if (!class_exists('Inbound_Landing_Pages_Plugin')) {
38
  */
39
  private static function load_constants() {
40
 
41
- define('LANDINGPAGES_CURRENT_VERSION', '1.8.8' );
42
  define('LANDINGPAGES_URLPATH', plugins_url( '/' , __FILE__ ) );
43
  define('LANDINGPAGES_PATH', WP_PLUGIN_DIR.'/'.plugin_basename( dirname(__FILE__) ).'/' );
44
  define('LANDINGPAGES_PLUGIN_SLUG', plugin_basename( dirname(__FILE__) ) );
45
  define('LANDINGPAGES_FILE', __FILE__ );
46
- define('LANDINGPAGES_STORE_URL', 'http://www.inboundnow.com/' );
47
  $uploads = wp_upload_dir();
48
  define('LANDINGPAGES_UPLOADS_PATH', $uploads['basedir'].'/landing-pages/templates/' );
49
  define('LANDINGPAGES_UPLOADS_URLPATH', $uploads['baseurl'].'/landing-pages/templates/' );
50
-
51
  }
52
 
53
  /**
54
  * Include required plugin files
55
  */
56
  private static function load_files() {
57
-
58
  /* load core files */
59
  switch (is_admin()) :
60
  case true :
61
  /* loads admin files */
62
- include_once('modules/module.language-support.php');
63
- include_once('modules/module.javascript-admin.php');
64
- include_once('classes/class.activation.php');
65
- include_once('classes/class.activation.upgrade-routines.php');
66
- include_once('modules/module.global-settings.php');
67
- include_once('modules/module.clone.php');
68
- include_once('modules/module.extension-updater.php');
69
- include_once('modules/module.extension-licensing.php');
70
- include_once('modules/module.admin-menus.php');
71
- include_once('modules/module.welcome.php');
72
- include_once('modules/module.install.php');
73
- include_once('modules/module.alert.php');
74
- include_once('modules/module.metaboxes.php');
75
- include_once('modules/module.metaboxes-global.php');
76
- include_once('modules/module.landing-page.php');
77
- include_once('classes/class.load-extensions.php');
78
- include_once('modules/module.post-type.php');
79
- include_once('modules/module.track.php');
80
- include_once('modules/module.ajax-setup.php');
81
- include_once('modules/module.utils.php');
82
- include_once('modules/module.sidebar.php');
83
- include_once('modules/module.widgets.php');
84
- include_once('modules/module.cookies.php');
85
- include_once('modules/module.ab-testing.php');
86
- include_once('modules/module.click-tracking.php');
87
- include_once('modules/module.templates.php');
88
- include_once('modules/module.store.php');
89
- include_once('modules/module.customizer.php');
90
- //include_once('classes/class.branching.php');
91
 
92
 
93
  BREAK;
94
 
95
  case false :
96
  /* load front-end files */
97
- include_once('modules/module.javascript-frontend.php');
98
- include_once('modules/module.post-type.php');
99
- include_once('modules/module.track.php');
100
- include_once('modules/module.ajax-setup.php');
101
- include_once('modules/module.utils.php');
102
- include_once('modules/module.sidebar.php');
103
- include_once('modules/module.widgets.php');
104
- include_once('modules/module.cookies.php');
105
- include_once('modules/module.ab-testing.php');
106
- include_once('modules/module.click-tracking.php');
107
- include_once('modules/module.landing-page.php');
108
- include_once('classes/class.load-extensions.php');
109
- include_once('modules/module.customizer.php');
110
 
111
  BREAK;
112
  endswitch;
@@ -228,4 +226,4 @@ if (!class_exists('Inbound_Landing_Pages_Plugin')) {
228
  return true;
229
  }
230
 
231
- }
3
  Plugin Name: Landing Pages
4
  Plugin URI: http://www.inboundnow.com/landing-pages/
5
  Description: The first true all-in-one Landing Page solution for WordPress, including ongoing conversion metrics, a/b split testing, unlimited design options and so much more!
6
+ Version: 1.9.0
7
  Author: Inbound Now
8
  Author URI: http://www.inboundnow.com/
9
  Text Domain: landing-pages
38
  */
39
  private static function load_constants() {
40
 
41
+ define('LANDINGPAGES_CURRENT_VERSION', '1.9.0' );
42
  define('LANDINGPAGES_URLPATH', plugins_url( '/' , __FILE__ ) );
43
  define('LANDINGPAGES_PATH', WP_PLUGIN_DIR.'/'.plugin_basename( dirname(__FILE__) ).'/' );
44
  define('LANDINGPAGES_PLUGIN_SLUG', plugin_basename( dirname(__FILE__) ) );
45
  define('LANDINGPAGES_FILE', __FILE__ );
46
+ define('LANDINGPAGES_STORE_URL', 'http://www.inboundnow.com/market' );
47
  $uploads = wp_upload_dir();
48
  define('LANDINGPAGES_UPLOADS_PATH', $uploads['basedir'].'/landing-pages/templates/' );
49
  define('LANDINGPAGES_UPLOADS_URLPATH', $uploads['baseurl'].'/landing-pages/templates/' );
 
50
  }
51
 
52
  /**
53
  * Include required plugin files
54
  */
55
  private static function load_files() {
 
56
  /* load core files */
57
  switch (is_admin()) :
58
  case true :
59
  /* loads admin files */
60
+ include_once( LANDINGPAGES_PATH . 'modules/module.language-support.php');
61
+ include_once( LANDINGPAGES_PATH . 'modules/module.javascript-admin.php');
62
+ include_once( LANDINGPAGES_PATH . 'classes/class.activation.php');
63
+ include_once( LANDINGPAGES_PATH . 'classes/class.activation.upgrade-routines.php');
64
+ include_once( LANDINGPAGES_PATH . 'modules/module.global-settings.php');
65
+ include_once( LANDINGPAGES_PATH . 'modules/module.clone.php');
66
+ include_once( LANDINGPAGES_PATH . 'modules/module.extension-updater.php');
67
+ include_once( LANDINGPAGES_PATH . 'modules/module.extension-licensing.php');
68
+ include_once( LANDINGPAGES_PATH . 'modules/module.admin-menus.php');
69
+ include_once( LANDINGPAGES_PATH . 'modules/module.welcome.php');
70
+ include_once( LANDINGPAGES_PATH . 'modules/module.install.php');
71
+ include_once( LANDINGPAGES_PATH . 'modules/module.alert.php');
72
+ include_once( LANDINGPAGES_PATH . 'modules/module.metaboxes.php');
73
+ include_once( LANDINGPAGES_PATH . 'modules/module.metaboxes-global.php');
74
+ include_once( LANDINGPAGES_PATH . 'modules/module.landing-page.php');
75
+ include_once( LANDINGPAGES_PATH . 'classes/class.load-extensions.php');
76
+ include_once( LANDINGPAGES_PATH . 'modules/module.post-type.php');
77
+ include_once( LANDINGPAGES_PATH . 'modules/module.track.php');
78
+ include_once( LANDINGPAGES_PATH . 'modules/module.ajax-setup.php');
79
+ include_once( LANDINGPAGES_PATH . 'modules/module.utils.php');
80
+ include_once( LANDINGPAGES_PATH . 'modules/module.sidebar.php');
81
+ include_once( LANDINGPAGES_PATH . 'modules/module.widgets.php');
82
+ include_once( LANDINGPAGES_PATH . 'modules/module.cookies.php');
83
+ include_once( LANDINGPAGES_PATH . 'modules/module.ab-testing.php');
84
+ include_once( LANDINGPAGES_PATH . 'modules/module.click-tracking.php');
85
+ include_once( LANDINGPAGES_PATH . 'modules/module.templates.php');
86
+ include_once( LANDINGPAGES_PATH . 'modules/module.store.php');
87
+ include_once( LANDINGPAGES_PATH . 'modules/module.customizer.php');
88
+ //include_once( LANDINGPAGES_PATH . 'classes/class.branching.php');
89
 
90
 
91
  BREAK;
92
 
93
  case false :
94
  /* load front-end files */
95
+ include_once( LANDINGPAGES_PATH . 'modules/module.javascript-frontend.php');
96
+ include_once( LANDINGPAGES_PATH . 'modules/module.post-type.php');
97
+ include_once( LANDINGPAGES_PATH . 'modules/module.track.php');
98
+ include_once( LANDINGPAGES_PATH . 'modules/module.ajax-setup.php');
99
+ include_once( LANDINGPAGES_PATH . 'modules/module.utils.php');
100
+ include_once( LANDINGPAGES_PATH . 'modules/module.sidebar.php');
101
+ include_once( LANDINGPAGES_PATH . 'modules/module.widgets.php');
102
+ include_once( LANDINGPAGES_PATH . 'modules/module.cookies.php');
103
+ include_once( LANDINGPAGES_PATH . 'modules/module.ab-testing.php');
104
+ include_once( LANDINGPAGES_PATH . 'modules/module.click-tracking.php');
105
+ include_once( LANDINGPAGES_PATH . 'modules/module.landing-page.php');
106
+ include_once( LANDINGPAGES_PATH . 'classes/class.load-extensions.php');
107
+ include_once( LANDINGPAGES_PATH . 'modules/module.customizer.php');
108
 
109
  BREAK;
110
  endswitch;
226
  return true;
227
  }
228
 
229
+ }
libraries/class-tgm-plugin-activation.php CHANGED
@@ -2,20 +2,35 @@
2
  /**
3
  * Plugin installation and activation for WordPress themes.
4
  *
 
 
 
 
 
5
  * @package TGM-Plugin-Activation
6
- * @version 2.3.6
7
- * @author Thomas Griffin <thomas@thomasgriffinmedia.com>
8
- * @author Gary Jones <gamajo@gamajo.com>
9
- * @copyright Copyright (c) 2012, Thomas Griffin
10
- * @license http://opensource.org/licenses/gpl-2.0.php GPL v2 or later
11
- * @link https://github.com/thomasgriffin/TGM-Plugin-Activation
 
 
 
 
 
 
 
 
 
 
12
  */
13
 
14
  /*
15
- Copyright 2012 Thomas Griffin (email : thomas@thomasgriffinmedia.com)
16
 
17
  This program is free software; you can redistribute it and/or modify
18
- it under the terms of the GNU General Public License, version 3, as
19
  published by the Free Software Foundation.
20
 
21
  This program is distributed in the hope that it will be useful,
@@ -28,955 +43,1867 @@
28
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29
  */
30
 
31
- if ( ! class_exists( 'TGM_Plugin_Activation' ) ) {
32
- /**
33
- * Automatic plugin installation and activation library.
34
- *
35
- * Creates a way to automatically install and activate plugins from within themes.
36
- * The plugins can be either pre-packaged, downloaded from the WordPress
37
- * Plugin Repository or downloaded from a private repository.
38
- *
39
- * @since 1.0.0
40
- *
41
- * @package TGM-Plugin-Activation
42
- * @author Thomas Griffin <thomas@thomasgriffinmedia.com>
43
- * @author Gary Jones <gamajo@gamajo.com>
44
- */
45
- class TGM_Plugin_Activation {
46
-
47
- /**
48
- * Holds a copy of itself, so it can be referenced by the class name.
49
- *
50
- * @since 1.0.0
51
- *
52
- * @var TGM_Plugin_Activation
53
- */
54
- static $instance;
55
-
56
- /**
57
- * Holds arrays of plugin details.
58
- *
59
- * @since 1.0.0
60
- *
61
- * @var array
62
- */
63
- public $plugins = array();
64
-
65
- /**
66
- * Parent menu slug for plugins page.
67
- *
68
- * @since 2.2.0
69
- *
70
- * @var string Parent menu slug. Defaults to 'themes.php'.
71
- */
72
- public $parent_menu_slug = 'themes.php';
73
-
74
- /**
75
- * Parent URL slug for URL references.
76
- *
77
- * This is useful if you want to place the custom plugins page as a
78
- * submenu item under a custom parent menu.
79
- *
80
- * @since 2.2.0
81
- *
82
- * @var string Parent URL slug. Defaults to 'themes.php'.
83
- */
84
- public $parent_url_slug = 'themes.php';
85
-
86
- /**
87
- * Name of the querystring argument for the admin page.
88
- *
89
- * @since 1.0.0
90
- *
91
- * @var string
92
- */
93
- public $menu = 'install-required-plugins';
94
-
95
- /**
96
- * Text domain for localization support.
97
- *
98
- * @since 1.1.0
99
- *
100
- * @var string
101
- */
102
- public $domain = 'tgmpa';
103
-
104
- /**
105
- * Default absolute path to folder containing pre-packaged plugin zip files.
106
- *
107
- * @since 2.0.0
108
- *
109
- * @var string Absolute path prefix to packaged zip file location. Default is empty string.
110
- */
111
- public $default_path = '';
112
-
113
- /**
114
- * Flag to show admin notices or not.
115
- *
116
- * @since 2.1.0
117
- *
118
- * @var boolean
119
- */
120
- public $has_notices = true;
121
-
122
- /**
123
- * Flag to set automatic activation of plugins. Off by default.
124
- *
125
- * @since 2.2.0
126
- *
127
- * @var boolean
128
- */
129
- public $is_automatic = false;
130
-
131
- /**
132
- * Optional message to display before the plugins table.
133
- *
134
- * @since 2.2.0
135
- *
136
- * @var string Message filtered by wp_kses_post(). Default is empty string.
137
- */
138
- public $message = '';
139
-
140
- /**
141
- * Holds configurable array of strings.
142
- *
143
- * Default values are added in the constructor.
144
- *
145
- * @since 2.0.0
146
- *
147
- * @var array
148
- */
149
- public $strings = array();
150
-
151
- /**
152
- * Adds a reference of this object to $instance, populates default strings,
153
- * does the tgmpa_init action hook, and hooks in the interactions to init.
154
- *
155
- * @since 1.0.0
156
- *
157
- * @see TGM_Plugin_Activation::init()
158
- */
159
- public function __construct() {
160
-
161
- self::$instance =& $this;
162
-
163
- $this->strings = array(
164
- 'page_title' => __( 'Install Required Plugins', $this->domain ),
165
- 'menu_title' => __( 'Install Plugins', $this->domain ),
166
- 'installing' => __( 'Installing Plugin: %s', $this->domain ),
167
- 'oops' => __( 'Something went wrong.', $this->domain ),
168
- 'notice_can_install_required' => _n_noop( 'This theme requires the following plugin: %1$s.', 'This theme requires the following plugins: %1$s.' ),
169
- 'notice_can_install_recommended' => _n_noop( 'This theme recommends the following plugin: %1$s.', 'This theme recommends the following plugins: %1$s.' ),
170
- 'notice_cannot_install' => _n_noop( 'Sorry, but you do not have the correct permissions to install the %s plugin. Contact the administrator of this site for help on getting the plugin installed.', 'Sorry, but you do not have the correct permissions to install the %s plugins. Contact the administrator of this site for help on getting the plugins installed.' ),
171
- 'notice_can_activate_required' => _n_noop( 'The following required plugin is currently inactive: %1$s.', 'The following required plugins are currently inactive: %1$s.' ),
172
- 'notice_can_activate_recommended' => _n_noop( 'The following recommended plugin is currently inactive: %1$s.', 'The following recommended plugins are currently inactive: %1$s.' ),
173
- 'notice_cannot_activate' => _n_noop( 'Sorry, but you do not have the correct permissions to activate the %s plugin. Contact the administrator of this site for help on getting the plugin activated.', 'Sorry, but you do not have the correct permissions to activate the %s plugins. Contact the administrator of this site for help on getting the plugins activated.' ),
174
- 'notice_ask_to_update' => _n_noop( 'The following plugin needs to be updated to its latest version to ensure maximum compatibility with this theme: %1$s.', 'The following plugins need to be updated to their latest version to ensure maximum compatibility with this theme: %1$s.' ),
175
- 'notice_cannot_update' => _n_noop( 'Sorry, but you do not have the correct permissions to update the %s plugin. Contact the administrator of this site for help on getting the plugin updated.', 'Sorry, but you do not have the correct permissions to update the %s plugins. Contact the administrator of this site for help on getting the plugins updated.' ),
176
- 'install_link' => _n_noop( 'Begin installing plugin', 'Begin installing plugins' ),
177
- 'activate_link' => _n_noop( 'Activate installed plugin', 'Activate installed plugins' ),
178
- 'return' => __( 'Return to Required Plugins Installer', $this->domain ),
179
- 'plugin_activated' => __( 'Plugin activated successfully.', $this->domain ),
180
- 'complete' => __( 'All plugins installed and activated successfully. %1$s', $this->domain ),
181
- );
182
-
183
- /** Annouce that the class is ready, and pass the object (for advanced use) */
184
- do_action_ref_array( 'tgmpa_init', array( &$this ) );
185
-
186
- /** When the rest of WP has loaded, kick-start the rest of the class */
187
- add_action( 'init', array( &$this, 'init' ) );
188
-
189
- }
190
-
191
- /**
192
- * Initialise the interactions between this class and WordPress.
193
- *
194
- * Hooks in three new methods for the class: admin_menu, notices and styles.
195
- *
196
- * @since 2.0.0
197
- *
198
- * @see TGM_Plugin_Activation::admin_menu()
199
- * @see TGM_Plugin_Activation::notices()
200
- * @see TGM_Plugin_Activation::styles()
201
- */
202
- public function init() {
203
-
204
- do_action( 'tgmpa_register' );
205
- /** After this point, the plugins should be registered and the configuration set */
206
-
207
- /** Proceed only if we have plugins to handle */
208
- if ( $this->plugins ) {
209
- $sorted = array(); // Prepare variable for sorting
210
-
211
- foreach ( $this->plugins as $plugin )
212
- $sorted[] = $plugin['name'];
213
-
214
- array_multisort( $sorted, SORT_ASC, $this->plugins ); // Sort plugins alphabetically by name
215
-
216
- add_action( 'admin_menu', array( &$this, 'admin_menu' ) );
217
- add_action( 'admin_head', array( &$this, 'dismiss' ) );
218
- add_filter( 'install_plugin_complete_actions', array( &$this, 'actions' ) );
219
-
220
- /** Load admin bar in the header to remove flash when installing plugins */
221
- if ( $this->is_tgmpa_page() ) {
222
- remove_action( 'wp_footer', 'wp_admin_bar_render', 1000 );
223
- remove_action( 'admin_footer', 'wp_admin_bar_render', 1000 );
224
- add_action( 'wp_head', 'wp_admin_bar_render', 1000 );
225
- add_action( 'admin_head', 'wp_admin_bar_render', 1000 );
226
- }
227
-
228
- if ( $this->has_notices ) {
229
- add_action( 'admin_notices', array( &$this, 'notices' ) );
230
- add_action( 'admin_init', array( &$this, 'admin_init' ), 1 );
231
- add_action( 'admin_enqueue_scripts', array( &$this, 'thickbox' ) );
232
- add_action( 'switch_theme', array( &$this, 'update_dismiss' ) );
233
- }
234
-
235
- /** Setup the force activation hook */
236
- foreach ( $this->plugins as $plugin ) {
237
- if ( isset( $plugin['force_activation'] ) && true === $plugin['force_activation'] ) {
238
- add_action( 'admin_init', array( &$this, 'force_activation' ) );
239
- break;
240
- }
241
- }
242
-
243
- /** Setup the force deactivation hook */
244
- foreach ( $this->plugins as $plugin ) {
245
- if ( isset( $plugin['force_deactivation'] ) && true === $plugin['force_deactivation'] ) {
246
- add_action( 'switch_theme', array( &$this, 'force_deactivation' ) );
247
- break;
248
- }
249
- }
250
- }
251
-
252
- }
253
-
254
- /**
255
- * Handles calls to show plugin information via links in the notices.
256
- *
257
- * We get the links in the admin notices to point to the TGMPA page, rather
258
- * than the typical plugin-install.php file, so we can prepare everything
259
- * beforehand.
260
- *
261
- * WP doesn't make it easy to show the plugin information in the thickbox -
262
- * here we have to require a file that includes a function that does the
263
- * main work of displaying it, enqueue some styles, set up some globals and
264
- * finally call that function before exiting.
265
- *
266
- * Down right easy once you know how...
267
- *
268
- * @since 2.1.0
269
- *
270
- * @global string $tab Used as iframe div class names, helps with styling
271
- * @global string $body_id Used as the iframe body ID, helps with styling
272
- * @return null Returns early if not the TGMPA page.
273
- */
274
- public function admin_init() {
275
-
276
- if ( ! $this->is_tgmpa_page() )
277
- return;
278
-
279
- if ( isset( $_REQUEST['tab'] ) && 'plugin-information' == $_REQUEST['tab'] ) {
280
- require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; // Need for install_plugin_information()
281
-
282
- wp_enqueue_style( 'plugin-install' );
283
-
284
- global $tab, $body_id;
285
- $body_id = $tab = 'plugin-information';
286
-
287
- install_plugin_information();
288
-
289
- exit;
290
- }
291
-
292
- }
293
-
294
- /**
295
- * Enqueues thickbox scripts/styles for plugin info.
296
- *
297
- * Thickbox is not automatically included on all admin pages, so we must
298
- * manually enqueue it for those pages.
299
- *
300
- * Thickbox is only loaded if the user has not dismissed the admin
301
- * notice or if there are any plugins left to install and activate.
302
- *
303
- * @since 2.1.0
304
- */
305
- public function thickbox() {
306
-
307
- if ( ! get_user_meta( get_current_user_id(), 'tgmpa_dismissed_notice', true ) )
308
- add_thickbox();
309
-
310
- }
311
-
312
- /**
313
- * Adds submenu page under 'Appearance' tab.
314
- *
315
- * This method adds the submenu page letting users know that a required
316
- * plugin needs to be installed.
317
- *
318
- * This page disappears once the plugin has been installed and activated.
319
- *
320
- * @since 1.0.0
321
- *
322
- * @see TGM_Plugin_Activation::init()
323
- * @see TGM_Plugin_Activation::install_plugins_page()
324
- */
325
- public function admin_menu() {
326
-
327
- // Make sure privileges are correct to see the page
328
- if ( ! current_user_can( 'install_plugins' ) )
329
- return;
330
-
331
- $this->populate_file_path();
332
-
333
- foreach ( $this->plugins as $plugin ) {
334
- if ( ! is_plugin_active( $plugin['file_path'] ) ) {
335
- add_submenu_page(
336
- $this->parent_menu_slug, // Parent menu slug
337
- $this->strings['page_title'], // Page title
338
- $this->strings['menu_title'], // Menu title
339
- 'edit_theme_options', // Capability
340
- $this->menu, // Menu slug
341
- array( &$this, 'install_plugins_page' ) // Callback
342
- );
343
- break;
344
- }
345
- }
346
-
347
- }
348
-
349
- /**
350
- * Echoes plugin installation form.
351
- *
352
- * This method is the callback for the admin_menu method function.
353
- * This displays the admin page and form area where the user can select to install and activate the plugin.
354
- *
355
- * @since 1.0.0
356
- *
357
- * @return null Aborts early if we're processing a plugin installation action
358
- */
359
- public function install_plugins_page() {
360
-
361
- /** Store new instance of plugin table in object */
362
- $plugin_table = new TGMPA_List_Table;
363
-
364
- /** Return early if processing a plugin installation action */
365
- if ( isset( $_POST[sanitize_key( 'action' )] ) && 'tgmpa-bulk-install' == $_POST[sanitize_key( 'action' )] && $plugin_table->process_bulk_actions() || $this->do_plugin_install() )
366
- return;
367
-
368
- ?>
369
- <div class="tgmpa wrap">
370
-
371
- <?php screen_icon( apply_filters( 'tgmpa_default_screen_icon', 'themes' ) ); ?>
372
- <h2><?php echo esc_html( get_admin_page_title() ); ?></h2>
373
- <?php $plugin_table->prepare_items(); ?>
374
-
375
- <?php if ( isset( $this->message ) ) _e( wp_kses_post( $this->message ), $this->domain ); ?>
376
-
377
- <form id="tgmpa-plugins" action="" method="post">
378
- <input type="hidden" name="tgmpa-page" value="<?php echo $this->menu; ?>" />
379
- <?php $plugin_table->display(); ?>
380
- </form>
381
-
382
- </div>
383
- <?php
384
-
385
- }
386
-
387
- /**
388
- * Installs a plugin or activates a plugin depending on the hover
389
- * link clicked by the user.
390
- *
391
- * Checks the $_GET variable to see which actions have been
392
- * passed and responds with the appropriate method.
393
- *
394
- * Uses WP_Filesystem to process and handle the plugin installation
395
- * method.
396
- *
397
- * @since 1.0.0
398
- *
399
- * @uses WP_Filesystem
400
- * @uses WP_Error
401
- * @uses WP_Upgrader
402
- * @uses Plugin_Upgrader
403
- * @uses Plugin_Installer_Skin
404
- *
405
- * @return boolean True on success, false on failure
406
- */
407
- protected function do_plugin_install() {
408
-
409
- /** All plugin information will be stored in an array for processing */
410
- $plugin = array();
411
-
412
- /** Checks for actions from hover links to process the installation */
413
- if ( isset( $_GET[sanitize_key( 'plugin' )] ) && ( isset( $_GET[sanitize_key( 'tgmpa-install' )] ) && 'install-plugin' == $_GET[sanitize_key( 'tgmpa-install' )] ) ) {
414
- check_admin_referer( 'tgmpa-install' );
415
-
416
- $plugin['name'] = $_GET[sanitize_key( 'plugin_name' )]; // Plugin name
417
- $plugin['slug'] = $_GET[sanitize_key( 'plugin' )]; // Plugin slug
418
- $plugin['source'] = $_GET[sanitize_key( 'plugin_source' )]; // Plugin source
419
-
420
- /** Pass all necessary information via URL if WP_Filesystem is needed */
421
- $url = wp_nonce_url(
422
- add_query_arg(
423
- array(
424
- 'page' => $this->menu,
425
- 'plugin' => $plugin['slug'],
426
- 'plugin_name' => $plugin['name'],
427
- 'plugin_source' => $plugin['source'],
428
- 'tgmpa-install' => 'install-plugin',
429
- ),
430
- admin_url( $this->parent_url_slug )
431
- ),
432
- 'tgmpa-install'
433
- );
434
- $method = ''; // Leave blank so WP_Filesystem can populate it as necessary
435
- $fields = array( sanitize_key( 'tgmpa-install' ) ); // Extra fields to pass to WP_Filesystem
436
-
437
- if ( false === ( $creds = request_filesystem_credentials( $url, $method, false, false, $fields ) ) )
438
- return true;
439
-
440
- if ( ! WP_Filesystem( $creds ) ) {
441
- request_filesystem_credentials( $url, $method, true, false, $fields ); // Setup WP_Filesystem
442
- return true;
443
- }
444
-
445
- require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; // Need for plugins_api
446
- require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; // Need for upgrade classes
447
-
448
- /** Set plugin source to WordPress API link if available */
449
- if ( isset( $plugin['source'] ) && 'repo' == $plugin['source'] ) {
450
- $api = plugins_api( 'plugin_information', array( 'slug' => $plugin['slug'], 'fields' => array( 'sections' => false ) ) );
451
-
452
- if ( is_wp_error( $api ) )
453
- wp_die( $this->strings['oops'] . var_dump( $api ) );
454
-
455
- if ( isset( $api->download_link ) )
456
- $plugin['source'] = $api->download_link;
457
- }
458
-
459
- /** Set type, based on whether the source starts with http:// or https:// */
460
- $type = preg_match( '|^http(s)?://|', $plugin['source'] ) ? 'web' : 'upload';
461
-
462
- /** Prep variables for Plugin_Installer_Skin class */
463
- $title = sprintf( $this->strings['installing'], $plugin['name'] );
464
- $url = add_query_arg( array( 'action' => 'install-plugin', 'plugin' => $plugin['slug'] ), 'update.php' );
465
- if ( isset( $_GET['from'] ) )
466
- $url .= add_query_arg( 'from', urlencode( stripslashes( $_GET['from'] ) ), $url );
467
-
468
- $nonce = 'install-plugin_' . $plugin['slug'];
469
-
470
- /** Prefix a default path to pre-packaged plugins */
471
- $source = ( 'upload' == $type ) ? $this->default_path . $plugin['source'] : $plugin['source'];
472
-
473
- /** Create a new instance of Plugin_Upgrader */
474
- $upgrader = new Plugin_Upgrader( $skin = new Plugin_Installer_Skin( compact( 'type', 'title', 'url', 'nonce', 'plugin', 'api' ) ) );
475
-
476
- /** Perform the action and install the plugin from the $source urldecode() */
477
- $upgrader->install( $source );
478
-
479
- /** Flush plugins cache so we can make sure that the installed plugins list is always up to date */
480
- wp_cache_flush();
481
-
482
- /** Only activate plugins if the config option is set to true */
483
- if ( $this->is_automatic ) {
484
- $plugin_activate = $upgrader->plugin_info(); // Grab the plugin info from the Plugin_Upgrader method
485
- $activate = activate_plugin( $plugin_activate ); // Activate the plugin
486
- $this->populate_file_path(); // Re-populate the file path now that the plugin has been installed and activated
487
-
488
- if ( is_wp_error( $activate ) ) {
489
- echo '<div id="message" class="error"><p>' . $activate->get_error_message() . '</p></div>';
490
- echo '<p><a href="' . add_query_arg( 'page', $this->menu, admin_url( $this->parent_url_slug ) ) . '" title="' . esc_attr( $this->strings['return'] ) . '" target="_parent">' . __( 'Return to Required Plugins Installer', $this->domain ) . '</a></p>';
491
- return true; // End it here if there is an error with automatic activation
492
- }
493
- else {
494
- echo '<p>' . $this->strings['plugin_activated'] . '</p>';
495
- }
496
- }
497
-
498
- /** Display message based on if all plugins are now active or not */
499
- $complete = array();
500
- foreach ( $this->plugins as $plugin ) {
501
- if ( ! is_plugin_active( $plugin['file_path'] ) ) {
502
- echo '<p><a href="' . add_query_arg( 'page', $this->menu, admin_url( $this->parent_url_slug ) ) . '" title="' . esc_attr( $this->strings['return'] ) . '" target="_parent">' . __( $this->strings['return'], $this->domain ) . '</a></p>';
503
- $complete[] = $plugin;
504
- break;
505
- }
506
- /** Nothing to store */
507
- else {
508
- $complete[] = '';
509
- }
510
- }
511
-
512
- /** Filter out any empty entries */
513
- $complete = array_filter( $complete );
514
-
515
- /** All plugins are active, so we display the complete string and hide the plugin menu */
516
- if ( empty( $complete ) ) {
517
- echo '<p>' . sprintf( $this->strings['complete'], '<a href="' . admin_url() . '" title="' . __( 'Return to the Dashboard', $this->domain ) . '">' . __( 'Return to the Dashboard', $this->domain ) . '</a>' ) . '</p>';
518
- echo '<style type="text/css">#adminmenu .wp-submenu li.current { display: none !important; }</style>';
519
- }
520
-
521
- return true;
522
- }
523
- /** Checks for actions from hover links to process the activation */
524
- elseif ( isset( $_GET[sanitize_key( 'plugin' )] ) && ( isset( $_GET[sanitize_key( 'tgmpa-activate' )] ) && 'activate-plugin' == $_GET[sanitize_key( 'tgmpa-activate' )] ) ) {
525
- check_admin_referer( 'tgmpa-activate', 'tgmpa-activate-nonce' );
526
-
527
- /** Populate $plugin array with necessary information */
528
- $plugin['name'] = $_GET[sanitize_key( 'plugin_name' )];
529
- $plugin['slug'] = $_GET[sanitize_key( 'plugin' )];
530
- $plugin['source'] = $_GET[sanitize_key( 'plugin_source' )];
531
-
532
- $plugin_data = get_plugins( '/' . $plugin['slug'] ); // Retrieve all plugins
533
- $plugin_file = array_keys( $plugin_data ); // Retrieve all plugin files from installed plugins
534
- $plugin_to_activate = $plugin['slug'] . '/' . $plugin_file[0]; // Match plugin slug with appropriate plugin file
535
- $activate = activate_plugin( $plugin_to_activate ); // Activate the plugin
536
-
537
- if ( is_wp_error( $activate ) ) {
538
- echo '<div id="message" class="error"><p>' . $activate->get_error_message() . '</p></div>';
539
- echo '<p><a href="' . add_query_arg( 'page', $this->menu, admin_url( $this->parent_url_slug ) ) . '" title="' . esc_attr( $this->strings['return'] ) . '" target="_parent">' . __( $this->strings['return'], $this->domain ) . '</a></p>';
540
- return true; // End it here if there is an error with activation
541
- }
542
- else {
543
- /** Make sure message doesn't display again if bulk activation is performed immediately after a single activation */
544
- if ( ! isset( $_POST[sanitize_key( 'action' )] ) ) {
545
- $msg = sprintf( __( 'The following plugin was activated successfully: %s.', $this->domain ), '<strong>' . $plugin['name'] . '</strong>' );
546
- echo '<div id="message" class="updated"><p>' . $msg . '</p></div>';
547
- }
548
- }
549
- }
550
-
551
- return false;
552
-
553
- }
554
-
555
- /**
556
- * Echoes required plugin notice.
557
- *
558
- * Outputs a message telling users that a specific plugin is required for
559
- * their theme. If appropriate, it includes a link to the form page where
560
- * users can install and activate the plugin.
561
- *
562
- * @since 1.0.0
563
- *
564
- * @global object $current_screen
565
- * @return null Returns early if we're on the Install page
566
- */
567
- public function notices() {
568
-
569
- global $current_screen;
570
-
571
- /** Remove nag on the install page */
572
- if ( $this->is_tgmpa_page() )
573
- return;
574
-
575
- $installed_plugins = get_plugins(); // Retrieve a list of all the plugins
576
- $this->populate_file_path();
577
-
578
- $message = array(); // Store the messages in an array to be outputted after plugins have looped through
579
- $install_link = false; // Set to false, change to true in loop if conditions exist, used for action link 'install'
580
- $install_link_count = 0; // Used to determine plurality of install action link text
581
- $activate_link = false; // Set to false, change to true in loop if conditions exist, used for action link 'activate'
582
- $activate_link_count = 0; // Used to determine plurality of activate action link text
583
-
584
- foreach ( $this->plugins as $plugin ) {
585
- /** If the plugin is installed and active, check for minimum version argument before moving forward */
586
- if ( is_plugin_active( $plugin['file_path'] ) ) {
587
- /** A minimum version has been specified */
588
- if ( isset( $plugin['version'] ) ) {
589
- if ( isset( $installed_plugins[$plugin['file_path']]['Version'] ) ) {
590
- /** If the current version is less than the minimum required version, we display a message */
591
- if ( version_compare( $installed_plugins[$plugin['file_path']]['Version'], $plugin['version'], '<' ) ) {
592
- if ( current_user_can( 'install_plugins' ) )
593
- $message['notice_ask_to_update'][] = $plugin['name'];
594
- else
595
- $message['notice_cannot_update'][] = $plugin['name'];
596
- }
597
- }
598
- /** Can't find the plugin, so iterate to the next condition */
599
- else {
600
- continue;
601
- }
602
- }
603
- /** No minimum version specified, so iterate over the plugin */
604
- else {
605
- continue;
606
- }
607
- }
608
-
609
- /** Not installed */
610
- if ( ! isset( $installed_plugins[$plugin['file_path']] ) ) {
611
- $install_link = true; // We need to display the 'install' action link
612
- $install_link_count++; // Increment the install link count
613
- if ( current_user_can( 'install_plugins' ) ) {
614
- if ( $plugin['required'] )
615
- $message['notice_can_install_required'][] = $plugin['name'];
616
- /** This plugin is only recommended */
617
- else
618
- $message['notice_can_install_recommended'][] = $plugin['name'];
619
- }
620
- /** Need higher privileges to install the plugin */
621
- else {
622
- $message['notice_cannot_install'][] = $plugin['name'];
623
- }
624
- }
625
- /** Installed but not active */
626
- elseif ( is_plugin_inactive( $plugin['file_path'] ) ) {
627
- $activate_link = true; // We need to display the 'activate' action link
628
- $activate_link_count++; // Increment the activate link count
629
- if ( current_user_can( 'activate_plugins' ) ) {
630
- if ( ( isset( $plugin['required'] ) ) && ( $plugin['required'] ) )
631
- $message['notice_can_activate_required'][] = $plugin['name'];
632
- /** This plugin is only recommended */
633
- else {
634
- $message['notice_can_activate_recommended'][] = $plugin['name'];
635
- }
636
- }
637
- /** Need higher privileges to activate the plugin */
638
- else {
639
- $message['notice_cannot_activate'][] = $plugin['name'];
640
- }
641
- }
642
- }
643
-
644
- /** Only process the nag messages if the user has not dismissed them already */
645
- if ( ! get_user_meta( get_current_user_id(), 'tgmpa_dismissed_notice', true ) ) {
646
- /** If we have notices to display, we move forward */
647
- if ( ! empty( $message ) ) {
648
- krsort( $message ); // Sort messages
649
- $rendered = ''; // Display all nag messages as strings
650
-
651
- /** Grab all plugin names */
652
- foreach ( $message as $type => $plugin_groups ) {
653
- $linked_plugin_groups = array();
654
-
655
- /** Count number of plugins in each message group to calculate singular/plural message */
656
- $count = count( $plugin_groups );
657
-
658
- /** Loop through the plugin names to make the ones pulled from the .org repo linked */
659
- foreach ( $plugin_groups as $plugin_group_single_name ) {
660
- $external_url = $this->_get_plugin_data_from_name( $plugin_group_single_name, 'external_url' );
661
- $source = $this->_get_plugin_data_from_name( $plugin_group_single_name, 'source' );
662
-
663
- if ( $external_url && preg_match( '|^http(s)?://|', $external_url ) ) {
664
- $linked_plugin_groups[] = '<a href="' . esc_url( $external_url ) . '" title="' . $plugin_group_single_name . '" target="_blank">' . $plugin_group_single_name . '</a>';
665
- }
666
- elseif ( ! $source || preg_match( '|^http://wordpress.org/extend/plugins/|', $source ) ) {
667
- $url = add_query_arg(
668
- array(
669
- 'tab' => 'plugin-information',
670
- 'plugin' => $this->_get_plugin_data_from_name( $plugin_group_single_name ),
671
- 'TB_iframe' => 'true',
672
- 'width' => '640',
673
- 'height' => '500',
674
- ),
675
- admin_url( 'plugin-install.php' )
676
- );
677
-
678
- $linked_plugin_groups[] = '<a href="' . esc_url( $url ) . '" class="thickbox" title="' . $plugin_group_single_name . '">' . $plugin_group_single_name . '</a>';
679
- }
680
- else {
681
- $linked_plugin_groups[] = $plugin_group_single_name; // No hyperlink
682
- }
683
-
684
- if ( isset( $linked_plugin_groups ) && (array) $linked_plugin_groups )
685
- $plugin_groups = $linked_plugin_groups;
686
- }
687
-
688
- $last_plugin = array_pop( $plugin_groups ); // Pop off last name to prep for readability
689
- $imploded = empty( $plugin_groups ) ? '<em>' . $last_plugin . '</em>' : '<em>' . ( implode( ', ', $plugin_groups ) . '</em><em>' . $last_plugin . '</em>' );
690
-
691
- $rendered .= '<p>' . sprintf( translate_nooped_plural( $this->strings[$type], $count, $this->domain ), $imploded, $count ) . '</p>'; // All messages now stored
692
- }
693
-
694
- /** Setup variables to determine if action links are needed */
695
- $show_install_link = $install_link ? '<a href="' . add_query_arg( 'page', $this->menu, admin_url( $this->parent_url_slug ) ) . '">' . translate_nooped_plural( $this->strings['install_link'], $install_link_count, $this->domain ) . '</a>' : '';
696
- $show_activate_link = $activate_link ? '<a href="' . admin_url( 'themes.php?page=install-inbound-plugins' ) . '">' . translate_nooped_plural( $this->strings['activate_link'], $activate_link_count, $this->domain ) . '</a>' : '';
697
-
698
- /** Define all of the action links */
699
- $action_links = apply_filters(
700
- 'tgmpa_notice_action_links',
701
- array(
702
- 'install' => ( current_user_can( 'install_plugins' ) ) ? $show_install_link : '',
703
- 'activate' => ( current_user_can( 'activate_plugins' ) ) ? $show_activate_link : '',
704
- 'dismiss' => '<a class="dismiss-notice" href="' . add_query_arg( 'tgmpa-dismiss', 'dismiss_admin_notices' ) . '" target="_parent">' . __( 'Dismiss this notice', $this->domain ) . '</a>',
705
- )
706
- );
707
-
708
- $action_links = array_filter( $action_links ); // Remove any empty array items
709
- if ( $action_links )
710
- $rendered .= '<p>' . implode( ' | ', $action_links ) . '</p>';
711
-
712
- /** Register the nag messages and prepare them to be processed */
713
- if ( isset( $this->strings['nag_type'] ) )
714
- add_settings_error( 'tgmpa', 'tgmpa', $rendered, sanitize_html_class( strtolower( $this->strings['nag_type'] ), 'updated' ) );
715
- else
716
- add_settings_error( 'tgmpa', 'tgmpa', $rendered, 'updated' );
717
- }
718
- }
719
-
720
- /** Admin options pages already output settings_errors, so this is to avoid duplication */
721
- if ( 'options-general' !== $current_screen->parent_base )
722
- settings_errors( 'tgmpa' );
723
-
724
- }
725
-
726
- /**
727
- * Add dismissable admin notices.
728
- *
729
- * Appends a link to the admin nag messages. If clicked, the admin notice disappears and no longer is visible to users.
730
- *
731
- * @since 2.1.0
732
- */
733
- public function dismiss() {
734
-
735
- if ( isset( $_GET[sanitize_key( 'tgmpa-dismiss' )] ) )
736
- update_user_meta( get_current_user_id(), 'tgmpa_dismissed_notice', 1 );
737
-
738
- }
739
-
740
- /**
741
- * Add individual plugin to our collection of plugins.
742
- *
743
- * If the required keys are not set, the plugin is not added.
744
- *
745
- * @since 2.0.0
746
- *
747
- * @param array $plugin Array of plugin arguments.
748
- */
749
- public function register( $plugin ) {
750
-
751
- if ( ! isset( $plugin['slug'] ) || ! isset( $plugin['name'] ) )
752
- return;
753
-
754
- $this->plugins[] = $plugin;
755
-
756
- }
757
-
758
- /**
759
- * Amend default configuration settings.
760
- *
761
- * @since 2.0.0
762
- *
763
- * @param array $config
764
- */
765
- public function config( $config ) {
766
-
767
- $keys = array( 'default_path', 'parent_menu_slug', 'parent_url_slug', 'domain', 'has_notices', 'menu', 'is_automatic', 'message', 'strings' );
768
-
769
- foreach ( $keys as $key ) {
770
- if ( isset( $config[$key] ) ) {
771
- if ( is_array( $config[$key] ) ) {
772
- foreach ( $config[$key] as $subkey => $value )
773
- $this->{$key}[$subkey] = $value;
774
- } else {
775
- $this->$key = $config[$key];
776
- }
777
- }
778
- }
779
-
780
- }
781
-
782
- /**
783
- * Amend action link after plugin installation.
784
- *
785
- * @since 2.0.0
786
- *
787
- * @param array $install_actions Existing array of actions
788
- * @return array Amended array of actions
789
- */
790
- public function actions( $install_actions ) {
791
-
792
- /** Remove action links on the TGMPA install page */
793
- if ( $this->is_tgmpa_page() )
794
- return false;
795
-
796
- return $install_actions;
797
-
798
- }
799
-
800
- /**
801
- * Set file_path key for each installed plugin.
802
- *
803
- * @since 2.1.0
804
- */
805
- public function populate_file_path() {
806
-
807
- /** Add file_path key for all plugins */
808
- foreach ( $this->plugins as $plugin => $values )
809
- $this->plugins[$plugin]['file_path'] = $this->_get_plugin_basename_from_slug( $values['slug'] );
810
-
811
- }
812
-
813
- /**
814
- * Helper function to extract the file path of the plugin file from the
815
- * plugin slug, if the plugin is installed.
816
- *
817
- * @since 2.0.0
818
- *
819
- * @param string $slug Plugin slug (typically folder name) as provided by the developer
820
- * @return string Either file path for plugin if installed, or just the plugin slug
821
- */
822
- protected function _get_plugin_basename_from_slug( $slug ) {
823
-
824
- $keys = array_keys( get_plugins() );
825
-
826
- foreach ( $keys as $key ) {
827
- if ( preg_match( '|^' . $slug .'|', $key ) )
828
- return $key;
829
- }
830
-
831
- return $slug;
832
-
833
- }
834
-
835
- /**
836
- * Retrieve plugin data, given the plugin name.
837
- *
838
- * Loops through the registered plugins looking for $name. If it finds it,
839
- * it returns the $data from that plugin. Otherwise, returns false.
840
- *
841
- * @since 2.1.0
842
- *
843
- * @param string $name Name of the plugin, as it was registered
844
- * @param string $data Optional. Array key of plugin data to return. Default is slug
845
- * @return string|boolean Plugin slug if found, false otherwise.
846
- */
847
- protected function _get_plugin_data_from_name( $name, $data = 'slug' ) {
848
-
849
- foreach ( $this->plugins as $plugin => $values ) {
850
- if ( $name == $values['name'] && isset( $values[$data] ) )
851
- return $values[$data];
852
- }
853
-
854
- return false;
855
-
856
- }
857
-
858
- /**
859
- * Determine if we're on the TGMPA Install page.
860
- *
861
- * We use $current_screen when it is available, and a slightly less ideal
862
- * conditional when it isn't (like when displaying the plugin information
863
- * thickbox).
864
- *
865
- * @since 2.1.0
866
- *
867
- * @global object $current_screen
868
- * @return boolean True when on the TGMPA page, false otherwise.
869
- */
870
- protected function is_tgmpa_page() {
871
-
872
- global $current_screen;
873
-
874
- if ( ! is_null( $current_screen ) && $this->parent_menu_slug == $current_screen->parent_file && isset( $_GET['page'] ) && $this->menu === $_GET['page'] )
875
- return true;
876
-
877
- if ( isset( $_GET['page'] ) && $this->menu === $_GET['page'] )
878
- return true;
879
-
880
- return false;
881
-
882
- }
883
-
884
- /**
885
- * Delete dismissable nag option when theme is switched.
886
- *
887
- * This ensures that the user is again reminded via nag of required
888
- * and/or recommended plugins if they re-activate the theme.
889
- *
890
- * @since 2.1.1
891
- */
892
- public function update_dismiss() {
893
-
894
- delete_user_meta( get_current_user_id(), 'tgmpa_dismissed_notice' );
895
-
896
- }
897
-
898
- /**
899
- * Forces plugin activation if the parameter 'force_activation' is
900
- * set to true.
901
- *
902
- * This allows theme authors to specify certain plugins that must be
903
- * active at all times while using the current theme.
904
- *
905
- * Please take special care when using this parameter as it has the
906
- * potential to be harmful if not used correctly. Setting this parameter
907
- * to true will not allow the specified plugin to be deactivated unless
908
- * the user switches themes.
909
- *
910
- * @since 2.2.0
911
- */
912
- public function force_activation() {
913
-
914
- /** Set file_path parameter for any installed plugins */
915
- $this->populate_file_path();
916
-
917
- $installed_plugins = get_plugins();
918
-
919
- foreach ( $this->plugins as $plugin ) {
920
- /** Oops, plugin isn't there so iterate to next condition */
921
- if ( isset( $plugin['force_activation'] ) && $plugin['force_activation'] && ! isset( $installed_plugins[$plugin['file_path']] ) )
922
- continue;
923
- /** There we go, activate the plugin */
924
- elseif ( isset( $plugin['force_activation'] ) && $plugin['force_activation'] && is_plugin_inactive( $plugin['file_path'] ) )
925
- activate_plugin( $plugin['file_path'] );
926
- }
927
-
928
- }
929
-
930
- /**
931
- * Forces plugin deactivation if the parameter 'force_deactivation'
932
- * is set to true.
933
- *
934
- * This allows theme authors to specify certain plugins that must be
935
- * deactived upon switching from the current theme to another.
936
- *
937
- * Please take special care when using this parameter as it has the
938
- * potential to be harmful if not used correctly.
939
- *
940
- * @since 2.2.0
941
- */
942
- public function force_deactivation() {
943
-
944
- /** Set file_path parameter for any installed plugins */
945
- $this->populate_file_path();
946
-
947
- foreach ( $this->plugins as $plugin ) {
948
- /** Only proceed forward if the paramter is set to true and plugin is active */
949
- if ( isset( $plugin['force_deactivation'] ) && $plugin['force_deactivation'] && is_plugin_active( $plugin['file_path'] ) )
950
- deactivate_plugins( $plugin['file_path'] );
951
- }
952
-
953
- }
954
-
955
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
956
  }
957
 
958
- /** Create a new instance of the class */
959
- new TGM_Plugin_Activation;
960
-
961
- if ( ! function_exists( 'tgmpa' ) ) {
962
- /**
963
- * Helper function to register a collection of required plugins.
964
- *
965
- * @since 2.0.0
966
- * @api
967
- *
968
- * @param array $plugins An array of plugin arrays
969
- * @param array $config Optional. An array of configuration values
970
- */
971
- function tgmpa( $plugins, $config = array() ) {
972
-
973
- foreach ( $plugins as $plugin )
974
- TGM_Plugin_Activation::$instance->register( $plugin );
975
-
976
- if ( $config )
977
- TGM_Plugin_Activation::$instance->config( $config );
978
-
979
- }
980
  }
981
 
982
  /**
@@ -985,599 +1912,939 @@ if ( ! function_exists( 'tgmpa' ) ) {
985
  *
986
  * @since 2.2.0
987
  */
988
- if ( ! class_exists( 'WP_List_Table' ) )
989
- require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
990
-
991
- if ( ! class_exists( 'TGMPA_List_Table' ) ) {
992
- /**
993
- * List table class for handling plugins.
994
- *
995
- * Extends the WP_List_Table class to provide a future-compatible
996
- * way of listing out all required/recommended plugins.
997
- *
998
- * Gives users an interface similar to the Plugin Administration
999
- * area with similar (albeit stripped down) capabilities.
1000
- *
1001
- * This class also allows for the bulk install of plugins.
1002
- *
1003
- * @since 2.2.0
1004
- *
1005
- * @package TGM-Plugin-Activation
1006
- * @author Thomas Griffin <thomas@thomasgriffinmedia.com>
1007
- * @author Gary Jones <gamajo@gamajo.com>
1008
- */
1009
- class TGMPA_List_Table extends WP_List_Table {
1010
-
1011
- /**
1012
- * References parent constructor and sets defaults for class.
1013
- *
1014
- * The constructor also grabs a copy of $instance from the TGMPA class
1015
- * and stores it in the global object TGM_Plugin_Activation::$instance.
1016
- *
1017
- * @since 2.2.0
1018
- *
1019
- * @global unknown $status
1020
- * @global string $page
1021
- */
1022
- public function __construct() {
1023
-
1024
- global $status, $page;
1025
-
1026
- parent::__construct(
1027
- array(
1028
- 'singular' => 'plugin',
1029
- 'plural' => 'plugins',
1030
- 'ajax' => false,
1031
- )
1032
- );
1033
-
1034
- }
1035
-
1036
- /**
1037
- * Gathers and renames all of our plugin information to be used by
1038
- * WP_List_Table to create our table.
1039
- *
1040
- * @since 2.2.0
1041
- *
1042
- * @return array $table_data Information for use in table
1043
- */
1044
- protected function _gather_plugin_data() {
1045
-
1046
- /** Load thickbox for plugin links */
1047
- TGM_Plugin_Activation::$instance->admin_init();
1048
- TGM_Plugin_Activation::$instance->thickbox();
1049
-
1050
- /** Prep variables for use and grab list of all installed plugins */
1051
- $table_data = array();
1052
- $i = 0;
1053
- $installed_plugins = get_plugins();
1054
-
1055
- foreach ( TGM_Plugin_Activation::$instance->plugins as $plugin ) {
1056
- if ( is_plugin_active( $plugin['file_path'] ) )
1057
- continue; // No need to display plugins if they are installed and activated
1058
-
1059
- $table_data[$i]['sanitized_plugin'] = $plugin['name'];
1060
- $table_data[$i]['slug'] = $this->_get_plugin_data_from_name( $plugin['name'] );
1061
-
1062
- $external_url = $this->_get_plugin_data_from_name( $plugin['name'], 'external_url' );
1063
- $source = $this->_get_plugin_data_from_name( $plugin['name'], 'source' );
1064
-
1065
- if ( $external_url && preg_match( '|^http(s)?://|', $external_url ) ) {
1066
- $table_data[$i]['plugin'] = '<strong><a href="' . esc_url( $external_url ) . '" title="' . $plugin['name'] . '" target="_blank">' . $plugin['name'] . '</a></strong>';
1067
- }
1068
- elseif ( ! $source || preg_match( '|^http://wordpress.org/extend/plugins/|', $source ) ) {
1069
- $url = add_query_arg(
1070
- array(
1071
- 'tab' => 'plugin-information',
1072
- 'plugin' => $this->_get_plugin_data_from_name( $plugin['name'] ),
1073
- 'TB_iframe' => 'true',
1074
- 'width' => '640',
1075
- 'height' => '500',
1076
- ),
1077
- admin_url( 'plugin-install.php' )
1078
- );
1079
-
1080
- $table_data[$i]['plugin'] = '<strong><a href="' . esc_url( $url ) . '" class="thickbox" title="' . $plugin['name'] . '">' . $plugin['name'] . '</a></strong>';
1081
- }
1082
- else {
1083
- $table_data[$i]['plugin'] = '<strong>' . $plugin['name'] . '</strong>'; // No hyperlink
1084
- }
1085
-
1086
- if ( isset( $table_data[$i]['plugin'] ) && (array) $table_data[$i]['plugin'] )
1087
- $plugin['name'] = $table_data[$i]['plugin'];
1088
-
1089
- if ( isset( $plugin['external_url'] ) ) {
1090
- /** The plugin is linked to an external source */
1091
- $table_data[$i]['source'] = __( 'External Link', TGM_Plugin_Activation::$instance->domain );
1092
- }
1093
- elseif ( isset( $plugin['source'] ) ) {
1094
- /** The plugin must be from a private repository */
1095
- if ( preg_match( '|^http(s)?://|', $plugin['source'] ) )
1096
- $table_data[$i]['source'] = __( 'Private Repository', TGM_Plugin_Activation::$instance->domain );
1097
- /** The plugin is pre-packaged with the theme */
1098
- else
1099
- $table_data[$i]['source'] = __( 'Pre-Packaged', TGM_Plugin_Activation::$instance->domain );
1100
- }
1101
- /** The plugin is from the WordPress repository */
1102
- else {
1103
- $table_data[$i]['source'] = __( 'WordPress Repository', TGM_Plugin_Activation::$instance->domain );
1104
- }
1105
-
1106
- $table_data[$i]['type'] = $plugin['required'] ? __( 'Required', TGM_Plugin_Activation::$instance->domain ) : __( 'Recommended', TGM_Plugin_Activation::$instance->domain );
1107
-
1108
- if ( ! isset( $installed_plugins[$plugin['file_path']] ) )
1109
- $table_data[$i]['status'] = sprintf( '%1$s', __( 'Not Installed', TGM_Plugin_Activation::$instance->domain ) );
1110
- elseif ( is_plugin_inactive( $plugin['file_path'] ) )
1111
- $table_data[$i]['status'] = sprintf( '%1$s', __( 'Installed But Not Activated', TGM_Plugin_Activation::$instance->domain ) );
1112
-
1113
- $table_data[$i]['file_path'] = $plugin['file_path'];
1114
- $table_data[$i]['url'] = isset( $plugin['source'] ) ? $plugin['source'] : 'repo';
1115
-
1116
- $i++;
1117
- }
1118
-
1119
- /** Sort plugins by Required/Recommended type and by alphabetical listing within each type */
1120
- $resort = array();
1121
- $req = array();
1122
- $rec = array();
1123
-
1124
- /** Grab all the plugin types */
1125
- foreach ( $table_data as $plugin )
1126
- $resort[] = $plugin['type'];
1127
-
1128
- /** Sort each plugin by type */
1129
- foreach ( $resort as $type )
1130
- if ( 'Required' == $type )
1131
- $req[] = $type;
1132
- else
1133
- $rec[] = $type;
1134
-
1135
- /** Sort alphabetically each plugin type array, merge them and then sort in reverse (lists Required plugins first) */
1136
- sort( $req );
1137
- sort( $rec );
1138
- array_merge( $resort, $req, $rec );
1139
- array_multisort( $resort, SORT_DESC, $table_data );
1140
-
1141
- return $table_data;
1142
-
1143
- }
1144
-
1145
- /**
1146
- * Retrieve plugin data, given the plugin name. Taken from the
1147
- * TGM_Plugin_Activation class.
1148
- *
1149
- * Loops through the registered plugins looking for $name. If it finds it,
1150
- * it returns the $data from that plugin. Otherwise, returns false.
1151
- *
1152
- * @since 2.2.0
1153
- *
1154
- * @param string $name Name of the plugin, as it was registered
1155
- * @param string $data Optional. Array key of plugin data to return. Default is slug
1156
- * @return string|boolean Plugin slug if found, false otherwise
1157
- */
1158
- protected function _get_plugin_data_from_name( $name, $data = 'slug' ) {
1159
-
1160
- foreach ( TGM_Plugin_Activation::$instance->plugins as $plugin => $values ) {
1161
- if ( $name == $values['name'] && isset( $values[$data] ) )
1162
- return $values[$data];
1163
- }
1164
-
1165
- return false;
1166
-
1167
- }
1168
-
1169
- /**
1170
- * Create default columns to display important plugin information
1171
- * like type, action and status.
1172
- *
1173
- * @since 2.2.0
1174
- *
1175
- * @param array $item
1176
- * @param string $column_name
1177
- */
1178
- public function column_default( $item, $column_name ) {
1179
-
1180
- switch ( $column_name ) {
1181
- case 'source':
1182
- case 'type':
1183
- case 'status':
1184
- return $item[$column_name];
1185
- }
1186
-
1187
- }
1188
-
1189
- /**
1190
- * Create default title column along with action links of 'Install'
1191
- * and 'Activate'.
1192
- *
1193
- * @since 2.2.0
1194
- *
1195
- * @param array $item
1196
- * @return string The action hover links
1197
- */
1198
- public function column_plugin( $item ) {
1199
-
1200
- $installed_plugins = get_plugins();
1201
-
1202
- /** No need to display any hover links */
1203
- if ( is_plugin_active( $item['file_path'] ) )
1204
- $actions = array();
1205
-
1206
- /** We need to display the 'Install' hover link */
1207
- if ( ! isset( $installed_plugins[$item['file_path']] ) ) {
1208
- $actions = array(
1209
- 'install' => sprintf(
1210
- '<a href="%1$s" title="Install %2$s">Install</a>',
1211
- wp_nonce_url(
1212
- add_query_arg(
1213
- array(
1214
- 'page' => TGM_Plugin_Activation::$instance->menu,
1215
- 'plugin' => $item['slug'],
1216
- 'plugin_name' => $item['sanitized_plugin'],
1217
- 'plugin_source' => $item['url'],
1218
- 'tgmpa-install' => 'install-plugin',
1219
- ),
1220
- admin_url( TGM_Plugin_Activation::$instance->parent_url_slug )
1221
- ),
1222
- 'tgmpa-install'
1223
- ),
1224
- $item['sanitized_plugin']
1225
- ),
1226
- );
1227
- }
1228
- /** We need to display the 'Activate' hover link */
1229
- elseif ( is_plugin_inactive( $item['file_path'] ) ) {
1230
- $actions = array(
1231
- 'activate' => sprintf(
1232
- '<a href="%1$s" title="Activate %2$s">Activate</a>',
1233
- add_query_arg(
1234
- array(
1235
- 'page' => TGM_Plugin_Activation::$instance->menu,
1236
- 'plugin' => $item['slug'],
1237
- 'plugin_name' => $item['sanitized_plugin'],
1238
- 'plugin_source' => $item['url'],
1239
- 'tgmpa-activate' => 'activate-plugin',
1240
- 'tgmpa-activate-nonce' => wp_create_nonce( 'tgmpa-activate' ),
1241
- ),
1242
- admin_url( TGM_Plugin_Activation::$instance->parent_url_slug )
1243
- ),
1244
- $item['sanitized_plugin']
1245
- ),
1246
- );
1247
- }
1248
-
1249
- return sprintf( '%1$s %2$s', $item['plugin'], $this->row_actions( $actions ) );
1250
-
1251
- }
1252
-
1253
- /**
1254
- * Required for bulk installing.
1255
- *
1256
- * Adds a checkbox for each plugin.
1257
- *
1258
- * @since 2.2.0
1259
- *
1260
- * @param array $item
1261
- * @return string The input checkbox with all necessary info
1262
- */
1263
- public function column_cb( $item ) {
1264
-
1265
- $value = $item['file_path'] . ',' . $item['url'] . ',' . $item['sanitized_plugin'];
1266
- return sprintf( '<input type="checkbox" name="%1$s[]" value="%2$s" id="%3$s" />', $this->_args['singular'], $value, $item['sanitized_plugin'] );
1267
-
1268
- }
1269
-
1270
- /**
1271
- * Sets default message within the plugins table if no plugins
1272
- * are left for interaction.
1273
- *
1274
- * Hides the menu item to prevent the user from clicking and
1275
- * getting a permissions error.
1276
- *
1277
- * @since 2.2.0
1278
- */
1279
- public function no_items() {
1280
-
1281
- printf( __( 'No plugins to install or activate. <a href="%1$s" title="Return to the Dashboard">Return to the Dashboard</a>', TGM_Plugin_Activation::$instance->domain ), admin_url() );
1282
- echo '<style type="text/css">#adminmenu .wp-submenu li.current { display: none !important; }</style>';
1283
-
1284
- }
1285
-
1286
- /**
1287
- * Output all the column information within the table.
1288
- *
1289
- * @since 2.2.0
1290
- *
1291
- * @return array $columns The column names
1292
- */
1293
- public function get_columns() {
1294
-
1295
- $columns = array(
1296
- 'cb' => '<input type="checkbox" />',
1297
- 'plugin' => __( 'Plugin', TGM_Plugin_Activation::$instance->domain ),
1298
- 'source' => __( 'Source', TGM_Plugin_Activation::$instance->domain ),
1299
- 'type' => __( 'Type', TGM_Plugin_Activation::$instance->domain ),
1300
- 'status' => __( 'Status', TGM_Plugin_Activation::$instance->domain )
1301
- );
1302
-
1303
- return $columns;
1304
-
1305
- }
1306
-
1307
- /**
1308
- * Defines all types of bulk actions for handling
1309
- * registered plugins.
1310
- *
1311
- * @since 2.2.0
1312
- *
1313
- * @return array $actions The bulk actions for the plugin install table
1314
- */
1315
- public function get_bulk_actions() {
1316
-
1317
- $actions = array(
1318
- 'tgmpa-bulk-install' => __( 'Install', TGM_Plugin_Activation::$instance->domain ),
1319
- 'tgmpa-bulk-activate' => __( 'Activate', TGM_Plugin_Activation::$instance->domain ),
1320
- );
1321
-
1322
- return $actions;
1323
-
1324
- }
1325
-
1326
- /**
1327
- * Processes bulk installation and activation actions.
1328
- *
1329
- * The bulk installation process looks either for the $_POST
1330
- * information or for the plugin info within the $_GET variable if
1331
- * a user has to use WP_Filesystem to enter their credentials.
1332
- *
1333
- * @since 2.2.0
1334
- */
1335
- public function process_bulk_actions() {
1336
-
1337
- /** Bulk installation process */
1338
- if ( 'tgmpa-bulk-install' === $this->current_action() ) {
1339
- check_admin_referer( 'bulk-' . $this->_args['plural'] );
1340
-
1341
- /** Prep variables to be populated */
1342
- $plugins_to_install = array();
1343
- $plugin_installs = array();
1344
- $plugin_path = array();
1345
- $plugin_name = array();
1346
-
1347
- /** Look first to see if information has been passed via WP_Filesystem */
1348
- if ( isset( $_GET[sanitize_key( 'plugins' )] ) )
1349
- $plugins = explode( ',', stripslashes( $_GET[sanitize_key( 'plugins' )] ) );
1350
- /** Looks like the user can use the direct method, take from $_POST */
1351
- elseif ( isset( $_POST[sanitize_key( 'plugin' )] ) )
1352
- $plugins = (array) $_POST[sanitize_key( 'plugin' )];
1353
- /** Nothing has been submitted */
1354
- else
1355
- $plugins = array();
1356
-
1357
- $a = 0; // Incremental variable
1358
-
1359
- /** Grab information from $_POST if available */
1360
- if ( isset( $_POST[sanitize_key( 'plugin' )] ) ) {
1361
- foreach ( $plugins as $plugin_data )
1362
- $plugins_to_install[] = explode( ',', $plugin_data );
1363
-
1364
- foreach ( $plugins_to_install as $plugin_data ) {
1365
- $plugin_installs[] = $plugin_data[0];
1366
- $plugin_path[] = $plugin_data[1];
1367
- $plugin_name[] = $plugin_data[2];
1368
- }
1369
- }
1370
- /** Information has been passed via $_GET */
1371
- else {
1372
- foreach ( $plugins as $key => $value ) {
1373
- /** Grab plugin slug for each plugin */
1374
- if ( 0 == $key % 3 || 0 == $key ) {
1375
- $plugins_to_install[] = $value;
1376
- $plugin_installs[] = $value;
1377
- }
1378
- $a++;
1379
- }
1380
- }
1381
-
1382
- /** Look first to see if information has been passed via WP_Filesystem */
1383
- if ( isset( $_GET[sanitize_key( 'plugin_paths' )] ) )
1384
- $plugin_paths = explode( ',', stripslashes( $_GET[sanitize_key( 'plugin_paths' )] ) );
1385
- /** Looks like the user doesn't need to enter his FTP creds */
1386
- elseif ( isset( $_POST[sanitize_key( 'plugin' )] ) )
1387
- $plugin_paths = (array) $plugin_path;
1388
- /** Nothing has been submitted */
1389
- else
1390
- $plugin_paths = array();
1391
-
1392
- /** Look first to see if information has been passed via WP_Filesystem */
1393
- if ( isset( $_GET[sanitize_key( 'plugin_names' )] ) )
1394
- $plugin_names = explode( ',', stripslashes( $_GET[sanitize_key( 'plugin_names' )] ) );
1395
- /** Looks like the user doesn't need to enter his FTP creds */
1396
- elseif ( isset( $_POST[sanitize_key( 'plugin' )] ) )
1397
- $plugin_names = (array) $plugin_name;
1398
- /** Nothing has been submitted */
1399
- else
1400
- $plugin_names = array();
1401
-
1402
- $b = 0; // Incremental variable
1403
-
1404
- /** Loop through plugin slugs and remove already installed plugins from the list */
1405
- foreach ( $plugin_installs as $key => $plugin ) {
1406
- if ( preg_match( '|.php$|', $plugin ) ) {
1407
- unset( $plugin_installs[$key] );
1408
-
1409
- /** If the plugin path isn't in the $_GET variable, we can unset the corresponding path */
1410
- if ( ! isset( $_GET[sanitize_key( 'plugin_paths' )] ) )
1411
- unset( $plugin_paths[$b] );
1412
-
1413
- /** If the plugin name isn't in the $_GET variable, we can unset the corresponding name */
1414
- if ( ! isset( $_GET[sanitize_key( 'plugin_names' )] ) )
1415
- unset( $plugin_names[$b] );
1416
- }
1417
- $b++;
1418
- }
1419
-
1420
- /** No need to proceed further if we have no plugins to install */
1421
- if ( empty( $plugin_installs ) )
1422
- return false;
1423
-
1424
- /** Reset array indexes in case we removed already installed plugins */
1425
- $plugin_installs = array_values( $plugin_installs );
1426
- $plugin_paths = array_values( $plugin_paths );
1427
- $plugin_names = array_values( $plugin_names );
1428
-
1429
- /** If we grabbed our plugin info from $_GET, we need to decode it for use */
1430
- $plugin_installs = array_map( 'urldecode', $plugin_installs );
1431
- $plugin_paths = array_map( 'urldecode', $plugin_paths );
1432
- $plugin_names = array_map( 'urldecode', $plugin_names );
1433
-
1434
- /** Pass all necessary information via URL if WP_Filesystem is needed */
1435
- $url = wp_nonce_url(
1436
- add_query_arg(
1437
- array(
1438
- 'page' => TGM_Plugin_Activation::$instance->menu,
1439
- 'tgmpa-action' => 'install-selected',
1440
- 'plugins' => urlencode( implode( ',', $plugins ) ),
1441
- 'plugin_paths' => urlencode( implode( ',', $plugin_paths ) ),
1442
- 'plugin_names' => urlencode( implode( ',', $plugin_names ) ),
1443
- ),
1444
- admin_url( TGM_Plugin_Activation::$instance->parent_url_slug )
1445
- ),
1446
- 'bulk-plugins'
1447
- );
1448
- $method = ''; // Leave blank so WP_Filesystem can populate it as necessary
1449
- $fields = array( sanitize_key( 'action' ), sanitize_key( '_wp_http_referer' ), sanitize_key( '_wpnonce' ) ); // Extra fields to pass to WP_Filesystem
1450
-
1451
- if ( false === ( $creds = request_filesystem_credentials( $url, $method, false, false, $fields ) ) )
1452
- return true;
1453
-
1454
- if ( ! WP_Filesystem( $creds ) ) {
1455
- request_filesystem_credentials( $url, $method, true, false, $fields ); // Setup WP_Filesystem
1456
- return true;
1457
- }
1458
-
1459
- require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; // Need for plugins_api
1460
- require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; // Need for upgrade classes
1461
-
1462
- /** Store all information in arrays since we are processing a bulk installation */
1463
- $api = array();
1464
- $sources = array();
1465
- $install_path = array();
1466
-
1467
- $c = 0; // Incremental variable
1468
-
1469
- /** Loop through each plugin to install and try to grab information from WordPress API, if not create 'tgmpa-empty' scalar */
1470
- foreach ( $plugin_installs as $plugin ) {
1471
- $api[$c] = plugins_api( 'plugin_information', array( 'slug' => $plugin, 'fields' => array( 'sections' => false ) ) ) ? plugins_api( 'plugin_information', array( 'slug' => $plugin, 'fields' => array( 'sections' => false ) ) ) : (object) $api[$c] = 'tgmpa-empty';
1472
- $c++;
1473
- }
1474
-
1475
- if ( is_wp_error( $api ) )
1476
- wp_die( TGM_Plugin_Activation::$instance->strings['oops'] . var_dump( $api ) );
1477
-
1478
- $d = 0; // Incremental variable
1479
-
1480
- /** Capture download links from $api or set install link to pre-packaged/private repo */
1481
- foreach ( $api as $object ) {
1482
- $sources[$d] = isset( $object->download_link ) && 'repo' == $plugin_paths[$d] ? $object->download_link : $plugin_paths[$d];
1483
- $d++;
1484
- }
1485
-
1486
- /** Finally, all the data is prepared to be sent to the installer */
1487
- $url = add_query_arg( array( 'page' => TGM_Plugin_Activation::$instance->menu ), admin_url( TGM_Plugin_Activation::$instance->parent_url_slug ) );
1488
- $nonce = 'bulk-plugins';
1489
- $names = $plugin_names;
1490
-
1491
- /** Create a new instance of TGM_Bulk_Installer */
1492
- $installer = new TGM_Bulk_Installer( $skin = new TGM_Bulk_Installer_Skin( compact( 'url', 'nonce', 'names' ) ) );
1493
-
1494
- /** Wrap the install process with the appropriate HTML */
1495
- echo '<div class="tgmpa wrap">';
1496
- screen_icon( apply_filters( 'tgmpa_default_screen_icon', 'themes' ) );
1497
- echo '<h2>' . esc_html( get_admin_page_title() ) . '</h2>';
1498
- /** Process the bulk installation submissions */
1499
- $installer->bulk_install( $sources );
1500
- echo '</div>';
1501
-
1502
- return true;
1503
- }
1504
-
1505
- /** Bulk activation process */
1506
- if ( 'tgmpa-bulk-activate' === $this->current_action() ) {
1507
- check_admin_referer( 'bulk-' . $this->_args['plural'] );
1508
-
1509
- /** Grab plugin data from $_POST */
1510
- $plugins = isset( $_POST[sanitize_key( 'plugin' )] ) ? (array) $_POST[sanitize_key( 'plugin' )] : array();
1511
- $plugins_to_activate = array();
1512
-
1513
- /** Split plugin value into array with plugin file path, plugin source and plugin name */
1514
- foreach ( $plugins as $i => $plugin )
1515
- $plugins_to_activate[] = explode( ',', $plugin );
1516
-
1517
- foreach ( $plugins_to_activate as $i => $array ) {
1518
- if ( ! preg_match( '|.php$|', $array[0] ) ) // Plugins that haven't been installed yet won't have the correct file path
1519
- unset( $plugins_to_activate[$i] );
1520
- }
1521
-
1522
- /** Return early if there are no plugins to activate */
1523
- if ( empty( $plugins_to_activate ) )
1524
- return;
1525
-
1526
- $plugins = array();
1527
- $plugin_names = array();
1528
-
1529
- foreach ( $plugins_to_activate as $plugin_string ) {
1530
- $plugins[] = $plugin_string[0];
1531
- $plugin_names[] = $plugin_string[2];
1532
- }
1533
-
1534
- $count = count( $plugin_names ); // Count so we can use _n function
1535
- $last_plugin = array_pop( $plugin_names ); // Pop off last name to prep for readability
1536
- $imploded = empty( $plugin_names ) ? '<strong>' . $last_plugin . '</strong>' : '<strong>' . ( implode( ', ', $plugin_names ) . '</strong> and <strong>' . $last_plugin . '</strong>.' );
1537
-
1538
- /** Now we are good to go - let's start activating plugins */
1539
- $activate = activate_plugins( $plugins );
1540
-
1541
- if ( is_wp_error( $activate ) )
1542
- echo '<div id="message" class="error"><p>' . $activate->get_error_message() . '</p></div>';
1543
- else
1544
- printf( '<div id="message" class="updated"><p>%1$s %2$s</p></div>', _n( 'The following plugin was activated successfully:', 'The following plugins were activated successfully:', $count, TGM_Plugin_Activation::$instance->domain ), $imploded );
1545
-
1546
- /** Update recently activated plugins option */
1547
- $recent = (array) get_option( 'recently_activated' );
1548
-
1549
- foreach ( $plugins as $plugin => $time )
1550
- if ( isset( $recent[$plugin] ) )
1551
- unset( $recent[$plugin] );
1552
-
1553
- update_option( 'recently_activated', $recent );
1554
-
1555
- unset( $_POST ); // Reset the $_POST variable in case user wants to perform one action after another
1556
- }
1557
- }
1558
-
1559
- /**
1560
- * Prepares all of our information to be outputted into a usable table.
1561
- *
1562
- * @since 2.2.0
1563
- */
1564
- public function prepare_items() {
1565
-
1566
- $per_page = 100; // Set it high so we shouldn't have to worry about pagination
1567
- $columns = $this->get_columns(); // Get all necessary column information
1568
- $hidden = array(); // No columns to hide, but we must set as an array
1569
- $sortable = array(); // No reason to make sortable columns
1570
- $this->_column_headers = array( $columns, $hidden, $sortable ); // Get all necessary column headers
1571
-
1572
- /** Process our bulk actions here */
1573
- $this->process_bulk_actions();
1574
-
1575
- /** Store all of our plugin data into $items array so WP_List_Table can use it */
1576
- $this->items = $this->_gather_plugin_data();
1577
-
1578
- }
1579
 
1580
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1581
  }
1582
 
1583
  /**
@@ -1585,508 +2852,690 @@ if ( ! class_exists( 'TGMPA_List_Table' ) ) {
1585
  * we load it here.
1586
  *
1587
  * We check to make sure no action or activation keys are set so that WordPress
1588
- * doesn't try to re-include the class when processing upgrades or installs outside
1589
  * of the class.
1590
  *
1591
  * @since 2.2.0
1592
  */
1593
- if ( ! class_exists( 'WP_Upgrader' ) && ( isset( $_GET[sanitize_key( 'page' )] ) && TGM_Plugin_Activation::$instance->menu = $_GET[sanitize_key( 'page' )] ) ) {
1594
- require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1595
-
1596
- if ( ! class_exists( 'TGM_Bulk_Installer' ) ) {
1597
- /**
1598
- * Installer class to handle bulk plugin installations.
1599
- *
1600
- * Extends WP_Upgrader and customizes to suit the installation of multiple
1601
- * plugins.
1602
- *
1603
- * @since 2.2.0
1604
- *
1605
- * @package TGM-Plugin-Activation
1606
- * @author Thomas Griffin <thomas@thomasgriffinmedia.com>
1607
- * @author Gary Jones <gamajo@gamajo.com>
1608
- */
1609
- class TGM_Bulk_Installer extends WP_Upgrader {
1610
-
1611
- /**
1612
- * Holds result of bulk plugin installation.
1613
- *
1614
- * @since 2.2.0
1615
- *
1616
- * @var string
1617
- */
1618
- public $result;
1619
-
1620
- /**
1621
- * Flag to check if bulk installation is occurring or not.
1622
- *
1623
- * @since 2.2.0
1624
- *
1625
- * @var boolean
1626
- */
1627
- public $bulk = false;
1628
-
1629
- /**
1630
- * Processes the bulk installation of plugins.
1631
- *
1632
- * @since 2.2.0
1633
- *
1634
- * @param array $packages The plugin sources needed for installation
1635
- * @return string|boolean Install confirmation messages on success, false on failure
1636
- */
1637
- public function bulk_install( $packages ) {
1638
-
1639
- /** Pass installer skin object and set bulk property to true */
1640
- $this->init();
1641
- $this->bulk = true;
1642
-
1643
- /** Set install strings and automatic activation strings (if config option is set to true) */
1644
- $this->install_strings();
1645
- if ( TGM_Plugin_Activation::$instance->is_automatic )
1646
- $this->activate_strings();
1647
-
1648
- /** Run the header string to notify user that the process has begun */
1649
- $this->skin->header();
1650
-
1651
- /** Connect to the Filesystem */
1652
- $res = $this->fs_connect( array( WP_CONTENT_DIR, WP_PLUGIN_DIR ) );
1653
- if ( ! $res ) {
1654
- $this->skin->footer();
1655
- return false;
1656
- }
1657
-
1658
- /** Set the bulk header and prepare results array */
1659
- $this->skin->bulk_header();
1660
- $results = array();
1661
-
1662
- /** Get the total number of packages being processed and iterate as each package is successfully installed */
1663
- $this->update_count = count( $packages );
1664
- $this->update_current = 0;
1665
-
1666
- /** Loop through each plugin and process the installation */
1667
- foreach ( $packages as $plugin ) {
1668
- $this->update_current++; // Increment counter
1669
-
1670
- /** Do the plugin install */
1671
- $result = $this->run(
1672
- array(
1673
- 'package' => $plugin, // The plugin source
1674
- 'destination' => WP_PLUGIN_DIR, // The destination dir
1675
- 'clear_destination' => false, // Do we want to clear the destination or not?
1676
- 'clear_working' => true, // Remove original install file
1677
- 'is_multi' => true, // Are we processing multiple installs?
1678
- 'hook_extra' => array( 'plugin' => $plugin, ), // Pass plugin source as extra data
1679
- )
1680
- );
1681
-
1682
- /** Store installation results in result property */
1683
- $results[$plugin] = $this->result;
1684
-
1685
- /** Prevent credentials auth screen from displaying multiple times */
1686
- if ( false === $result )
1687
- break;
1688
- }
1689
-
1690
- /** Pass footer skin strings */
1691
- $this->skin->bulk_footer();
1692
- $this->skin->footer();
1693
-
1694
- /** Return our results */
1695
- return $results;
1696
-
1697
- }
1698
-
1699
- /**
1700
- * Performs the actual installation of each plugin.
1701
- *
1702
- * This method also activates the plugin in the automatic flag has been
1703
- * set to true for the TGMPA class.
1704
- *
1705
- * @since 2.2.0
1706
- *
1707
- * @param array $options The installation cofig options
1708
- * @return null/array Return early if error, array of installation data on success
1709
- */
1710
- public function run( $options ) {
1711
-
1712
- /** Default config options */
1713
- $defaults = array(
1714
- 'package' => '',
1715
- 'destination' => '',
1716
- 'clear_destination' => false,
1717
- 'clear_working' => true,
1718
- 'is_multi' => false,
1719
- 'hook_extra' => array(),
1720
- );
1721
-
1722
- /** Parse default options with config options from $this->bulk_upgrade and extract them */
1723
- $options = wp_parse_args( $options, $defaults );
1724
- extract( $options );
1725
-
1726
- /** Connect to the Filesystem */
1727
- $res = $this->fs_connect( array( WP_CONTENT_DIR, $destination ) );
1728
- if ( ! $res )
1729
- return false;
1730
-
1731
- /** Return early if there is an error connecting to the Filesystem */
1732
- if ( is_wp_error( $res ) ) {
1733
- $this->skin->error( $res );
1734
- return $res;
1735
- }
1736
-
1737
- /** Call $this->header separately if running multiple times */
1738
- if ( ! $is_multi )
1739
- $this->skin->header();
1740
-
1741
- /** Set strings before the package is installed */
1742
- $this->skin->before();
1743
-
1744
- /** Download the package (this just returns the filename of the file if the package is a local file) */
1745
- $download = $this->download_package( $package );
1746
- if ( is_wp_error( $download ) ) {
1747
- $this->skin->error( $download );
1748
- $this->skin->after();
1749
- return $download;
1750
- }
1751
-
1752
- /** Don't accidentally delete a local file */
1753
- $delete_package = ( $download != $package );
1754
-
1755
- /** Unzip file into a temporary working directory */
1756
- $working_dir = $this->unpack_package( $download, $delete_package );
1757
- if ( is_wp_error( $working_dir ) ) {
1758
- $this->skin->error( $working_dir );
1759
- $this->skin->after();
1760
- return $working_dir;
1761
- }
1762
-
1763
- /** Install the package into the working directory with all passed config options */
1764
- $result = $this->install_package(
1765
- array(
1766
- 'source' => $working_dir,
1767
- 'destination' => $destination,
1768
- 'clear_destination' => $clear_destination,
1769
- 'clear_working' => $clear_working,
1770
- 'hook_extra' => $hook_extra,
1771
- )
1772
- );
1773
-
1774
- /** Pass the result of the installation */
1775
- $this->skin->set_result( $result );
1776
-
1777
- /** Set correct strings based on results */
1778
- if ( is_wp_error( $result ) ) {
1779
- $this->skin->error( $result );
1780
- $this->skin->feedback( 'process_failed' );
1781
- }
1782
- /** The plugin install is successful */
1783
- else {
1784
- $this->skin->feedback( 'process_success' );
1785
- }
1786
-
1787
- /** Only process the activation of installed plugins if the automatic flag is set to true */
1788
- if ( TGM_Plugin_Activation::$instance->is_automatic ) {
1789
- /** Flush plugins cache so we can make sure that the installed plugins list is always up to date */
1790
- wp_cache_flush();
1791
-
1792
- /** Get the installed plugin file and activate it */
1793
- $plugin_info = $this->plugin_info( $package );
1794
- $activate = activate_plugin( $plugin_info );
1795
-
1796
- /** Re-populate the file path now that the plugin has been installed and activated */
1797
- TGM_Plugin_Activation::$instance->populate_file_path();
1798
-
1799
- /** Set correct strings based on results */
1800
- if ( is_wp_error( $activate ) ) {
1801
- $this->skin->error( $activate );
1802
- $this->skin->feedback( 'activation_failed' );
1803
- }
1804
- /** The plugin activation is successful */
1805
- else {
1806
- $this->skin->feedback( 'activation_success' );
1807
- }
1808
- }
1809
-
1810
- /** Flush plugins cache so we can make sure that the installed plugins list is always up to date */
1811
- wp_cache_flush();
1812
-
1813
- /** Set install footer strings */
1814
- $this->skin->after();
1815
- if ( ! $is_multi )
1816
- $this->skin->footer();
1817
-
1818
- return $result;
1819
-
1820
- }
1821
-
1822
- /**
1823
- * Sets the correct install strings for the installer skin to use.
1824
- *
1825
- * @since 2.2.0
1826
- */
1827
- public function install_strings() {
1828
-
1829
- $this->strings['no_package'] = __( 'Install package not available.', TGM_Plugin_Activation::$instance->domain );
1830
- $this->strings['downloading_package'] = __( 'Downloading install package from <span class="code">%s</span>&#8230;', TGM_Plugin_Activation::$instance->domain );
1831
- $this->strings['unpack_package'] = __( 'Unpacking the package&#8230;', TGM_Plugin_Activation::$instance->domain );
1832
- $this->strings['installing_package'] = __( 'Installing the plugin&#8230;', TGM_Plugin_Activation::$instance->domain );
1833
- $this->strings['process_failed'] = __( 'Plugin install failed.', TGM_Plugin_Activation::$instance->domain );
1834
- $this->strings['process_success'] = __( 'Plugin installed successfully.', TGM_Plugin_Activation::$instance->domain );
1835
-
1836
- }
1837
-
1838
- /**
1839
- * Sets the correct activation strings for the installer skin to use.
1840
- *
1841
- * @since 2.2.0
1842
- */
1843
- public function activate_strings() {
1844
-
1845
- $this->strings['activation_failed'] = __( 'Plugin activation failed.', TGM_Plugin_Activation::$instance->domain );
1846
- $this->strings['activation_success'] = __( 'Plugin activated successfully.', TGM_Plugin_Activation::$instance->domain );
1847
-
1848
- }
1849
-
1850
- /**
1851
- * Grabs the plugin file from an installed plugin.
1852
- *
1853
- * @since 2.2.0
1854
- *
1855
- * @return string|boolean Return plugin file on success, false on failure
1856
- */
1857
- public function plugin_info() {
1858
-
1859
- /** Return false if installation result isn't an array or the destination name isn't set */
1860
- if ( ! is_array( $this->result ) )
1861
- return false;
1862
- if ( empty( $this->result['destination_name'] ) )
1863
- return false;
1864
-
1865
- /** Get the installed plugin file or return false if it isn't set */
1866
- $plugin = get_plugins( '/' . $this->result['destination_name'] );
1867
- if ( empty( $plugin ) )
1868
- return false;
1869
-
1870
- /** Assume the requested plugin is the first in the list */
1871
- $pluginfiles = array_keys( $plugin );
1872
-
1873
- return $this->result['destination_name'] . '/' . $pluginfiles[0];
1874
-
1875
- }
1876
-
1877
- }
1878
- }
1879
-
1880
- if ( ! class_exists( 'TGM_Bulk_Installer_Skin' ) ) {
1881
- /**
1882
- * Installer skin to set strings for the bulk plugin installations..
1883
- *
1884
- * Extends Bulk_Upgrader_Skin and customizes to suit the installation of multiple
1885
- * plugins.
1886
- *
1887
- * @since 2.2.0
1888
- *
1889
- * @package TGM-Plugin-Activation
1890
- * @author Thomas Griffin <thomas@thomasgriffinmedia.com>
1891
- * @author Gary Jones <gamajo@gamajo.com>
1892
- */
1893
- class TGM_Bulk_Installer_Skin extends Bulk_Upgrader_Skin {
1894
-
1895
- /**
1896
- * Holds plugin info for each individual plugin installation.
1897
- *
1898
- * @since 2.2.0
1899
- *
1900
- * @var array
1901
- */
1902
- public $plugin_info = array();
1903
-
1904
- /**
1905
- * Holds names of plugins that are undergoing bulk installations.
1906
- *
1907
- * @since 2.2.0
1908
- *
1909
- * @var array
1910
- */
1911
- public $plugin_names = array();
1912
-
1913
- /**
1914
- * Integer to use for iteration through each plugin installation.
1915
- *
1916
- * @since 2.2.0
1917
- *
1918
- * @var integer
1919
- */
1920
- public $i = 0;
1921
-
1922
- /**
1923
- * Constructor. Parses default args with new ones and extracts them for use.
1924
- *
1925
- * @since 2.2.0
1926
- *
1927
- * @param array $args Arguments to pass for use within the class
1928
- */
1929
- public function __construct( $args = array() ) {
1930
-
1931
- /** Parse default and new args */
1932
- $defaults = array( 'url' => '', 'nonce' => '', 'names' => array() );
1933
- $args = wp_parse_args( $args, $defaults );
1934
-
1935
- /** Set plugin names to $this->plugin_names property */
1936
- $this->plugin_names = $args['names'];
1937
-
1938
- /** Extract the new args */
1939
- parent::__construct( $args );
1940
-
1941
- }
1942
-
1943
- /**
1944
- * Sets install skin strings for each individual plugin.
1945
- *
1946
- * Checks to see if the automatic activation flag is set and uses the
1947
- * the proper strings accordingly.
1948
- *
1949
- * @since 2.2.0
1950
- */
1951
- public function add_strings() {
1952
-
1953
- /** Automatic activation strings */
1954
- if ( TGM_Plugin_Activation::$instance->is_automatic ) {
1955
- $this->upgrader->strings['skin_upgrade_start'] = __( 'The installation and activation process is starting. This process may take a while on some hosts, so please be patient.', TGM_Plugin_Activation::$instance->domain );
1956
- $this->upgrader->strings['skin_update_successful'] = __( '%1$s installed and activated successfully.', TGM_Plugin_Activation::$instance->domain ) . ' <a onclick="%2$s" href="#" class="hide-if-no-js"><span>' . __( 'Show Details', TGM_Plugin_Activation::$instance->domain ) . '</span><span class="hidden">' . __( 'Hide Details', TGM_Plugin_Activation::$instance->domain ) . '</span>.</a>';
1957
- $this->upgrader->strings['skin_upgrade_end'] = __( 'All installations and activations have been completed.', TGM_Plugin_Activation::$instance->domain );
1958
- $this->upgrader->strings['skin_before_update_header'] = __( 'Installing and Activating Plugin %1$s (%2$d/%3$d)', TGM_Plugin_Activation::$instance->domain );
1959
- }
1960
- /** Default installation strings */
1961
- else {
1962
- $this->upgrader->strings['skin_upgrade_start'] = __( 'The installation process is starting. This process may take a while on some hosts, so please be patient.', TGM_Plugin_Activation::$instance->domain );
1963
- $this->upgrader->strings['skin_update_failed_error'] = __( 'An error occurred while installing %1$s: <strong>%2$s</strong>.', TGM_Plugin_Activation::$instance->domain );
1964
- $this->upgrader->strings['skin_update_failed'] = __( 'The installation of %1$s failed.', TGM_Plugin_Activation::$instance->domain );
1965
- $this->upgrader->strings['skin_update_successful'] = __( '%1$s installed successfully.', TGM_Plugin_Activation::$instance->domain ) . ' <a onclick="%2$s" href="#" class="hide-if-no-js"><span>' . __( 'Show Details', TGM_Plugin_Activation::$instance->domain ) . '</span><span class="hidden">' . __( 'Hide Details', TGM_Plugin_Activation::$instance->domain ) . '</span>.</a>';
1966
- $this->upgrader->strings['skin_upgrade_end'] = __( 'All installations have been completed.', TGM_Plugin_Activation::$instance->domain );
1967
- $this->upgrader->strings['skin_before_update_header'] = __( 'Installing Plugin %1$s (%2$d/%3$d)', TGM_Plugin_Activation::$instance->domain );
1968
- }
1969
-
1970
- }
1971
-
1972
- /**
1973
- * Outputs the header strings and necessary JS before each plugin installation.
1974
- *
1975
- * @since 2.2.0
1976
- */
1977
- public function before( $title = '' ) {
1978
-
1979
- /** We are currently in the plugin installation loop, so set to true */
1980
- $this->in_loop = true;
1981
-
1982
- printf( '<h4>' . $this->upgrader->strings['skin_before_update_header'] . ' <img alt="" src="' . admin_url( 'images/wpspin_light.gif' ) . '" class="hidden waiting-' . $this->upgrader->update_current . '" style="vertical-align:middle;" /></h4>', $this->plugin_names[$this->i], $this->upgrader->update_current, $this->upgrader->update_count );
1983
- echo '<script type="text/javascript">jQuery(\'.waiting-' . esc_js( $this->upgrader->update_current ) . '\').show();</script>';
1984
- echo '<div class="update-messages hide-if-js" id="progress-' . esc_attr( $this->upgrader->update_current ) . '"><p>';
1985
-
1986
- /** Flush header output buffer */
1987
- $this->before_flush_output();
1988
-
1989
- }
1990
-
1991
- /**
1992
- * Outputs the footer strings and necessary JS after each plugin installation.
1993
- *
1994
- * Checks for any errors and outputs them if they exist, else output
1995
- * success strings.
1996
- *
1997
- * @since 2.2.0
1998
- */
1999
- public function after( $title = '') {
2000
-
2001
- /** Close install strings */
2002
- echo '</p></div>';
2003
-
2004
- /** Output error strings if an error has occurred */
2005
- if ( $this->error || ! $this->result ) {
2006
- if ( $this->error )
2007
- echo '<div class="error"><p>' . sprintf( $this->upgrader->strings['skin_update_failed_error'], $this->plugin_names[$this->i], $this->error ) . '</p></div>';
2008
- else
2009
- echo '<div class="error"><p>' . sprintf( $this->upgrader->strings['skin_update_failed'], $this->plugin_names[$this->i] ) . '</p></div>';
2010
-
2011
- echo '<script type="text/javascript">jQuery(\'#progress-' . esc_js( $this->upgrader->update_current ) . '\').show();</script>';
2012
- }
2013
-
2014
- /** If the result is set and there are no errors, success! */
2015
- if ( ! empty( $this->result ) && ! is_wp_error( $this->result ) ) {
2016
- echo '<div class="updated"><p>' . sprintf( $this->upgrader->strings['skin_update_successful'], $this->plugin_names[$this->i], 'jQuery(\'#progress-' . esc_js( $this->upgrader->update_current ) . '\').toggle();jQuery(\'span\', this).toggle(); return false;' ) . '</p></div>';
2017
- echo '<script type="text/javascript">jQuery(\'.waiting-' . esc_js( $this->upgrader->update_current ) . '\').hide();</script>';
2018
- }
2019
-
2020
- /** Set in_loop and error to false and flush footer output buffer */
2021
- $this->reset();
2022
- $this->after_flush_output();
2023
-
2024
- }
2025
-
2026
- /**
2027
- * Outputs links after bulk plugin installation is complete.
2028
- *
2029
- * @since 2.2.0
2030
- */
2031
- public function bulk_footer() {
2032
-
2033
- /** Serve up the string to say installations (and possibly activations) are complete */
2034
- parent::bulk_footer();
2035
-
2036
- /** Flush plugins cache so we can make sure that the installed plugins list is always up to date */
2037
- wp_cache_flush();
2038
-
2039
- /** Display message based on if all plugins are now active or not */
2040
- $complete = array();
2041
- foreach ( TGM_Plugin_Activation::$instance->plugins as $plugin ) {
2042
- if ( ! is_plugin_active( $plugin['file_path'] ) ) {
2043
- echo '<p><a href="' . add_query_arg( 'page', TGM_Plugin_Activation::$instance->menu, admin_url( TGM_Plugin_Activation::$instance->parent_url_slug ) ) . '" title="' . esc_attr( TGM_Plugin_Activation::$instance->strings['return'] ) . '" target="_parent">' . __( TGM_Plugin_Activation::$instance->strings['return'], TGM_Plugin_Activation::$instance->domain ) . '</a></p>';
2044
- $complete[] = $plugin;
2045
- break;
2046
- }
2047
- /** Nothing to store */
2048
- else {
2049
- $complete[] = '';
2050
- }
2051
- }
2052
-
2053
- /** Filter out any empty entries */
2054
- $complete = array_filter( $complete );
2055
-
2056
- /** All plugins are active, so we display the complete string and hide the menu to protect users */
2057
- if ( empty( $complete ) ) {
2058
- echo '<p>' . sprintf( TGM_Plugin_Activation::$instance->strings['complete'], '<a href="' . admin_url() . '" title="' . __( 'Return to the Dashboard', TGM_Plugin_Activation::$instance->domain ) . '">' . __( 'Return to the Dashboard', TGM_Plugin_Activation::$instance->domain ) . '</a>' ) . '</p>';
2059
- echo '<style type="text/css">#adminmenu .wp-submenu li.current { display: none !important; }</style>';
2060
- }
2061
-
2062
- }
2063
-
2064
- /**
2065
- * Flush header output buffer.
2066
- *
2067
- * @since 2.2.0
2068
- */
2069
- public function before_flush_output() {
2070
-
2071
- wp_ob_end_flush_all();
2072
- flush();
2073
-
2074
- }
2075
-
2076
- /**
2077
- * Flush footer output buffer and iterate $this->i to make sure the
2078
- * installation strings reference the correct plugin.
2079
- *
2080
- * @since 2.2.0
2081
- */
2082
- public function after_flush_output() {
2083
-
2084
- wp_ob_end_flush_all();
2085
- flush();
2086
- $this->i++;
2087
-
2088
- }
2089
-
2090
- }
2091
- }
2092
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  /**
3
  * Plugin installation and activation for WordPress themes.
4
  *
5
+ * Please note that this is a drop-in library for a theme or plugin.
6
+ * The authors of this library (Thomas, Gary and Juliette) are NOT responsible
7
+ * for the support of your plugin or theme. Please contact the plugin
8
+ * or theme author for support.
9
+ *
10
  * @package TGM-Plugin-Activation
11
+ * @version 2.5.0
12
+ * @link http://tgmpluginactivation.com/
13
+ * @author Thomas Griffin, Gary Jones, Juliette Reinders Folmer
14
+ * @copyright Copyright (c) 2011, Thomas Griffin
15
+ * @license GPL-2.0+
16
+ *
17
+ * @wordpress-plugin
18
+ * Plugin Name: TGM Plugin Activation
19
+ * Plugin URI:
20
+ * Description: Plugin installation and activation for WordPress themes.
21
+ * Version: 2.5.0
22
+ * Author: Thomas Griffin, Gary Jones, Juliette Reinders Folmer
23
+ * Author URI: http://tgmpluginactivation.com/
24
+ * Text Domain: tgmpa
25
+ * Domain Path: /languages/
26
+ * Copyright: 2011, Thomas Griffin
27
  */
28
 
29
  /*
30
+ Copyright 2011 Thomas Griffin (thomasgriffinmedia.com)
31
 
32
  This program is free software; you can redistribute it and/or modify
33
+ it under the terms of the GNU General Public License, version 2, as
34
  published by the Free Software Foundation.
35
 
36
  This program is distributed in the hope that it will be useful,
43
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
44
  */
45
 
46
+ if ( ! class_exists( 'INBOUND_Plugin_Activation' ) ) {
47
+
48
+ /**
49
+ * Automatic plugin installation and activation library.
50
+ *
51
+ * Creates a way to automatically install and activate plugins from within themes.
52
+ * The plugins can be either bundled, downloaded from the WordPress
53
+ * Plugin Repository or downloaded from another external source.
54
+ *
55
+ * @since 1.0.0
56
+ *
57
+ * @package TGM-Plugin-Activation
58
+ * @author Thomas Griffin
59
+ * @author Gary Jones
60
+ */
61
+ class INBOUND_Plugin_Activation {
62
+ /**
63
+ * TGMPA version number.
64
+ *
65
+ * @since 2.5.0
66
+ *
67
+ * @const string Version number.
68
+ */
69
+ const TGMPA_VERSION = '2.5.0';
70
+
71
+ /**
72
+ * Regular expression to test if a URL is a WP plugin repo URL.
73
+ *
74
+ * @const string Regex.
75
+ *
76
+ * @since 2.5.0
77
+ */
78
+ const WP_REPO_REGEX = '|^http[s]?://wordpress\.org/(?:extend/)?plugins/|';
79
+
80
+ /**
81
+ * Arbitrary regular expression to test if a string starts with a URL.
82
+ *
83
+ * @const string Regex.
84
+ *
85
+ * @since 2.5.0
86
+ */
87
+ const IS_URL_REGEX = '|^http[s]?://|';
88
+
89
+ /**
90
+ * Holds a copy of itself, so it can be referenced by the class name.
91
+ *
92
+ * @since 1.0.0
93
+ *
94
+ * @var INBOUND_Plugin_Activation
95
+ */
96
+ public static $instance;
97
+
98
+ /**
99
+ * Holds arrays of plugin details.
100
+ *
101
+ * @since 1.0.0
102
+ *
103
+ * @since 2.5.0 the array has the plugin slug as an associative key.
104
+ *
105
+ * @var array
106
+ */
107
+ public $plugins = array();
108
+
109
+ /**
110
+ * Holds arrays of plugin names to use to sort the plugins array.
111
+ *
112
+ * @since 2.5.0
113
+ *
114
+ * @var array
115
+ */
116
+ protected $sort_order = array();
117
+
118
+ /**
119
+ * Whether any plugins have the 'force_activation' setting set to true.
120
+ *
121
+ * @since 2.5.0
122
+ *
123
+ * @var bool
124
+ */
125
+ protected $has_forced_activation = false;
126
+
127
+ /**
128
+ * Whether any plugins have the 'force_deactivation' setting set to true.
129
+ *
130
+ * @since 2.5.0
131
+ *
132
+ * @var bool
133
+ */
134
+ protected $has_forced_deactivation = false;
135
+
136
+ /**
137
+ * Name of the unique ID to hash notices.
138
+ *
139
+ * @since 2.4.0
140
+ *
141
+ * @var string
142
+ */
143
+ public $id = 'tgmpa';
144
+
145
+ /**
146
+ * Name of the query-string argument for the admin page.
147
+ *
148
+ * @since 1.0.0
149
+ *
150
+ * @var string
151
+ */
152
+ public $menu = 'tgmpa-install-plugins';
153
+
154
+ /**
155
+ * Parent menu file slug.
156
+ *
157
+ * @since 2.5.0
158
+ *
159
+ * @var string
160
+ */
161
+ public $parent_slug = 'themes.php';
162
+
163
+ /**
164
+ * Capability needed to view the plugin installation menu item.
165
+ *
166
+ * @since 2.5.0
167
+ *
168
+ * @var string
169
+ */
170
+ public $capability = 'edit_theme_options';
171
+
172
+ /**
173
+ * Default absolute path to folder containing bundled plugin zip files.
174
+ *
175
+ * @since 2.0.0
176
+ *
177
+ * @var string Absolute path prefix to zip file location for bundled plugins. Default is empty string.
178
+ */
179
+ public $default_path = '';
180
+
181
+ /**
182
+ * Flag to show admin notices or not.
183
+ *
184
+ * @since 2.1.0
185
+ *
186
+ * @var boolean
187
+ */
188
+ public $has_notices = true;
189
+
190
+ /**
191
+ * Flag to determine if the user can dismiss the notice nag.
192
+ *
193
+ * @since 2.4.0
194
+ *
195
+ * @var boolean
196
+ */
197
+ public $dismissable = true;
198
+
199
+ /**
200
+ * Message to be output above nag notice if dismissable is false.
201
+ *
202
+ * @since 2.4.0
203
+ *
204
+ * @var string
205
+ */
206
+ public $dismiss_msg = '';
207
+
208
+ /**
209
+ * Flag to set automatic activation of plugins. Off by default.
210
+ *
211
+ * @since 2.2.0
212
+ *
213
+ * @var boolean
214
+ */
215
+ public $is_automatic = false;
216
+
217
+ /**
218
+ * Optional message to display before the plugins table.
219
+ *
220
+ * @since 2.2.0
221
+ *
222
+ * @var string Message filtered by wp_kses_post(). Default is empty string.
223
+ */
224
+ public $message = '';
225
+
226
+ /**
227
+ * Holds configurable array of strings.
228
+ *
229
+ * Default values are added in the constructor.
230
+ *
231
+ * @since 2.0.0
232
+ *
233
+ * @var array
234
+ */
235
+ public $strings = array();
236
+
237
+ /**
238
+ * Holds the version of WordPress.
239
+ *
240
+ * @since 2.4.0
241
+ *
242
+ * @var int
243
+ */
244
+ public $wp_version;
245
+
246
+ /**
247
+ * Holds the hook name for the admin page.
248
+ *
249
+ * @since 2.5.0
250
+ *
251
+ * @var string
252
+ */
253
+ public $page_hook;
254
+
255
+ /**
256
+ * Adds a reference of this object to $instance, populates default strings,
257
+ * does the tgmpa_init action hook, and hooks in the interactions to init.
258
+ *
259
+ * @since 1.0.0
260
+ *
261
+ * @see INBOUND_Plugin_Activation::init()
262
+ */
263
+ protected function __construct() {
264
+ // Set the current WordPress version.
265
+ $this->wp_version = $GLOBALS['wp_version'];
266
+
267
+ // Announce that the class is ready, and pass the object (for advanced use).
268
+ do_action_ref_array( 'tgmpa_init', array( $this ) );
269
+
270
+ // When the rest of WP has loaded, kick-start the rest of the class.
271
+ add_action( 'init', array( $this, 'init' ) );
272
+ }
273
+
274
+ /**
275
+ * Initialise the interactions between this class and WordPress.
276
+ *
277
+ * Hooks in three new methods for the class: admin_menu, notices and styles.
278
+ *
279
+ * @since 2.0.0
280
+ *
281
+ * @see INBOUND_Plugin_Activation::admin_menu()
282
+ * @see INBOUND_Plugin_Activation::notices()
283
+ * @see INBOUND_Plugin_Activation::styles()
284
+ */
285
+ public function init() {
286
+ /**
287
+ * By default TGMPA only loads on the WP back-end and not in an Ajax call. Using this filter
288
+ * you can overrule that behaviour.
289
+ *
290
+ * @since 2.5.0
291
+ *
292
+ * @param bool $load Whether or not TGMPA should load.
293
+ * Defaults to the return of `is_admin() && ! defined( 'DOING_AJAX' )`.
294
+ */
295
+ if ( true !== apply_filters( 'tgmpa_load', ( is_admin() && ! defined( 'DOING_AJAX' ) ) ) ) {
296
+ return;
297
+ }
298
+
299
+ // Load class strings.
300
+ $this->strings = array(
301
+ 'page_title' => __( 'Install Required Plugins', 'tgmpa' ),
302
+ 'menu_title' => __( 'Install Plugins', 'tgmpa' ),
303
+ 'installing' => __( 'Installing Plugin: %s', 'tgmpa' ),
304
+ 'oops' => __( 'Something went wrong with the plugin API.', 'tgmpa' ),
305
+ 'notice_can_install_required' => _n_noop(
306
+ 'This theme requires the following plugin: %1$s.',
307
+ 'This theme requires the following plugins: %1$s.',
308
+ 'tgmpa'
309
+ ),
310
+ 'notice_can_install_recommended' => _n_noop(
311
+ 'This theme recommends the following plugin: %1$s.',
312
+ 'This theme recommends the following plugins: %1$s.',
313
+ 'tgmpa'
314
+ ),
315
+ 'notice_cannot_install' => _n_noop(
316
+ 'Sorry, but you do not have the correct permissions to install the %1$s plugin.',
317
+ 'Sorry, but you do not have the correct permissions to install the %1$s plugins.',
318
+ 'tgmpa'
319
+ ),
320
+ 'notice_ask_to_update' => _n_noop(
321
+ 'The following plugin needs to be updated to its latest version to ensure maximum compatibility with this theme: %1$s.',
322
+ 'The following plugins need to be updated to their latest version to ensure maximum compatibility with this theme: %1$s.',
323
+ 'tgmpa'
324
+ ),
325
+ 'notice_ask_to_update_maybe' => _n_noop(
326
+ 'There is an update available for: %1$s.',
327
+ 'There are updates available for the following plugins: %1$s.',
328
+ 'tgmpa'
329
+ ),
330
+ 'notice_cannot_update' => _n_noop(
331
+ 'Sorry, but you do not have the correct permissions to update the %1$s plugin.',
332
+ 'Sorry, but you do not have the correct permissions to update the %1$s plugins.',
333
+ 'tgmpa'
334
+ ),
335
+ 'notice_can_activate_required' => _n_noop(
336
+ 'The following required plugin is currently inactive: %1$s.',
337
+ 'The following required plugins are currently inactive: %1$s.',
338
+ 'tgmpa'
339
+ ),
340
+ 'notice_can_activate_recommended' => _n_noop(
341
+ 'The following recommended plugin is currently inactive: %1$s.',
342
+ 'The following recommended plugins are currently inactive: %1$s.',
343
+ 'tgmpa'
344
+ ),
345
+ 'notice_cannot_activate' => _n_noop(
346
+ 'Sorry, but you do not have the correct permissions to activate the %1$s plugin.',
347
+ 'Sorry, but you do not have the correct permissions to activate the %1$s plugins.',
348
+ 'tgmpa'
349
+ ),
350
+ 'install_link' => _n_noop(
351
+ 'Begin installing plugin',
352
+ 'Begin installing plugins',
353
+ 'tgmpa'
354
+ ),
355
+ 'update_link' => _n_noop(
356
+ 'Begin updating plugin',
357
+ 'Begin updating plugins',
358
+ 'tgmpa'
359
+ ),
360
+ 'activate_link' => _n_noop(
361
+ 'Begin activating plugin',
362
+ 'Begin activating plugins',
363
+ 'tgmpa'
364
+ ),
365
+ 'return' => __( 'Return to Required Plugins Installer', 'tgmpa' ),
366
+ 'dashboard' => __( 'Return to the dashboard', 'tgmpa' ),
367
+ 'plugin_activated' => __( 'Plugin activated successfully.', 'tgmpa' ),
368
+ 'activated_successfully' => __( 'The following plugin was activated successfully:', 'tgmpa' ),
369
+ 'plugin_already_active' => __( 'No action taken. Plugin %1$s was already active.', 'tgmpa' ),
370
+ 'plugin_needs_higher_version' => __( 'Plugin not activated. A higher version of %s is needed for this theme. Please update the plugin.', 'tgmpa' ),
371
+ 'complete' => __( 'All plugins installed and activated successfully. %1$s', 'tgmpa' ),
372
+ 'dismiss' => __( 'Dismiss this notice', 'tgmpa' ),
373
+ 'contact_admin' => __( 'Please contact the administrator of this site for help.', 'tgmpa' ),
374
+ );
375
+
376
+ do_action( 'tgmpa_register' );
377
+
378
+ /* After this point, the plugins should be registered and the configuration set. */
379
+
380
+ // Proceed only if we have plugins to handle.
381
+ if ( empty( $this->plugins ) || ! is_array( $this->plugins ) ) {
382
+ return;
383
+ }
384
+
385
+ // Set up the menu and notices if we still have outstanding actions.
386
+ if ( true !== $this->is_tgmpa_complete() ) {
387
+ // Sort the plugins.
388
+ array_multisort( $this->sort_order, SORT_ASC, $this->plugins );
389
+
390
+ add_action( 'admin_menu', array( $this, 'admin_menu' ) );
391
+ add_action( 'admin_head', array( $this, 'dismiss' ) );
392
+
393
+ // Prevent the normal links from showing underneath a single install/update page.
394
+ add_filter( 'install_plugin_complete_actions', array( $this, 'actions' ) );
395
+ add_filter( 'update_plugin_complete_actions', array( $this, 'actions' ) );
396
+
397
+ if ( $this->has_notices ) {
398
+ add_action( 'admin_notices', array( $this, 'notices' ) );
399
+ add_action( 'admin_init', array( $this, 'admin_init' ), 1 );
400
+ add_action( 'admin_enqueue_scripts', array( $this, 'thickbox' ) );
401
+ }
402
+
403
+ add_action( 'load-plugins.php', array( $this, 'add_plugin_action_link_filters' ), 1 );
404
+ }
405
+
406
+ // Make sure things get reset on switch theme.
407
+ add_action( 'switch_theme', array( $this, 'flush_plugins_cache' ) );
408
+
409
+ if ( $this->has_notices ) {
410
+ add_action( 'switch_theme', array( $this, 'update_dismiss' ) );
411
+ }
412
+
413
+ // Setup the force activation hook.
414
+ if ( true === $this->has_forced_activation ) {
415
+ add_action( 'admin_init', array( $this, 'force_activation' ) );
416
+ }
417
+
418
+ // Setup the force deactivation hook.
419
+ if ( true === $this->has_forced_deactivation ) {
420
+ add_action( 'switch_theme', array( $this, 'force_deactivation' ) );
421
+ }
422
+ }
423
+
424
+ /**
425
+ * Prevent activation of plugins which don't meet the minimum version requirement from the
426
+ * WP native plugins page.
427
+ *
428
+ * @since 2.5.0
429
+ */
430
+ public function add_plugin_action_link_filters() {
431
+ foreach ( $this->plugins as $slug => $plugin ) {
432
+ if ( false === $this->can_plugin_activate( $slug ) ) {
433
+ add_filter( 'plugin_action_links_' . $plugin['file_path'], array( $this, 'filter_plugin_action_links_activate' ), 20 );
434
+ }
435
+
436
+ if ( true === $plugin['force_activation'] ) {
437
+ add_filter( 'plugin_action_links_' . $plugin['file_path'], array( $this, 'filter_plugin_action_links_deactivate' ), 20 );
438
+ }
439
+
440
+ if ( false !== $this->does_plugin_require_update( $slug ) ) {
441
+ add_filter( 'plugin_action_links_' . $plugin['file_path'], array( $this, 'filter_plugin_action_links_update' ), 20 );
442
+ }
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Remove the 'Activate' link on the WP native plugins page if the plugin does not meet the
448
+ * minimum version requirements.
449
+ *
450
+ * @since 2.5.0
451
+ *
452
+ * @param array $actions Action links.
453
+ * @return array
454
+ */
455
+ public function filter_plugin_action_links_activate( $actions ) {
456
+ unset( $actions['activate'] );
457
+
458
+ return $actions;
459
+ }
460
+
461
+ /**
462
+ * Remove the 'Deactivate' link on the WP native plugins page if the plugin has been set to force activate.
463
+ *
464
+ * @since 2.5.0
465
+ *
466
+ * @param array $actions Action links.
467
+ * @return array
468
+ */
469
+ public function filter_plugin_action_links_deactivate( $actions ) {
470
+ unset( $actions['deactivate'] );
471
+
472
+ return $actions;
473
+ }
474
+
475
+ /**
476
+ * Add a 'Requires update' link on the WP native plugins page if the plugin does not meet the
477
+ * minimum version requirements.
478
+ *
479
+ * @since 2.5.0
480
+ *
481
+ * @param array $actions Action links.
482
+ * @return array
483
+ */
484
+ public function filter_plugin_action_links_update( $actions ) {
485
+ $actions['update'] = sprintf(
486
+ '<a href="%1$s" title="%2$s" class="edit">%3$s</a>',
487
+ esc_url( $this->get_tgmpa_status_url( 'update' ) ),
488
+ esc_attr__( 'This plugin needs to be updated to be compatible with your theme.', 'tgmpa' ),
489
+ esc_html__( 'Update Required', 'tgmpa' )
490
+ );
491
+
492
+ return $actions;
493
+ }
494
+
495
+ /**
496
+ * Handles calls to show plugin information via links in the notices.
497
+ *
498
+ * We get the links in the admin notices to point to the TGMPA page, rather
499
+ * than the typical plugin-install.php file, so we can prepare everything
500
+ * beforehand.
501
+ *
502
+ * WP does not make it easy to show the plugin information in the thickbox -
503
+ * here we have to require a file that includes a function that does the
504
+ * main work of displaying it, enqueue some styles, set up some globals and
505
+ * finally call that function before exiting.
506
+ *
507
+ * Down right easy once you know how...
508
+ *
509
+ * Returns early if not the TGMPA page.
510
+ *
511
+ * @since 2.1.0
512
+ *
513
+ * @global string $tab Used as iframe div class names, helps with styling
514
+ * @global string $body_id Used as the iframe body ID, helps with styling
515
+ *
516
+ * @return null Returns early if not the TGMPA page.
517
+ */
518
+ public function admin_init() {
519
+ if ( ! $this->is_tgmpa_page() ) {
520
+ return;
521
+ }
522
+
523
+ if ( isset( $_REQUEST['tab'] ) && 'plugin-information' === $_REQUEST['tab'] ) {
524
+ // Needed for install_plugin_information().
525
+ require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
526
+
527
+ wp_enqueue_style( 'plugin-install' );
528
+
529
+ global $tab, $body_id;
530
+ $body_id = 'plugin-information';
531
+ // @codingStandardsIgnoreStart
532
+ $tab = 'plugin-information';
533
+ // @codingStandardsIgnoreEnd
534
+
535
+ install_plugin_information();
536
+
537
+ exit;
538
+ }
539
+ }
540
+
541
+ /**
542
+ * Enqueue thickbox scripts/styles for plugin info.
543
+ *
544
+ * Thickbox is not automatically included on all admin pages, so we must
545
+ * manually enqueue it for those pages.
546
+ *
547
+ * Thickbox is only loaded if the user has not dismissed the admin
548
+ * notice or if there are any plugins left to install and activate.
549
+ *
550
+ * @since 2.1.0
551
+ */
552
+ public function thickbox() {
553
+ if ( ! get_user_meta( get_current_user_id(), 'tgmpa_dismissed_notice_' . $this->id, true ) ) {
554
+ add_thickbox();
555
+ }
556
+ }
557
+
558
+ /**
559
+ * Adds submenu page if there are plugin actions to take.
560
+ *
561
+ * This method adds the submenu page letting users know that a required
562
+ * plugin needs to be installed.
563
+ *
564
+ * This page disappears once the plugin has been installed and activated.
565
+ *
566
+ * @since 1.0.0
567
+ *
568
+ * @see INBOUND_Plugin_Activation::init()
569
+ * @see INBOUND_Plugin_Activation::install_plugins_page()
570
+ *
571
+ * @return null Return early if user lacks capability to install a plugin.
572
+ */
573
+ public function admin_menu() {
574
+ // Make sure privileges are correct to see the page.
575
+ if ( ! current_user_can( 'install_plugins' ) ) {
576
+ return;
577
+ }
578
+
579
+ $args = apply_filters(
580
+ 'tgmpa_admin_menu_args',
581
+ array(
582
+ 'parent_slug' => $this->parent_slug, // Parent Menu slug.
583
+ 'page_title' => $this->strings['page_title'], // Page title.
584
+ 'menu_title' => $this->strings['menu_title'], // Menu title.
585
+ 'capability' => $this->capability, // Capability.
586
+ 'menu_slug' => $this->menu, // Menu slug.
587
+ 'function' => array( $this, 'install_plugins_page' ), // Callback.
588
+ )
589
+ );
590
+
591
+ $this->add_admin_menu( $args );
592
+ }
593
+
594
+ /**
595
+ * Add the menu item.
596
+ *
597
+ * @since 2.5.0
598
+ *
599
+ * @param array $args Menu item configuration.
600
+ */
601
+ protected function add_admin_menu( array $args ) {
602
+ if ( has_filter( 'tgmpa_admin_menu_use_add_theme_page' ) ) {
603
+ _deprecated_function( 'The "tgmpa_admin_menu_use_add_theme_page" filter', '2.5.0', esc_html__( 'Set the parent_slug config variable instead.', 'tgmpa' ) );
604
+ }
605
+
606
+ if ( 'themes.php' === $this->parent_slug ) {
607
+ $this->page_hook = call_user_func( 'add_theme_page', $args['page_title'], $args['menu_title'], $args['capability'], $args['menu_slug'], $args['function'] );
608
+ } else {
609
+ $this->page_hook = call_user_func( 'add_submenu_page', $args['parent_slug'], $args['page_title'], $args['menu_title'], $args['capability'], $args['menu_slug'], $args['function'] );
610
+ }
611
+ }
612
+
613
+ /**
614
+ * Echoes plugin installation form.
615
+ *
616
+ * This method is the callback for the admin_menu method function.
617
+ * This displays the admin page and form area where the user can select to install and activate the plugin.
618
+ * Aborts early if we're processing a plugin installation action.
619
+ *
620
+ * @since 1.0.0
621
+ *
622
+ * @return null Aborts early if we're processing a plugin installation action.
623
+ */
624
+ public function install_plugins_page() {
625
+ // Store new instance of plugin table in object.
626
+ $plugin_table = new INBOUND_TGMPA_List_Table;
627
+
628
+ // Return early if processing a plugin installation action.
629
+ if ( ( ( 'tgmpa-bulk-install' === $plugin_table->current_action() || 'tgmpa-bulk-update' === $plugin_table->current_action() ) && $plugin_table->process_bulk_actions() ) || $this->do_plugin_install() ) {
630
+ return;
631
+ }
632
+
633
+ // Force refresh of available plugin information so we'll know about manual updates/deletes.
634
+ wp_clean_plugins_cache( false );
635
+
636
+ ?>
637
+ <div class="tgmpa wrap">
638
+ <h2><?php echo esc_html( get_admin_page_title() ); ?></h2>
639
+ <?php $plugin_table->prepare_items(); ?>
640
+
641
+ <?php
642
+ if ( ! empty( $this->message ) && is_string( $this->message ) ) {
643
+ echo wp_kses_post( $this->message );
644
+ }
645
+ ?>
646
+ <?php $plugin_table->views(); ?>
647
+
648
+ <form id="tgmpa-plugins" action="" method="post">
649
+ <input type="hidden" name="tgmpa-page" value="<?php echo esc_attr( $this->menu ); ?>" />
650
+ <input type="hidden" name="plugin_status" value="<?php echo esc_attr( $plugin_table->view_context ); ?>" />
651
+ <?php $plugin_table->display(); ?>
652
+ </form>
653
+ </div>
654
+ <?php
655
+ }
656
+
657
+ /**
658
+ * Installs, updates or activates a plugin depending on the action link clicked by the user.
659
+ *
660
+ * Checks the $_GET variable to see which actions have been
661
+ * passed and responds with the appropriate method.
662
+ *
663
+ * Uses WP_Filesystem to process and handle the plugin installation
664
+ * method.
665
+ *
666
+ * @since 1.0.0
667
+ *
668
+ * @uses WP_Filesystem
669
+ * @uses WP_Error
670
+ * @uses WP_Upgrader
671
+ * @uses Plugin_Upgrader
672
+ * @uses Plugin_Installer_Skin
673
+ * @uses Plugin_Upgrader_Skin
674
+ *
675
+ * @return boolean True on success, false on failure.
676
+ */
677
+ protected function do_plugin_install() {
678
+ if ( empty( $_GET['plugin'] ) ) {
679
+ return false;
680
+ }
681
+
682
+ // All plugin information will be stored in an array for processing.
683
+ $slug = $this->sanitize_key( urldecode( $_GET['plugin'] ) );
684
+
685
+ if ( ! isset( $this->plugins[ $slug ] ) ) {
686
+ return false;
687
+ }
688
+
689
+ // Was an install or upgrade action link clicked?
690
+ if ( ( isset( $_GET['tgmpa-install'] ) && 'install-plugin' === $_GET['tgmpa-install'] ) || ( isset( $_GET['tgmpa-update'] ) && 'update-plugin' === $_GET['tgmpa-update'] ) ) {
691
+
692
+ $install_type = 'install';
693
+ if ( isset( $_GET['tgmpa-update'] ) && 'update-plugin' === $_GET['tgmpa-update'] ) {
694
+ $install_type = 'update';
695
+ }
696
+
697
+ check_admin_referer( 'tgmpa-' . $install_type, 'tgmpa-nonce' );
698
+
699
+ // Pass necessary information via URL if WP_Filesystem is needed.
700
+ $url = wp_nonce_url(
701
+ add_query_arg(
702
+ array(
703
+ 'plugin' => urlencode( $slug ),
704
+ 'tgmpa-' . $install_type => $install_type . '-plugin',
705
+ ),
706
+ $this->get_tgmpa_url()
707
+ ),
708
+ 'tgmpa-' . $install_type,
709
+ 'tgmpa-nonce'
710
+ );
711
+
712
+ $method = ''; // Leave blank so WP_Filesystem can populate it as necessary.
713
+
714
+ if ( false === ( $creds = request_filesystem_credentials( esc_url_raw( $url ), $method, false, false, array() ) ) ) {
715
+ return true;
716
+ }
717
+
718
+ if ( ! WP_Filesystem( $creds ) ) {
719
+ request_filesystem_credentials( esc_url_raw( $url ), $method, true, false, array() ); // Setup WP_Filesystem.
720
+ return true;
721
+ }
722
+
723
+ /* If we arrive here, we have the filesystem. */
724
+
725
+ // Prep variables for Plugin_Installer_Skin class.
726
+ $extra = array();
727
+ $extra['slug'] = $slug; // Needed for potentially renaming of directory name.
728
+ $source = $this->get_download_url( $slug );
729
+ $api = ( 'repo' === $this->plugins[ $slug ]['source_type'] ) ? $this->get_plugins_api( $slug ) : null;
730
+ $api = ( false !== $api ) ? $api : null;
731
+
732
+ $url = add_query_arg(
733
+ array(
734
+ 'action' => $install_type . '-plugin',
735
+ 'plugin' => urlencode( $slug ),
736
+ ),
737
+ 'update.php'
738
+ );
739
+
740
+ if ( ! class_exists( 'Plugin_Upgrader', false ) ) {
741
+ require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
742
+ }
743
+
744
+ $skin_args = array(
745
+ 'type' => ( 'bundled' !== $this->plugins[ $slug ]['source_type'] ) ? 'web' : 'upload',
746
+ 'title' => sprintf( $this->strings['installing'], $this->plugins[ $slug ]['name'] ),
747
+ 'url' => esc_url_raw( $url ),
748
+ 'nonce' => $install_type . '-plugin_' . $slug,
749
+ 'plugin' => '',
750
+ 'api' => $api,
751
+ 'extra' => $extra,
752
+ );
753
+
754
+ if ( 'update' === $install_type ) {
755
+ $skin_args['plugin'] = $this->plugins[ $slug ]['file_path'];
756
+ $skin = new Plugin_Upgrader_Skin( $skin_args );
757
+ } else {
758
+ $skin = new Plugin_Installer_Skin( $skin_args );
759
+ }
760
+
761
+ // Create a new instance of Plugin_Upgrader.
762
+ $upgrader = new Plugin_Upgrader( $skin );
763
+
764
+ // Perform the action and install the plugin from the $source urldecode().
765
+ add_filter( 'upgrader_source_selection', array( $this, 'maybe_adjust_source_dir' ), 1, 3 );
766
+
767
+ if ( 'update' === $install_type ) {
768
+ // Inject our info into the update transient.
769
+ $to_inject = array( $slug => $this->plugins[ $slug ] );
770
+ $to_inject[ $slug ]['source'] = $source;
771
+ $this->inject_update_info( $to_inject );
772
+
773
+ $upgrader->upgrade( $this->plugins[ $slug ]['file_path'] );
774
+ } else {
775
+ $upgrader->install( $source );
776
+ }
777
+
778
+ remove_filter( 'upgrader_source_selection', array( $this, 'maybe_adjust_source_dir' ), 1, 3 );
779
+
780
+ // Make sure we have the correct file path now the plugin is installed/updated.
781
+ $this->populate_file_path( $slug );
782
+
783
+ // Only activate plugins if the config option is set to true and the plugin isn't
784
+ // already active (upgrade).
785
+ if ( $this->is_automatic && ! $this->is_plugin_active( $slug ) ) {
786
+ $plugin_activate = $upgrader->plugin_info(); // Grab the plugin info from the Plugin_Upgrader method.
787
+ if ( false === $this->activate_single_plugin( $plugin_activate, $slug, true ) ) {
788
+ return true; // Finish execution of the function early as we encountered an error.
789
+ }
790
+ }
791
+
792
+ $this->show_tgmpa_version();
793
+
794
+ // Display message based on if all plugins are now active or not.
795
+ if ( $this->is_tgmpa_complete() ) {
796
+ echo '<p>', sprintf( esc_html( $this->strings['complete'] ), '<a href="' . esc_url( self_admin_url() ) . '">' . esc_html__( 'Return to the Dashboard', 'tgmpa' ) . '</a>' ), '</p>';
797
+ echo '<style type="text/css">#adminmenu .wp-submenu li.current { display: none !important; }</style>';
798
+ } else {
799
+ echo '<p><a href="', esc_url( $this->get_tgmpa_url() ), '" target="_parent">', esc_html( $this->strings['return'] ), '</a></p>';
800
+ }
801
+
802
+ return true;
803
+ } elseif ( isset( $this->plugins[ $slug ]['file_path'], $_GET['tgmpa-activate'] ) && 'activate-plugin' === $_GET['tgmpa-activate'] ) {
804
+ // Activate action link was clicked.
805
+ check_admin_referer( 'tgmpa-activate', 'tgmpa-nonce' );
806
+
807
+ if ( false === $this->activate_single_plugin( $this->plugins[ $slug ]['file_path'], $slug ) ) {
808
+ return true; // Finish execution of the function early as we encountered an error.
809
+ }
810
+ }
811
+
812
+ return false;
813
+ }
814
+
815
+ /**
816
+ * Inject information into the 'update_plugins' site transient as WP checks that before running an update.
817
+ *
818
+ * @since 2.5.0
819
+ *
820
+ * @param array $plugins The plugin information for the plugins which are to be updated.
821
+ */
822
+ public function inject_update_info( $plugins ) {
823
+ $repo_updates = get_site_transient( 'update_plugins' );
824
+
825
+ if ( ! is_object( $repo_updates ) ) {
826
+ $repo_updates = new stdClass;
827
+ }
828
+
829
+ foreach ( $plugins as $slug => $plugin ) {
830
+ $file_path = $plugin['file_path'];
831
+
832
+ if ( empty( $repo_updates->response[ $file_path ] ) ) {
833
+ $repo_updates->response[ $file_path ] = new stdClass;
834
+ }
835
+
836
+ // We only really need to set package, but let's do all we can in case WP changes something.
837
+ $repo_updates->response[ $file_path ]->slug = $slug;
838
+ $repo_updates->response[ $file_path ]->plugin = $file_path;
839
+ $repo_updates->response[ $file_path ]->new_version = $plugin['version'];
840
+ $repo_updates->response[ $file_path ]->package = $plugin['source'];
841
+ if ( empty( $repo_updates->response[ $file_path ]->url ) && ! empty( $plugin['external_url'] ) ) {
842
+ $repo_updates->response[ $file_path ]->url = $plugin['external_url'];
843
+ }
844
+ }
845
+
846
+ set_site_transient( 'update_plugins', $repo_updates );
847
+ }
848
+
849
+ /**
850
+ * Adjust the plugin directory name if necessary.
851
+ *
852
+ * The final destination directory of a plugin is based on the subdirectory name found in the
853
+ * (un)zipped source. In some cases - most notably GitHub repository plugin downloads -, this
854
+ * subdirectory name is not the same as the expected slug and the plugin will not be recognized
855
+ * as installed. This is fixed by adjusting the temporary unzipped source subdirectory name to
856
+ * the expected plugin slug.
857
+ *
858
+ * @since 2.5.0
859
+ *
860
+ * @param string $source Path to upgrade/zip-file-name.tmp/subdirectory/.
861
+ * @param string $remote_source Path to upgrade/zip-file-name.tmp.
862
+ * @param \WP_Upgrader $upgrader Instance of the upgrader which installs the plugin.
863
+ * @return string $source
864
+ */
865
+ public function maybe_adjust_source_dir( $source, $remote_source, $upgrader ) {
866
+ if ( ! $this->is_tgmpa_page() || ! is_object( $GLOBALS['wp_filesystem'] ) ) {
867
+ return $source;
868
+ }
869
+
870
+ // Check for single file plugins.
871
+ $source_files = array_keys( $GLOBALS['wp_filesystem']->dirlist( $remote_source ) );
872
+ if ( 1 === count( $source_files ) && false === $GLOBALS['wp_filesystem']->is_dir( $source ) ) {
873
+ return $source;
874
+ }
875
+
876
+ // Multi-file plugin, let's see if the directory is correctly named.
877
+ $desired_slug = '';
878
+
879
+ // Figure out what the slug is supposed to be.
880
+ if ( false === $upgrader->bulk && ! empty( $upgrader->skin->options['extra']['slug'] ) ) {
881
+ $desired_slug = $upgrader->skin->options['extra']['slug'];
882
+ } else {
883
+ // Bulk installer contains less info, so fall back on the info registered here.
884
+ foreach ( $this->plugins as $slug => $plugin ) {
885
+ if ( ! empty( $upgrader->skin->plugin_names[ $upgrader->skin->i ] ) && $plugin['name'] === $upgrader->skin->plugin_names[ $upgrader->skin->i ] ) {
886
+ $desired_slug = $slug;
887
+ break;
888
+ }
889
+ }
890
+ unset( $slug, $plugin );
891
+ }
892
+
893
+ if ( ! empty( $desired_slug ) ) {
894
+ $subdir_name = untrailingslashit( str_replace( trailingslashit( $remote_source ), '', $source ) );
895
+
896
+ if ( ! empty( $subdir_name ) && $subdir_name !== $desired_slug ) {
897
+ $from = untrailingslashit( $source );
898
+ $to = trailingslashit( $remote_source ) . $desired_slug;
899
+
900
+ if ( true === $GLOBALS['wp_filesystem']->move( $from, $to ) ) {
901
+ return trailingslashit( $to );
902
+ } else {
903
+ return new WP_Error( 'rename_failed', esc_html__( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.', 'tgmpa' ) . ' ' . esc_html__( 'Please contact the plugin provider and ask them to package their plugin according to the WordPress guidelines.', 'tgmpa' ), array( 'found' => $subdir_name, 'expected' => $desired_slug ) );
904
+ }
905
+ } elseif ( empty( $subdir_name ) ) {
906
+ return new WP_Error( 'packaged_wrong', esc_html__( 'The remote plugin package consists of more than one file, but the files are not packaged in a folder.', 'tgmpa' ) . ' ' . esc_html__( 'Please contact the plugin provider and ask them to package their plugin according to the WordPress guidelines.', 'tgmpa' ), array( 'found' => $subdir_name, 'expected' => $desired_slug ) );
907
+ }
908
+ }
909
+
910
+ return $source;
911
+ }
912
+
913
+ /**
914
+ * Activate a single plugin and send feedback about the result to the screen.
915
+ *
916
+ * @since 2.5.0
917
+ *
918
+ * @param string $file_path Path within wp-plugins/ to main plugin file.
919
+ * @param string $slug Plugin slug.
920
+ * @param bool $automatic Whether this is an automatic activation after an install. Defaults to false.
921
+ * This determines the styling of the output messages.
922
+ * @return bool False if an error was encountered, true otherwise.
923
+ */
924
+ protected function activate_single_plugin( $file_path, $slug, $automatic = false ) {
925
+ if ( $this->can_plugin_activate( $slug ) ) {
926
+ $activate = activate_plugin( $file_path );
927
+
928
+ if ( is_wp_error( $activate ) ) {
929
+ echo '<div id="message" class="error"><p>', wp_kses_post( $activate->get_error_message() ), '</p></div>',
930
+ '<p><a href="', esc_url( $this->get_tgmpa_url() ), '" target="_parent">', esc_html( $this->strings['return'] ), '</a></p>';
931
+
932
+ return false; // End it here if there is an error with activation.
933
+ } else {
934
+ if ( ! $automatic ) {
935
+ // Make sure message doesn't display again if bulk activation is performed
936
+ // immediately after a single activation.
937
+ if ( ! isset( $_POST['action'] ) ) { // WPCS: CSRF OK.
938
+ echo '<div id="message" class="updated"><p>', esc_html( $this->strings['activated_successfully'] ), ' <strong>', esc_html( $this->plugins[ $slug ]['name'] ), '.</strong></p></div>';
939
+ }
940
+ } else {
941
+ // Simpler message layout for use on the plugin install page.
942
+ echo '<p>', esc_html( $this->strings['plugin_activated'] ), '</p>';
943
+ }
944
+ }
945
+ } elseif ( $this->is_plugin_active( $slug ) ) {
946
+ // No simpler message format provided as this message should never be encountered
947
+ // on the plugin install page.
948
+ echo '<div id="message" class="error"><p>',
949
+ sprintf(
950
+ esc_html( $this->strings['plugin_already_active'] ),
951
+ '<strong>' . esc_html( $this->plugins[ $slug ]['name'] ) . '</strong>'
952
+ ),
953
+ '</p></div>';
954
+ } elseif ( $this->does_plugin_require_update( $slug ) ) {
955
+ if ( ! $automatic ) {
956
+ // Make sure message doesn't display again if bulk activation is performed
957
+ // immediately after a single activation.
958
+ if ( ! isset( $_POST['action'] ) ) { // WPCS: CSRF OK.
959
+ echo '<div id="message" class="error"><p>',
960
+ sprintf(
961
+ esc_html( $this->strings['plugin_needs_higher_version'] ),
962
+ '<strong>' . esc_html( $this->plugins[ $slug ]['name'] ) . '</strong>'
963
+ ),
964
+ '</p></div>';
965
+ }
966
+ } else {
967
+ // Simpler message layout for use on the plugin install page.
968
+ echo '<p>', sprintf( esc_html( $this->strings['plugin_needs_higher_version'] ), esc_html( $this->plugins[ $slug ]['name'] ) ), '</p>';
969
+ }
970
+ }
971
+
972
+ return true;
973
+ }
974
+
975
+ /**
976
+ * Echoes required plugin notice.
977
+ *
978
+ * Outputs a message telling users that a specific plugin is required for
979
+ * their theme. If appropriate, it includes a link to the form page where
980
+ * users can install and activate the plugin.
981
+ *
982
+ * Returns early if we're on the Install page.
983
+ *
984
+ * @since 1.0.0
985
+ *
986
+ * @global object $current_screen
987
+ *
988
+ * @return null Returns early if we're on the Install page.
989
+ */
990
+ public function notices() {
991
+ // Remove nag on the install page / Return early if the nag message has been dismissed.
992
+ if ( $this->is_tgmpa_page() || get_user_meta( get_current_user_id(), 'tgmpa_dismissed_notice_' . $this->id, true ) ) {
993
+ return;
994
+ }
995
+
996
+ // Store for the plugin slugs by message type.
997
+ $message = array();
998
+
999
+ // Initialize counters used to determine plurality of action link texts.
1000
+ $install_link_count = 0;
1001
+ $update_link_count = 0;
1002
+ $activate_link_count = 0;
1003
+
1004
+ foreach ( $this->plugins as $slug => $plugin ) {
1005
+ if ( $this->is_plugin_active( $slug ) && false === $this->does_plugin_have_update( $slug ) ) {
1006
+ continue;
1007
+ }
1008
+
1009
+ if ( ! $this->is_plugin_installed( $slug ) ) {
1010
+ if ( current_user_can( 'install_plugins' ) ) {
1011
+ $install_link_count++;
1012
+
1013
+ if ( true === $plugin['required'] ) {
1014
+ $message['notice_can_install_required'][] = $slug;
1015
+ } else {
1016
+ $message['notice_can_install_recommended'][] = $slug;
1017
+ }
1018
+ } else {
1019
+ // Need higher privileges to install the plugin.
1020
+ $message['notice_cannot_install'][] = $slug;
1021
+ }
1022
+ } else {
1023
+ if ( ! $this->is_plugin_active( $slug ) && $this->can_plugin_activate( $slug ) ) {
1024
+ if ( current_user_can( 'activate_plugins' ) ) {
1025
+ $activate_link_count++;
1026
+
1027
+ if ( true === $plugin['required'] ) {
1028
+ $message['notice_can_activate_required'][] = $slug;
1029
+ } else {
1030
+ $message['notice_can_activate_recommended'][] = $slug;
1031
+ }
1032
+ } else {
1033
+ // Need higher privileges to activate the plugin.
1034
+ $message['notice_cannot_activate'][] = $slug;
1035
+ }
1036
+ }
1037
+
1038
+ if ( $this->does_plugin_require_update( $slug ) || false !== $this->does_plugin_have_update( $slug ) ) {
1039
+
1040
+ if ( current_user_can( 'install_plugins' ) ) {
1041
+ $update_link_count++;
1042
+
1043
+ if ( $this->does_plugin_require_update( $slug ) ) {
1044
+ $message['notice_ask_to_update'][] = $slug;
1045
+ } elseif ( false !== $this->does_plugin_have_update( $slug ) ) {
1046
+ $message['notice_ask_to_update_maybe'][] = $slug;
1047
+ }
1048
+ } else {
1049
+ // Need higher privileges to update the plugin.
1050
+ $message['notice_cannot_update'][] = $slug;
1051
+ }
1052
+ }
1053
+ }
1054
+ }
1055
+ unset( $slug, $plugin );
1056
+
1057
+ // If we have notices to display, we move forward.
1058
+ if ( ! empty( $message ) ) {
1059
+ krsort( $message ); // Sort messages.
1060
+ $rendered = '';
1061
+
1062
+ // As add_settings_error() wraps the final message in a <p> and as the final message can't be
1063
+ // filtered, using <p>'s in our html would render invalid html output.
1064
+ $line_template = '<span style="display: block; margin: 0.5em 0.5em 0 0; clear: both;">%s</span>' . "\n";
1065
+
1066
+ // If dismissable is false and a message is set, output it now.
1067
+ if ( ! $this->dismissable && ! empty( $this->dismiss_msg ) ) {
1068
+ $rendered .= sprintf( $line_template, wp_kses_post( $this->dismiss_msg ) );
1069
+ }
1070
+
1071
+ // Render the individual message lines for the notice.
1072
+ foreach ( $message as $type => $plugin_group ) {
1073
+ $linked_plugins = array();
1074
+
1075
+ // Get the external info link for a plugin if one is available.
1076
+ foreach ( $plugin_group as $plugin_slug ) {
1077
+ $linked_plugins[] = $this->get_info_link( $plugin_slug );
1078
+ }
1079
+ unset( $plugin_slug );
1080
+
1081
+ $count = count( $plugin_group );
1082
+ $linked_plugins = array_map( array( 'INBOUND_TGM_Utils', 'wrap_in_em' ), $linked_plugins );
1083
+ $last_plugin = array_pop( $linked_plugins ); // Pop off last name to prep for readability.
1084
+
1085
+ $imploded = empty( $linked_plugins ) ? $last_plugin : ( implode( ', ', $linked_plugins ) . ' <span class="inbound-and">and</span>' . ' ' . $last_plugin );
1086
+
1087
+ $rendered .= sprintf(
1088
+ $line_template,
1089
+ sprintf(
1090
+ translate_nooped_plural( $this->strings[ $type ], $count, 'tgmpa' ),
1091
+ $imploded,
1092
+ $count
1093
+ )
1094
+ );
1095
+
1096
+ if ( 0 === strpos( $type, 'notice_cannot' ) ) {
1097
+ $rendered .= $this->strings['contact_admin'];
1098
+ }
1099
+ }
1100
+ unset( $type, $plugin_group, $linked_plugins, $count, $last_plugin, $imploded );
1101
+
1102
+ // Setup action links.
1103
+ $action_links = array(
1104
+ 'install' => '',
1105
+ 'update' => '',
1106
+ 'activate' => '',
1107
+ 'dismiss' => $this->dismissable ? '<a href="' . esc_url( add_query_arg( 'tgmpa-dismiss', 'dismiss_admin_notices' ) ) . '" class="dismiss-notice" target="_parent">' . esc_html( $this->strings['dismiss'] ) . '</a>' : '',
1108
+ );
1109
+
1110
+ $link_template = '<a href="%2$s">%1$s</a>';
1111
+
1112
+ if ( current_user_can( 'install_plugins' ) ) {
1113
+ if ( $install_link_count > 0 ) {
1114
+ $action_links['install'] = sprintf(
1115
+ $link_template,
1116
+ translate_nooped_plural( $this->strings['install_link'], $install_link_count, 'tgmpa' ),
1117
+ esc_url( $this->get_tgmpa_status_url( 'install' ) )
1118
+ );
1119
+ }
1120
+ if ( $update_link_count > 0 ) {
1121
+ $action_links['update'] = sprintf(
1122
+ $link_template,
1123
+ translate_nooped_plural( $this->strings['update_link'], $update_link_count, 'tgmpa' ),
1124
+ esc_url( $this->get_tgmpa_status_url( 'update' ) )
1125
+ );
1126
+ }
1127
+ }
1128
+
1129
+ if ( current_user_can( 'activate_plugins' ) && $activate_link_count > 0 ) {
1130
+ $action_links['activate'] = sprintf(
1131
+ $link_template,
1132
+ translate_nooped_plural( $this->strings['activate_link'], $activate_link_count, 'tgmpa' ),
1133
+ esc_url( $this->get_tgmpa_status_url( 'activate' ) )
1134
+ );
1135
+ }
1136
+
1137
+ $action_links = apply_filters( 'tgmpa_notice_action_links', $action_links );
1138
+
1139
+ $action_links = array_filter( (array) $action_links ); // Remove any empty array items.
1140
+
1141
+ if ( ! empty( $action_links ) && is_array( $action_links ) ) {
1142
+ $action_links = sprintf( $line_template, implode( ' | ', $action_links ) );
1143
+ $rendered .= apply_filters( 'tgmpa_notice_rendered_action_links', $action_links );
1144
+ }
1145
+
1146
+ // Register the nag messages and prepare them to be processed.
1147
+ if ( ! empty( $this->strings['nag_type'] ) ) {
1148
+ add_settings_error( 'tgmpa', 'tgmpa', $rendered, sanitize_html_class( strtolower( $this->strings['nag_type'] ) ) );
1149
+ } else {
1150
+ $nag_class = version_compare( $this->wp_version, '3.8', '<' ) ? 'updated' : 'update-nag';
1151
+ add_settings_error( 'tgmpa', 'tgmpa', $rendered, $nag_class );
1152
+ }
1153
+ }
1154
+
1155
+ // Admin options pages already output settings_errors, so this is to avoid duplication.
1156
+ if ( 'options-general' !== $GLOBALS['current_screen']->parent_base ) {
1157
+ $this->display_settings_errors();
1158
+ }
1159
+ }
1160
+
1161
+ /**
1162
+ * Display settings errors and remove those which have been displayed to avoid duplicate messages showing
1163
+ *
1164
+ * @since 2.5.0
1165
+ */
1166
+ protected function display_settings_errors() {
1167
+ global $wp_settings_errors;
1168
+
1169
+ settings_errors( 'tgmpa' );
1170
+
1171
+ foreach ( (array) $wp_settings_errors as $key => $details ) {
1172
+ if ( 'tgmpa' === $details['setting'] ) {
1173
+ unset( $wp_settings_errors[ $key ] );
1174
+ break;
1175
+ }
1176
+ }
1177
+ }
1178
+
1179
+ /**
1180
+ * Add dismissable admin notices.
1181
+ *
1182
+ * Appends a link to the admin nag messages. If clicked, the admin notice disappears and no longer is visible to users.
1183
+ *
1184
+ * @since 2.1.0
1185
+ */
1186
+ public function dismiss() {
1187
+ if ( isset( $_GET['tgmpa-dismiss'] ) ) {
1188
+ update_user_meta( get_current_user_id(), 'tgmpa_dismissed_notice_' . $this->id, 1 );
1189
+ }
1190
+ }
1191
+
1192
+ /**
1193
+ * Add individual plugin to our collection of plugins.
1194
+ *
1195
+ * If the required keys are not set or the plugin has already
1196
+ * been registered, the plugin is not added.
1197
+ *
1198
+ * @since 2.0.0
1199
+ *
1200
+ * @param array|null $plugin Array of plugin arguments or null if invalid argument.
1201
+ * @return null Return early if incorrect argument.
1202
+ */
1203
+ public function register( $plugin ) {
1204
+ if ( empty( $plugin['slug'] ) || empty( $plugin['name'] ) ) {
1205
+ return;
1206
+ }
1207
+
1208
+ if ( empty( $plugin['slug'] ) || ! is_string( $plugin['slug'] ) || isset( $this->plugins[ $plugin['slug'] ] ) ) {
1209
+ return;
1210
+ }
1211
+
1212
+ $defaults = array(
1213
+ 'name' => '', // String
1214
+ 'slug' => '', // String
1215
+ 'source' => 'repo', // String
1216
+ 'required' => false, // Boolean
1217
+ 'version' => '', // String
1218
+ 'force_activation' => false, // Boolean
1219
+ 'force_deactivation' => false, // Boolean
1220
+ 'external_url' => '', // String
1221
+ 'is_callable' => '', // String|Array.
1222
+ );
1223
+
1224
+ // Prepare the received data.
1225
+ $plugin = wp_parse_args( $plugin, $defaults );
1226
+
1227
+ // Standardize the received slug.
1228
+ $plugin['slug'] = $this->sanitize_key( $plugin['slug'] );
1229
+
1230
+ // Forgive users for using string versions of booleans or floats for version number.
1231
+ $plugin['version'] = (string) $plugin['version'];
1232
+ $plugin['source'] = empty( $plugin['source'] ) ? 'repo' : $plugin['source'];
1233
+ $plugin['required'] = INBOUND_TGM_Utils::validate_bool( $plugin['required'] );
1234
+ $plugin['force_activation'] = INBOUND_TGM_Utils::validate_bool( $plugin['force_activation'] );
1235
+ $plugin['force_deactivation'] = INBOUND_TGM_Utils::validate_bool( $plugin['force_deactivation'] );
1236
+
1237
+ // Enrich the received data.
1238
+ $plugin['file_path'] = $this->_get_plugin_basename_from_slug( $plugin['slug'] );
1239
+ $plugin['source_type'] = $this->get_plugin_source_type( $plugin['source'] );
1240
+
1241
+ // Set the class properties.
1242
+ $this->plugins[ $plugin['slug'] ] = $plugin;
1243
+ $this->sort_order[ $plugin['slug'] ] = $plugin['name'];
1244
+
1245
+ // Should we add the force activation hook ?
1246
+ if ( true === $plugin['force_activation'] ) {
1247
+ $this->has_forced_activation = true;
1248
+ }
1249
+
1250
+ // Should we add the force deactivation hook ?
1251
+ if ( true === $plugin['force_deactivation'] ) {
1252
+ $this->has_forced_deactivation = true;
1253
+ }
1254
+ }
1255
+
1256
+ /**
1257
+ * Determine what type of source the plugin comes from.
1258
+ *
1259
+ * @since 2.5.0
1260
+ *
1261
+ * @param string $source The source of the plugin as provided, either empty (= WP repo), a file path
1262
+ * (= bundled) or an external URL.
1263
+ * @return string 'repo', 'external', or 'bundled'
1264
+ */
1265
+ protected function get_plugin_source_type( $source ) {
1266
+ if ( 'repo' === $source || preg_match( self::WP_REPO_REGEX, $source ) ) {
1267
+ return 'repo';
1268
+ } elseif ( preg_match( self::IS_URL_REGEX, $source ) ) {
1269
+ return 'external';
1270
+ } else {
1271
+ return 'bundled';
1272
+ }
1273
+ }
1274
+
1275
+ /**
1276
+ * Sanitizes a string key.
1277
+ *
1278
+ * Near duplicate of WP Core `sanitize_key()`. The difference is that uppercase characters *are*
1279
+ * allowed, so as not to break upgrade paths from non-standard bundled plugins using uppercase
1280
+ * characters in the plugin directory path/slug. Silly them.
1281
+ *
1282
+ * @see https://developer.wordpress.org/reference/hooks/sanitize_key/
1283
+ *
1284
+ * @since 2.5.0
1285
+ *
1286
+ * @param string $key String key.
1287
+ * @return string Sanitized key
1288
+ */
1289
+ public function sanitize_key( $key ) {
1290
+ $raw_key = $key;
1291
+ $key = preg_replace( '`[^A-Za-z0-9_-]`', '', $key );
1292
+
1293
+ /**
1294
+ * Filter a sanitized key string.
1295
+ *
1296
+ * @since 3.0.0
1297
+ *
1298
+ * @param string $key Sanitized key.
1299
+ * @param string $raw_key The key prior to sanitization.
1300
+ */
1301
+ return apply_filters( 'tgmpa_sanitize_key', $key, $raw_key );
1302
+ }
1303
+
1304
+ /**
1305
+ * Amend default configuration settings.
1306
+ *
1307
+ * @since 2.0.0
1308
+ *
1309
+ * @param array $config Array of config options to pass as class properties.
1310
+ */
1311
+ public function config( $config ) {
1312
+ $keys = array(
1313
+ 'id',
1314
+ 'default_path',
1315
+ 'has_notices',
1316
+ 'dismissable',
1317
+ 'dismiss_msg',
1318
+ 'menu',
1319
+ 'parent_slug',
1320
+ 'capability',
1321
+ 'is_automatic',
1322
+ 'message',
1323
+ 'strings',
1324
+ );
1325
+
1326
+ foreach ( $keys as $key ) {
1327
+ if ( isset( $config[ $key ] ) ) {
1328
+ if ( is_array( $config[ $key ] ) ) {
1329
+ $this->$key = array_merge( $this->$key, $config[ $key ] );
1330
+ } else {
1331
+ $this->$key = $config[ $key ];
1332
+ }
1333
+ }
1334
+ }
1335
+ }
1336
+
1337
+ /**
1338
+ * Amend action link after plugin installation.
1339
+ *
1340
+ * @since 2.0.0
1341
+ *
1342
+ * @param array $install_actions Existing array of actions.
1343
+ * @return array Amended array of actions.
1344
+ */
1345
+ public function actions( $install_actions ) {
1346
+ // Remove action links on the TGMPA install page.
1347
+ if ( $this->is_tgmpa_page() ) {
1348
+ return false;
1349
+ }
1350
+
1351
+ return $install_actions;
1352
+ }
1353
+
1354
+ /**
1355
+ * Flushes the plugins cache on theme switch to prevent stale entries
1356
+ * from remaining in the plugin table.
1357
+ *
1358
+ * @since 2.4.0
1359
+ *
1360
+ * @param bool $clear_update_cache Optional. Whether to clear the Plugin updates cache.
1361
+ * Parameter added in v2.5.0.
1362
+ */
1363
+ public function flush_plugins_cache( $clear_update_cache = true ) {
1364
+ wp_clean_plugins_cache( $clear_update_cache );
1365
+ }
1366
+
1367
+ /**
1368
+ * Set file_path key for each installed plugin.
1369
+ *
1370
+ * @since 2.1.0
1371
+ *
1372
+ * @param string $plugin_slug Optional. If set, only (re-)populates the file path for that specific plugin.
1373
+ * Parameter added in v2.5.0.
1374
+ */
1375
+ public function populate_file_path( $plugin_slug = '' ) {
1376
+ if ( ! empty( $plugin_slug ) && is_string( $plugin_slug ) && isset( $this->plugins[ $plugin_slug ] ) ) {
1377
+ $this->plugins[ $plugin_slug ]['file_path'] = $this->_get_plugin_basename_from_slug( $plugin_slug );
1378
+ } else {
1379
+ // Add file_path key for all plugins.
1380
+ foreach ( $this->plugins as $slug => $values ) {
1381
+ $this->plugins[ $slug ]['file_path'] = $this->_get_plugin_basename_from_slug( $slug );
1382
+ }
1383
+ }
1384
+ }
1385
+
1386
+ /**
1387
+ * Helper function to extract the file path of the plugin file from the
1388
+ * plugin slug, if the plugin is installed.
1389
+ *
1390
+ * @since 2.0.0
1391
+ *
1392
+ * @param string $slug Plugin slug (typically folder name) as provided by the developer.
1393
+ * @return string Either file path for plugin if installed, or just the plugin slug.
1394
+ */
1395
+ protected function _get_plugin_basename_from_slug( $slug ) {
1396
+ $keys = array_keys( $this->get_plugins() );
1397
+
1398
+ foreach ( $keys as $key ) {
1399
+ if ( preg_match( '|^' . $slug . '/|', $key ) ) {
1400
+ return $key;
1401
+ }
1402
+ }
1403
+
1404
+ return $slug;
1405
+ }
1406
+
1407
+ /**
1408
+ * Retrieve plugin data, given the plugin name.
1409
+ *
1410
+ * Loops through the registered plugins looking for $name. If it finds it,
1411
+ * it returns the $data from that plugin. Otherwise, returns false.
1412
+ *
1413
+ * @since 2.1.0
1414
+ *
1415
+ * @param string $name Name of the plugin, as it was registered.
1416
+ * @param string $data Optional. Array key of plugin data to return. Default is slug.
1417
+ * @return string|boolean Plugin slug if found, false otherwise.
1418
+ */
1419
+ public function _get_plugin_data_from_name( $name, $data = 'slug' ) {
1420
+ foreach ( $this->plugins as $values ) {
1421
+ if ( $name === $values['name'] && isset( $values[ $data ] ) ) {
1422
+ return $values[ $data ];
1423
+ }
1424
+ }
1425
+
1426
+ return false;
1427
+ }
1428
+
1429
+ /**
1430
+ * Retrieve the download URL for a package.
1431
+ *
1432
+ * @since 2.5.0
1433
+ *
1434
+ * @param string $slug Plugin slug.
1435
+ * @return string Plugin download URL or path to local file or empty string if undetermined.
1436
+ */
1437
+ public function get_download_url( $slug ) {
1438
+ $dl_source = '';
1439
+
1440
+ switch ( $this->plugins[ $slug ]['source_type'] ) {
1441
+ case 'repo':
1442
+ return $this->get_wp_repo_download_url( $slug );
1443
+ case 'external':
1444
+ return $this->plugins[ $slug ]['source'];
1445
+ case 'bundled':
1446
+ return $this->default_path . $this->plugins[ $slug ]['source'];
1447
+ }
1448
+
1449
+ return $dl_source; // Should never happen.
1450
+ }
1451
+
1452
+ /**
1453
+ * Retrieve the download URL for a WP repo package.
1454
+ *
1455
+ * @since 2.5.0
1456
+ *
1457
+ * @param string $slug Plugin slug.
1458
+ * @return string Plugin download URL.
1459
+ */
1460
+ protected function get_wp_repo_download_url( $slug ) {
1461
+ $source = '';
1462
+ $api = $this->get_plugins_api( $slug );
1463
+
1464
+ if ( false !== $api && isset( $api->download_link ) ) {
1465
+ $source = $api->download_link;
1466
+ }
1467
+
1468
+ return $source;
1469
+ }
1470
+
1471
+ /**
1472
+ * Try to grab information from WordPress API.
1473
+ *
1474
+ * @since 2.5.0
1475
+ *
1476
+ * @param string $slug Plugin slug.
1477
+ * @return object Plugins_api response object on success, WP_Error on failure.
1478
+ */
1479
+ protected function get_plugins_api( $slug ) {
1480
+ static $api = array(); // Cache received responses.
1481
+
1482
+ if ( ! isset( $api[ $slug ] ) ) {
1483
+ if ( ! function_exists( 'plugins_api' ) ) {
1484
+ require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
1485
+ }
1486
+
1487
+ $response = plugins_api( 'plugin_information', array( 'slug' => $slug, 'fields' => array( 'sections' => false ) ) );
1488
+
1489
+ $api[ $slug ] = false;
1490
+
1491
+ if ( is_wp_error( $response ) ) {
1492
+ wp_die( esc_html( $this->strings['oops'] ) );
1493
+ } else {
1494
+ $api[ $slug ] = $response;
1495
+ }
1496
+ }
1497
+
1498
+ return $api[ $slug ];
1499
+ }
1500
+
1501
+ /**
1502
+ * Retrieve a link to a plugin information page.
1503
+ *
1504
+ * @since 2.5.0
1505
+ *
1506
+ * @param string $slug Plugin slug.
1507
+ * @return string Fully formed html link to a plugin information page if available
1508
+ * or the plugin name if not.
1509
+ */
1510
+ public function get_info_link( $slug ) {
1511
+ if ( ! empty( $this->plugins[ $slug ]['external_url'] ) && preg_match( self::IS_URL_REGEX, $this->plugins[ $slug ]['external_url'] ) ) {
1512
+ $link = sprintf(
1513
+ '<a href="%1$s" target="_blank">%2$s</a>',
1514
+ esc_url( $this->plugins[ $slug ]['external_url'] ),
1515
+ esc_html( $this->plugins[ $slug ]['name'] )
1516
+ );
1517
+ } elseif ( 'repo' === $this->plugins[ $slug ]['source_type'] ) {
1518
+ $url = add_query_arg(
1519
+ array(
1520
+ 'tab' => 'plugin-information',
1521
+ 'plugin' => urlencode( $slug ),
1522
+ 'TB_iframe' => 'true',
1523
+ 'width' => '640',
1524
+ 'height' => '500',
1525
+ ),
1526
+ self_admin_url( 'plugin-install.php' )
1527
+ );
1528
+
1529
+ $link = sprintf(
1530
+ '<a href="%1$s" class="thickbox">%2$s</a>',
1531
+ esc_url( $url ),
1532
+ $this->plugins[ $slug ]['name']
1533
+ );
1534
+ } else {
1535
+ $link = esc_html( $this->plugins[ $slug ]['name'] ); // No hyperlink.
1536
+ }
1537
+
1538
+ return $link;
1539
+ }
1540
+
1541
+ /**
1542
+ * Determine if we're on the TGMPA Install page.
1543
+ *
1544
+ * @since 2.1.0
1545
+ *
1546
+ * @return boolean True when on the TGMPA page, false otherwise.
1547
+ */
1548
+ protected function is_tgmpa_page() {
1549
+ return isset( $_GET['page'] ) && $this->menu === $_GET['page'];
1550
+ }
1551
+
1552
+ /**
1553
+ * Retrieve the URL to the TGMPA Install page.
1554
+ *
1555
+ * I.e. depending on the config settings passed something along the lines of:
1556
+ * http://example.com/wp-admin/themes.php?page=tgmpa-install-plugins
1557
+ *
1558
+ * @since 2.5.0
1559
+ *
1560
+ * @return string Properly encoded URL (not escaped).
1561
+ */
1562
+ public function get_tgmpa_url() {
1563
+ static $url;
1564
+
1565
+ if ( ! isset( $url ) ) {
1566
+ $parent = $this->parent_slug;
1567
+ if ( false === strpos( $parent, '.php' ) ) {
1568
+ $parent = 'admin.php';
1569
+ }
1570
+ $url = add_query_arg(
1571
+ array(
1572
+ 'page' => urlencode( $this->menu ),
1573
+ ),
1574
+ self_admin_url( $parent )
1575
+ );
1576
+ }
1577
+
1578
+ return $url;
1579
+ }
1580
+
1581
+ /**
1582
+ * Retrieve the URL to the TGMPA Install page for a specific plugin status (view).
1583
+ *
1584
+ * I.e. depending on the config settings passed something along the lines of:
1585
+ * http://example.com/wp-admin/themes.php?page=tgmpa-install-plugins&plugin_status=install
1586
+ *
1587
+ * @since 2.5.0
1588
+ *
1589
+ * @param string $status Plugin status - either 'install', 'update' or 'activate'.
1590
+ * @return string Properly encoded URL (not escaped).
1591
+ */
1592
+ public function get_tgmpa_status_url( $status ) {
1593
+ return add_query_arg(
1594
+ array(
1595
+ 'plugin_status' => urlencode( $status ),
1596
+ ),
1597
+ $this->get_tgmpa_url()
1598
+ );
1599
+ }
1600
+
1601
+ /**
1602
+ * Determine whether there are open actions for plugins registered with TGMPA.
1603
+ *
1604
+ * @since 2.5.0
1605
+ *
1606
+ * @return bool True if complete, i.e. no outstanding actions. False otherwise.
1607
+ */
1608
+ public function is_tgmpa_complete() {
1609
+ $complete = true;
1610
+ foreach ( $this->plugins as $slug => $plugin ) {
1611
+ if ( ! $this->is_plugin_active( $slug ) || false !== $this->does_plugin_have_update( $slug ) ) {
1612
+ $complete = false;
1613
+ break;
1614
+ }
1615
+ }
1616
+
1617
+ return $complete;
1618
+ }
1619
+
1620
+ /**
1621
+ * Check if a plugin is installed. Does not take must-use plugins into account.
1622
+ *
1623
+ * @since 2.5.0
1624
+ *
1625
+ * @param string $slug Plugin slug.
1626
+ * @return bool True if installed, false otherwise.
1627
+ */
1628
+ public function is_plugin_installed( $slug ) {
1629
+ $installed_plugins = $this->get_plugins(); // Retrieve a list of all installed plugins (WP cached).
1630
+
1631
+ return ( ! empty( $installed_plugins[ $this->plugins[ $slug ]['file_path'] ] ) );
1632
+ }
1633
+
1634
+ /**
1635
+ * Check if a plugin is active.
1636
+ *
1637
+ * @since 2.5.0
1638
+ *
1639
+ * @param string $slug Plugin slug.
1640
+ * @return bool True if active, false otherwise.
1641
+ */
1642
+ public function is_plugin_active( $slug ) {
1643
+ return ( ( ! empty( $this->plugins[ $slug ]['is_callable'] ) && is_callable( $this->plugins[ $slug ]['is_callable'] ) ) || is_plugin_active( $this->plugins[ $slug ]['file_path'] ) );
1644
+ }
1645
+
1646
+ /**
1647
+ * Check if a plugin can be updated, i.e. if we have information on the minimum WP version required
1648
+ * available, check whether the current install meets them.
1649
+ *
1650
+ * @since 2.5.0
1651
+ *
1652
+ * @param string $slug Plugin slug.
1653
+ * @return bool True if OK to update, false otherwise.
1654
+ */
1655
+ public function can_plugin_update( $slug ) {
1656
+ // We currently can't get reliable info on non-WP-repo plugins - issue #380.
1657
+ if ( 'repo' !== $this->plugins[ $slug ]['source_type'] ) {
1658
+ return true;
1659
+ }
1660
+
1661
+ $api = $this->get_plugins_api( $slug );
1662
+
1663
+ if ( false !== $api && isset( $api->requires ) ) {
1664
+ return version_compare( $GLOBALS['wp_version'], $api->requires, '>=' );
1665
+ }
1666
+
1667
+ // No usable info received from the plugins API, presume we can update.
1668
+ return true;
1669
+ }
1670
+
1671
+ /**
1672
+ * Check if a plugin can be activated, i.e. is not currently active and meets the minimum
1673
+ * plugin version requirements set in TGMPA (if any).
1674
+ *
1675
+ * @since 2.5.0
1676
+ *
1677
+ * @param string $slug Plugin slug.
1678
+ * @return bool True if OK to activate, false otherwise.
1679
+ */
1680
+ public function can_plugin_activate( $slug ) {
1681
+ return ( ! $this->is_plugin_active( $slug ) && ! $this->does_plugin_require_update( $slug ) );
1682
+ }
1683
+
1684
+ /**
1685
+ * Retrieve the version number of an installed plugin.
1686
+ *
1687
+ * @since 2.5.0
1688
+ *
1689
+ * @param string $slug Plugin slug.
1690
+ * @return string Version number as string or an empty string if the plugin is not installed
1691
+ * or version unknown (plugins which don't comply with the plugin header standard).
1692
+ */
1693
+ public function get_installed_version( $slug ) {
1694
+ $installed_plugins = $this->get_plugins(); // Retrieve a list of all installed plugins (WP cached).
1695
+
1696
+ if ( ! empty( $installed_plugins[ $this->plugins[ $slug ]['file_path'] ]['Version'] ) ) {
1697
+ return $installed_plugins[ $this->plugins[ $slug ]['file_path'] ]['Version'];
1698
+ }
1699
+
1700
+ return '';
1701
+ }
1702
+
1703
+ /**
1704
+ * Check whether a plugin complies with the minimum version requirements.
1705
+ *
1706
+ * @since 2.5.0
1707
+ *
1708
+ * @param string $slug Plugin slug.
1709
+ * @return bool True when a plugin needs to be updated, otherwise false.
1710
+ */
1711
+ public function does_plugin_require_update( $slug ) {
1712
+ $installed_version = $this->get_installed_version( $slug );
1713
+ $minimum_version = $this->plugins[ $slug ]['version'];
1714
+
1715
+ return version_compare( $minimum_version, $installed_version, '>' );
1716
+ }
1717
+
1718
+ /**
1719
+ * Check whether there is an update available for a plugin.
1720
+ *
1721
+ * @since 2.5.0
1722
+ *
1723
+ * @param string $slug Plugin slug.
1724
+ * @return false|string Version number string of the available update or false if no update available.
1725
+ */
1726
+ public function does_plugin_have_update( $slug ) {
1727
+ // Presume bundled and external plugins will point to a package which meets the minimum required version.
1728
+ if ( 'repo' !== $this->plugins[ $slug ]['source_type'] ) {
1729
+ if ( $this->does_plugin_require_update( $slug ) ) {
1730
+ return $this->plugins[ $slug ]['version'];
1731
+ }
1732
+
1733
+ return false;
1734
+ }
1735
+
1736
+ $repo_updates = get_site_transient( 'update_plugins' );
1737
+
1738
+ if ( isset( $repo_updates->response[ $this->plugins[ $slug ]['file_path'] ]->new_version ) ) {
1739
+ return $repo_updates->response[ $this->plugins[ $slug ]['file_path'] ]->new_version;
1740
+ }
1741
+
1742
+ return false;
1743
+ }
1744
+
1745
+ /**
1746
+ * Retrieve potential upgrade notice for a plugin.
1747
+ *
1748
+ * @since 2.5.0
1749
+ *
1750
+ * @param string $slug Plugin slug.
1751
+ * @return string The upgrade notice or an empty string if no message was available or provided.
1752
+ */
1753
+ public function get_upgrade_notice( $slug ) {
1754
+ // We currently can't get reliable info on non-WP-repo plugins - issue #380.
1755
+ if ( 'repo' !== $this->plugins[ $slug ]['source_type'] ) {
1756
+ return '';
1757
+ }
1758
+
1759
+ $repo_updates = get_site_transient( 'update_plugins' );
1760
+
1761
+ if ( ! empty( $repo_updates->response[ $this->plugins[ $slug ]['file_path'] ]->upgrade_notice ) ) {
1762
+ return $repo_updates->response[ $this->plugins[ $slug ]['file_path'] ]->upgrade_notice;
1763
+ }
1764
+
1765
+ return '';
1766
+ }
1767
+
1768
+ /**
1769
+ * Wrapper around the core WP get_plugins function, making sure it's actually available.
1770
+ *
1771
+ * @since 2.5.0
1772
+ *
1773
+ * @param string $plugin_folder Optional. Relative path to single plugin folder.
1774
+ * @return array Array of installed plugins with plugin information.
1775
+ */
1776
+ public function get_plugins( $plugin_folder = '' ) {
1777
+ if ( ! function_exists( 'get_plugins' ) ) {
1778
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
1779
+ }
1780
+
1781
+ return get_plugins( $plugin_folder );
1782
+ }
1783
+
1784
+ /**
1785
+ * Delete dismissable nag option when theme is switched.
1786
+ *
1787
+ * This ensures that the user(s) is/are again reminded via nag of required
1788
+ * and/or recommended plugins if they re-activate the theme.
1789
+ *
1790
+ * @since 2.1.1
1791
+ */
1792
+ public function update_dismiss() {
1793
+ delete_metadata( 'user', null, 'tgmpa_dismissed_notice_' . $this->id, null, true );
1794
+ }
1795
+
1796
+ /**
1797
+ * Forces plugin activation if the parameter 'force_activation' is
1798
+ * set to true.
1799
+ *
1800
+ * This allows theme authors to specify certain plugins that must be
1801
+ * active at all times while using the current theme.
1802
+ *
1803
+ * Please take special care when using this parameter as it has the
1804
+ * potential to be harmful if not used correctly. Setting this parameter
1805
+ * to true will not allow the specified plugin to be deactivated unless
1806
+ * the user switches themes.
1807
+ *
1808
+ * @since 2.2.0
1809
+ */
1810
+ public function force_activation() {
1811
+ foreach ( $this->plugins as $slug => $plugin ) {
1812
+ if ( true === $plugin['force_activation'] ) {
1813
+ if ( ! $this->is_plugin_installed( $slug ) ) {
1814
+ // Oops, plugin isn't there so iterate to next condition.
1815
+ continue;
1816
+ } elseif ( $this->can_plugin_activate( $slug ) ) {
1817
+ // There we go, activate the plugin.
1818
+ activate_plugin( $plugin['file_path'] );
1819
+ }
1820
+ }
1821
+ }
1822
+ }
1823
+
1824
+ /**
1825
+ * Forces plugin deactivation if the parameter 'force_deactivation'
1826
+ * is set to true.
1827
+ *
1828
+ * This allows theme authors to specify certain plugins that must be
1829
+ * deactivated upon switching from the current theme to another.
1830
+ *
1831
+ * Please take special care when using this parameter as it has the
1832
+ * potential to be harmful if not used correctly.
1833
+ *
1834
+ * @since 2.2.0
1835
+ */
1836
+ public function force_deactivation() {
1837
+ foreach ( $this->plugins as $slug => $plugin ) {
1838
+ // Only proceed forward if the parameter is set to true and plugin is active.
1839
+ if ( true === $plugin['force_deactivation'] && $this->is_plugin_active( $slug ) ) {
1840
+ deactivate_plugins( $plugin['file_path'] );
1841
+ }
1842
+ }
1843
+ }
1844
+
1845
+ /**
1846
+ * Echo the current TGMPA version number to the page.
1847
+ */
1848
+ public function show_tgmpa_version() {
1849
+ echo '<p style="float: right; padding: 0em 1.5em 0.5em 0;"><strong><small>',
1850
+ esc_html( sprintf( _x( 'TGMPA v%s', '%s = version number', 'tgmpa' ), self::TGMPA_VERSION ) ),
1851
+ '</small></strong></p>';
1852
+ }
1853
+
1854
+ /**
1855
+ * Returns the singleton instance of the class.
1856
+ *
1857
+ * @since 2.4.0
1858
+ *
1859
+ * @return object The INBOUND_Plugin_Activation object.
1860
+ */
1861
+ public static function get_instance() {
1862
+ if ( ! isset( self::$instance ) && ! ( self::$instance instanceof self ) ) {
1863
+ self::$instance = new self();
1864
+ }
1865
+
1866
+ return self::$instance;
1867
+ }
1868
+ }
1869
+
1870
+ if ( ! function_exists( 'load_tgm_plugin_activation' ) ) {
1871
+ /**
1872
+ * Ensure only one instance of the class is ever invoked.
1873
+ */
1874
+ function load_tgm_plugin_activation() {
1875
+ $GLOBALS['tgmpa'] = INBOUND_Plugin_Activation::get_instance();
1876
+ }
1877
+ }
1878
+
1879
+ if ( did_action( 'plugins_loaded' ) ) {
1880
+ load_tgm_plugin_activation();
1881
+ } else {
1882
+ add_action( 'plugins_loaded', 'load_tgm_plugin_activation' );
1883
+ }
1884
  }
1885
 
1886
+ if ( ! function_exists( 'inbound_activate' ) ) {
1887
+ /**
1888
+ * Helper function to register a collection of required plugins.
1889
+ *
1890
+ * @since 2.0.0
1891
+ * @api
1892
+ *
1893
+ * @param array $plugins An array of plugin arrays.
1894
+ * @param array $config Optional. An array of configuration values.
1895
+ */
1896
+ function inbound_activate( $plugins, $config = array() ) {
1897
+ $instance = call_user_func( array( get_class( $GLOBALS['tgmpa'] ), 'get_instance' ) );
1898
+
1899
+ foreach ( $plugins as $plugin ) {
1900
+ call_user_func( array( $instance, 'register' ), $plugin );
1901
+ }
1902
+
1903
+ if ( ! empty( $config ) && is_array( $config ) ) {
1904
+ call_user_func( array( $instance, 'config' ), $config );
1905
+ }
1906
+ }
 
1907
  }
1908
 
1909
  /**
1912
  *
1913
  * @since 2.2.0
1914
  */
1915
+ if ( ! class_exists( 'WP_List_Table' ) ) {
1916
+ require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
1917
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1918
 
1919
+ if ( ! class_exists( 'INBOUND_TGMPA_List_Table' ) ) {
1920
+
1921
+ /**
1922
+ * List table class for handling plugins.
1923
+ *
1924
+ * Extends the WP_List_Table class to provide a future-compatible
1925
+ * way of listing out all required/recommended plugins.
1926
+ *
1927
+ * Gives users an interface similar to the Plugin Administration
1928
+ * area with similar (albeit stripped down) capabilities.
1929
+ *
1930
+ * This class also allows for the bulk install of plugins.
1931
+ *
1932
+ * @since 2.2.0
1933
+ *
1934
+ * @package TGM-Plugin-Activation
1935
+ * @author Thomas Griffin
1936
+ * @author Gary Jones
1937
+ */
1938
+ class INBOUND_TGMPA_List_Table extends WP_List_Table {
1939
+ /**
1940
+ * TGMPA instance.
1941
+ *
1942
+ * @since 2.5.0
1943
+ *
1944
+ * @var object
1945
+ */
1946
+ protected $tgmpa;
1947
+
1948
+ /**
1949
+ * The currently chosen view.
1950
+ *
1951
+ * @since 2.5.0
1952
+ *
1953
+ * @var string One of: 'all', 'install', 'update', 'activate'
1954
+ */
1955
+ public $view_context = 'all';
1956
+
1957
+ /**
1958
+ * The plugin counts for the various views.
1959
+ *
1960
+ * @since 2.5.0
1961
+ *
1962
+ * @var array
1963
+ */
1964
+ protected $view_totals = array(
1965
+ 'all' => 0,
1966
+ 'install' => 0,
1967
+ 'update' => 0,
1968
+ 'activate' => 0,
1969
+ );
1970
+
1971
+ /**
1972
+ * References parent constructor and sets defaults for class.
1973
+ *
1974
+ * @since 2.2.0
1975
+ */
1976
+ public function __construct() {
1977
+ $this->tgmpa = call_user_func( array( get_class( $GLOBALS['tgmpa'] ), 'get_instance' ) );
1978
+
1979
+ parent::__construct(
1980
+ array(
1981
+ 'singular' => 'plugin',
1982
+ 'plural' => 'plugins',
1983
+ 'ajax' => false,
1984
+ )
1985
+ );
1986
+
1987
+ if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'install', 'update', 'activate' ), true ) ) {
1988
+ $this->view_context = sanitize_key( $_REQUEST['plugin_status'] );
1989
+ }
1990
+
1991
+ add_filter( 'tgmpa_table_data_items', array( $this, 'sort_table_items' ) );
1992
+ }
1993
+
1994
+ /**
1995
+ * Get a list of CSS classes for the <table> tag.
1996
+ *
1997
+ * Overruled to prevent the 'plural' argument from being added.
1998
+ *
1999
+ * @since 2.5.0
2000
+ *
2001
+ * @return array CSS classnames.
2002
+ */
2003
+ public function get_table_classes() {
2004
+ return array( 'widefat', 'fixed' );
2005
+ }
2006
+
2007
+ /**
2008
+ * Gathers and renames all of our plugin information to be used by WP_List_Table to create our table.
2009
+ *
2010
+ * @since 2.2.0
2011
+ *
2012
+ * @return array $table_data Information for use in table.
2013
+ */
2014
+ protected function _gather_plugin_data() {
2015
+ // Load thickbox for plugin links.
2016
+ $this->tgmpa->admin_init();
2017
+ $this->tgmpa->thickbox();
2018
+
2019
+ // Categorize the plugins which have open actions.
2020
+ $plugins = $this->categorize_plugins_to_views();
2021
+
2022
+ // Set the counts for the view links.
2023
+ $this->set_view_totals( $plugins );
2024
+
2025
+ // Prep variables for use and grab list of all installed plugins.
2026
+ $table_data = array();
2027
+ $i = 0;
2028
+
2029
+ // Redirect to the 'all' view if no plugins where found for the selected view context.
2030
+ if ( empty( $plugins[ $this->view_context ] ) ) {
2031
+ $this->view_context = 'all';
2032
+ }
2033
+
2034
+ foreach ( $plugins[ $this->view_context ] as $slug => $plugin ) {
2035
+ $table_data[ $i ]['sanitized_plugin'] = $plugin['name'];
2036
+ $table_data[ $i ]['slug'] = $slug;
2037
+ $table_data[ $i ]['plugin'] = '<strong>' . $this->tgmpa->get_info_link( $slug ) . '</strong>';
2038
+ $table_data[ $i ]['source'] = $this->get_plugin_source_type_text( $plugin['source_type'] );
2039
+ $table_data[ $i ]['type'] = $this->get_plugin_advise_type_text( $plugin['required'] );
2040
+ $table_data[ $i ]['status'] = $this->get_plugin_status_text( $slug );
2041
+ $table_data[ $i ]['installed_version'] = $this->tgmpa->get_installed_version( $slug );
2042
+ $table_data[ $i ]['minimum_version'] = $plugin['version'];
2043
+ $table_data[ $i ]['available_version'] = $this->tgmpa->does_plugin_have_update( $slug );
2044
+
2045
+ // Prep the upgrade notice info.
2046
+ $upgrade_notice = $this->tgmpa->get_upgrade_notice( $slug );
2047
+ if ( ! empty( $upgrade_notice ) ) {
2048
+ $table_data[ $i ]['upgrade_notice'] = $upgrade_notice;
2049
+
2050
+ add_action( "tgmpa_after_plugin_row_$slug", array( $this, 'wp_plugin_update_row' ), 10, 2 );
2051
+ }
2052
+
2053
+ $table_data[ $i ] = apply_filters( 'tgmpa_table_data_item', $table_data[ $i ], $plugin );
2054
+
2055
+ $i++;
2056
+ }
2057
+
2058
+ return $table_data;
2059
+ }
2060
+
2061
+ /**
2062
+ * Categorize the plugins which have open actions into views for the TGMPA page.
2063
+ *
2064
+ * @since 2.5.0
2065
+ */
2066
+ protected function categorize_plugins_to_views() {
2067
+ $plugins = array(
2068
+ 'all' => array(), // Meaning: all plugins which still have open actions.
2069
+ 'install' => array(),
2070
+ 'update' => array(),
2071
+ 'activate' => array(),
2072
+ );
2073
+
2074
+ foreach ( $this->tgmpa->plugins as $slug => $plugin ) {
2075
+ if ( $this->tgmpa->is_plugin_active( $slug ) && false === $this->tgmpa->does_plugin_have_update( $slug ) ) {
2076
+ // No need to display plugins if they are installed, up-to-date and active.
2077
+ continue;
2078
+ } else {
2079
+ $plugins['all'][ $slug ] = $plugin;
2080
+
2081
+ if ( ! $this->tgmpa->is_plugin_installed( $slug ) ) {
2082
+ $plugins['install'][ $slug ] = $plugin;
2083
+ } else {
2084
+ if ( false !== $this->tgmpa->does_plugin_have_update( $slug ) ) {
2085
+ $plugins['update'][ $slug ] = $plugin;
2086
+ }
2087
+
2088
+ if ( $this->tgmpa->can_plugin_activate( $slug ) ) {
2089
+ $plugins['activate'][ $slug ] = $plugin;
2090
+ }
2091
+ }
2092
+ }
2093
+ }
2094
+
2095
+ return $plugins;
2096
+ }
2097
+
2098
+ /**
2099
+ * Set the counts for the view links.
2100
+ *
2101
+ * @since 2.5.0
2102
+ *
2103
+ * @param array $plugins Plugins order by view.
2104
+ */
2105
+ protected function set_view_totals( $plugins ) {
2106
+ foreach ( $plugins as $type => $list ) {
2107
+ $this->view_totals[ $type ] = count( $list );
2108
+ }
2109
+ }
2110
+
2111
+ /**
2112
+ * Get the plugin required/recommended text string.
2113
+ *
2114
+ * @since 2.5.0
2115
+ *
2116
+ * @param string $required Plugin required setting.
2117
+ * @return string
2118
+ */
2119
+ protected function get_plugin_advise_type_text( $required ) {
2120
+ if ( true === $required ) {
2121
+ return __( 'Required', 'tgmpa' );
2122
+ }
2123
+
2124
+ return __( 'Recommended', 'tgmpa' );
2125
+ }
2126
+
2127
+ /**
2128
+ * Get the plugin source type text string.
2129
+ *
2130
+ * @since 2.5.0
2131
+ *
2132
+ * @param string $type Plugin type.
2133
+ * @return string
2134
+ */
2135
+ protected function get_plugin_source_type_text( $type ) {
2136
+ $string = '';
2137
+
2138
+ switch ( $type ) {
2139
+ case 'repo':
2140
+ $string = __( 'WordPress Repository', 'tgmpa' );
2141
+ break;
2142
+ case 'external':
2143
+ $string = __( 'External Source', 'tgmpa' );
2144
+ break;
2145
+ case 'bundled':
2146
+ $string = __( 'Pre-Packaged', 'tgmpa' );
2147
+ break;
2148
+ }
2149
+
2150
+ return $string;
2151
+ }
2152
+
2153
+ /**
2154
+ * Determine the plugin status message.
2155
+ *
2156
+ * @since 2.5.0
2157
+ *
2158
+ * @param string $slug Plugin slug.
2159
+ * @return string
2160
+ */
2161
+ protected function get_plugin_status_text( $slug ) {
2162
+ if ( ! $this->tgmpa->is_plugin_installed( $slug ) ) {
2163
+ return __( 'Not Installed', 'tgmpa' );
2164
+ }
2165
+
2166
+ if ( ! $this->tgmpa->is_plugin_active( $slug ) ) {
2167
+ $install_status = __( 'Installed But Not Activated', 'tgmpa' );
2168
+ } else {
2169
+ $install_status = __( 'Active', 'tgmpa' );
2170
+ }
2171
+
2172
+ $update_status = '';
2173
+
2174
+ if ( $this->tgmpa->does_plugin_require_update( $slug ) && false === $this->tgmpa->does_plugin_have_update( $slug ) ) {
2175
+ $update_status = __( 'Required Update not Available', 'tgmpa' );
2176
+
2177
+ } elseif ( $this->tgmpa->does_plugin_require_update( $slug ) ) {
2178
+ $update_status = __( 'Requires Update', 'tgmpa' );
2179
+
2180
+ } elseif ( false !== $this->tgmpa->does_plugin_have_update( $slug ) ) {
2181
+ $update_status = __( 'Update recommended', 'tgmpa' );
2182
+ }
2183
+
2184
+ if ( '' === $update_status ) {
2185
+ return $install_status;
2186
+ }
2187
+
2188
+ return sprintf(
2189
+ _x( '%1$s, %2$s', '%1$s = install status, %2$s = update status', 'tgmpa' ),
2190
+ $install_status,
2191
+ $update_status
2192
+ );
2193
+ }
2194
+
2195
+ /**
2196
+ * Sort plugins by Required/Recommended type and by alphabetical plugin name within each type.
2197
+ *
2198
+ * @since 2.5.0
2199
+ *
2200
+ * @param array $items Prepared table items.
2201
+ * @return array Sorted table items.
2202
+ */
2203
+ public function sort_table_items( $items ) {
2204
+ $type = array();
2205
+ $name = array();
2206
+
2207
+ foreach ( $items as $i => $plugin ) {
2208
+ $type[ $i ] = $plugin['type']; // Required / recommended.
2209
+ $name[ $i ] = $plugin['sanitized_plugin'];
2210
+ }
2211
+
2212
+ array_multisort( $type, SORT_DESC, $name, SORT_ASC, $items );
2213
+
2214
+ return $items;
2215
+ }
2216
+
2217
+ /**
2218
+ * Get an associative array ( id => link ) of the views available on this table.
2219
+ *
2220
+ * @since 2.5.0
2221
+ *
2222
+ * @return array
2223
+ */
2224
+ public function get_views() {
2225
+ $status_links = array();
2226
+
2227
+ foreach ( $this->view_totals as $type => $count ) {
2228
+ if ( $count < 1 ) {
2229
+ continue;
2230
+ }
2231
+
2232
+ switch ( $type ) {
2233
+ case 'all':
2234
+ $text = _nx( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $count, 'plugins', 'tgmpa' );
2235
+ break;
2236
+ case 'install':
2237
+ $text = _n( 'To Install <span class="count">(%s)</span>', 'To Install <span class="count">(%s)</span>', $count, 'tgmpa' );
2238
+ break;
2239
+ case 'update':
2240
+ $text = _n( 'Update Available <span class="count">(%s)</span>', 'Update Available <span class="count">(%s)</span>', $count, 'tgmpa' );
2241
+ break;
2242
+ case 'activate':
2243
+ $text = _n( 'To Activate <span class="count">(%s)</span>', 'To Activate <span class="count">(%s)</span>', $count, 'tgmpa' );
2244
+ break;
2245
+ default:
2246
+ $text = '';
2247
+ break;
2248
+ }
2249
+
2250
+ if ( ! empty( $text ) ) {
2251
+
2252
+ $status_links[ $type ] = sprintf(
2253
+ '<a href="%s"%s>%s</a>',
2254
+ esc_url( $this->tgmpa->get_tgmpa_status_url( $type ) ),
2255
+ ( $type === $this->view_context ) ? ' class="current"' : '',
2256
+ sprintf( $text, number_format_i18n( $count ) )
2257
+ );
2258
+ }
2259
+ }
2260
+
2261
+ return $status_links;
2262
+ }
2263
+
2264
+ /**
2265
+ * Create default columns to display important plugin information
2266
+ * like type, action and status.
2267
+ *
2268
+ * @since 2.2.0
2269
+ *
2270
+ * @param array $item Array of item data.
2271
+ * @param string $column_name The name of the column.
2272
+ * @return string
2273
+ */
2274
+ public function column_default( $item, $column_name ) {
2275
+ return $item[ $column_name ];
2276
+ }
2277
+
2278
+ /**
2279
+ * Required for bulk installing.
2280
+ *
2281
+ * Adds a checkbox for each plugin.
2282
+ *
2283
+ * @since 2.2.0
2284
+ *
2285
+ * @param array $item Array of item data.
2286
+ * @return string The input checkbox with all necessary info.
2287
+ */
2288
+ public function column_cb( $item ) {
2289
+ return sprintf(
2290
+ '<input type="checkbox" name="%1$s[]" value="%2$s" id="%3$s" />',
2291
+ esc_attr( $this->_args['singular'] ),
2292
+ esc_attr( $item['slug'] ),
2293
+ esc_attr( $item['sanitized_plugin'] )
2294
+ );
2295
+ }
2296
+
2297
+ /**
2298
+ * Create default title column along with the action links.
2299
+ *
2300
+ * @since 2.2.0
2301
+ *
2302
+ * @param array $item Array of item data.
2303
+ * @return string The plugin name and action links.
2304
+ */
2305
+ public function column_plugin( $item ) {
2306
+ return sprintf(
2307
+ '%1$s %2$s',
2308
+ $item['plugin'],
2309
+ $this->row_actions( $this->get_row_actions( $item ), true )
2310
+ );
2311
+ }
2312
+
2313
+ /**
2314
+ * Create version information column.
2315
+ *
2316
+ * @since 2.5.0
2317
+ *
2318
+ * @param array $item Array of item data.
2319
+ * @return string HTML-formatted version information.
2320
+ */
2321
+ public function column_version( $item ) {
2322
+ $output = array();
2323
+
2324
+ if ( $this->tgmpa->is_plugin_installed( $item['slug'] ) ) {
2325
+ $installed = ! empty( $item['installed_version'] ) ? $item['installed_version'] : _x( 'unknown', 'as in: "version nr unknown"', 'tgmpa' );
2326
+
2327
+ $color = '';
2328
+ if ( ! empty( $item['minimum_version'] ) && $this->tgmpa->does_plugin_require_update( $item['slug'] ) ) {
2329
+ $color = ' color: #ff0000; font-weight: bold;';
2330
+ }
2331
+
2332
+ $output[] = sprintf(
2333
+ '<p><span style="min-width: 32px; text-align: right; float: right;%1$s">%2$s</span>' . __( 'Installed version:', 'tgmpa' ) . '</p>',
2334
+ $color,
2335
+ $installed
2336
+ );
2337
+ }
2338
+
2339
+ if ( ! empty( $item['minimum_version'] ) ) {
2340
+ $output[] = sprintf(
2341
+ '<p><span style="min-width: 32px; text-align: right; float: right;">%1$s</span>' . __( 'Minimum required version:', 'tgmpa' ) . '</p>',
2342
+ $item['minimum_version']
2343
+ );
2344
+ }
2345
+
2346
+ if ( ! empty( $item['available_version'] ) ) {
2347
+ $color = '';
2348
+ if ( ! empty( $item['minimum_version'] ) && version_compare( $item['available_version'], $item['minimum_version'], '>=' ) ) {
2349
+ $color = ' color: #71C671; font-weight: bold;';
2350
+ }
2351
+
2352
+ $output[] = sprintf(
2353
+ '<p><span style="min-width: 32px; text-align: right; float: right;%1$s">%2$s</span>' . __( 'Available version:', 'tgmpa' ) . '</p>',
2354
+ $color,
2355
+ $item['available_version']
2356
+ );
2357
+ }
2358
+
2359
+ if ( empty( $output ) ) {
2360
+ return '&nbsp;'; // Let's not break the table layout.
2361
+ } else {
2362
+ return implode( "\n", $output );
2363
+ }
2364
+ }
2365
+
2366
+ /**
2367
+ * Sets default message within the plugins table if no plugins
2368
+ * are left for interaction.
2369
+ *
2370
+ * Hides the menu item to prevent the user from clicking and
2371
+ * getting a permissions error.
2372
+ *
2373
+ * @since 2.2.0
2374
+ */
2375
+ public function no_items() {
2376
+ printf( wp_kses_post( __( 'No plugins to install, update or activate. <a href="%1$s">Return to the Dashboard</a>', 'tgmpa' ) ), esc_url( self_admin_url() ) );
2377
+ echo '<style type="text/css">#adminmenu .wp-submenu li.current { display: none !important; }</style>';
2378
+ }
2379
+
2380
+ /**
2381
+ * Output all the column information within the table.
2382
+ *
2383
+ * @since 2.2.0
2384
+ *
2385
+ * @return array $columns The column names.
2386
+ */
2387
+ public function get_columns() {
2388
+ $columns = array(
2389
+ 'cb' => '<input type="checkbox" />',
2390
+ 'plugin' => __( 'Plugin', 'tgmpa' ),
2391
+ 'source' => __( 'Source', 'tgmpa' ),
2392
+ 'type' => __( 'Type', 'tgmpa' ),
2393
+ );
2394
+
2395
+ if ( 'all' === $this->view_context || 'update' === $this->view_context ) {
2396
+ $columns['version'] = __( 'Version', 'tgmpa' );
2397
+ $columns['status'] = __( 'Status', 'tgmpa' );
2398
+ }
2399
+
2400
+ return apply_filters( 'tgmpa_table_columns', $columns );
2401
+ }
2402
+
2403
+ /**
2404
+ * Get name of default primary column
2405
+ *
2406
+ * @since 2.5.0 / WP 4.3+ compatibility
2407
+ * @access protected
2408
+ *
2409
+ * @return string
2410
+ */
2411
+ protected function get_default_primary_column_name() {
2412
+ return 'plugin';
2413
+ }
2414
+
2415
+ /**
2416
+ * Get the name of the primary column.
2417
+ *
2418
+ * @since 2.5.0 / WP 4.3+ compatibility
2419
+ * @access protected
2420
+ *
2421
+ * @return string The name of the primary column.
2422
+ */
2423
+ protected function get_primary_column_name() {
2424
+ if ( method_exists( 'WP_List_Table', 'get_primary_column_name' ) ) {
2425
+ return parent::get_primary_column_name();
2426
+ } else {
2427
+ return $this->get_default_primary_column_name();
2428
+ }
2429
+ }
2430
+
2431
+ /**
2432
+ * Get the actions which are relevant for a specific plugin row.
2433
+ *
2434
+ * @since 2.5.0
2435
+ *
2436
+ * @param array $item Array of item data.
2437
+ * @return array Array with relevant action links.
2438
+ */
2439
+ protected function get_row_actions( $item ) {
2440
+ $actions = array();
2441
+ $action_links = array();
2442
+
2443
+ // Display the 'Install' action link if the plugin is not yet available.
2444
+ if ( ! $this->tgmpa->is_plugin_installed( $item['slug'] ) ) {
2445
+ $actions['install'] = _x( 'Install %2$s', '%2$s = plugin name in screen reader markup', 'tgmpa' );
2446
+ } else {
2447
+ // Display the 'Update' action link if an update is available and WP complies with plugin minimum.
2448
+ if ( false !== $this->tgmpa->does_plugin_have_update( $item['slug'] ) && $this->tgmpa->can_plugin_update( $item['slug'] ) ) {
2449
+ $actions['update'] = _x( 'Update %2$s', '%2$s = plugin name in screen reader markup', 'tgmpa' );
2450
+ }
2451
+
2452
+ // Display the 'Activate' action link, but only if the plugin meets the minimum version.
2453
+ if ( $this->tgmpa->can_plugin_activate( $item['slug'] ) ) {
2454
+ $actions['activate'] = _x( 'Activate %2$s', '%2$s = plugin name in screen reader markup', 'tgmpa' );
2455
+ }
2456
+ }
2457
+
2458
+ // Create the actual links.
2459
+ foreach ( $actions as $action => $text ) {
2460
+ $nonce_url = wp_nonce_url(
2461
+ add_query_arg(
2462
+ array(
2463
+ 'plugin' => urlencode( $item['slug'] ),
2464
+ 'tgmpa-' . $action => $action . '-plugin',
2465
+ ),
2466
+ $this->tgmpa->get_tgmpa_url()
2467
+ ),
2468
+ 'tgmpa-' . $action,
2469
+ 'tgmpa-nonce'
2470
+ );
2471
+
2472
+ $action_links[ $action ] = sprintf(
2473
+ '<a href="%1$s">' . esc_html( $text ) . '</a>',
2474
+ esc_url( $nonce_url ),
2475
+ '<span class="screen-reader-text">' . esc_html( $item['sanitized_plugin'] ) . '</span>'
2476
+ );
2477
+ }
2478
+
2479
+ $prefix = ( defined( 'WP_NETWORK_ADMIN' ) && WP_NETWORK_ADMIN ) ? 'network_admin_' : '';
2480
+ return apply_filters( "tgmpa_{$prefix}plugin_action_links", array_filter( $action_links ), $item['slug'], $item, $this->view_context );
2481
+ }
2482
+
2483
+ /**
2484
+ * Generates content for a single row of the table.
2485
+ *
2486
+ * @since 2.5.0
2487
+ *
2488
+ * @param object $item The current item.
2489
+ */
2490
+ public function single_row( $item ) {
2491
+ parent::single_row( $item );
2492
+
2493
+ /**
2494
+ * Fires after each specific row in the TGMPA Plugins list table.
2495
+ *
2496
+ * The dynamic portion of the hook name, `$item['slug']`, refers to the slug
2497
+ * for the plugin.
2498
+ *
2499
+ * @since 2.5.0
2500
+ */
2501
+ do_action( "tgmpa_after_plugin_row_{$item['slug']}", $item['slug'], $item, $this->view_context );
2502
+ }
2503
+
2504
+ /**
2505
+ * Show the upgrade notice below a plugin row if there is one.
2506
+ *
2507
+ * @since 2.5.0
2508
+ *
2509
+ * @see /wp-admin/includes/update.php
2510
+ *
2511
+ * @param string $slug Plugin slug.
2512
+ * @param array $item The information available in this table row.
2513
+ * @return null Return early if upgrade notice is empty.
2514
+ */
2515
+ public function wp_plugin_update_row( $slug, $item ) {
2516
+ if ( empty( $item['upgrade_notice'] ) ) {
2517
+ return;
2518
+ }
2519
+
2520
+ echo '
2521
+ <tr class="plugin-update-tr">
2522
+ <td colspan="', absint( $this->get_column_count() ), '" class="plugin-update colspanchange">
2523
+ <div class="update-message">',
2524
+ esc_html__( 'Upgrade message from the plugin author:', 'tgmpa' ),
2525
+ ' <strong>', wp_kses_data( $item['upgrade_notice'] ), '</strong>
2526
+ </div>
2527
+ </td>
2528
+ </tr>';
2529
+ }
2530
+
2531
+ /**
2532
+ * Extra controls to be displayed between bulk actions and pagination.
2533
+ *
2534
+ * @since 2.5.0
2535
+ *
2536
+ * @param string $which 'top' or 'bottom' table navigation.
2537
+ */
2538
+ public function extra_tablenav( $which ) {
2539
+ if ( 'bottom' === $which ) {
2540
+ $this->tgmpa->show_tgmpa_version();
2541
+ }
2542
+ }
2543
+
2544
+ /**
2545
+ * Defines the bulk actions for handling registered plugins.
2546
+ *
2547
+ * @since 2.2.0
2548
+ *
2549
+ * @return array $actions The bulk actions for the plugin install table.
2550
+ */
2551
+ public function get_bulk_actions() {
2552
+
2553
+ $actions = array();
2554
+
2555
+ if ( 'update' !== $this->view_context && 'activate' !== $this->view_context ) {
2556
+ if ( current_user_can( 'install_plugins' ) ) {
2557
+ $actions['tgmpa-bulk-install'] = __( 'Install', 'tgmpa' );
2558
+ }
2559
+ }
2560
+
2561
+ if ( 'install' !== $this->view_context ) {
2562
+ if ( current_user_can( 'update_plugins' ) ) {
2563
+ $actions['tgmpa-bulk-update'] = __( 'Update', 'tgmpa' );
2564
+ }
2565
+ if ( current_user_can( 'activate_plugins' ) ) {
2566
+ $actions['tgmpa-bulk-activate'] = __( 'Activate', 'tgmpa' );
2567
+ }
2568
+ }
2569
+
2570
+ return $actions;
2571
+ }
2572
+
2573
+ /**
2574
+ * Processes bulk installation and activation actions.
2575
+ *
2576
+ * The bulk installation process looks for the $_POST information and passes that
2577
+ * through if a user has to use WP_Filesystem to enter their credentials.
2578
+ *
2579
+ * @since 2.2.0
2580
+ */
2581
+ public function process_bulk_actions() {
2582
+ // Bulk installation process.
2583
+ if ( 'tgmpa-bulk-install' === $this->current_action() || 'tgmpa-bulk-update' === $this->current_action() ) {
2584
+
2585
+ check_admin_referer( 'bulk-' . $this->_args['plural'] );
2586
+
2587
+ $install_type = 'install';
2588
+ if ( 'tgmpa-bulk-update' === $this->current_action() ) {
2589
+ $install_type = 'update';
2590
+ }
2591
+
2592
+ $plugins_to_install = array();
2593
+
2594
+ // Did user actually select any plugins to install/update ?
2595
+ if ( empty( $_POST['plugin'] ) ) {
2596
+ if ( 'install' === $install_type ) {
2597
+ $message = __( 'No plugins were selected to be installed. No action taken.', 'tgmpa' );
2598
+ } else {
2599
+ $message = __( 'No plugins were selected to be updated. No action taken.', 'tgmpa' );
2600
+ }
2601
+
2602
+ echo '<div id="message" class="error"><p>', esc_html( $message ), '</p></div>';
2603
+
2604
+ return false;
2605
+ }
2606
+
2607
+ if ( is_array( $_POST['plugin'] ) ) {
2608
+ $plugins_to_install = (array) $_POST['plugin'];
2609
+ } elseif ( is_string( $_POST['plugin'] ) ) {
2610
+ // Received via Filesystem page - un-flatten array (WP bug #19643).
2611
+ $plugins_to_install = explode( ',', $_POST['plugin'] );
2612
+ }
2613
+
2614
+ // Sanitize the received input.
2615
+ $plugins_to_install = array_map( 'urldecode', $plugins_to_install );
2616
+ $plugins_to_install = array_map( array( $this->tgmpa, 'sanitize_key' ), $plugins_to_install );
2617
+
2618
+ // Validate the received input.
2619
+ foreach ( $plugins_to_install as $key => $slug ) {
2620
+ // Check if the plugin was registered with TGMPA and remove if not.
2621
+ if ( ! isset( $this->tgmpa->plugins[ $slug ] ) ) {
2622
+ unset( $plugins_to_install[ $key ] );
2623
+ continue;
2624
+ }
2625
+
2626
+ // For updates: make sure this is a plugin we *can* update (update available and WP version ok).
2627
+ if ( 'update' === $install_type && ( $this->tgmpa->is_plugin_installed( $slug ) && ( false === $this->tgmpa->does_plugin_have_update( $slug ) || ! $this->tgmpa->can_plugin_update( $slug ) ) ) ) {
2628
+ unset( $plugins_to_install[ $key ] );
2629
+ }
2630
+ }
2631
+
2632
+ // No need to proceed further if we have no plugins to handle.
2633
+ if ( empty( $plugins_to_install ) ) {
2634
+ if ( 'install' === $install_type ) {
2635
+ $message = __( 'No plugins are available to be installed at this time.', 'tgmpa' );
2636
+ } else {
2637
+ $message = __( 'No plugins are available to be updated at this time.', 'tgmpa' );
2638
+ }
2639
+
2640
+ echo '<div id="message" class="error"><p>', esc_html( $message ), '</p></div>';
2641
+
2642
+ return false;
2643
+ }
2644
+
2645
+ // Pass all necessary information if WP_Filesystem is needed.
2646
+ $url = wp_nonce_url(
2647
+ $this->tgmpa->get_tgmpa_url(),
2648
+ 'bulk-' . $this->_args['plural']
2649
+ );
2650
+
2651
+ // Give validated data back to $_POST which is the only place the filesystem looks for extra fields.
2652
+ $_POST['plugin'] = implode( ',', $plugins_to_install ); // Work around for WP bug #19643.
2653
+
2654
+ $method = ''; // Leave blank so WP_Filesystem can populate it as necessary.
2655
+ $fields = array_keys( $_POST ); // Extra fields to pass to WP_Filesystem.
2656
+
2657
+ if ( false === ( $creds = request_filesystem_credentials( esc_url_raw( $url ), $method, false, false, $fields ) ) ) {
2658
+ return true; // Stop the normal page form from displaying, credential request form will be shown.
2659
+ }
2660
+
2661
+ // Now we have some credentials, setup WP_Filesystem.
2662
+ if ( ! WP_Filesystem( $creds ) ) {
2663
+ // Our credentials were no good, ask the user for them again.
2664
+ request_filesystem_credentials( esc_url_raw( $url ), $method, true, false, $fields );
2665
+
2666
+ return true;
2667
+ }
2668
+
2669
+ /* If we arrive here, we have the filesystem */
2670
+
2671
+ // Store all information in arrays since we are processing a bulk installation.
2672
+ $names = array();
2673
+ $sources = array(); // Needed for installs.
2674
+ $file_paths = array(); // Needed for upgrades.
2675
+ $to_inject = array(); // Information to inject into the update_plugins transient.
2676
+
2677
+ // Prepare the data for validated plugins for the install/upgrade.
2678
+ foreach ( $plugins_to_install as $slug ) {
2679
+ $name = $this->tgmpa->plugins[ $slug ]['name'];
2680
+ $source = $this->tgmpa->get_download_url( $slug );
2681
+
2682
+ if ( ! empty( $name ) && ! empty( $source ) ) {
2683
+ $names[] = $name;
2684
+
2685
+ switch ( $install_type ) {
2686
+
2687
+ case 'install':
2688
+ $sources[] = $source;
2689
+ break;
2690
+
2691
+ case 'update':
2692
+ $file_paths[] = $this->tgmpa->plugins[ $slug ]['file_path'];
2693
+ $to_inject[ $slug ] = $this->tgmpa->plugins[ $slug ];
2694
+ $to_inject[ $slug ]['source'] = $source;
2695
+ break;
2696
+ }
2697
+ }
2698
+ }
2699
+ unset( $slug, $name, $source );
2700
+
2701
+ // Create a new instance of INBOUND_TGM_Bulk_Installer.
2702
+ $installer = new INBOUND_TGM_Bulk_Installer(
2703
+ new INBOUND_TGM_Bulk_Installer_Skin(
2704
+ array(
2705
+ 'url' => esc_url_raw( $this->tgmpa->get_tgmpa_url() ),
2706
+ 'nonce' => 'bulk-' . $this->_args['plural'],
2707
+ 'names' => $names,
2708
+ 'install_type' => $install_type,
2709
+ )
2710
+ )
2711
+ );
2712
+
2713
+ // Wrap the install process with the appropriate HTML.
2714
+ echo '<div class="tgmpa wrap">',
2715
+ '<h2>', esc_html( get_admin_page_title() ), '</h2>';
2716
+
2717
+ // Process the bulk installation submissions.
2718
+ add_filter( 'upgrader_source_selection', array( $this->tgmpa, 'maybe_adjust_source_dir' ), 1, 3 );
2719
+
2720
+ if ( 'tgmpa-bulk-update' === $this->current_action() ) {
2721
+ // Inject our info into the update transient.
2722
+ $this->tgmpa->inject_update_info( $to_inject );
2723
+
2724
+ $installer->bulk_upgrade( $file_paths );
2725
+ } else {
2726
+ $installer->bulk_install( $sources );
2727
+ }
2728
+
2729
+ remove_filter( 'upgrader_source_selection', array( $this->tgmpa, 'maybe_adjust_source_dir' ), 1, 3 );
2730
+
2731
+ echo '</div>';
2732
+
2733
+ return true;
2734
+ }
2735
+
2736
+ // Bulk activation process.
2737
+ if ( 'tgmpa-bulk-activate' === $this->current_action() ) {
2738
+ check_admin_referer( 'bulk-' . $this->_args['plural'] );
2739
+
2740
+ // Did user actually select any plugins to activate ?
2741
+ if ( empty( $_POST['plugin'] ) ) {
2742
+ echo '<div id="message" class="error"><p>', esc_html__( 'No plugins were selected to be activated. No action taken.', 'tgmpa' ), '</p></div>';
2743
+
2744
+ return false;
2745
+ }
2746
+
2747
+ // Grab plugin data from $_POST.
2748
+ $plugins = array();
2749
+ if ( isset( $_POST['plugin'] ) ) {
2750
+ $plugins = array_map( 'urldecode', (array) $_POST['plugin'] );
2751
+ $plugins = array_map( array( $this->tgmpa, 'sanitize_key' ), $plugins );
2752
+ }
2753
+
2754
+ $plugins_to_activate = array();
2755
+ $plugin_names = array();
2756
+
2757
+ // Grab the file paths for the selected & inactive plugins from the registration array.
2758
+ foreach ( $plugins as $slug ) {
2759
+ if ( $this->tgmpa->can_plugin_activate( $slug ) ) {
2760
+ $plugins_to_activate[] = $this->tgmpa->plugins[ $slug ]['file_path'];
2761
+ $plugin_names[] = $this->tgmpa->plugins[ $slug ]['name'];
2762
+ }
2763
+ }
2764
+ unset( $slug );
2765
+
2766
+ // Return early if there are no plugins to activate.
2767
+ if ( empty( $plugins_to_activate ) ) {
2768
+ echo '<div id="message" class="error"><p>', esc_html__( 'No plugins are available to be activated at this time.', 'tgmpa' ), '</p></div>';
2769
+
2770
+ return false;
2771
+ }
2772
+
2773
+ // Now we are good to go - let's start activating plugins.
2774
+ $activate = activate_plugins( $plugins_to_activate );
2775
+
2776
+ if ( is_wp_error( $activate ) ) {
2777
+ echo '<div id="message" class="error"><p>', wp_kses_post( $activate->get_error_message() ), '</p></div>';
2778
+ } else {
2779
+ $count = count( $plugin_names ); // Count so we can use _n function.
2780
+ $plugin_names = array_map( array( 'INBOUND_TGM_Utils', 'wrap_in_strong' ), $plugin_names );
2781
+ $last_plugin = array_pop( $plugin_names ); // Pop off last name to prep for readability.
2782
+ $imploded = empty( $plugin_names ) ? $last_plugin : ( implode( ', ', $plugin_names ) . ' ' . esc_html_x( 'and', 'plugin A *and* plugin B', 'tgmpa' ) . ' ' . $last_plugin );
2783
+
2784
+ printf( // WPCS: xss ok.
2785
+ '<div id="message" class="updated"><p>%1$s %2$s.</p></div>',
2786
+ esc_html( _n( 'The following plugin was activated successfully:', 'The following plugins were activated successfully:', $count, 'tgmpa' ) ),
2787
+ $imploded
2788
+ );
2789
+
2790
+ // Update recently activated plugins option.
2791
+ $recent = (array) get_option( 'recently_activated' );
2792
+ foreach ( $plugins_to_activate as $plugin => $time ) {
2793
+ if ( isset( $recent[ $plugin ] ) ) {
2794
+ unset( $recent[ $plugin ] );
2795
+ }
2796
+ }
2797
+ update_option( 'recently_activated', $recent );
2798
+ }
2799
+
2800
+ unset( $_POST ); // Reset the $_POST variable in case user wants to perform one action after another.
2801
+
2802
+ return true;
2803
+ }
2804
+
2805
+ return false;
2806
+ }
2807
+
2808
+ /**
2809
+ * Prepares all of our information to be outputted into a usable table.
2810
+ *
2811
+ * @since 2.2.0
2812
+ */
2813
+ public function prepare_items() {
2814
+ $columns = $this->get_columns(); // Get all necessary column information.
2815
+ $hidden = array(); // No columns to hide, but we must set as an array.
2816
+ $sortable = array(); // No reason to make sortable columns.
2817
+ $primary = $this->get_primary_column_name(); // Column which has the row actions.
2818
+ $this->_column_headers = array( $columns, $hidden, $sortable, $primary ); // Get all necessary column headers.
2819
+
2820
+ // Process our bulk activations here.
2821
+ if ( 'tgmpa-bulk-activate' === $this->current_action() ) {
2822
+ $this->process_bulk_actions();
2823
+ }
2824
+
2825
+ // Store all of our plugin data into $items array so WP_List_Table can use it.
2826
+ $this->items = apply_filters( 'tgmpa_table_data_items', $this->_gather_plugin_data() );
2827
+ }
2828
+
2829
+ /* *********** DEPRECATED METHODS *********** */
2830
+
2831
+ /**
2832
+ * Retrieve plugin data, given the plugin name.
2833
+ *
2834
+ * @since 2.2.0
2835
+ * @deprecated 2.5.0 use {@see INBOUND_Plugin_Activation::_get_plugin_data_from_name()} instead.
2836
+ * @see INBOUND_Plugin_Activation::_get_plugin_data_from_name()
2837
+ *
2838
+ * @param string $name Name of the plugin, as it was registered.
2839
+ * @param string $data Optional. Array key of plugin data to return. Default is slug.
2840
+ * @return string|boolean Plugin slug if found, false otherwise.
2841
+ */
2842
+ protected function _get_plugin_data_from_name( $name, $data = 'slug' ) {
2843
+ _deprecated_function( __FUNCTION__, 'TGMPA 2.5.0', 'INBOUND_Plugin_Activation::_get_plugin_data_from_name()' );
2844
+
2845
+ return $this->tgmpa->_get_plugin_data_from_name( $name, $data );
2846
+ }
2847
+ }
2848
  }
2849
 
2850
  /**
2852
  * we load it here.
2853
  *
2854
  * We check to make sure no action or activation keys are set so that WordPress
2855
+ * does not try to re-include the class when processing upgrades or installs outside
2856
  * of the class.
2857
  *
2858
  * @since 2.2.0
2859
  */
2860
+ add_action( 'admin_init', 'tgmpa_load_bulk_installer' );
2861
+ if ( ! function_exists( 'tgmpa_load_bulk_installer' ) ) {
2862
+ /**
2863
+ * Load bulk installer
2864
+ */
2865
+ function tgmpa_load_bulk_installer() {
2866
+ // Get TGMPA class instance.
2867
+ $tgmpa_instance = call_user_func( array( get_class( $GLOBALS['tgmpa'] ), 'get_instance' ) );
2868
+
2869
+ if ( isset( $_GET['page'] ) && $tgmpa_instance->menu === $_GET['page'] ) {
2870
+ if ( ! class_exists( 'Plugin_Upgrader', false ) ) {
2871
+ require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
2872
+ }
2873
+
2874
+ if ( ! class_exists( 'INBOUND_TGM_Bulk_Installer' ) ) {
2875
+
2876
+ /**
2877
+ * Installer class to handle bulk plugin installations.
2878
+ *
2879
+ * Extends WP_Upgrader and customizes to suit the installation of multiple
2880
+ * plugins.
2881
+ *
2882
+ * @since 2.2.0
2883
+ *
2884
+ * @internal Since 2.5.0 the class is an extension of Plugin_Upgrader rather than WP_Upgrader
2885
+ *
2886
+ * @package TGM-Plugin-Activation
2887
+ * @author Thomas Griffin
2888
+ * @author Gary Jones
2889
+ */
2890
+ class INBOUND_TGM_Bulk_Installer extends Plugin_Upgrader {
2891
+ /**
2892
+ * Holds result of bulk plugin installation.
2893
+ *
2894
+ * @since 2.2.0
2895
+ *
2896
+ * @var string
2897
+ */
2898
+ public $result;
2899
+
2900
+ /**
2901
+ * Flag to check if bulk installation is occurring or not.
2902
+ *
2903
+ * @since 2.2.0
2904
+ *
2905
+ * @var boolean
2906
+ */
2907
+ public $bulk = false;
2908
+
2909
+ /**
2910
+ * TGMPA instance
2911
+ *
2912
+ * @since 2.5.0
2913
+ *
2914
+ * @var object
2915
+ */
2916
+ protected $tgmpa;
2917
+
2918
+ /**
2919
+ * Whether or not the destination directory needs to be cleared ( = on update).
2920
+ *
2921
+ * @since 2.5.0
2922
+ *
2923
+ * @var bool
2924
+ */
2925
+ protected $clear_destination = false;
2926
+
2927
+ /**
2928
+ * References parent constructor and sets defaults for class.
2929
+ *
2930
+ * @since 2.2.0
2931
+ *
2932
+ * @param \Bulk_Upgrader_Skin|null $skin Installer skin.
2933
+ */
2934
+ public function __construct( $skin = null ) {
2935
+ // Get TGMPA class instance.
2936
+ $this->tgmpa = call_user_func( array( get_class( $GLOBALS['tgmpa'] ), 'get_instance' ) );
2937
+
2938
+ parent::__construct( $skin );
2939
+
2940
+ if ( isset( $this->skin->options['install_type'] ) && 'update' === $this->skin->options['install_type'] ) {
2941
+ $this->clear_destination = true;
2942
+ }
2943
+
2944
+ if ( $this->tgmpa->is_automatic ) {
2945
+ $this->activate_strings();
2946
+ }
2947
+
2948
+ add_action( 'upgrader_process_complete', array( $this->tgmpa, 'populate_file_path' ) );
2949
+ }
2950
+
2951
+ /**
2952
+ * Sets the correct activation strings for the installer skin to use.
2953
+ *
2954
+ * @since 2.2.0
2955
+ */
2956
+ public function activate_strings() {
2957
+ $this->strings['activation_failed'] = __( 'Plugin activation failed.', 'tgmpa' );
2958
+ $this->strings['activation_success'] = __( 'Plugin activated successfully.', 'tgmpa' );
2959
+ }
2960
+
2961
+ /**
2962
+ * Performs the actual installation of each plugin.
2963
+ *
2964
+ * @since 2.2.0
2965
+ *
2966
+ * @see WP_Upgrader::run()
2967
+ *
2968
+ * @param array $options The installation config options.
2969
+ * @return null|array Return early if error, array of installation data on success.
2970
+ */
2971
+ public function run( $options ) {
2972
+ $result = parent::run( $options );
2973
+
2974
+ // Reset the strings in case we changed one during automatic activation.
2975
+ if ( $this->tgmpa->is_automatic ) {
2976
+ if ( 'update' === $this->skin->options['install_type'] ) {
2977
+ $this->upgrade_strings();
2978
+ } else {
2979
+ $this->install_strings();
2980
+ }
2981
+ }
2982
+
2983
+ return $result;
2984
+ }
2985
+
2986
+ /**
2987
+ * Processes the bulk installation of plugins.
2988
+ *
2989
+ * @since 2.2.0
2990
+ *
2991
+ * @internal This is basically a near identical copy of the WP Core Plugin_Upgrader::bulk_upgrade()
2992
+ * method, with minor adjustments to deal with new installs instead of upgrades.
2993
+ * For ease of future synchronizations, the adjustments are clearly commented, but no other
2994
+ * comments are added. Code style has been made to comply.
2995
+ *
2996
+ * @see Plugin_Upgrader::bulk_upgrade()
2997
+ * @see https://core.trac.wordpress.org/browser/tags/4.2.1/src/wp-admin/includes/class-wp-upgrader.php#L838
2998
+ *
2999
+ * @param array $plugins The plugin sources needed for installation.
3000
+ * @param array $args Arbitrary passed extra arguments.
3001
+ * @return string|bool Install confirmation messages on success, false on failure.
3002
+ */
3003
+ public function bulk_install( $plugins, $args = array() ) {
3004
+ // [TGMPA + ] Hook auto-activation in.
3005
+ add_filter( 'upgrader_post_install', array( $this, 'auto_activate' ), 10 );
3006
+
3007
+ $defaults = array(
3008
+ 'clear_update_cache' => true,
3009
+ );
3010
+ $parsed_args = wp_parse_args( $args, $defaults );
3011
+
3012
+ $this->init();
3013
+ $this->bulk = true;
3014
+
3015
+ $this->install_strings(); // [TGMPA + ] adjusted.
3016
+
3017
+ /* [TGMPA - ] $current = get_site_transient( 'update_plugins' ); */
3018
+
3019
+ /* [TGMPA - ] add_filter('upgrader_clear_destination', array($this, 'delete_old_plugin'), 10, 4); */
3020
+
3021
+ $this->skin->header();
3022
+
3023
+ // Connect to the Filesystem first.
3024
+ $res = $this->fs_connect( array( WP_CONTENT_DIR, WP_PLUGIN_DIR ) );
3025
+ if ( ! $res ) {
3026
+ $this->skin->footer();
3027
+
3028
+ return false;
3029
+ }
3030
+
3031
+ $this->skin->bulk_header();
3032
+
3033
+ // Only start maintenance mode if:
3034
+ // - running Multisite and there are one or more plugins specified, OR
3035
+ // - a plugin with an update available is currently active.
3036
+ // @TODO: For multisite, maintenance mode should only kick in for individual sites if at all possible.
3037
+ $maintenance = ( is_multisite() && ! empty( $plugins ) );
3038
+
3039
+ /*
3040
+ [TGMPA - ]
3041
+ foreach ( $plugins as $plugin )
3042
+ $maintenance = $maintenance || ( is_plugin_active( $plugin ) && isset( $current->response[ $plugin] ) );
3043
+ */
3044
+ if ( $maintenance ) {
3045
+ $this->maintenance_mode( true );
3046
+ }
3047
+
3048
+ $results = array();
3049
+
3050
+ $this->update_count = count( $plugins );
3051
+ $this->update_current = 0;
3052
+ foreach ( $plugins as $plugin ) {
3053
+ $this->update_current++;
3054
+
3055
+ /*
3056
+ [TGMPA - ]
3057
+ $this->skin->plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, false, true);
3058
+
3059
+ if ( !isset( $current->response[ $plugin ] ) ) {
3060
+ $this->skin->set_result('up_to_date');
3061
+ $this->skin->before();
3062
+ $this->skin->feedback('up_to_date');
3063
+ $this->skin->after();
3064
+ $results[$plugin] = true;
3065
+ continue;
3066
+ }
3067
+
3068
+ // Get the URL to the zip file
3069
+ $r = $current->response[ $plugin ];
3070
+
3071
+ $this->skin->plugin_active = is_plugin_active($plugin);
3072
+ */
3073
+
3074
+ $result = $this->run( array(
3075
+ 'package' => $plugin, // [TGMPA + ] adjusted.
3076
+ 'destination' => WP_PLUGIN_DIR,
3077
+ 'clear_destination' => false, // [TGMPA + ] adjusted.
3078
+ 'clear_working' => true,
3079
+ 'is_multi' => true,
3080
+ 'hook_extra' => array(
3081
+ 'plugin' => $plugin,
3082
+ ),
3083
+ ) );
3084
+
3085
+ $results[ $plugin ] = $this->result;
3086
+
3087
+ // Prevent credentials auth screen from displaying multiple times.
3088
+ if ( false === $result ) {
3089
+ break;
3090
+ }
3091
+ } //end foreach $plugins
3092
+
3093
+ $this->maintenance_mode( false );
3094
+
3095
+ /**
3096
+ * Fires when the bulk upgrader process is complete.
3097
+ *
3098
+ * @since WP 3.6.0 / TGMPA 2.5.0
3099
+ *
3100
+ * @param Plugin_Upgrader $this Plugin_Upgrader instance. In other contexts, $this, might
3101
+ * be a Theme_Upgrader or Core_Upgrade instance.
3102
+ * @param array $data {
3103
+ * Array of bulk item update data.
3104
+ *
3105
+ * @type string $action Type of action. Default 'update'.
3106
+ * @type string $type Type of update process. Accepts 'plugin', 'theme', or 'core'.
3107
+ * @type bool $bulk Whether the update process is a bulk update. Default true.
3108
+ * @type array $packages Array of plugin, theme, or core packages to update.
3109
+ * }
3110
+ */
3111
+ do_action( 'upgrader_process_complete', $this, array(
3112
+ 'action' => 'install', // [TGMPA + ] adjusted.
3113
+ 'type' => 'plugin',
3114
+ 'bulk' => true,
3115
+ 'plugins' => $plugins,
3116
+ ) );
3117
+
3118
+ $this->skin->bulk_footer();
3119
+
3120
+ $this->skin->footer();
3121
+
3122
+ // Cleanup our hooks, in case something else does a upgrade on this connection.
3123
+ /* [TGMPA - ] remove_filter('upgrader_clear_destination', array($this, 'delete_old_plugin')); */
3124
+
3125
+ // [TGMPA + ] Remove our auto-activation hook.
3126
+ remove_filter( 'upgrader_post_install', array( $this, 'auto_activate' ), 10 );
3127
+
3128
+ // Force refresh of plugin update information.
3129
+ wp_clean_plugins_cache( $parsed_args['clear_update_cache'] );
3130
+
3131
+ return $results;
3132
+ }
3133
+
3134
+ /**
3135
+ * Handle a bulk upgrade request.
3136
+ *
3137
+ * @since 2.5.0
3138
+ *
3139
+ * @see Plugin_Upgrader::bulk_upgrade()
3140
+ *
3141
+ * @param array $plugins The local WP file_path's of the plugins which should be upgraded.
3142
+ * @param array $args Arbitrary passed extra arguments.
3143
+ * @return string|bool Install confirmation messages on success, false on failure.
3144
+ */
3145
+ public function bulk_upgrade( $plugins, $args = array() ) {
3146
+
3147
+ add_filter( 'upgrader_post_install', array( $this, 'auto_activate' ), 10 );
3148
+
3149
+ $result = parent::bulk_upgrade( $plugins, $args );
3150
+
3151
+ remove_filter( 'upgrader_post_install', array( $this, 'auto_activate' ), 10 );
3152
+
3153
+ return $result;
3154
+ }
3155
+
3156
+ /**
3157
+ * Abuse a filter to auto-activate plugins after installation.
3158
+ *
3159
+ * Hooked into the 'upgrader_post_install' filter hook.
3160
+ *
3161
+ * @since 2.5.0
3162
+ *
3163
+ * @param bool $bool The value we need to give back (true).
3164
+ * @return bool
3165
+ */
3166
+ public function auto_activate( $bool ) {
3167
+ // Only process the activation of installed plugins if the automatic flag is set to true.
3168
+ if ( $this->tgmpa->is_automatic ) {
3169
+ // Flush plugins cache so the headers of the newly installed plugins will be read correctly.
3170
+ wp_clean_plugins_cache();
3171
+
3172
+ // Get the installed plugin file.
3173
+ $plugin_info = $this->plugin_info();
3174
+
3175
+ // Don't try to activate on upgrade of active plugin as WP will do this already.
3176
+ if ( ! is_plugin_active( $plugin_info ) ) {
3177
+ $activate = activate_plugin( $plugin_info );
3178
+
3179
+ // Adjust the success string based on the activation result.
3180
+ $this->strings['process_success'] = $this->strings['process_success'] . "<br />\n";
3181
+
3182
+ if ( is_wp_error( $activate ) ) {
3183
+ $this->skin->error( $activate );
3184
+ $this->strings['process_success'] .= $this->strings['activation_failed'];
3185
+ } else {
3186
+ $this->strings['process_success'] .= $this->strings['activation_success'];
3187
+ }
3188
+ }
3189
+ }
3190
+
3191
+ return $bool;
3192
+ }
3193
+ }
3194
+ }
3195
+
3196
+ if ( ! class_exists( 'INBOUND_TGM_Bulk_Installer_Skin' ) ) {
3197
+
3198
+ /**
3199
+ * Installer skin to set strings for the bulk plugin installations..
3200
+ *
3201
+ * Extends Bulk_Upgrader_Skin and customizes to suit the installation of multiple
3202
+ * plugins.
3203
+ *
3204
+ * @since 2.2.0
3205
+ *
3206
+ * @see https://core.trac.wordpress.org/browser/trunk/src/wp-admin/includes/class-wp-upgrader-skins.php
3207
+ *
3208
+ * @package TGM-Plugin-Activation
3209
+ * @author Thomas Griffin
3210
+ * @author Gary Jones
3211
+ */
3212
+ class INBOUND_TGM_Bulk_Installer_Skin extends Bulk_Upgrader_Skin {
3213
+ /**
3214
+ * Holds plugin info for each individual plugin installation.
3215
+ *
3216
+ * @since 2.2.0
3217
+ *
3218
+ * @var array
3219
+ */
3220
+ public $plugin_info = array();
3221
+
3222
+ /**
3223
+ * Holds names of plugins that are undergoing bulk installations.
3224
+ *
3225
+ * @since 2.2.0
3226
+ *
3227
+ * @var array
3228
+ */
3229
+ public $plugin_names = array();
3230
+
3231
+ /**
3232
+ * Integer to use for iteration through each plugin installation.
3233
+ *
3234
+ * @since 2.2.0
3235
+ *
3236
+ * @var integer
3237
+ */
3238
+ public $i = 0;
3239
+
3240
+ /**
3241
+ * TGMPA instance
3242
+ *
3243
+ * @since 2.5.0
3244
+ *
3245
+ * @var object
3246
+ */
3247
+ protected $tgmpa;
3248
+
3249
+ /**
3250
+ * Constructor. Parses default args with new ones and extracts them for use.
3251
+ *
3252
+ * @since 2.2.0
3253
+ *
3254
+ * @param array $args Arguments to pass for use within the class.
3255
+ */
3256
+ public function __construct( $args = array() ) {
3257
+ // Get TGMPA class instance.
3258
+ $this->tgmpa = call_user_func( array( get_class( $GLOBALS['tgmpa'] ), 'get_instance' ) );
3259
+
3260
+ // Parse default and new args.
3261
+ $defaults = array(
3262
+ 'url' => '',
3263
+ 'nonce' => '',
3264
+ 'names' => array(),
3265
+ 'install_type' => 'install',
3266
+ );
3267
+ $args = wp_parse_args( $args, $defaults );
3268
+
3269
+ // Set plugin names to $this->plugin_names property.
3270
+ $this->plugin_names = $args['names'];
3271
+
3272
+ // Extract the new args.
3273
+ parent::__construct( $args );
3274
+ }
3275
+
3276
+ /**
3277
+ * Sets install skin strings for each individual plugin.
3278
+ *
3279
+ * Checks to see if the automatic activation flag is set and uses the
3280
+ * the proper strings accordingly.
3281
+ *
3282
+ * @since 2.2.0
3283
+ */
3284
+ public function add_strings() {
3285
+ if ( 'update' === $this->options['install_type'] ) {
3286
+ parent::add_strings();
3287
+ $this->upgrader->strings['skin_before_update_header'] = __( 'Updating Plugin %1$s (%2$d/%3$d)', 'tgmpa' );
3288
+ } else {
3289
+ $this->upgrader->strings['skin_update_failed_error'] = __( 'An error occurred while installing %1$s: <strong>%2$s</strong>.', 'tgmpa' );
3290
+ $this->upgrader->strings['skin_update_failed'] = __( 'The installation of %1$s failed.', 'tgmpa' );
3291
+
3292
+ if ( $this->tgmpa->is_automatic ) {
3293
+ // Automatic activation strings.
3294
+ $this->upgrader->strings['skin_upgrade_start'] = __( 'The installation and activation process is starting. This process may take a while on some hosts, so please be patient.', 'tgmpa' );
3295
+ $this->upgrader->strings['skin_update_successful'] = __( '%1$s installed and activated successfully.', 'tgmpa' ) . ' <a href="#" class="hide-if-no-js" onclick="%2$s"><span>' . esc_html__( 'Show Details', 'tgmpa' ) . '</span><span class="hidden">' . esc_html__( 'Hide Details', 'tgmpa' ) . '</span>.</a>';
3296
+ $this->upgrader->strings['skin_upgrade_end'] = __( 'All installations and activations have been completed.', 'tgmpa' );
3297
+ $this->upgrader->strings['skin_before_update_header'] = __( 'Installing and Activating Plugin %1$s (%2$d/%3$d)', 'tgmpa' );
3298
+ } else {
3299
+ // Default installation strings.
3300
+ $this->upgrader->strings['skin_upgrade_start'] = __( 'The installation process is starting. This process may take a while on some hosts, so please be patient.', 'tgmpa' );
3301
+ $this->upgrader->strings['skin_update_successful'] = esc_html__( '%1$s installed successfully.', 'tgmpa' ) . ' <a href="#" class="hide-if-no-js" onclick="%2$s"><span>' . esc_html__( 'Show Details', 'tgmpa' ) . '</span><span class="hidden">' . esc_html__( 'Hide Details', 'tgmpa' ) . '</span>.</a>';
3302
+ $this->upgrader->strings['skin_upgrade_end'] = __( 'All installations have been completed.', 'tgmpa' );
3303
+ $this->upgrader->strings['skin_before_update_header'] = __( 'Installing Plugin %1$s (%2$d/%3$d)', 'tgmpa' );
3304
+ }
3305
+ }
3306
+ }
3307
+
3308
+ /**
3309
+ * Outputs the header strings and necessary JS before each plugin installation.
3310
+ *
3311
+ * @since 2.2.0
3312
+ *
3313
+ * @param string $title Unused in this implementation.
3314
+ */
3315
+ public function before( $title = '' ) {
3316
+ if ( empty( $title ) ) {
3317
+ $title = esc_html( $this->plugin_names[ $this->i ] );
3318
+ }
3319
+ parent::before( $title );
3320
+ }
3321
+
3322
+ /**
3323
+ * Outputs the footer strings and necessary JS after each plugin installation.
3324
+ *
3325
+ * Checks for any errors and outputs them if they exist, else output
3326
+ * success strings.
3327
+ *
3328
+ * @since 2.2.0
3329
+ *
3330
+ * @param string $title Unused in this implementation.
3331
+ */
3332
+ public function after( $title = '' ) {
3333
+ if ( empty( $title ) ) {
3334
+ $title = esc_html( $this->plugin_names[ $this->i ] );
3335
+ }
3336
+ parent::after( $title );
3337
+
3338
+ $this->i++;
3339
+ }
3340
+
3341
+ /**
3342
+ * Outputs links after bulk plugin installation is complete.
3343
+ *
3344
+ * @since 2.2.0
3345
+ */
3346
+ public function bulk_footer() {
3347
+ // Serve up the string to say installations (and possibly activations) are complete.
3348
+ parent::bulk_footer();
3349
+
3350
+ // Flush plugins cache so we can make sure that the installed plugins list is always up to date.
3351
+ wp_clean_plugins_cache();
3352
+
3353
+ $this->tgmpa->show_tgmpa_version();
3354
+
3355
+ // Display message based on if all plugins are now active or not.
3356
+ $update_actions = array();
3357
+
3358
+ if ( $this->tgmpa->is_tgmpa_complete() ) {
3359
+ // All plugins are active, so we display the complete string and hide the menu to protect users.
3360
+ echo '<style type="text/css">#adminmenu .wp-submenu li.current { display: none !important; }</style>';
3361
+ $update_actions['dashboard'] = sprintf(
3362
+ esc_html( $this->tgmpa->strings['complete'] ),
3363
+ '<a href="' . esc_url( self_admin_url() ) . '">' . esc_html__( 'Return to the Dashboard', 'tgmpa' ) . '</a>'
3364
+ );
3365
+ } else {
3366
+ $update_actions['tgmpa_page'] = '<a href="' . esc_url( $this->tgmpa->get_tgmpa_url() ) . '" target="_parent">' . esc_html( $this->tgmpa->strings['return'] ) . '</a>';
3367
+ }
3368
+
3369
+ /**
3370
+ * Filter the list of action links available following bulk plugin installs/updates.
3371
+ *
3372
+ * @since 2.5.0
3373
+ *
3374
+ * @param array $update_actions Array of plugin action links.
3375
+ * @param array $plugin_info Array of information for the last-handled plugin.
3376
+ */
3377
+ $update_actions = apply_filters( 'tgmpa_update_bulk_plugins_complete_actions', $update_actions, $this->plugin_info );
3378
+
3379
+ if ( ! empty( $update_actions ) ) {
3380
+ $this->feedback( implode( ' | ', (array) $update_actions ) );
3381
+ }
3382
+ }
3383
+
3384
+ /* *********** DEPRECATED METHODS *********** */
3385
+
3386
+ /**
3387
+ * Flush header output buffer.
3388
+ *
3389
+ * @since 2.2.0
3390
+ * @deprecated 2.5.0 use {@see Bulk_Upgrader_Skin::flush_output()} instead
3391
+ * @see Bulk_Upgrader_Skin::flush_output()
3392
+ */
3393
+ public function before_flush_output() {
3394
+ _deprecated_function( __FUNCTION__, 'TGMPA 2.5.0', 'Bulk_Upgrader_Skin::flush_output()' );
3395
+ $this->flush_output();
3396
+ }
3397
+
3398
+ /**
3399
+ * Flush footer output buffer and iterate $this->i to make sure the
3400
+ * installation strings reference the correct plugin.
3401
+ *
3402
+ * @since 2.2.0
3403
+ * @deprecated 2.5.0 use {@see Bulk_Upgrader_Skin::flush_output()} instead
3404
+ * @see Bulk_Upgrader_Skin::flush_output()
3405
+ */
3406
+ public function after_flush_output() {
3407
+ _deprecated_function( __FUNCTION__, 'TGMPA 2.5.0', 'Bulk_Upgrader_Skin::flush_output()' );
3408
+ $this->flush_output();
3409
+ $this->i++;
3410
+ }
3411
+ }
3412
+ }
3413
+ }
3414
+ }
3415
+ }
3416
+
3417
+ if ( ! class_exists( 'INBOUND_TGM_Utils' ) ) {
3418
+
3419
+ /**
3420
+ * Generic utilities for TGMPA.
3421
+ *
3422
+ * All methods are static, poor-dev name-spacing class wrapper.
3423
+ *
3424
+ * @since 2.5.0
3425
+ *
3426
+ * @package TGM-Plugin-Activation
3427
+ * @author Juliette Reinders Folmer
3428
+ */
3429
+ class INBOUND_TGM_Utils {
3430
+ /**
3431
+ * Whether the PHP filter extension is enabled.
3432
+ *
3433
+ * @see http://php.net/book.filter
3434
+ *
3435
+ * @since 2.5.0
3436
+ *
3437
+ * @static
3438
+ *
3439
+ * @var bool $has_filters True is the extension is enabled.
3440
+ */
3441
+ public static $has_filters;
3442
+
3443
+ /**
3444
+ * Wrap an arbitrary string in <em> tags. Meant to be used in combination with array_map().
3445
+ *
3446
+ * @since 2.5.0
3447
+ *
3448
+ * @static
3449
+ *
3450
+ * @param string $string Text to be wrapped.
3451
+ * @return string
3452
+ */
3453
+ public static function wrap_in_em( $string ) {
3454
+ return '<em>' . $string . '</em>';
3455
+ }
3456
+
3457
+ /**
3458
+ * Wrap an arbitrary string in <strong> tags. Meant to be used in combination with array_map().
3459
+ *
3460
+ * @since 2.5.0
3461
+ *
3462
+ * @static
3463
+ *
3464
+ * @param string $string Text to be wrapped.
3465
+ * @return string
3466
+ */
3467
+ public static function wrap_in_strong( $string ) {
3468
+ return '<strong>' . wp_kses_post( $string ) . '</strong>';
3469
+ }
3470
+
3471
+ /**
3472
+ * Helper function: Validate a value as boolean
3473
+ *
3474
+ * @since 2.5.0
3475
+ *
3476
+ * @static
3477
+ *
3478
+ * @param mixed $value Arbitrary value.
3479
+ * @return bool
3480
+ */
3481
+ public static function validate_bool( $value ) {
3482
+ if ( ! isset( self::$has_filters ) ) {
3483
+ self::$has_filters = extension_loaded( 'filter' );
3484
+ }
3485
+
3486
+ if ( self::$has_filters ) {
3487
+ return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
3488
+ } else {
3489
+ return self::emulate_filter_bool( $value );
3490
+ }
3491
+ }
3492
+
3493
+ /**
3494
+ * Helper function: Cast a value to bool
3495
+ *
3496
+ * @since 2.5.0
3497
+ *
3498
+ * @static
3499
+ *
3500
+ * @param mixed $value Value to cast.
3501
+ * @return bool
3502
+ */
3503
+ protected static function emulate_filter_bool( $value ) {
3504
+ // @codingStandardsIgnoreStart
3505
+ static $true = array(
3506
+ '1',
3507
+ 'true', 'True', 'TRUE',
3508
+ 'y', 'Y',
3509
+ 'yes', 'Yes', 'YES',
3510
+ 'on', 'On', 'ON',
3511
+ );
3512
+ static $false = array(
3513
+ '0',
3514
+ 'false', 'False', 'FALSE',
3515
+ 'n', 'N',
3516
+ 'no', 'No', 'NO',
3517
+ 'off', 'Off', 'OFF',
3518
+ );
3519
+ // @codingStandardsIgnoreEnd
3520
+
3521
+ if ( is_bool( $value ) ) {
3522
+ return $value;
3523
+ } else if ( is_int( $value ) && ( 0 === $value || 1 === $value ) ) {
3524
+ return (bool) $value;
3525
+ } else if ( ( is_float( $value ) && ! is_nan( $value ) ) && ( (float) 0 === $value || (float) 1 === $value ) ) {
3526
+ return (bool) $value;
3527
+ } else if ( is_string( $value ) ) {
3528
+ $value = trim( $value );
3529
+ if ( in_array( $value, $true, true ) ) {
3530
+ return true;
3531
+ } else if ( in_array( $value, $false, true ) ) {
3532
+ return false;
3533
+ } else {
3534
+ return false;
3535
+ }
3536
+ }
3537
+
3538
+ return false;
3539
+ }
3540
+ } // End of class INBOUND_TGM_Utils
3541
+ } // End of class_exists wrapper
libraries/shareme/sharrre/jquery.sharrre-1.3.3.min.js CHANGED
@@ -1,7 +1 @@
1
- /*
2
- * Sharrre.com - Make your sharing widget!
3
- * Version: beta 1.3.3
4
- * Author: Julien Hany
5
- * License: MIT http://en.wikipedia.org/wiki/MIT_License or GPLv2 http://en.wikipedia.org/wiki/GNU_General_Public_License
6
- */
7
- eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}(';(6($,g,h,i){l j=\'29\',1W={2Q:\'29\',J:{O:B,C:B,z:B,I:B,p:B,L:B,K:B,A:B},26:0,17:\'\',Y:\'\',3:h.2N.14,w:h.Y,1o:\'29.2W\',x:{},1n:0,1w:r,2Y:r,2Z:r,24:B,23:6(){},30:6(){},1I:6(){},2q:6(){},8:{O:{3:\'\',19:B,1l:\'33\',Z:\'34-3y\',2d:\'\'},C:{3:\'\',19:B,T:\'1O\',13:\'3V\',F:\'\',1x:\'B\',1X:\'B\',2s:\'\',1v:\'\',Z:\'3D\'},z:{3:\'\',19:B,x:\'38\',2f:\'\',15:\'\',1Q:\'\',Z:\'34\'},I:{3:\'\',19:B,P:\'41\'},p:{3:\'\',19:B,1l:\'33\'},L:{3:\'\',19:B,13:\'1\'},K:{3:\'\',19:B,28:\'\'},A:{3:\'\',1y:\'\',1s:\'\',13:\'38\'}}},1q={O:"",C:"Q://4B.C.q/?2c={3}&1B=?",z:"Q://3W.3Z.z.q/1/40/x.2K?3={3}&1B=?",I:"Q://46.I.q/2.0/4a.4c?4g={3}&P=1f&1B=?",p:\'Q://4X.p.q/53/2K/3q/n?3={3}&1B=?\',L:"",K:"Q://1m.K.q/3E/x/J?3N=3O&3={3}&1B=?",A:""},39={O:6(b){l c=b.4.8.O;$(b.o).V(\'.8\').11(\'<m G="S 4i"><m G="g-22" n-1l="\'+c.1l+\'" n-14="\'+(c.3!==\'\'?c.3:b.4.3)+\'" n-2d="\'+c.2d+\'"></m></m>\');g.4U={Z:b.4.8.O.Z};l d=0;9(y 37===\'E\'&&d==0){d=1;(6(){l a=h.1e(\'N\');a.P=\'w/1f\';a.1g=r;a.18=\'//3Y.32.q/W/22.W\';l s=h.1d(\'N\')[0];s.1c.1b(a,s)})()}H{37.22.4d()}},C:6(c){l e=c.4.8.C;$(c.o).V(\'.8\').11(\'<m G="S C"><m 2c="1G-4m"></m><m G="1G-1O" n-14="\'+(e.3!==\'\'?e.3:c.4.3)+\'" n-1x="\'+e.1x+\'" n-13="\'+e.13+\'" n-F="\'+e.F+\'" n-4t-1X="\'+e.1X+\'" n-T="\'+e.T+\'" n-2s="\'+e.2s+\'" n-1v="\'+e.1v+\'" n-15="\'+e.15+\'"></m></m>\');l f=0;9(y 1p===\'E\'&&f==0){f=1;(6(d,s,a){l b,2g=d.1d(s)[0];9(d.52(a)){1r}b=d.1e(s);b.2c=a;b.18=\'//5d.C.3f/\'+e.Z+\'/3g.W#3l=1\';2g.1c.1b(b,2g)}(h,\'N\',\'C-3m\'))}H{1p.3n.3o()}},z:6(b){l c=b.4.8.z;$(b.o).V(\'.8\').11(\'<m G="S z"><a 14="1E://z.q/J" G="z-J-S" n-3="\'+(c.3!==\'\'?c.3:b.4.3)+\'" n-x="\'+c.x+\'" n-w="\'+b.4.w+\'" n-15="\'+c.15+\'" n-2f="\'+c.2f+\'" n-1Q="\'+c.1Q+\'" n-Z="\'+c.Z+\'">3u</a></m>\');l d=0;9(y 2n===\'E\'&&d==0){d=1;(6(){l a=h.1e(\'N\');a.P=\'w/1f\';a.1g=r;a.18=\'//1D.z.q/1M.W\';l s=h.1d(\'N\')[0];s.1c.1b(a,s)})()}H{$.3F({3:\'//1D.z.q/1M.W\',3G:\'N\',3I:r})}},I:6(a){l b=a.4.8.I;$(a.o).V(\'.8\').11(\'<m G="S I"><a G="3J \'+b.P+\'" 3K="3L 3M" 14="Q://I.q/2X?3=\'+R((b.3!==\'\'?b.3:a.4.3))+\'"></a></m>\');l c=0;9(y 3P===\'E\'&&c==0){c=1;(6(){l s=h.1e(\'2V\'),1Z=h.1d(\'2V\')[0];s.P=\'w/1f\';s.1g=r;s.18=\'//1M.I.q/8.W\';1Z.1c.1b(s,1Z)})()}},p:6(a){9(a.4.8.p.1l==\'3X\'){l b=\'F:20;\',21=\'D:2R;F:20;1v-1l:42;1t-D:2R;\',25=\'D:2P;1t-D:2P;27-5o:1K;\'}H{l b=\'F:4h;\',21=\'2a:4l;2b:0 1K;D:1z;F:4u;1t-D:1z;\',25=\'2a:4A;D:1z;1t-D:1z;\'}l c=a.1w(a.4.x.p);9(y c==="E"){c=0}$(a.o).V(\'.8\').11(\'<m G="S p"><m 1C="\'+b+\'1v:4F 4I,4K,4M-4O;4S:4T;1N:#4W;3d:50-2J;2a:2I;D:1z;1t-D:54;27:0;2b:0;w-55:0;57-2h:3e;">\'+\'<m 1C="\'+21+\'2G-1N:#2F;27-3h:3i;3j:3k;w-2h:2C;1R:2B 2A #3p;1R-2y:1K;">\'+c+\'</m>\'+\'<m 1C="\'+25+\'3d:2J;2b:0;w-2h:2C;w-3r:2I;F:20;2G-1N:#3s;1R:2B 2A #3t;1R-2y:1K;1N:#2F;">\'+\'<2x 18="Q://1m.p.q/3v/2x/p.3w.3x" D="10" F="10" 3z="3A" /> 3B</m></m></m>\');$(a.o).V(\'.p\').3C(\'1I\',6(){a.2w(\'p\')})},L:6(b){l c=b.4.8.L;$(b.o).V(\'.8\').11(\'<m G="S L"><2v:2t 13="\'+c.13+\'" 2N="\'+(c.3!==\'\'?c.3:b.4.3)+\'"></2v:2t></m>\');l d=0;9(y 1V===\'E\'&&d==0){d=1;(6(){l a=h.1e(\'N\');a.P=\'w/1f\';a.1g=r;a.18=\'//1D.L.q/1/1M.W\';l s=h.1d(\'N\')[0];s.1c.1b(a,s)})();s=g.3H(6(){9(y 1V!==\'E\'){1V.2S();2r(s)}},2p)}H{1V.2S()}},K:6(b){l c=b.4.8.K;$(b.o).V(\'.8\').11(\'<m G="S K"><N P="2o/J" n-3="\'+(c.3!==\'\'?c.3:b.4.3)+\'" n-28="\'+c.28+\'"></N></m>\');l d=0;9(y g.2z===\'E\'&&d==0){d=1;(6(){l a=h.1e(\'N\');a.P=\'w/1f\';a.1g=r;a.18=\'//1D.K.q/2o.W\';l s=h.1d(\'N\')[0];s.1c.1b(a,s)})()}H{g.2z.1T()}},A:6(b){l c=b.4.8.A;$(b.o).V(\'.8\').11(\'<m G="S A"><a 14="Q://A.q/1S/2k/S/?3=\'+(c.3!==\'\'?c.3:b.4.3)+\'&1y=\'+c.1y+\'&1s=\'+c.1s+\'" G="1S-3Q-S" x-13="\'+c.13+\'">3R 3S</a></m>\');(6(){l a=h.1e(\'N\');a.P=\'w/1f\';a.1g=r;a.18=\'//3T.A.q/W/3U.W\';l s=h.1d(\'N\')[0];s.1c.1b(a,s)})()}},2D={O:6(){},C:6(){1G=g.2E(6(){9(y 1p!==\'E\'){1p.2j.2i(\'2H.2k\',6(a){1i.1j([\'1k\',\'C\',\'1O\',a])});1p.2j.2i(\'2H.43\',6(a){1i.1j([\'1k\',\'C\',\'44\',a])});1p.2j.2i(\'45.1x\',6(a){1i.1j([\'1k\',\'C\',\'1x\',a])});2r(1G)}},2L)},z:6(){2M=g.2E(6(){9(y 2n!==\'E\'){2n.48.49(\'1J\',6(a){9(a){1i.1j([\'1k\',\'z\',\'1J\'])}});2r(2M)}},2L)},I:6(){},p:6(){},L:6(){},K:6(){6 4b(){1i.1j([\'1k\',\'K\',\'J\'])}},A:6(){}},2O={O:6(a){g.1a("1E://4e.32.q/J?4f="+a.8.O.Z+"&3="+R((a.8.O.3!==\'\'?a.8.O.3:a.3)),"","16=0, 1H=0, F=2u, D=2p")},C:6(a){g.1a("Q://1m.C.q/4j.2W?u="+R((a.8.C.3!==\'\'?a.8.C.3:a.3))+"&t="+a.w+"","","16=0, 1H=0, F=2u, D=2p")},z:6(a){g.1a("1E://z.q/4k/1J?w="+R(a.w)+"&3="+R((a.8.z.3!==\'\'?a.8.z.3:a.3))+(a.8.z.15!==\'\'?\'&15=\'+a.8.z.15:\'\'),"","16=0, 1H=0, F=2T, D=2U")},I:6(a){g.1a("Q://I.q/4n/4o/2X?3="+R((a.8.I.3!==\'\'?a.8.I.3:a.3))+"&Y="+a.w+"&1Q=r&1C=r","","16=0, 1H=0, F=2T, D=2U")},p:6(a){g.1a(\'Q://1m.p.q/4p?v=5&4q&4r=4s&3=\'+R((a.8.p.3!==\'\'?a.8.p.3:a.3))+\'&Y=\'+a.w,\'p\',\'16=1F,F=1h,D=1h\')},L:6(a){g.1a(\'Q://1m.L.q/2t/?3=\'+R((a.8.p.3!==\'\'?a.8.p.3:a.3)),\'L\',\'16=1F,F=1h,D=1h\')},K:6(a){g.1a(\'1E://1m.K.q/4v/J?3=\'+R((a.8.p.3!==\'\'?a.8.p.3:a.3))+\'&4w=&4x=r\',\'K\',\'16=1F,F=1h,D=1h\')},A:6(a){g.1a(\'Q://A.q/1S/2k/S/?3=\'+R((a.8.A.3!==\'\'?a.8.A.3:a.3))+\'&1y=\'+R(a.8.A.1y)+\'&1s=\'+a.8.A.1s,\'A\',\'16=1F,F=4y,D=4z\')}};6 U(a,b){7.o=a;7.4=$.4C(r,{},1W,b);7.4.J=b.J;7.4D=1W;7.4E=j;7.1T()};U.X.1T=6(){l c=7;9(7.4.1o!==\'\'){1q.O=7.4.1o+\'?3={3}&P=O\';1q.L=7.4.1o+\'?3={3}&P=L\';1q.A=7.4.1o+\'?3={3}&P=A\'}$(7.o).4G(7.4.2Q);9(y $(7.o).n(\'Y\')!==\'E\'){7.4.Y=$(7.o).4H(\'n-Y\')}9(y $(7.o).n(\'3\')!==\'E\'){7.4.3=$(7.o).n(\'3\')}9(y $(7.o).n(\'w\')!==\'E\'){7.4.w=$(7.o).n(\'w\')}$.1u(7.4.J,6(a,b){9(b===r){c.4.26++}});9(c.4.2Z===r){$.1u(7.4.J,6(a,b){9(b===r){4J{c.31(a)}4L(e){}}})}H 9(c.4.17!==\'\'){7.4.2q(7,7.4)}H{7.1Y()}$(7.o).23(6(){9($(7).V(\'.8\').4N===0&&c.4.2Y===r){c.1Y()}c.4.23(c,c.4)},6(){c.4.30(c,c.4)});$(7.o).1I(6(){c.4.1I(c,c.4);1r B})};U.X.1Y=6(){l c=7;$(7.o).11(\'<m G="8"></m>\');$.1u(c.4.J,6(a,b){9(b==r){39[a](c);9(c.4.24===r){2D[a]()}}})};U.X.31=6(c){l d=7,x=0,3=1q[c].1A(\'{3}\',R(7.4.3));9(7.4.8[c].19===r&&7.4.8[c].3!==\'\'){3=1q[c].1A(\'{3}\',7.4.8[c].3)}9(3!=\'\'&&d.4.1o!==\'\'){$.4P(3,6(a){9(y a.x!=="E"){l b=a.x+\'\';b=b.1A(\'\\4Q\\4R\',\'\');x+=1L(b,10)}H 9(y a.35!=="E"){x+=1L(a.35,10)}H 9(y a.36!=="E"){x+=1L(a.36,10)}H 9(y a[0]!=="E"){x+=1L(a[0].4V,10)}H 9(y a[0]!=="E"){}d.4.x[c]=x;d.4.1n+=x;d.2e();d.1P()}).4Y(6(){d.4.x[c]=0;d.1P()})}H{d.2e();d.4.x[c]=0;d.1P()}};U.X.1P=6(){l a=0;4Z(e 2o 7.4.x){a++}9(a===7.4.26){7.4.2q(7,7.4)}};U.X.2e=6(){l a=7.4.1n,17=7.4.17;9(7.4.1w===r){a=7.1w(a)}9(17!==\'\'){17=17.1A(\'{1n}\',a);$(7.o).1U(17)}H{$(7.o).1U(\'<m G="51"><a G="x" 14="#">\'+a+\'</a>\'+(7.4.Y!==\'\'?\'<a G="J" 14="#">\'+7.4.Y+\'</a>\':\'\')+\'</m>\')}};U.X.1w=6(a){9(a>=3a){a=(a/3a).3b(2)+"M"}H 9(a>=3c){a=(a/3c).3b(1)+"k"}1r a};U.X.2w=6(a){2O[a](7.4);9(7.4.24===r){l b={O:{12:\'56\',T:\'+1\'},C:{12:\'C\',T:\'1O\'},z:{12:\'z\',T:\'1J\'},I:{12:\'I\',T:\'2m\'},p:{12:\'p\',T:\'2m\'},L:{12:\'L\',T:\'2m\'},K:{12:\'K\',T:\'J\'},A:{12:\'A\',T:\'1S\'}};1i.1j([\'1k\',b[a].12,b[a].T])}};U.X.58=6(){l a=$(7.o).1U();$(7.o).1U(a.1A(7.4.1n,7.4.1n+1))};U.X.59=6(a,b){9(a!==\'\'){7.4.3=a}9(b!==\'\'){7.4.w=b}};$.5a[j]=6(b){l c=5b;9(b===i||y b===\'5c\'){1r 7.1u(6(){9(!$.n(7,\'2l\'+j)){$.n(7,\'2l\'+j,5e U(7,b))}})}H 9(y b===\'5f\'&&b[0]!==\'5g\'&&b!==\'1T\'){1r 7.1u(6(){l a=$.n(7,\'2l\'+j);9(a 5h U&&y a[b]===\'6\'){a[b].5i(a,5j.X.5k.5l(c,1))}})}}})(5m,5n,47);',62,335,'|||url|options||function|this|buttons|if||||||||||||var|div|data|element|delicious|com|true|||||text|count|typeof|twitter|pinterest|false|facebook|height|undefined|width|class|else|digg|share|linkedin|stumbleupon||script|googlePlus|type|http|encodeURIComponent|button|action|Plugin|find|js|prototype|title|lang||append|site|layout|href|via|toolbar|template|src|urlCount|open|insertBefore|parentNode|getElementsByTagName|createElement|javascript|async|550|_gaq|push|_trackSocial|size|www|total|urlCurl|FB|urlJson|return|description|line|each|font|shorterTotal|send|media|20px|replace|callback|style|platform|https|no|fb|status|click|tweet|3px|parseInt|widgets|color|like|rendererPerso|related|border|pin|init|html|STMBLPN|defaults|faces|loadButtons|s1|50px|cssCount|plusone|hover|enableTracking|cssShare|shareTotal|margin|counter|sharrre|float|padding|id|annotation|renderer|hashtags|fjs|align|subscribe|Event|create|plugin_|add|twttr|in|500|render|clearInterval|colorscheme|badge|900|su|openPopup|img|radius|IN|solid|1px|center|tracking|setInterval|fff|background|edge|none|block|json|1000|tw|location|popup|18px|className|35px|processWidgets|650|360|SCRIPT|php|submit|enableHover|enableCounter|hide|getSocialJson|google|medium|en|likes|shares|gapi|horizontal|loadButton|1e6|toFixed|1e3|display|baseline|net|all|bottom|5px|overflow|hidden|xfbml|jssdk|XFBML|parse|ccc|urlinfo|decoration|7EACEE|40679C|Tweet|static|small|gif|US|alt|Delicious|Add|on|en_US|countserv|ajax|dataType|setTimeout|cache|DiggThisButton|rel|nofollow|external|format|jsonp|__DBW|it|Pin|It|assets|pinit|button_count|cdn|tall|apis|api|urls|DiggCompact|15px|remove|unlike|message|services|document|events|bind|story|LinkedInShare|getInfo|go|plus|hl|links|93px|googleplus|sharer|intent|right|root|tools|diggthis|save|noui|jump|close|show|26px|cws|token|isFramed|700|300|left|graph|extend|_defaults|_name|12px|addClass|attr|Arial|try|Helvetica|catch|sans|length|serif|getJSON|u00c2|u00a0|cursor|pointer|___gcfg|total_posts|666666|feeds|error|for|inline|box|getElementById|v2|normal|indent|Google|vertical|simulateClick|update|fn|arguments|object|connect|new|string|_|instanceof|apply|Array|slice|call|jQuery|window|top'.split('|'),0,{}))
1
+ !function(t,o,n,i){function r(e,o){this.element=e,this.options=t.extend(!0,{},l,o),this.options.share=o.share,this._defaults=l,this._name=a,this.init()}var a="sharrre",l={className:"sharrre",share:{googlePlus:!1,facebook:!1,twitter:!1,digg:!1,delicious:!1,stumbleupon:!1,linkedin:!1,pinterest:!1},shareTotal:0,template:"",title:"",url:n.location.href,text:n.title,urlCurl:"sharrre.php",count:{},total:0,shorterTotal:!0,enableHover:!0,enableCounter:!0,enableTracking:!1,hover:function(){},hide:function(){},click:function(){},render:function(){},buttons:{googlePlus:{url:"",urlCount:!1,size:"medium",lang:"en-US",annotation:""},facebook:{url:"",urlCount:!1,action:"like",layout:"button_count",width:"",send:"false",faces:"false",colorscheme:"",font:"",lang:"en_US"},twitter:{url:"",urlCount:!1,count:"horizontal",hashtags:"",via:"",related:"",lang:"en"},digg:{url:"",urlCount:!1,type:"DiggCompact"},delicious:{url:"",urlCount:!1,size:"medium"},stumbleupon:{url:"",urlCount:!1,layout:"1"},linkedin:{url:"",urlCount:!1,counter:""},pinterest:{url:"",media:"",description:"",layout:"horizontal"}}},u={googlePlus:"",facebook:"http://graph.facebook.com/?id={url}&callback=?",twitter:"http://cdn.api.twitter.com/1/urls/count.json?url={url}&callback=?",digg:"http://services.digg.com/2.0/story.getInfo?links={url}&type=javascript&callback=?",delicious:"http://feeds.delicious.com/v2/json/urlinfo/data?url={url}&callback=?",stumbleupon:"",linkedin:"http://www.linkedin.com/countserv/count/share?format=jsonp&url={url}&callback=?",pinterest:""},c={googlePlus:function(e){var i=e.options.buttons.googlePlus;t(e.element).find(".buttons").append('<div class="button googleplus"><div class="g-plusone" data-size="'+i.size+'" data-href="'+(""!==i.url?i.url:e.options.url)+'" data-annotation="'+i.annotation+'"></div></div>'),o.___gcfg={lang:e.options.buttons.googlePlus.lang};var s=0;"undefined"==typeof gapi&&0==s?(s=1,function(){var t=n.createElement("script");t.type="text/javascript",t.async=!0,t.src="//apis.google.com/js/plusone.js";var e=n.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e)}()):gapi.plusone.go()},facebook:function(e){var o=e.options.buttons.facebook;t(e.element).find(".buttons").append('<div class="button facebook"><div id="fb-root"></div><div class="fb-like" data-href="'+(""!==o.url?o.url:e.options.url)+'" data-send="'+o.send+'" data-layout="'+o.layout+'" data-width="'+o.width+'" data-show-faces="'+o.faces+'" data-action="'+o.action+'" data-colorscheme="'+o.colorscheme+'" data-font="'+o.font+'" data-via="'+o.via+'"></div></div>');var i=0;"undefined"==typeof FB&&0==i?(i=1,function(t,e,n){var i,s=t.getElementsByTagName(e)[0];t.getElementById(n)||(i=t.createElement(e),i.id=n,i.src="//connect.facebook.net/"+o.lang+"/all.js#xfbml=1",s.parentNode.insertBefore(i,s))}(n,"script","facebook-jssdk")):FB.XFBML.parse()},twitter:function(e){var o=e.options.buttons.twitter;t(e.element).find(".buttons").append('<div class="button twitter"><a href="https://twitter.com/share" class="twitter-share-button" data-url="'+(""!==o.url?o.url:e.options.url)+'" data-count="'+o.count+'" data-text="'+e.options.text+'" data-via="'+o.via+'" data-hashtags="'+o.hashtags+'" data-related="'+o.related+'" data-lang="'+o.lang+'">Tweet</a></div>');var i=0;"undefined"==typeof twttr&&0==i?(i=1,function(){var t=n.createElement("script");t.type="text/javascript",t.async=!0,t.src="//platform.twitter.com/widgets.js";var e=n.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e)}()):t.ajax({url:"//platform.twitter.com/widgets.js",dataType:"script",cache:!0})},digg:function(e){var o=e.options.buttons.digg;t(e.element).find(".buttons").append('<div class="button digg"><a class="DiggThisButton '+o.type+'" rel="nofollow external" href="http://digg.com/submit?url='+encodeURIComponent(""!==o.url?o.url:e.options.url)+'"></a></div>');var i=0;"undefined"==typeof __DBW&&0==i&&(i=1,function(){var t=n.createElement("SCRIPT"),e=n.getElementsByTagName("SCRIPT")[0];t.type="text/javascript",t.async=!0,t.src="//widgets.digg.com/buttons.js",e.parentNode.insertBefore(t,e)}())},delicious:function(e){if("tall"==e.options.buttons.delicious.size)var o="width:50px;",n="height:35px;width:50px;font-size:15px;line-height:35px;",i="height:18px;line-height:18px;margin-top:3px;";else var o="width:93px;",n="float:right;padding:0 3px;height:20px;width:26px;line-height:20px;",i="float:left;height:20px;line-height:20px;";var s=e.shorterTotal(e.options.count.delicious);"undefined"==typeof s&&(s=0),t(e.element).find(".buttons").append('<div class="button delicious"><div style="'+o+'font:12px Arial,Helvetica,sans-serif;cursor:pointer;color:#666666;display:inline-block;float:none;height:20px;line-height:normal;margin:0;padding:0;text-indent:0;vertical-align:baseline;"><div style="'+n+'background-color:#fff;margin-bottom:5px;overflow:hidden;text-align:center;border:1px solid #ccc;border-radius:3px;">'+s+'</div><div style="'+i+'display:block;padding:0;text-align:center;text-decoration:none;width:50px;background-color:#7EACEE;border:1px solid #40679C;border-radius:3px;color:#fff;"><img src="http://www.delicious.com/static/img/delicious.small.gif" height="10" width="10" alt="Delicious" /> Add</div></div></div>'),t(e.element).find(".delicious").on("click",function(){e.openPopup("delicious")})},stumbleupon:function(e){var i=e.options.buttons.stumbleupon;t(e.element).find(".buttons").append('<div class="button stumbleupon"><su:badge layout="'+i.layout+'" location="'+(""!==i.url?i.url:e.options.url)+'"></su:badge></div>');var r=0;"undefined"==typeof STMBLPN&&0==r?(r=1,function(){var t=n.createElement("script");t.type="text/javascript",t.async=!0,t.src="//platform.stumbleupon.com/1/widgets.js";var e=n.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e)}(),s=o.setTimeout(function(){"undefined"!=typeof STMBLPN&&(STMBLPN.processWidgets(),clearInterval(s))},500)):STMBLPN.processWidgets()},linkedin:function(e){var i=e.options.buttons.linkedin;t(e.element).find(".buttons").append('<div class="button linkedin"><script type="in/share" data-url="'+(""!==i.url?i.url:e.options.url)+'" data-counter="'+i.counter+'"></script></div>');var s=0;"undefined"==typeof o.IN&&0==s?(s=1,function(){var t=n.createElement("script");t.type="text/javascript",t.async=!0,t.src="//platform.linkedin.com/in.js";var e=n.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e)}()):o.IN.init()},pinterest:function(e){var o=e.options.buttons.pinterest;t(e.element).find(".buttons").append('<div class="button pinterest"><a href="http://pinterest.com/pin/create/button/?url='+(""!==o.url?o.url:e.options.url)+"&media="+o.media+"&description="+o.description+'" class="pin-it-button" count-layout="'+o.layout+'">Pin It</a></div>'),function(){var t=n.createElement("script");t.type="text/javascript",t.async=!0,t.src="//assets.pinterest.com/js/pinit.js";var e=n.getElementsByTagName("script")[0];e.parentNode.insertBefore(t,e)}()}},p={googlePlus:function(){},facebook:function(){fb=o.setInterval(function(){"undefined"!=typeof FB&&(FB.Event.subscribe("edge.create",function(t){_gaq.push(["_trackSocial","facebook","like",t])}),FB.Event.subscribe("edge.remove",function(t){_gaq.push(["_trackSocial","facebook","unlike",t])}),FB.Event.subscribe("message.send",function(t){_gaq.push(["_trackSocial","facebook","send",t])}),clearInterval(fb))},1e3)},twitter:function(){tw=o.setInterval(function(){"undefined"!=typeof twttr&&(twttr.events.bind("tweet",function(t){t&&_gaq.push(["_trackSocial","twitter","tweet"])}),clearInterval(tw))},1e3)},digg:function(){},delicious:function(){},stumbleupon:function(){},linkedin:function(){},pinterest:function(){}},d={googlePlus:function(t){o.open("https://plus.google.com/share?hl="+t.buttons.googlePlus.lang+"&url="+encodeURIComponent(""!==t.buttons.googlePlus.url?t.buttons.googlePlus.url:t.url),"","toolbar=0, status=0, width=900, height=500")},facebook:function(t){o.open("http://www.facebook.com/sharer.php?u="+encodeURIComponent(""!==t.buttons.facebook.url?t.buttons.facebook.url:t.url)+"&t="+t.text,"","toolbar=0, status=0, width=900, height=500")},twitter:function(t){o.open("https://twitter.com/intent/tweet?text="+encodeURIComponent(t.text)+"&url="+encodeURIComponent(""!==t.buttons.twitter.url?t.buttons.twitter.url:t.url)+(""!==t.buttons.twitter.via?"&via="+t.buttons.twitter.via:""),"","toolbar=0, status=0, width=650, height=360")},digg:function(t){o.open("http://digg.com/tools/diggthis/submit?url="+encodeURIComponent(""!==t.buttons.digg.url?t.buttons.digg.url:t.url)+"&title="+t.text+"&related=true&style=true","","toolbar=0, status=0, width=650, height=360")},delicious:function(t){o.open("http://www.delicious.com/save?v=5&noui&jump=close&url="+encodeURIComponent(""!==t.buttons.delicious.url?t.buttons.delicious.url:t.url)+"&title="+t.text,"delicious","toolbar=no,width=550,height=550")},stumbleupon:function(t){o.open("http://www.stumbleupon.com/badge/?url="+encodeURIComponent(""!==t.buttons.delicious.url?t.buttons.delicious.url:t.url),"stumbleupon","toolbar=no,width=550,height=550")},linkedin:function(t){o.open("https://www.linkedin.com/cws/share?url="+encodeURIComponent(""!==t.buttons.delicious.url?t.buttons.delicious.url:t.url)+"&token=&isFramed=true","linkedin","toolbar=no,width=550,height=550")},pinterest:function(t){o.open("http://pinterest.com/pin/create/button/?url="+encodeURIComponent(""!==t.buttons.pinterest.url?t.buttons.pinterest.url:t.url)+"&media="+encodeURIComponent(t.buttons.pinterest.media)+"&description="+t.buttons.pinterest.description,"pinterest","toolbar=no,width=700,height=300")}};r.prototype.init=function(){var e=this;""!==this.options.urlCurl&&(u.googlePlus=this.options.urlCurl+"?url={url}&type=googlePlus",u.stumbleupon=this.options.urlCurl+"?url={url}&type=stumbleupon",u.pinterest=this.options.urlCurl+"?url={url}&type=pinterest"),t(this.element).addClass(this.options.className),"undefined"!=typeof t(this.element).data("title")&&(this.options.title=t(this.element).attr("data-title")),"undefined"!=typeof t(this.element).data("url")&&(this.options.url=t(this.element).data("url")),"undefined"!=typeof t(this.element).data("text")&&(this.options.text=t(this.element).data("text")),t.each(this.options.share,function(t,o){o===!0&&e.options.shareTotal++}),e.options.enableCounter===!0?t.each(this.options.share,function(t,o){if(o===!0)try{e.getSocialJson(t)}catch(n){}}):""!==e.options.template?this.options.render(this,this.options):this.loadButtons(),t(this.element).hover(function(){0===t(this).find(".buttons").length&&e.options.enableHover===!0&&e.loadButtons(),e.options.hover(e,e.options)},function(){e.options.hide(e,e.options)}),t(this.element).click(function(){return e.options.click(e,e.options),!1})},r.prototype.loadButtons=function(){var e=this;t(this.element).append('<div class="buttons"></div>'),t.each(e.options.share,function(t,o){1==o&&(c[t](e),e.options.enableTracking===!0&&p[t]())})},r.prototype.getSocialJson=function(e){var o=this,n=0,i=u[e].replace("{url}",encodeURIComponent(this.options.url));this.options.buttons[e].urlCount===!0&&""!==this.options.buttons[e].url&&(i=u[e].replace("{url}",this.options.buttons[e].url)),""!=i&&""!==o.options.urlCurl?t.getJSON(i,function(t){if("undefined"!=typeof t.count){var i=t.count+"";i=i.replace(" ",""),n+=parseInt(i,10)}else"undefined"!=typeof t.likes?n+=parseInt(t.likes,10):"undefined"!=typeof t.shares?n+=parseInt(t.shares,10):"undefined"!=typeof t[0]?n+=parseInt(t[0].total_posts,10):"undefined"!=typeof t[0];o.options.count[e]=n,o.options.total+=n,o.renderer(),o.rendererPerso()}).error(function(){o.options.count[e]=0,o.rendererPerso()}):(o.renderer(),o.options.count[e]=0,o.rendererPerso())},r.prototype.rendererPerso=function(){var t=0;for(e in this.options.count)t++;t===this.options.shareTotal&&this.options.render(this,this.options)},r.prototype.renderer=function(){var e=this.options.total,o=this.options.template;this.options.shorterTotal===!0&&(e=this.shorterTotal(e)),""!==o?(o=o.replace("{total}",e),t(this.element).html(o)):t(this.element).html('<div class="box"><a class="count" href="#">'+e+"</a>"+(""!==this.options.title?'<a class="share" href="#">'+this.options.title+"</a>":"")+"</div>")},r.prototype.shorterTotal=function(t){return t>=1e6?t=(t/1e6).toFixed(2)+"M":t>=1e3&&(t=(t/1e3).toFixed(1)+"k"),t},r.prototype.openPopup=function(t){if(d[t](this.options),this.options.enableTracking===!0){var e={googlePlus:{site:"Google",action:"+1"},facebook:{site:"facebook",action:"like"},twitter:{site:"twitter",action:"tweet"},digg:{site:"digg",action:"add"},delicious:{site:"delicious",action:"add"},stumbleupon:{site:"stumbleupon",action:"add"},linkedin:{site:"linkedin",action:"share"},pinterest:{site:"pinterest",action:"pin"}};_gaq.push(["_trackSocial",e[t].site,e[t].action])}},r.prototype.simulateClick=function(){var e=t(this.element).html();t(this.element).html(e.replace(this.options.total,this.options.total+1))},r.prototype.update=function(t,e){""!==t&&(this.options.url=t),""!==e&&(this.options.text=e)},t.fn[a]=function(e){var o=arguments;return e===i||"object"==typeof e?this.each(function(){t.data(this,"plugin_"+a)||t.data(this,"plugin_"+a,new r(this,e))}):"string"==typeof e&&"_"!==e[0]&&"init"!==e?this.each(function(){var n=t.data(this,"plugin_"+a);n instanceof r&&"function"==typeof n[e]&&n[e].apply(n,Array.prototype.slice.call(o,1))}):void 0}}(jQuery,window,document);
 
 
 
 
 
 
modules/module.ab-testing.php CHANGED
@@ -363,15 +363,13 @@ if (is_admin()) {
363
  }
364
 
365
  add_filter('lp_variation_selected_template','lp_ab_testing_lp_variation_selected_template', 10, 2);
366
- function lp_ab_testing_lp_variation_selected_template($selected_template, $post)
367
- {
368
  if (isset($_GET['new-variation']))
369
  return $selected_template;
370
 
371
  $current_variation_id = lp_ab_testing_get_current_variation_id();
372
 
373
- if ($current_variation_id>0)
374
- {
375
  $selected_template = get_post_meta( $post->ID , 'lp-selected-template-'.$current_variation_id , true);
376
  }
377
 
@@ -381,8 +379,7 @@ if (is_admin()) {
381
 
382
  //add filter to modify thumbnail preview
383
  add_filter('lp_live_screenshot_url', 'lp_ab_testing_prepare_screenshot');
384
- function lp_ab_testing_prepare_screenshot($link)
385
- {
386
  $variation_id = lp_ab_testing_get_current_variation_id();
387
  $link = $link."?lp-variation-id=".$variation_id;
388
  return $link;
@@ -391,10 +388,8 @@ if (is_admin()) {
391
 
392
 
393
  add_filter("post_type_link", "lp_ab_append_variation_id_to_adminbar_link", 10,2);
394
- function lp_ab_append_variation_id_to_adminbar_link($link, $post)
395
- {
396
- if( $post->post_type == 'landing-page' )
397
- {
398
  $current_variation_id = lp_ab_testing_get_current_variation_id();
399
 
400
  if ($current_variation_id>0)
@@ -409,14 +404,12 @@ if (is_admin()) {
409
  }
410
 
411
  add_filter('wp_insert_post_data','lp_ab_testing_wp_insert_post_data',10,2);
412
- function lp_ab_testing_wp_insert_post_data($data,$postarr)
413
- {
414
 
415
  //exit;
416
  //$variation_id = lp_ab_testing_get_current_variation_id();
417
  //echo $variation_id;exit;
418
- if (isset($postarr['lp-variation-id'])&&$postarr['lp-variation-id']>0)
419
- {
420
  $postarr = array();
421
  $data = array();
422
 
@@ -424,16 +417,13 @@ if (is_admin()) {
424
  remove_action('save_post','lp_ab_testing_save_post',10);
425
 
426
  $postID = $_POST['post_ID'];
427
- if($parent_id = wp_is_post_revision($_POST['post_ID']))
428
- {
429
  $postID = $parent_id;
430
  }
431
 
432
  lp_ab_testing_save_post($postID);
433
 
434
- }
435
- else
436
- {
437
  //echo "here";exit;
438
  //$this_data = json_encode($data);
439
  //mail('hudson.atwell@gmail.com','test2',$this_data);
@@ -444,20 +434,17 @@ if (is_admin()) {
444
  }
445
 
446
  add_action('save_post','lp_ab_testing_save_post');
447
- function lp_ab_testing_save_post($postID)
448
- {
449
  global $post;
450
 
451
  $var_final = (isset($_POST['lp-variation-id'])) ? $_POST['lp-variation-id'] : '0';
452
- if ( isset($_POST['post_type']) && $_POST['post_type']=='landing-page')
453
- {
454
- if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ||$_POST['post_type']=='revision')
455
- {
456
  return;
457
  }
458
 
459
- if($parent_id = wp_is_post_revision($postID))
460
- {
461
  $postID = $parent_id;
462
  }
463
 
@@ -468,23 +455,16 @@ if (is_admin()) {
468
 
469
  //first add to varation list if not present.
470
  $variations = get_post_meta($postID,'lp-ab-variations', true);
471
- if ($variations)
472
- {
473
  $array_variations = explode(',',$variations);
474
- if (!in_array($this_variation,$array_variations))
475
- {
476
  $array_variations[] = $this_variation;
477
  }
478
- }
479
- else
480
- {
481
- if ($this_variation>0)
482
- {
483
  $array_variations[] = 0;
484
  $array_variations[] = $this_variation;
485
- }
486
- else
487
- {
488
  $array_variations[] = $this_variation;
489
  }
490
  }
@@ -495,8 +475,7 @@ if (is_admin()) {
495
  //add_post_meta($postID, 'lp_ab_variation_status-'.$this_variation , 1);
496
 
497
  //echo $this_variation;exit;
498
- if ($this_variation==0)
499
- {
500
  return;
501
  }
502
  //echo $this_variation;exit;
@@ -512,20 +491,16 @@ if (is_admin()) {
512
  //$special_list = array('content','post-content');
513
  //print_r($_POST);exit;
514
  //echo $this_variation;exit;
515
- foreach ($_POST as $key=>$value)
516
- {
517
  //echo $key." : -{$this_variation} : $value<br>";
518
- if (!in_array($key,$ignore_list)&&!strstr($key,'nonce'))
519
- {
520
- if ($key=='post_content')
521
  $key = 'content';
 
522
 
523
- if (!strstr($key,"-{$this_variation}"))
524
- {
525
  $new_array[$key.'-'.$this_variation] = $value;
526
- }
527
- else
528
- {
529
  //echo $key." : -{$this_variation}<br>";
530
  $new_array[$key] = $value;
531
  }
@@ -535,8 +510,7 @@ if (is_admin()) {
535
 
536
  //print_r($new_array);exit;
537
 
538
- foreach($new_array as $key => $val)
539
- {
540
  $old = get_post_meta($postID, $key, true);
541
  $new = $val;
542
  //echo "$key : $old v. $new <br>";
@@ -595,17 +569,14 @@ else
595
  /*PERFORM ACTIONS REQUIRED ON BOTH FRONT AND BACKEND */
596
 
597
  add_filter('lp_content_area','lp_ab_testing_alter_content_area_admin', 10, 2);
598
- function lp_ab_testing_alter_content_area_admin($content)
599
- {
600
  global $post;
601
 
602
  $variation_id = lp_ab_testing_get_current_variation_id();
603
 
604
- if ($variation_id>0)
605
- {
606
  $content = get_post_meta($post->ID,'content-'.$variation_id, true);
607
- if ( !is_admin() )
608
- {
609
  $content = wpautop($content);
610
  $content = do_shortcode($content);
611
  }
@@ -630,34 +601,28 @@ function lp_ab_key_to_letter($key) {
630
  }
631
 
632
  /* GET CURRENT VARIATION ID */
633
- function lp_ab_testing_get_current_variation_id()
634
- {
635
- if ( isset($_GET['ab-action']) &&is_admin())
636
- {
637
  return $_SESSION['lp_ab_test_open_variation'];
638
  }
639
 
640
- if (!isset($_SESSION['lp_ab_test_open_variation'])&&!isset($_REQUEST['lp-variation-id']))
641
- {
642
  $current_variation_id = 0;
643
  }
644
  //echo $_REQUEST['lp-variation-id'];
645
- if (isset($_REQUEST['lp-variation-id']))
646
- {
647
  $_SESSION['lp_ab_test_open_variation'] = $_REQUEST['lp-variation-id'];
648
  $current_variation_id = $_REQUEST['lp-variation-id'];
649
  //echo "setting session $current_variation_id";
650
  }
651
 
652
- if (isset($_GET['message'])&&$_GET['message']==1&&isset( $_SESSION['lp_ab_test_open_variation'] ))
653
- {
654
  $current_variation_id = $_SESSION['lp_ab_test_open_variation'];
655
 
656
  //echo "here:".$_SESSION['lp_ab_test_open_variation'];
657
  }
658
 
659
- if (isset($_GET['ab-action'])&&$_GET['ab-action']=='delete-variation')
660
- {
661
  $current_variation_id = 0;
662
  $_SESSION['lp_ab_test_open_variation'] = 0;
663
  }
@@ -670,24 +635,17 @@ function lp_ab_testing_get_current_variation_id()
670
 
671
  //ready conversion area for displaying ab variations
672
  add_filter('lp_conversion_area_pre_standardize','lp_ab_testing_prepare_conversion_area' , 10 , 2 );
673
- function lp_ab_testing_prepare_conversion_area($content,$post=null)
674
- {
675
  $current_variation_id = lp_ab_testing_get_current_variation_id();
676
 
677
- if (isset($post))
678
- {
679
  $post_id = $post->ID;
680
- }
681
- else if (isset($_REQUEST['post']))
682
- {
683
  $post_id = $_REQUEST['post'];
684
- }
685
- else if (isset($_REQUEST['lp_id']))
686
- {
687
  $post_id = $_REQUEST['lp_id'];
688
  }
689
 
690
-
691
  if ($current_variation_id>0)
692
  $content = get_post_meta($post_id,'landing-page-myeditor-'.$current_variation_id, true);
693
 
@@ -696,21 +654,17 @@ function lp_ab_testing_prepare_conversion_area($content,$post=null)
696
 
697
  //ready conversion area for displaying ab variations
698
  add_filter('lp_conversion_area_position','lp_ab_testing_lp_conversion_area_position' , 10 , 2 );
699
- function lp_ab_testing_lp_conversion_area_position($position, $post = null, $key = 'default')
700
- {
701
 
702
  $current_variation_id = lp_ab_testing_get_current_variation_id();
703
 
704
- if (isset($post))
705
- {
706
  $post_id = $post->ID;
707
  }
708
- else if (isset($_REQUEST['post']))
709
- {
710
  $post_id = $_REQUEST['post'];
711
  }
712
- else if (isset($_REQUEST['lp_id']))
713
- {
714
  $post_id = $_REQUEST['lp_id'];
715
  }
716
 
@@ -722,44 +676,32 @@ function lp_ab_testing_lp_conversion_area_position($position, $post = null, $key
722
 
723
 
724
  add_filter('lp_main_headline','lp_ab_testing_prepare_headline', 10, 2);
725
- function lp_ab_testing_prepare_headline($main_headline, $post = null)
726
- {
727
 
728
  $current_variation_id = lp_ab_testing_get_current_variation_id();
729
 
730
- if (isset($post))
731
- {
732
  $post_id = $post->ID;
733
- }
734
- else if (isset($_REQUEST['post']))
735
- {
736
  $post_id = $_REQUEST['post'];
737
- }
738
- else if (isset($_REQUEST['lp_id']))
739
- {
740
  $post_id = $_REQUEST['lp_id'];
741
- }
742
- else if (isset($_REQUEST['post_id']))
743
- {
744
  $post_id = $_REQUEST['post_id'];
745
  }
746
 
747
-
748
  if ($current_variation_id>0)
749
  $main_headline = get_post_meta($post_id,'lp-main-headline-'.$current_variation_id, true);
750
 
751
- if (!$main_headline)
752
- {
753
  get_post_meta($post_id,'lp-main-headline', true);
754
  }
755
 
756
-
757
  return $main_headline;
758
  }
759
 
760
  add_action('init','lp_ab_testing_add_rewrite_rules');
761
- function lp_ab_testing_add_rewrite_rules()
762
- {
763
  $this_path = LANDINGPAGES_PATH;
764
  $this_path = explode('wp-content',$this_path);
765
  $this_path = "wp-content".$this_path[1];
@@ -773,13 +715,10 @@ function lp_ab_testing_add_rewrite_rules()
773
  add_rewrite_rule("landing-page=([^/]*)?", $this_path.'modules/module.redirect-ab-testing.php?permalink_name=$1','top');
774
  }
775
  add_filter('mod_rewrite_rules', 'lp_ab_testing_modify_rules', 1);
776
- function lp_ab_testing_modify_rules($rules)
777
- {
778
- if (!stristr($rules,'RewriteCond %{QUERY_STRING} !lp-variation-id'))
779
- {
780
  $rules_array = preg_split ('/$\R?^/m', $rules);
781
- if (count($rules_array)<3)
782
- {
783
  $rules_array = explode("\n", $rules);
784
  $rules_array = array_filter($rules_array);
785
  }
@@ -792,19 +731,15 @@ function lp_ab_testing_add_rewrite_rules()
792
  $slug = get_option( 'lp-main-landing-page-permalink-prefix', 'go' );
793
 
794
  $i = 0;
795
- foreach ($rules_array as $key=>$val)
796
- {
797
 
798
- if ( stristr($val,"RewriteRule ^{$slug}/([^/]*)? ") || stristr($val,"RewriteRule ^{$slug}/([^/]*)/([0-9]+)/ ") )
799
- {
800
  $new_val = "RewriteCond %{QUERY_STRING} !lp-variation-id";
801
  $rules_array[$i] = $new_val;
802
  $i++;
803
  $rules_array[$i] = $val;
804
  $i++;
805
- }
806
- else
807
- {
808
  $rules_array[$i] = $val;
809
  $i++;
810
  }
@@ -820,17 +755,16 @@ function lp_ab_testing_add_rewrite_rules()
820
 
821
 
822
  add_filter('lp_selected_template','lp_ab_testing_get_selected_template');//get correct selected template for each variation
823
- function lp_ab_testing_get_selected_template($template)
824
- {
825
  global $post;
826
 
827
  $current_variation_id = lp_ab_testing_get_current_variation_id();
828
 
829
- if ($current_variation_id>0)
830
- {
831
- $new_template = get_post_meta($post->ID, 'lp-selected-template-'.$current_variation_id, true);
832
- if ($new_template)
833
  $template = $new_template;
 
834
  }
835
 
836
  return $template;
@@ -839,12 +773,10 @@ function lp_ab_testing_get_selected_template($template)
839
  //prepare custom js and css for
840
  add_filter('lp_custom_js_name','lp_ab_testing_prepare_name');
841
  add_filter('lp_custom_css_name','lp_ab_testing_prepare_name');
842
- function lp_ab_testing_prepare_name($id)
843
- {
844
  $current_variation_id = lp_ab_testing_get_current_variation_id();
845
  //echo $current_variation_id;exit;
846
- if ($current_variation_id>0)
847
- {
848
  $id = $id.'-'.$current_variation_id;
849
  }
850
 
@@ -854,8 +786,7 @@ function lp_ab_testing_prepare_name($id)
854
  add_action('wp_ajax_lp_ab_testing_prepare_variation', 'lp_ab_testing_prepare_variation_callback');
855
  add_action('wp_ajax_nopriv_lp_ab_testing_prepare_variation', 'lp_ab_testing_prepare_variation_callback');
856
 
857
- function lp_ab_testing_prepare_variation_callback()
858
- {
859
 
860
  $page_id = lp_url_to_postid( trim($_POST['current_url']) );
861
 
@@ -865,8 +796,7 @@ function lp_ab_testing_prepare_variation_callback()
865
  $marker = 0;
866
  }
867
 
868
- if ($variations)
869
- {
870
  //echo $variations;
871
  $variations = explode(',',$variations);
872
  //print_r($variations);
@@ -875,8 +805,7 @@ function lp_ab_testing_prepare_variation_callback()
875
 
876
  $marker++;
877
 
878
- if ($marker>=count($variations))
879
- {
880
  //echo "here";
881
  $marker = 0;
882
  }
@@ -893,8 +822,7 @@ function lp_ab_testing_prepare_variation_callback()
893
 
894
  add_filter('the_content','lp_ab_testing_alter_content_area', 10, 2);
895
  add_filter('get_the_content','lp_ab_testing_alter_content_area', 10, 2);
896
- function lp_ab_testing_alter_content_area($content)
897
- {
898
  global $post;
899
 
900
  if ( !isset($post) || $post->post_type != 'landing-page' ) {
@@ -903,8 +831,7 @@ function lp_ab_testing_alter_content_area($content)
903
 
904
  $variation_id = lp_ab_testing_get_current_variation_id();
905
 
906
- if ($variation_id>0)
907
- {
908
  $content = do_shortcode(get_post_meta($post->ID,'content-'.$variation_id, true));
909
  }
910
 
@@ -930,12 +857,11 @@ function lp_ab_testing_alter_title_area( $content , $id = null)
930
  add_action('lp_record_impression','lp_ab_testing_record_impression', 10, 3 );
931
  function lp_ab_testing_record_impression($post_id, $post_type = 'landing-page' , $variation_id = 0 ) {
932
 
933
- /* If Landing Page Post Type */
934
  if ( $post_type == 'landing-page' ) {
 
935
  $meta_key = 'lp-ab-variation-impressions-'.$variation_id;
936
- }
937
- /* If Non Landing Page Post Type */
938
- else {
939
  $meta_key = '_inbound_impressions_count';
940
  }
941
 
363
  }
364
 
365
  add_filter('lp_variation_selected_template','lp_ab_testing_lp_variation_selected_template', 10, 2);
366
+ function lp_ab_testing_lp_variation_selected_template($selected_template, $post) {
 
367
  if (isset($_GET['new-variation']))
368
  return $selected_template;
369
 
370
  $current_variation_id = lp_ab_testing_get_current_variation_id();
371
 
372
+ if ($current_variation_id>0) {
 
373
  $selected_template = get_post_meta( $post->ID , 'lp-selected-template-'.$current_variation_id , true);
374
  }
375
 
379
 
380
  //add filter to modify thumbnail preview
381
  add_filter('lp_live_screenshot_url', 'lp_ab_testing_prepare_screenshot');
382
+ function lp_ab_testing_prepare_screenshot($link) {
 
383
  $variation_id = lp_ab_testing_get_current_variation_id();
384
  $link = $link."?lp-variation-id=".$variation_id;
385
  return $link;
388
 
389
 
390
  add_filter("post_type_link", "lp_ab_append_variation_id_to_adminbar_link", 10,2);
391
+ function lp_ab_append_variation_id_to_adminbar_link($link, $post) {
392
+ if( $post->post_type == 'landing-page' ) {
 
 
393
  $current_variation_id = lp_ab_testing_get_current_variation_id();
394
 
395
  if ($current_variation_id>0)
404
  }
405
 
406
  add_filter('wp_insert_post_data','lp_ab_testing_wp_insert_post_data',10,2);
407
+ function lp_ab_testing_wp_insert_post_data($data,$postarr) {
 
408
 
409
  //exit;
410
  //$variation_id = lp_ab_testing_get_current_variation_id();
411
  //echo $variation_id;exit;
412
+ if (isset($postarr['lp-variation-id'])&&$postarr['lp-variation-id']>0) {
 
413
  $postarr = array();
414
  $data = array();
415
 
417
  remove_action('save_post','lp_ab_testing_save_post',10);
418
 
419
  $postID = $_POST['post_ID'];
420
+ if($parent_id = wp_is_post_revision($_POST['post_ID'])) {
 
421
  $postID = $parent_id;
422
  }
423
 
424
  lp_ab_testing_save_post($postID);
425
 
426
+ } else {
 
 
427
  //echo "here";exit;
428
  //$this_data = json_encode($data);
429
  //mail('hudson.atwell@gmail.com','test2',$this_data);
434
  }
435
 
436
  add_action('save_post','lp_ab_testing_save_post');
437
+ function lp_ab_testing_save_post($postID) {
 
438
  global $post;
439
 
440
  $var_final = (isset($_POST['lp-variation-id'])) ? $_POST['lp-variation-id'] : '0';
441
+ if ( isset($_POST['post_type']) && $_POST['post_type']=='landing-page') {
442
+
443
+ if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ||$_POST['post_type']=='revision') {
 
444
  return;
445
  }
446
 
447
+ if($parent_id = wp_is_post_revision($postID)) {
 
448
  $postID = $parent_id;
449
  }
450
 
455
 
456
  //first add to varation list if not present.
457
  $variations = get_post_meta($postID,'lp-ab-variations', true);
458
+ if ($variations) {
 
459
  $array_variations = explode(',',$variations);
460
+ if (!in_array($this_variation,$array_variations)) {
 
461
  $array_variations[] = $this_variation;
462
  }
463
+ } else {
464
+ if ($this_variation>0) {
 
 
 
465
  $array_variations[] = 0;
466
  $array_variations[] = $this_variation;
467
+ } else {
 
 
468
  $array_variations[] = $this_variation;
469
  }
470
  }
475
  //add_post_meta($postID, 'lp_ab_variation_status-'.$this_variation , 1);
476
 
477
  //echo $this_variation;exit;
478
+ if ($this_variation==0) {
 
479
  return;
480
  }
481
  //echo $this_variation;exit;
491
  //$special_list = array('content','post-content');
492
  //print_r($_POST);exit;
493
  //echo $this_variation;exit;
494
+ foreach ($_POST as $key=>$value) {
 
495
  //echo $key." : -{$this_variation} : $value<br>";
496
+ if (!in_array($key,$ignore_list)&&!strstr($key,'nonce')) {
497
+ if ($key=='post_content') {
 
498
  $key = 'content';
499
+ }
500
 
501
+ if (!strstr($key,"-{$this_variation}")) {
 
502
  $new_array[$key.'-'.$this_variation] = $value;
503
+ } else {
 
 
504
  //echo $key." : -{$this_variation}<br>";
505
  $new_array[$key] = $value;
506
  }
510
 
511
  //print_r($new_array);exit;
512
 
513
+ foreach($new_array as $key => $val) {
 
514
  $old = get_post_meta($postID, $key, true);
515
  $new = $val;
516
  //echo "$key : $old v. $new <br>";
569
  /*PERFORM ACTIONS REQUIRED ON BOTH FRONT AND BACKEND */
570
 
571
  add_filter('lp_content_area','lp_ab_testing_alter_content_area_admin', 10, 2);
572
+ function lp_ab_testing_alter_content_area_admin($content) {
 
573
  global $post;
574
 
575
  $variation_id = lp_ab_testing_get_current_variation_id();
576
 
577
+ if ($variation_id>0) {
 
578
  $content = get_post_meta($post->ID,'content-'.$variation_id, true);
579
+ if ( !is_admin() ) {
 
580
  $content = wpautop($content);
581
  $content = do_shortcode($content);
582
  }
601
  }
602
 
603
  /* GET CURRENT VARIATION ID */
604
+ function lp_ab_testing_get_current_variation_id() {
605
+ if ( isset($_GET['ab-action']) && is_admin()) {
 
 
606
  return $_SESSION['lp_ab_test_open_variation'];
607
  }
608
 
609
+ if (!isset($_SESSION['lp_ab_test_open_variation'])&&!isset($_REQUEST['lp-variation-id'])) {
 
610
  $current_variation_id = 0;
611
  }
612
  //echo $_REQUEST['lp-variation-id'];
613
+ if (isset($_REQUEST['lp-variation-id'])) {
 
614
  $_SESSION['lp_ab_test_open_variation'] = $_REQUEST['lp-variation-id'];
615
  $current_variation_id = $_REQUEST['lp-variation-id'];
616
  //echo "setting session $current_variation_id";
617
  }
618
 
619
+ if (isset($_GET['message'])&&$_GET['message']==1&&isset( $_SESSION['lp_ab_test_open_variation'] )) {
 
620
  $current_variation_id = $_SESSION['lp_ab_test_open_variation'];
621
 
622
  //echo "here:".$_SESSION['lp_ab_test_open_variation'];
623
  }
624
 
625
+ if (isset($_GET['ab-action'])&&$_GET['ab-action']=='delete-variation') {
 
626
  $current_variation_id = 0;
627
  $_SESSION['lp_ab_test_open_variation'] = 0;
628
  }
635
 
636
  //ready conversion area for displaying ab variations
637
  add_filter('lp_conversion_area_pre_standardize','lp_ab_testing_prepare_conversion_area' , 10 , 2 );
638
+ function lp_ab_testing_prepare_conversion_area($content,$post=null) {
 
639
  $current_variation_id = lp_ab_testing_get_current_variation_id();
640
 
641
+ if (isset($post)) {
 
642
  $post_id = $post->ID;
643
+ } else if (isset($_REQUEST['post'])) {
 
 
644
  $post_id = $_REQUEST['post'];
645
+ } else if (isset($_REQUEST['lp_id'])) {
 
 
646
  $post_id = $_REQUEST['lp_id'];
647
  }
648
 
 
649
  if ($current_variation_id>0)
650
  $content = get_post_meta($post_id,'landing-page-myeditor-'.$current_variation_id, true);
651
 
654
 
655
  //ready conversion area for displaying ab variations
656
  add_filter('lp_conversion_area_position','lp_ab_testing_lp_conversion_area_position' , 10 , 2 );
657
+ function lp_ab_testing_lp_conversion_area_position($position, $post = null, $key = 'default') {
 
658
 
659
  $current_variation_id = lp_ab_testing_get_current_variation_id();
660
 
661
+ if (isset($post)) {
 
662
  $post_id = $post->ID;
663
  }
664
+ else if (isset($_REQUEST['post'])) {
 
665
  $post_id = $_REQUEST['post'];
666
  }
667
+ else if (isset($_REQUEST['lp_id'])) {
 
668
  $post_id = $_REQUEST['lp_id'];
669
  }
670
 
676
 
677
 
678
  add_filter('lp_main_headline','lp_ab_testing_prepare_headline', 10, 2);
679
+ function lp_ab_testing_prepare_headline($main_headline, $post = null) {
 
680
 
681
  $current_variation_id = lp_ab_testing_get_current_variation_id();
682
 
683
+ if (isset($post)) {
 
684
  $post_id = $post->ID;
685
+ } else if (isset($_REQUEST['post'])) {
 
 
686
  $post_id = $_REQUEST['post'];
687
+ } else if (isset($_REQUEST['lp_id'])) {
 
 
688
  $post_id = $_REQUEST['lp_id'];
689
+ } else if (isset($_REQUEST['post_id'])) {
 
 
690
  $post_id = $_REQUEST['post_id'];
691
  }
692
 
 
693
  if ($current_variation_id>0)
694
  $main_headline = get_post_meta($post_id,'lp-main-headline-'.$current_variation_id, true);
695
 
696
+ if (!$main_headline) {
 
697
  get_post_meta($post_id,'lp-main-headline', true);
698
  }
699
 
 
700
  return $main_headline;
701
  }
702
 
703
  add_action('init','lp_ab_testing_add_rewrite_rules');
704
+ function lp_ab_testing_add_rewrite_rules() {
 
705
  $this_path = LANDINGPAGES_PATH;
706
  $this_path = explode('wp-content',$this_path);
707
  $this_path = "wp-content".$this_path[1];
715
  add_rewrite_rule("landing-page=([^/]*)?", $this_path.'modules/module.redirect-ab-testing.php?permalink_name=$1','top');
716
  }
717
  add_filter('mod_rewrite_rules', 'lp_ab_testing_modify_rules', 1);
718
+ function lp_ab_testing_modify_rules($rules) {
719
+ if (!stristr($rules,'RewriteCond %{QUERY_STRING} !lp-variation-id')) {
 
 
720
  $rules_array = preg_split ('/$\R?^/m', $rules);
721
+ if (count($rules_array)<3) {
 
722
  $rules_array = explode("\n", $rules);
723
  $rules_array = array_filter($rules_array);
724
  }
731
  $slug = get_option( 'lp-main-landing-page-permalink-prefix', 'go' );
732
 
733
  $i = 0;
734
+ foreach ($rules_array as $key=>$val) {
 
735
 
736
+ if ( stristr($val,"RewriteRule ^{$slug}/([^/]*)? ") || stristr($val,"RewriteRule ^{$slug}/([^/]*)/([0-9]+)/ ") ) {
 
737
  $new_val = "RewriteCond %{QUERY_STRING} !lp-variation-id";
738
  $rules_array[$i] = $new_val;
739
  $i++;
740
  $rules_array[$i] = $val;
741
  $i++;
742
+ } else {
 
 
743
  $rules_array[$i] = $val;
744
  $i++;
745
  }
755
 
756
 
757
  add_filter('lp_selected_template','lp_ab_testing_get_selected_template');//get correct selected template for each variation
758
+ function lp_ab_testing_get_selected_template($template) {
 
759
  global $post;
760
 
761
  $current_variation_id = lp_ab_testing_get_current_variation_id();
762
 
763
+ if ($current_variation_id>0) {
764
+ $new_template = get_post_meta($post->ID, 'lp-selected-template-'.$current_variation_id, true);
765
+ if ($new_template) {
 
766
  $template = $new_template;
767
+ }
768
  }
769
 
770
  return $template;
773
  //prepare custom js and css for
774
  add_filter('lp_custom_js_name','lp_ab_testing_prepare_name');
775
  add_filter('lp_custom_css_name','lp_ab_testing_prepare_name');
776
+ function lp_ab_testing_prepare_name($id) {
 
777
  $current_variation_id = lp_ab_testing_get_current_variation_id();
778
  //echo $current_variation_id;exit;
779
+ if ($current_variation_id>0) {
 
780
  $id = $id.'-'.$current_variation_id;
781
  }
782
 
786
  add_action('wp_ajax_lp_ab_testing_prepare_variation', 'lp_ab_testing_prepare_variation_callback');
787
  add_action('wp_ajax_nopriv_lp_ab_testing_prepare_variation', 'lp_ab_testing_prepare_variation_callback');
788
 
789
+ function lp_ab_testing_prepare_variation_callback() {
 
790
 
791
  $page_id = lp_url_to_postid( trim($_POST['current_url']) );
792
 
796
  $marker = 0;
797
  }
798
 
799
+ if ($variations) {
 
800
  //echo $variations;
801
  $variations = explode(',',$variations);
802
  //print_r($variations);
805
 
806
  $marker++;
807
 
808
+ if ($marker>=count($variations)) {
 
809
  //echo "here";
810
  $marker = 0;
811
  }
822
 
823
  add_filter('the_content','lp_ab_testing_alter_content_area', 10, 2);
824
  add_filter('get_the_content','lp_ab_testing_alter_content_area', 10, 2);
825
+ function lp_ab_testing_alter_content_area($content) {
 
826
  global $post;
827
 
828
  if ( !isset($post) || $post->post_type != 'landing-page' ) {
831
 
832
  $variation_id = lp_ab_testing_get_current_variation_id();
833
 
834
+ if ($variation_id>0) {
 
835
  $content = do_shortcode(get_post_meta($post->ID,'content-'.$variation_id, true));
836
  }
837
 
857
  add_action('lp_record_impression','lp_ab_testing_record_impression', 10, 3 );
858
  function lp_ab_testing_record_impression($post_id, $post_type = 'landing-page' , $variation_id = 0 ) {
859
 
 
860
  if ( $post_type == 'landing-page' ) {
861
+ /* If Landing Page Post Type */
862
  $meta_key = 'lp-ab-variation-impressions-'.$variation_id;
863
+ } else {
864
+ /* If Non Landing Page Post Type */
 
865
  $meta_key = '_inbound_impressions_count';
866
  }
867
 
modules/module.admin-menus.php CHANGED
@@ -13,9 +13,12 @@ function lp_add_menu()
13
 
14
  add_submenu_page('edit.php?post_type=landing-page',__('Templates' , 'landing-pages'), __('Manage Templates' , 'landing-pages'), 'manage_options', 'lp_manage_templates','lp_manage_templates',100);
15
 
16
- add_submenu_page('edit.php?post_type=landing-page', __('Get Addons' , 'landing-pages'), __('Get Addons' , 'landing-pages'), 'manage_options', 'lp_store','lp_store_display',100);
17
 
18
- add_submenu_page('edit.php?post_type=landing-page', __('Global Settings' , 'landing-pages'), __('Global Settings' , 'landing-pages'), 'manage_options', 'lp_global_settings','lp_display_global_settings');
 
 
 
 
19
 
20
  }
21
  }
13
 
14
  add_submenu_page('edit.php?post_type=landing-page',__('Templates' , 'landing-pages'), __('Manage Templates' , 'landing-pages'), 'manage_options', 'lp_manage_templates','lp_manage_templates',100);
15
 
 
16
 
17
+ add_submenu_page('edit.php?post_type=landing-page', __('Settings' , 'landing-pages'), __('Settings' , 'landing-pages'), 'manage_options', 'lp_global_settings','lp_display_global_settings');
18
+
19
+
20
+
21
+ //add_submenu_page('edit.php?post_type=landing-page', __('Extensions' , 'landing-pages'),'<span style="color:#f18500">'.__('Extensions' , 'landing-pages').'</span>', 'manage_options', 'lp_store','lp_store_display',100);
22
 
23
  }
24
  }
modules/module.ajax-setup.php CHANGED
@@ -1 +1 @@
1
- <?php
2
  * Adds Ajax for Clear Stats button
3
  * clear stats for all variations
4
  */
5
  * Adds Ajax for Clear Stats button
6
  * clear stats for single variations
7
  */
8
  * Adds Ajax for Clear Stats button
9
  * clear stats for non lp post
10
  */
11
  * Click tracking moved to module.click-tracking.php
12
  * Adds ajax to record landing page impressions
13
  * future plans to integrate with google analytics
14
  * Adds Ajax Template Selection
15
  * @return prints out landing page meta options
16
  */
 
17
  * Adds Ajax for Clear Stats button
18
  * clear stats for all variations
19
  */
20
  global $wpdb;
21
  $newrules = "0";
22
  $post_id = mysql_real_escape_string($_POST['page_id']);
23
  $variations = get_post_meta($post_id, 'lp-ab-variations', true);
24
  if ($variations) {
25
  $variations = explode(",", $variations);
26
  foreach ($variations as $vid) {
27
  add_post_meta($post_id, 'lp-ab-variation-impressions-' . $vid, $newrules, true) or update_post_meta($post_id, 'lp-ab-variation-impressions-' . $vid, $newrules);
28
  add_post_meta($post_id, 'lp-ab-variation-conversions-' . $vid, $newrules, true) or update_post_meta($post_id, 'lp-ab-variation-conversions-' . $vid, $newrules);
29
  }
30
  } else {
31
  add_post_meta($post_id, 'lp-ab-variation-impressions-0', $newrules, true) or update_post_meta($post_id, 'lp-ab-variation-impressions-0', $newrules);
32
  }
33
  header('HTTP/1.1 200 OK');
34
  * Adds Ajax for Clear Stats button
35
  * clear stats for single variations
36
  */
37
  global $wpdb;
38
  $newrules = "0";
39
  $post_id = mysql_real_escape_string($_POST['page_id']);
40
  $vid = $_POST['variation'];
41
  add_post_meta($post_id, 'lp-ab-variation-impressions-' . $vid, $newrules, true) or update_post_meta($post_id, 'lp-ab-variation-impressions-' . $vid, $newrules);
42
  add_post_meta($post_id, 'lp-ab-variation-conversions-' . $vid, $newrules, true) or update_post_meta($post_id, 'lp-ab-variation-conversions-' . $vid, $newrules);
43
  header('HTTP/1.1 200 OK');
44
  * Adds Ajax for Clear Stats button
45
  * clear stats for non lp post
46
  */
47
  global $wpdb;
48
  $newrules = "0";
49
  $post_id = mysql_real_escape_string($_POST['post_id']);
50
  $vid = $_POST['variation'];
51
  update_post_meta($post_id, '_inbound_impressions_count', '0');
52
  update_post_meta($post_id, '_inbound_conversions_count', '0');
53
  header('HTTP/1.1 200 OK');
54
  * Adds Ajax Template Selection
55
  * @return prints out landing page meta options
56
  */
57
  global $wpdb;
58
  $current_template = $_POST['selected_template'];
59
  $post_id = $_POST['post_id'];
60
  $post = get_post($post_id);
61
  //echo $current_template; exit;
62
  $key['args']['key'] = $current_template;
63
  lp_show_metabox($post, $key);
64
  die();
 
1
  * Adds Ajax for Clear Stats button
2
  * clear stats for all variations
3
  */
4
  * Adds Ajax for Clear Stats button
5
  * clear stats for single variations
6
  */
7
  * Adds Ajax for Clear Stats button
8
  * clear stats for non lp post
9
  */
10
  * Click tracking moved to module.click-tracking.php
11
  * Adds ajax to record landing page impressions
12
  * future plans to integrate with google analytics
13
  * Adds Ajax Template Selection
14
  * @return prints out landing page meta options
15
  */
16
+ <?php
17
  * Adds Ajax for Clear Stats button
18
  * clear stats for all variations
19
  */
20
  global $wpdb;
21
  $newrules = "0";
22
  $post_id = mysql_real_escape_string($_POST['page_id']);
23
  $variations = get_post_meta($post_id, 'lp-ab-variations', true);
24
  if ($variations) {
25
  $variations = explode(",", $variations);
26
  foreach ($variations as $vid) {
27
  add_post_meta($post_id, 'lp-ab-variation-impressions-' . $vid, $newrules, true) or update_post_meta($post_id, 'lp-ab-variation-impressions-' . $vid, $newrules);
28
  add_post_meta($post_id, 'lp-ab-variation-conversions-' . $vid, $newrules, true) or update_post_meta($post_id, 'lp-ab-variation-conversions-' . $vid, $newrules);
29
  }
30
  } else {
31
  add_post_meta($post_id, 'lp-ab-variation-impressions-0', $newrules, true) or update_post_meta($post_id, 'lp-ab-variation-impressions-0', $newrules);
32
  }
33
  header('HTTP/1.1 200 OK');
34
  * Adds Ajax for Clear Stats button
35
  * clear stats for single variations
36
  */
37
  global $wpdb;
38
  $newrules = "0";
39
  $post_id = mysql_real_escape_string($_POST['page_id']);
40
  $vid = $_POST['variation'];
41
  add_post_meta($post_id, 'lp-ab-variation-impressions-' . $vid, $newrules, true) or update_post_meta($post_id, 'lp-ab-variation-impressions-' . $vid, $newrules);
42
  add_post_meta($post_id, 'lp-ab-variation-conversions-' . $vid, $newrules, true) or update_post_meta($post_id, 'lp-ab-variation-conversions-' . $vid, $newrules);
43
  header('HTTP/1.1 200 OK');
44
  * Adds Ajax for Clear Stats button
45
  * clear stats for non lp post
46
  */
47
  global $wpdb;
48
  $newrules = "0";
49
  $post_id = mysql_real_escape_string($_POST['post_id']);
50
  $vid = $_POST['variation'];
51
  update_post_meta($post_id, '_inbound_impressions_count', '0');
52
  update_post_meta($post_id, '_inbound_conversions_count', '0');
53
  header('HTTP/1.1 200 OK');
54
  * Adds Ajax Template Selection
55
  * @return prints out landing page meta options
56
  */
57
  global $wpdb;
58
  $current_template = $_POST['selected_template'];
59
  $post_id = $_POST['post_id'];
60
  $post = get_post($post_id);
61
  //echo $current_template; exit;
62
  $key['args']['key'] = $current_template;
63
  lp_show_metabox($post, $key);
64
  die();
modules/module.customizer.php CHANGED
@@ -1 +1 @@
1
- <?php
2
  wp_register_script('lp-customizer-load-js', LANDINGPAGES_URLPATH . 'js/customizer.load.js', array('jquery'));
3
  wp_enqueue_script('lp-customizer-load-js');
 
4
  wp_register_script('lp-customizer-load-js', LANDINGPAGES_URLPATH . 'js/customizer.load.js', array('jquery'));
5
  wp_enqueue_script('lp-customizer-load-js');
 
1
  wp_register_script('lp-customizer-load-js', LANDINGPAGES_URLPATH . 'js/customizer.load.js', array('jquery'));
2
  wp_enqueue_script('lp-customizer-load-js');
3
+ <?php
4
  wp_register_script('lp-customizer-load-js', LANDINGPAGES_URLPATH . 'js/customizer.load.js', array('jquery'));
5
  wp_enqueue_script('lp-customizer-load-js');
modules/module.install.php CHANGED
@@ -2,7 +2,7 @@
2
  // Added Demo Landing on Install
3
  add_action('admin_init', 'inbound_create_default_post_type');
4
  function inbound_create_default_post_type(){
5
-
6
 
7
  $lp_default_options = get_option( 'lp_settings_general' );
8
 
@@ -18,17 +18,16 @@ function inbound_create_default_post_type(){
18
  */
19
  function inbound_install_example_lander() {
20
 
21
-
22
  $landing_page_id = wp_insert_post(
23
  array(
24
  'post_title' => __( 'A/B Testing Landing Page Example' , 'landing-pages'),
25
  'post_content' => __( '<p>This is the first paragraph of your landing page where you want to draw the viewers in and quickly explain your value proposition.</p><p><strong>Use Bullet Points to:</strong><ul><li>Explain why they should fill out the form</li><li>What they will learn if they download</li><li>A problem this form will solve for them</li></ul></p><p>Short ending paragraph reiterating the value behind the form</p>' , 'post'),
26
  'post_status' => 'publish',
27
  'post_type' => 'landing-page',
28
- 'comment_status' => 'closed'
29
- )
30
  );
31
-
32
 
33
  // Variation A
34
  add_post_meta($landing_page_id, 'lp-main-headline', __( 'Main Catchy Headline (A)' , 'landing-pages') );
@@ -71,13 +70,12 @@ function inbound_install_example_lander() {
71
  add_post_meta($landing_page_id, 'svtle-sidebar-text-color-1', '000000');
72
  add_post_meta($landing_page_id, 'svtle-header-color-1', '51b0ef');
73
 
74
-
75
  // Store our page IDs
76
  $options = array(
77
  "default_landing_page" => $landing_page_id
78
  );
79
 
80
-
81
  update_option( "lp_settings_general" , $options );
82
 
83
  return $landing_page_id;
@@ -96,9 +94,11 @@ function activation_save_error(){
96
  /**
97
  * Include the TGM_Plugin_Activation class.
98
  */
 
 
 
 
99
 
100
- require_once(LANDINGPAGES_PATH."/libraries/class-tgm-plugin-activation.php");
101
- add_action( 'tgmpa_register', 'lp_install_register_required_plugins' );
102
  /**
103
  * Register the required plugins for this theme.
104
  *
@@ -134,12 +134,12 @@ function lp_install_register_required_plugins() {
134
 
135
  // This is an example of how to include a plugin from the WordPress Plugin Repository
136
  array(
137
- 'name' => __('WordPress Leads' , 'landing-pages') .' <span class=\'inbound-install-notice\'> - '. __('This <b>free</b> landing page addon will give you the ability to track and manage incoming web leads. Gather advanced Lead Intelligence and close more deals.' , 'landing-pages') .' <a class=\'inbound-install-notice-links\' href=\'http://wordpress.org/plugins/leads/\'> '. __('Learn more about WordPress Leads' , 'landing-pages') .'</a></span>',
138
  'slug' => 'leads',
139
  'required' => false,
140
  ),
141
  array(
142
- 'name' => __('WordPress Calls to Action' , 'landing-pages') .' <span class=\'inbound-install-notice\'> - '. __('This <b>free</b> landing page addon will drive more traffic into your Landing Pages with Targeted Calls to Action in your sites sidebars & content. Create popups to capture visitor attention and convert more leads.' , 'landing-pages') . ' <a class=\'inbound-install-notice-links\' href=\'http://wordpress.org/plugins/cta/\'> ' . __('Learn more about WordPress Calls to Action' , 'landing-pages') . '</a></span>',
143
  'slug' => 'cta',
144
  'required' => false,
145
  ),
@@ -187,6 +187,6 @@ function lp_install_register_required_plugins() {
187
  )
188
  );
189
 
190
- tgmpa( $plugins, $config );
191
 
192
  }
2
  // Added Demo Landing on Install
3
  add_action('admin_init', 'inbound_create_default_post_type');
4
  function inbound_create_default_post_type(){
5
+
6
 
7
  $lp_default_options = get_option( 'lp_settings_general' );
8
 
18
  */
19
  function inbound_install_example_lander() {
20
 
21
+
22
  $landing_page_id = wp_insert_post(
23
  array(
24
  'post_title' => __( 'A/B Testing Landing Page Example' , 'landing-pages'),
25
  'post_content' => __( '<p>This is the first paragraph of your landing page where you want to draw the viewers in and quickly explain your value proposition.</p><p><strong>Use Bullet Points to:</strong><ul><li>Explain why they should fill out the form</li><li>What they will learn if they download</li><li>A problem this form will solve for them</li></ul></p><p>Short ending paragraph reiterating the value behind the form</p>' , 'post'),
26
  'post_status' => 'publish',
27
  'post_type' => 'landing-page',
28
+ ) , true
 
29
  );
30
+ shell_exec( json_encode( $landing_page_id) );
31
 
32
  // Variation A
33
  add_post_meta($landing_page_id, 'lp-main-headline', __( 'Main Catchy Headline (A)' , 'landing-pages') );
70
  add_post_meta($landing_page_id, 'svtle-sidebar-text-color-1', '000000');
71
  add_post_meta($landing_page_id, 'svtle-header-color-1', '51b0ef');
72
 
 
73
  // Store our page IDs
74
  $options = array(
75
  "default_landing_page" => $landing_page_id
76
  );
77
 
78
+
79
  update_option( "lp_settings_general" , $options );
80
 
81
  return $landing_page_id;
94
  /**
95
  * Include the TGM_Plugin_Activation class.
96
  */
97
+ if(!defined('INBOUND_PRO_PATH')) {
98
+ require_once(LANDINGPAGES_PATH."/libraries/class-tgm-plugin-activation.php");
99
+ add_action( 'tgmpa_register', 'lp_install_register_required_plugins' );
100
+ }
101
 
 
 
102
  /**
103
  * Register the required plugins for this theme.
104
  *
134
 
135
  // This is an example of how to include a plugin from the WordPress Plugin Repository
136
  array(
137
+ 'name' => __('WordPress Leads' , 'landing-pages') .' <span class=\'inbound-install-notice\'> - '. __('This <b>free</b> landing page addon will give you the ability to track and manage incoming web leads. Gather advanced Lead Intelligence and close more deals.', 'landing-pages') .'</span>',
138
  'slug' => 'leads',
139
  'required' => false,
140
  ),
141
  array(
142
+ 'name' => __('WordPress Calls to Action' , 'landing-pages') .' <span class=\'inbound-install-notice\'> - '. __('This <b>free</b> landing page addon will drive more traffic into your Landing Pages with Targeted Calls to Action in your sites sidebars & content. Create popups to capture visitor attention and convert more leads.' , 'landing-pages') .'</span>',
143
  'slug' => 'cta',
144
  'required' => false,
145
  ),
187
  )
188
  );
189
 
190
+ inbound_activate( $plugins, $config );
191
 
192
  }
modules/module.javascript-admin.php CHANGED
@@ -79,7 +79,7 @@ function lp_admin_enqueue($hook) {
79
  }
80
  // Edit Screen
81
  if ( $hook == 'post.php' ){
82
-
83
  wp_enqueue_script('lp-post-edit-ui', LANDINGPAGES_URLPATH . 'js/admin/admin.post-edit.js');
84
  wp_localize_script( 'lp-post-edit-ui', 'lp_post_edit_ui', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'post_id' => $post->ID , 'wp_landing_page_meta_nonce' => wp_create_nonce('wp-landing-page-meta-nonce'), 'lp_template_nonce' => wp_create_nonce('lp-nonce') ) );
85
  wp_enqueue_style('admin-post-edit-css', LANDINGPAGES_URLPATH . 'css/admin-post-edit.css');
79
  }
80
  // Edit Screen
81
  if ( $hook == 'post.php' ){
82
+ wp_enqueue_script('jquery-zoomer', LANDINGPAGES_URLPATH . 'js/libraries/jquery.zoomer.js');
83
  wp_enqueue_script('lp-post-edit-ui', LANDINGPAGES_URLPATH . 'js/admin/admin.post-edit.js');
84
  wp_localize_script( 'lp-post-edit-ui', 'lp_post_edit_ui', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'post_id' => $post->ID , 'wp_landing_page_meta_nonce' => wp_create_nonce('wp-landing-page-meta-nonce'), 'lp_template_nonce' => wp_create_nonce('lp-nonce') ) );
85
  wp_enqueue_style('admin-post-edit-css', LANDINGPAGES_URLPATH . 'css/admin-post-edit.css');
modules/module.javascript-frontend.php CHANGED
@@ -8,7 +8,7 @@ function lp_fontend_enqueue_scripts($hook) {
8
  if (!isset($post)) {
9
  return;
10
  }
11
-
12
  /* dequeue third party scripts */
13
  global $wp_scripts;
14
  $store = '';
@@ -34,7 +34,7 @@ function lp_fontend_enqueue_scripts($hook) {
34
  show_admin_bar( false );
35
  wp_register_script('lp-customizer-load-js', LANDINGPAGES_URLPATH . 'js/customizer.load.js', array('jquery'));
36
  wp_enqueue_script('lp-customizer-load-js');
37
-
38
  }
39
  }
40
  /* Requeue third party scripts */
@@ -66,13 +66,22 @@ function lp_header_load(){
66
  <?php if (isset($_GET['lp-variation-id']) && !isset($_GET['template-customize']) && !isset($_GET['iframe_window']) && !isset($_GET['live-preview-area'])) {
67
  do_action('landing_page_header_script');
68
  ?>
 
 
 
 
 
 
 
 
69
  <?php if(!defined('Inbound_Now_Disable_URL_CLEAN')) { ?>
70
  <script type="text/javascript">
71
- //var inbound_param_overide = 'off';
72
- // Automation pass params to GA. Look for documentation
73
  if (typeof window.history.pushState == 'function') {
74
- var current=window.location.href;var cleanparams=current.split("?");var clean_url=cleanparams[0];history.replaceState({},"landing page",clean_url);
75
- }</script>
 
 
76
  <?php } ?>
77
  <?php }
78
  }
8
  if (!isset($post)) {
9
  return;
10
  }
11
+
12
  /* dequeue third party scripts */
13
  global $wp_scripts;
14
  $store = '';
34
  show_admin_bar( false );
35
  wp_register_script('lp-customizer-load-js', LANDINGPAGES_URLPATH . 'js/customizer.load.js', array('jquery'));
36
  wp_enqueue_script('lp-customizer-load-js');
37
+
38
  }
39
  }
40
  /* Requeue third party scripts */
66
  <?php if (isset($_GET['lp-variation-id']) && !isset($_GET['template-customize']) && !isset($_GET['iframe_window']) && !isset($_GET['live-preview-area'])) {
67
  do_action('landing_page_header_script');
68
  ?>
69
+ <script type="text/javascript">
70
+ /* For Iframe previews to stop saving page views */
71
+ var dont_save_page_view = _inbound.Utils.getParameterVal('dont_save', window.location.href);
72
+ if (dont_save_page_view) {
73
+ //console.log('turn off page tracking');
74
+ window.inbound_settings.page_tracking = 'off';
75
+ }
76
+ </script>
77
  <?php if(!defined('Inbound_Now_Disable_URL_CLEAN')) { ?>
78
  <script type="text/javascript">
79
+ /* Then strip params if pushstate exists */
 
80
  if (typeof window.history.pushState == 'function') {
81
+ var cleanparams=window.location.href.split("?");
82
+ var clean_url=cleanparams[0];history.replaceState({},"landing page",clean_url);
83
+ }
84
+ </script>
85
  <?php } ?>
86
  <?php }
87
  }
modules/module.metaboxes.php CHANGED
@@ -10,9 +10,10 @@ define('WYSIWYG_EDITOR_ID', 'landing-page-myeditor');
10
  define('WYSIWYG_META_KEY', 'lp-conversion-area');
11
 
12
  /* ADD THUMBNAIL METABOX TO SIDEBAR */
13
- //add_action('add_meta_boxes', 'lp_display_thumbnail_metabox');
14
  function lp_display_thumbnail_metabox() {
15
-
 
16
  add_meta_box(
17
  'lp-thumbnail-sidebar-preview',
18
  __( 'Template Preview', 'landing-pages'),
@@ -20,6 +21,7 @@ function lp_display_thumbnail_metabox() {
20
  'landing-page' ,
21
  'side',
22
  'high' );
 
23
  }
24
 
25
  function lp_thumbnail_metabox() {
@@ -27,9 +29,17 @@ function lp_thumbnail_metabox() {
27
 
28
  $template = get_post_meta($post->ID, 'lp-selected-template', true);
29
  $template = apply_filters('lp_selected_template',$template);
30
- $permalink = get_permalink($post->ID);
 
 
 
 
 
 
 
 
31
  $datetime = the_modified_date('YmjH',null,null,false);
32
- $permalink = $permalink.'?dt='.$datetime;
33
 
34
  if (in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'))) {
35
 
@@ -45,22 +55,40 @@ function lp_thumbnail_metabox() {
45
  }
46
  $permalink = apply_filters('lp_live_screenshot_url', $permalink);
47
  ?>
48
- <div >
49
- <div class="inside" style='margin-left:-8px;'>
50
- <table>
51
- <tr>
52
- <td>
53
- <?php
54
-
55
- echo "<a href='$permalink' target='_blank' ><img src='$thumbnail' style='width:250px;height:250px;' title='". __( 'Preview this theme' , 'landing-pages') ." , ({$template})'></a>";
56
- ?>
57
- </td>
58
- </tr>
59
- </table>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
  </div>
62
- </div>
63
- <?php
64
  }
65
 
66
  /* ADD CONVERSION AREA METABOX */
@@ -198,8 +226,7 @@ function lp_save_header_area( $post_id )
198
  delete_post_meta( $post_id, $key );
199
  }
200
 
201
- function lp_save_notes_area( $post_id )
202
- {
203
  if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
204
  return;
205
 
@@ -217,12 +244,9 @@ function lp_save_notes_area( $post_id )
217
 
218
  add_filter( 'enter_title_here', 'lp_change_enter_title_text', 10, 2 );
219
  function lp_change_enter_title_text( $text, $post ) {
220
- if ($post->post_type=='landing-page')
221
- {
222
  return __( 'Enter Landing Page Description' , 'landing-pages');
223
- }
224
- else
225
- {
226
  return $text;
227
  }
228
  }
@@ -247,7 +271,9 @@ function lp_display_meta_box_select_template() {
247
 
248
  $template = apply_filters('lp_selected_template',$template);
249
  //echo $template;
250
- if (!isset($template)||isset($template)&&!$template){ $template = 'default';}
 
 
251
 
252
  $name = apply_filters('lp_selected_template_id','lp-selected-template');
253
 
@@ -272,8 +298,7 @@ function lp_display_meta_box_select_template_container() {
272
 
273
  if (isset($post)&&$post->post_type!='landing-page'||!isset($post)){ return false; }
274
 
275
- ( !strstr( $current_url, 'post-new.php')) ? $toggle = "display:none" : $toggle = "";
276
-
277
 
278
  $extension_data = lp_get_extension_data();
279
  $extension_data_cats = Landing_Pages_Load_Extensions::get_template_categories();
@@ -324,8 +349,7 @@ function lp_display_meta_box_select_template_container() {
324
 
325
 
326
  $cats = explode( ',' , $data['info']['category'] );
327
- foreach ($cats as $key => $cat)
328
- {
329
  $cat = trim($cat);
330
  $cat = str_replace(' ', '-', $cat);
331
  $cats[$key] = trim(strtolower($cat));
@@ -335,20 +359,24 @@ function lp_display_meta_box_select_template_container() {
335
 
336
  $thumb = false;
337
  // Get Thumbnail
338
- if (file_exists(LANDINGPAGES_PATH.'templates/'.$this_extension."/thumbnail.png"))
339
- {
340
  if ($this_extension=='default') {
341
- $thumbnail = get_bloginfo('template_directory')."/screenshot.png";
 
 
342
  } else {
343
- $thumbnail = LANDINGPAGES_URLPATH.'templates/'.$this_extension."/thumbnail.png";
 
 
344
  }
345
  $thumb = true;
346
  }
347
- if (file_exists(LANDINGPAGES_UPLOADS_PATH.$this_extension."/thumbnail.png"))
348
- {
349
  $thumbnail = LANDINGPAGES_UPLOADS_URLPATH.$this_extension."/thumbnail.png";
350
  $thumb = true;
351
  }
 
352
  if ($thumb === false) {
353
  $thumbnail = LANDINGPAGES_URLPATH.'templates/default/thumbnail.png';
354
 
10
  define('WYSIWYG_META_KEY', 'lp-conversion-area');
11
 
12
  /* ADD THUMBNAIL METABOX TO SIDEBAR */
13
+ add_action('add_meta_boxes', 'lp_display_thumbnail_metabox');
14
  function lp_display_thumbnail_metabox() {
15
+ global $post;
16
+ if($post->post_status !== 'draft') {
17
  add_meta_box(
18
  'lp-thumbnail-sidebar-preview',
19
  __( 'Template Preview', 'landing-pages'),
21
  'landing-page' ,
22
  'side',
23
  'high' );
24
+ }
25
  }
26
 
27
  function lp_thumbnail_metabox() {
29
 
30
  $template = get_post_meta($post->ID, 'lp-selected-template', true);
31
  $template = apply_filters('lp_selected_template',$template);
32
+ $var_id = (isset($_GET['lp-variation-id'])) ? $_GET['lp-variation-id'] : '0';
33
+ $original_perma = get_permalink($post->ID);
34
+
35
+ if ( preg_match( '/lp-variation-id/', $original_perma ) ) {
36
+ $iframe_preview_link = get_permalink($post->ID) . "&cache_bust=true&dont_save=true";
37
+ } else {
38
+ $iframe_preview_link = get_permalink($post->ID) . "?lp-variation-id=$var_id&cache_bust=true&dont_save=true";
39
+ }
40
+
41
  $datetime = the_modified_date('YmjH',null,null,false);
42
+ $permalink = $original_perma.'?dt='.$datetime;
43
 
44
  if (in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'))) {
45
 
55
  }
56
  $permalink = apply_filters('lp_live_screenshot_url', $permalink);
57
  ?>
58
+
59
+ <style type="text/css">
60
+ #lp-thumbnail-sidebar-preview {
61
+ background: transparent !important;
62
+ }
63
+ #lp-thumbnail-sidebar-preview .handlediv, #lp-thumbnail-sidebar-preview .hndle {
64
+ display: none !important;
65
+ }
66
+ #lp-thumbnail-sidebar-preview .inside {
67
+ padding: 0px !important;
68
+ margin: 0px;
69
+ border: none !important;
70
+ margin-top: -20px !important;
71
+ margin-bottom: -10px;
72
+ }
73
+ #lp-thumbnail-sidebar-preview #zoomer-wrapper {
74
+ vertical-align: top;
75
+ }
76
+ #lp-thumbnail-sidebar-preview iframe#zoomer {
77
+ margin-top: -30px;
78
+ }
79
+ </style>
80
+ <?php if (!isset($_GET['new-variation']) ) { ?>
81
+
82
+ <div class="inside" >
83
+
84
+ <?php
85
+ echo "<iframe src='$iframe_preview_link' id='zoomer'></iframe>";
86
+ //echo "<a href='$permalink' target='_blank' ><img src='$thumbnail' style='width:250px;height:250px;' title='". __( 'Preview this theme' , 'landing-pages') ." , ({$template})'></a>";
87
+ ?>
88
 
89
  </div>
90
+
91
+ <?php }
92
  }
93
 
94
  /* ADD CONVERSION AREA METABOX */
226
  delete_post_meta( $post_id, $key );
227
  }
228
 
229
+ function lp_save_notes_area( $post_id ) {
 
230
  if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
231
  return;
232
 
244
 
245
  add_filter( 'enter_title_here', 'lp_change_enter_title_text', 10, 2 );
246
  function lp_change_enter_title_text( $text, $post ) {
247
+ if ($post->post_type=='landing-page') {
 
248
  return __( 'Enter Landing Page Description' , 'landing-pages');
249
+ } else {
 
 
250
  return $text;
251
  }
252
  }
271
 
272
  $template = apply_filters('lp_selected_template',$template);
273
  //echo $template;
274
+ if (!isset($template)||isset($template)&&!$template){
275
+ $template = 'default';
276
+ }
277
 
278
  $name = apply_filters('lp_selected_template_id','lp-selected-template');
279
 
298
 
299
  if (isset($post)&&$post->post_type!='landing-page'||!isset($post)){ return false; }
300
 
301
+ $toggle = ( !strstr( $current_url, 'post-new.php')) ? "display:none" : "";
 
302
 
303
  $extension_data = lp_get_extension_data();
304
  $extension_data_cats = Landing_Pages_Load_Extensions::get_template_categories();
349
 
350
 
351
  $cats = explode( ',' , $data['info']['category'] );
352
+ foreach ($cats as $key => $cat) {
 
353
  $cat = trim($cat);
354
  $cat = str_replace(' ', '-', $cat);
355
  $cats[$key] = trim(strtolower($cat));
359
 
360
  $thumb = false;
361
  // Get Thumbnail
362
+ if (file_exists(LANDINGPAGES_PATH.'templates/'.$this_extension."/thumbnail.png")) {
 
363
  if ($this_extension=='default') {
364
+
365
+ $thumbnail = get_bloginfo('template_directory')."/screenshot.png";
366
+
367
  } else {
368
+
369
+ $thumbnail = LANDINGPAGES_URLPATH.'templates/'.$this_extension."/thumbnail.png";
370
+
371
  }
372
  $thumb = true;
373
  }
374
+
375
+ if (file_exists(LANDINGPAGES_UPLOADS_PATH.$this_extension."/thumbnail.png")) {
376
  $thumbnail = LANDINGPAGES_UPLOADS_URLPATH.$this_extension."/thumbnail.png";
377
  $thumb = true;
378
  }
379
+
380
  if ($thumb === false) {
381
  $thumbnail = LANDINGPAGES_URLPATH.'templates/default/thumbnail.png';
382
 
modules/module.post-type.php CHANGED
@@ -1,15 +1,5 @@
1
  <?php
2
 
3
- add_action('admin_init', 'lp_rebuild_permalinks');
4
- function lp_rebuild_permalinks() {
5
- $activation_check = get_option('lp_activate_rewrite_check',0);
6
-
7
- if ($activation_check) {
8
- global $wp_rewrite;
9
- $wp_rewrite->flush_rules();
10
- update_option( 'lp_activate_rewrite_check', '0');
11
- }
12
- }
13
 
14
  add_action('init', 'landing_page_register');
15
  function landing_page_register() {
1
  <?php
2
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  add_action('init', 'landing_page_register');
5
  function landing_page_register() {
modules/module.store.php CHANGED
@@ -1 +1 @@
1
- <?php
2
  jQuery("#lp-store-iframe-container iframe").css('height', window.outerHeight + "px");
3
  }, 2000);
 
4
  * Inbound Now Store
5
  */
6
  echo '<div id="agreement" style="margin-top:30px;">
 
1
  jQuery("#lp-store-iframe-container iframe").css('height', window.outerHeight + "px");
2
  }, 2000);
3
+ <?php
4
  * Inbound Now Store
5
  */
6
  echo '<div id="agreement" style="margin-top:30px;">
modules/module.welcome.php CHANGED
@@ -36,6 +36,7 @@ class LandingPages_Welcome {
36
  add_action( 'admin_menu', array( $this, 'admin_menus') );
37
  add_action( 'admin_head', array( $this, 'admin_head' ) );
38
  add_action( 'admin_init', array( $this, 'welcome' ) );
 
39
  }
40
 
41
  /**
@@ -587,19 +588,37 @@ class LandingPages_Welcome {
587
  </div>
588
  <?php
589
  }
590
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
591
  /**
592
- * Sends user to the Welcome page on first activation of EDD as well as each
593
- * time EDD is upgraded to a new version
594
- *
595
- * @access public
596
- * @since 1.4
597
- * @global $edd_options Array of all the EDD Options
598
- * @return void
599
  */
600
  public function welcome() {
601
 
602
-
603
  // Bail if no activation redirect
604
  if ( ! get_transient( '_landing_page_activation_redirect' ) )
605
  return;
36
  add_action( 'admin_menu', array( $this, 'admin_menus') );
37
  add_action( 'admin_head', array( $this, 'admin_head' ) );
38
  add_action( 'admin_init', array( $this, 'welcome' ) );
39
+ add_action('admin_footer', array( $this, 'force_permalink_flush' ) );
40
  }
41
 
42
  /**
588
  </div>
589
  <?php
590
  }
591
+ public function force_permalink_flush(){
592
+ // Bail if multisite
593
+ if ( is_network_admin() || isset( $_GET['activate-multi'] ) ) {
594
+ return;
595
+ }
596
+ // check if flushed
597
+ $hasFlushed = get_option( 'inbound_permalink_flush' );
598
+ if($hasFlushed) {
599
+ return;
600
+ } else {
601
+ update_option( 'inbound_permalink_flush', true);
602
+ $link = admin_url( 'options-permalink.php' );
603
+ echo "<script type='text/javascript'>
604
+ jQuery(document).ready(function($) {
605
+ var link = '$link';
606
+ setTimeout(function() {
607
+ if(window.location.href !== link) {
608
+ jQuery('#flush_permalinks').attr('src', link);
609
+ }
610
+ }, 100);
611
+ });
612
+ </script>";
613
+ echo '<iframe style="display:none;" id="flush_permalinks"></iframe>';
614
+ }
615
+ }
616
  /**
617
+ * Sends user to the Welcome page on first activation of LPs as well as each
618
+ * time LPs is upgraded to a new version
 
 
 
 
 
619
  */
620
  public function welcome() {
621
 
 
622
  // Bail if no activation redirect
623
  if ( ! get_transient( '_landing_page_activation_redirect' ) )
624
  return;
package.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "landing-pages",
3
+ "version": "1.0.0",
4
+ "description": "Landing page plugin",
5
+ "main": "gulpfile.js",
6
+ "scripts": {
7
+ "test": "test"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://davidwells@github.com/inboundnow/landing-pages.git"
12
+ },
13
+ "keywords": [
14
+ "cta",
15
+ "calls",
16
+ "to",
17
+ "action"
18
+ ],
19
+ "author": "Inbound Now",
20
+ "license": "GPL",
21
+ "bugs": {
22
+ "url": "https://github.com/inboundnow/landing-pages/issues"
23
+ },
24
+ "homepage": "https://github.com/inboundnow/landing-pages",
25
+ "dependencies": {},
26
+ "devDependencies": {
27
+ "grunt-wp-readme-to-markdown": "^0.8.0",
28
+ "gulp": "^3.8.5",
29
+ "gulp-clean": "^0.2.4",
30
+ "gulp-concat": "~2.1.7",
31
+ "gulp-header": "^1.0.2",
32
+ "gulp-jshint": "^1.6.1",
33
+ "gulp-karma": "0.0.4",
34
+ "gulp-markdox": "^0.1.0",
35
+ "gulp-plumber": "~0.6.2",
36
+ "gulp-rename": "~1.1.0",
37
+ "gulp-uglify": "~0.3.0",
38
+ "jshint-stylish": "^0.2.0",
39
+ "karma": "^0.12.16",
40
+ "karma-chrome-launcher": "^0.1.5",
41
+ "karma-jasmine": "~0.2.0",
42
+ "karma-phantomjs-launcher": "^0.1.4",
43
+ "karma-spec-reporter": "0.0.13"
44
+ }
45
+ }
phpunit.xml.dist ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <phpunit bootstrap="tests/phpunit/bootstrap.php" backupGlobals="false" colors="true">
2
+ <testsuites>
3
+ <!-- Default test suite to run all tests -->
4
+ <testsuite>
5
+ <directory prefix="test." suffix=".php">tests/phpunit/</directory>
6
+ </testsuite>
7
+ </testsuites>
8
+ </phpunit>
readme.txt CHANGED
@@ -7,7 +7,7 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html
7
  Tags: landing pages, inbound marketing, conversion pages, split testing, a b test, a b testing, a/b test, a/b testing, coming soon page, email list, landing page, list building, maintenance page, squeeze page, inbound now, landing-pages, splash pages, cpa, click tracking, goal tracking, analytics, free landing page templates
8
  Requires at least: 3.8
9
  Tested up to: 4.2
10
- Stable Tag: 1.8.8
11
 
12
  Create landing pages for your WordPress site. Monitor and improve conversion rates, run A/B split tests, customize your own templates and more.
13
 
@@ -72,6 +72,10 @@ The plugin is also fully extendable and has a number of actions, filters, and ho
72
  4. Choose from a ton of pre-made templates, use your existing design, or design your own theme!
73
 
74
  == Changelog ==
 
 
 
 
75
  = 1.8.8 =
76
  * Security Patch for XSS in firefox
77
 
7
  Tags: landing pages, inbound marketing, conversion pages, split testing, a b test, a b testing, a/b test, a/b testing, coming soon page, email list, landing page, list building, maintenance page, squeeze page, inbound now, landing-pages, splash pages, cpa, click tracking, goal tracking, analytics, free landing page templates
8
  Requires at least: 3.8
9
  Tested up to: 4.2
10
+ Stable Tag: 1.9.0
11
 
12
  Create landing pages for your WordPress site. Monitor and improve conversion rates, run A/B split tests, customize your own templates and more.
13
 
72
  4. Choose from a ton of pre-made templates, use your existing design, or design your own theme!
73
 
74
  == Changelog ==
75
+ = 1.9.0 =
76
+ * New preview views in landing page edit screens
77
+ * Temporarily disabling geolocation services
78
+
79
  = 1.8.8 =
80
  * Security Patch for XSS in firefox
81
 
shared/assets/assets.loader.class.php CHANGED
@@ -47,6 +47,7 @@ if (!class_exists('Inbound_Asset_Loader')) {
47
  } else {
48
 
49
  global $wp_scripts;
 
50
 
51
  if ( !empty( $wp_scripts->queue ) ) {
52
  $store = $wp_scripts->queue; // store the scripts
47
  } else {
48
 
49
  global $wp_scripts;
50
+ $store = false;
51
 
52
  if ( !empty( $wp_scripts->queue ) ) {
53
  $store = $wp_scripts->queue; // store the scripts
shared/assets/js/frontend/analytics-src/analytics.forms.js CHANGED
@@ -276,7 +276,7 @@ var InboundForms = (function(_inbound) {
276
  attachFormSubmitEvent: function(form) {
277
  utils.addListener(form, 'submit', this.formListener);
278
  var email_input = document.querySelector('.inbound-email');
279
- utils.addListener(email_input, 'blur', this.mailCheck);
280
  },
281
  /* Ignore CC data */
282
  ignoreFieldByLabel: function(label) {
@@ -796,7 +796,7 @@ var InboundForms = (function(_inbound) {
796
  utils.removeElement(suggest);
797
  }
798
  var el = document.createElement("span");
799
- el.innerHTML = "<span class=\"email_suggestion\">Did you mean <b><i id='email_correction' style='cursor: pointer;' title=\"click to update\">" + suggestion.full + "</b></i>?</span>";
800
  email_input.parentNode.insertBefore(el, email_input.nextSibling);
801
  var update = document.getElementById('email_correction');
802
  utils.addListener(update, 'click', function() {
276
  attachFormSubmitEvent: function(form) {
277
  utils.addListener(form, 'submit', this.formListener);
278
  var email_input = document.querySelector('.inbound-email');
279
+ /* utils.addListener(email_input, 'blur', this.mailCheck); */
280
  },
281
  /* Ignore CC data */
282
  ignoreFieldByLabel: function(label) {
796
  utils.removeElement(suggest);
797
  }
798
  var el = document.createElement("span");
799
+ el.innerHTML = "<span class=\"email_suggestion\">Did youu mean <b><i id='email_correction' style='cursor: pointer;' title=\"click to update\">" + suggestion.full + "</b></i>?</span>";
800
  email_input.parentNode.insertBefore(el, email_input.nextSibling);
801
  var update = document.getElementById('email_correction');
802
  utils.addListener(update, 'click', function() {
shared/assets/js/frontend/analytics-src/analytics.page.js CHANGED
@@ -337,6 +337,7 @@ var _inboundPageTracking = (function(_inbound) {
337
  return;
338
  }
339
 
 
340
  var leadID = ( _inbound.Utils.readCookie('wp_lead_id') ) ? _inbound.Utils.readCookie('wp_lead_id') : '';
341
  var lead_uid = ( _inbound.Utils.readCookie('wp_lead_uid') ) ? _inbound.Utils.readCookie('wp_lead_uid') : '';
342
 
337
  return;
338
  }
339
 
340
+
341
  var leadID = ( _inbound.Utils.readCookie('wp_lead_id') ) ? _inbound.Utils.readCookie('wp_lead_id') : '';
342
  var lead_uid = ( _inbound.Utils.readCookie('wp_lead_uid') ) ? _inbound.Utils.readCookie('wp_lead_uid') : '';
343
 
shared/assets/js/frontend/analytics/inboundAnalytics.js CHANGED
@@ -1575,7 +1575,7 @@ var InboundForms = (function(_inbound) {
1575
  attachFormSubmitEvent: function(form) {
1576
  utils.addListener(form, 'submit', this.formListener);
1577
  var email_input = document.querySelector('.inbound-email');
1578
- utils.addListener(email_input, 'blur', this.mailCheck);
1579
  },
1580
  /* Ignore CC data */
1581
  ignoreFieldByLabel: function(label) {
@@ -2095,7 +2095,7 @@ var InboundForms = (function(_inbound) {
2095
  utils.removeElement(suggest);
2096
  }
2097
  var el = document.createElement("span");
2098
- el.innerHTML = "<span class=\"email_suggestion\">Did you mean <b><i id='email_correction' style='cursor: pointer;' title=\"click to update\">" + suggestion.full + "</b></i>?</span>";
2099
  email_input.parentNode.insertBefore(el, email_input.nextSibling);
2100
  var update = document.getElementById('email_correction');
2101
  utils.addListener(update, 'click', function() {
@@ -3396,6 +3396,7 @@ var _inboundPageTracking = (function(_inbound) {
3396
  return;
3397
  }
3398
 
 
3399
  var leadID = ( _inbound.Utils.readCookie('wp_lead_id') ) ? _inbound.Utils.readCookie('wp_lead_id') : '';
3400
  var lead_uid = ( _inbound.Utils.readCookie('wp_lead_uid') ) ? _inbound.Utils.readCookie('wp_lead_uid') : '';
3401
 
1575
  attachFormSubmitEvent: function(form) {
1576
  utils.addListener(form, 'submit', this.formListener);
1577
  var email_input = document.querySelector('.inbound-email');
1578
+ /* utils.addListener(email_input, 'blur', this.mailCheck); */
1579
  },
1580
  /* Ignore CC data */
1581
  ignoreFieldByLabel: function(label) {
2095
  utils.removeElement(suggest);
2096
  }
2097
  var el = document.createElement("span");
2098
+ el.innerHTML = "<span class=\"email_suggestion\">Did youu mean <b><i id='email_correction' style='cursor: pointer;' title=\"click to update\">" + suggestion.full + "</b></i>?</span>";
2099
  email_input.parentNode.insertBefore(el, email_input.nextSibling);
2100
  var update = document.getElementById('email_correction');
2101
  utils.addListener(update, 'click', function() {
3396
  return;
3397
  }
3398
 
3399
+
3400
  var leadID = ( _inbound.Utils.readCookie('wp_lead_id') ) ? _inbound.Utils.readCookie('wp_lead_id') : '';
3401
  var lead_uid = ( _inbound.Utils.readCookie('wp_lead_uid') ) ? _inbound.Utils.readCookie('wp_lead_uid') : '';
3402
 
shared/assets/js/frontend/analytics/inboundAnalytics.min.js CHANGED
@@ -1,3 +1,3 @@
1
  /*! Inbound Analyticsv1.0.0 | (c) 2015 Inbound Now | https://github.com/inboundnow/cta */
2
- var inbound_data=inbound_data||{},_inboundOptions=_inboundOptions||{},_gaq=_gaq||[],_inbound=function(e){var t={timeout:inbound_settings.is_admin?500:1e4,formAutoTracking:!0,formAutoPopulation:!0},n={init:function(){_inbound.Utils.init(),_inbound.Utils.domReady(window,function(){_inbound.DomLoaded()})},DomLoaded:function(){_inbound.PageTracking.init(),_inbound.Forms.init(),_inbound.Utils.setUrlParams(),_inbound.LeadsAPI.init(),setTimeout(function(){_inbound.Forms.init()},2e3),_inbound.trigger("analytics_ready")},extend:function(e,t){var n,i={};for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(i[n]=e[n]);for(n in t)Object.prototype.hasOwnProperty.call(t,n)&&(i[n]=t[n]);return i},debug:function(){},deBugger:function(e,t,n){if(console){var i,o,a,r=document.location.hash?document.location.hash:"",s=r.indexOf("#debug")>-1,t=t||!1;r&&r.match(/debug/)&&(r=r.split("-"),a=r[1]),o="true"===_inbound.Utils.readCookie("inbound_debug")?!0:!1,i="true"===_inbound.Utils.readCookie("inbound_debug_"+e)?!0:!1,(i||s||o)&&(t&&"string"==typeof t&&(o||"all"===a?console.log('logAll "'+e+'" =>',t):i?console.log('log "'+e+'" =>',t):e===a&&console.log('#log "'+e+'" =>',t)),n&&n instanceof Function&&n())}}},i=n.extend(t,e);return n.Settings=i||{},n}(_inboundOptions),_inboundHooks=function(e){var t=function(){function e(e,t,n,i){return"string"==typeof e&&"function"==typeof t&&(n=parseInt(n||10,10),s("actions",e,t,n,i)),d}function t(){var e=Array.prototype.slice.call(arguments),t=e.shift();return"string"==typeof t&&u("actions",t,e),d}function n(e,t){return"string"==typeof e&&r("actions",e,t),d}function i(e,t,n){return"string"==typeof e&&"function"==typeof t&&(n=parseInt(n||10,10),s("filters",e,t,n)),d}function o(){var e=Array.prototype.slice.call(arguments),t=e.shift();return"string"==typeof t?u("filters",t,e):d}function a(e,t){return"string"==typeof e&&r("filters",e,t),d}function r(e,t,n,i){if(c[e][t])if(n){var o,a=c[e][t];if(i)for(o=a.length;o--;){var r=a[o];r.callback===n&&r.context===i&&a.splice(o,1)}else for(o=a.length;o--;)a[o].callback===n&&a.splice(o,1)}else c[e][t]=[]}function s(e,t,n,i,o){var a={callback:n,priority:i,context:o},r=c[e][t];r?(r.push(a),r=l(r)):r=[a],c[e][t]=r}function l(e){for(var t,n,i,o=1,a=e.length;a>o;o++){for(t=e[o],n=o;(i=e[n-1])&&i.priority>t.priority;)e[n]=e[n-1],--n;e[n]=t}return e}function u(e,t,n){var i=c[e][t];if(!i)return"filters"===e?n[0]:!1;var o=0,a=i.length;if("filters"===e)for(;a>o;o++)n[0]=i[o].callback.apply(i[o].context,n);else for(;a>o;o++)i[o].callback.apply(i[o].context,n);return"filters"===e?n[0]:!0}var d={removeFilter:a,applyFilters:o,addFilter:i,removeAction:n,doAction:t,addAction:e},c={actions:{},filters:{}};return d};return e.hooks=new t,e.add_action=function(){var t=arguments[0].split(" ");for(k in t)arguments[0]="inbound."+t[k],e.hooks.addAction.apply(this,arguments);return this},e.remove_action=function(){return arguments[0]="inbound."+arguments[0],e.hooks.removeAction.apply(this,arguments),this},e.do_action=function(){return arguments[0]="inbound."+arguments[0],e.hooks.doAction.apply(this,arguments),this},e.add_filter=function(){return arguments[0]="inbound."+arguments[0],e.hooks.addFilter.apply(this,arguments),this},e.remove_filter=function(){return arguments[0]="inbound."+arguments[0],e.hooks.removeFilter.apply(this,arguments),this},e.apply_filters=function(){return arguments[0]="inbound."+arguments[0],e.hooks.applyFilters.apply(this,arguments)},e}(_inbound||{}),_inboundUtils=function(e){var t,n=window.XMLHttpRequest&&"withCredentials"in new XMLHttpRequest,i=(Object.prototype.toString,("https:"==location.protocol?"https://":"http://")+location.hostname+location.pathname.replace(/\/$/,"")),o={api_host:i,track_pageview:!0,track_links_timeout:300,cookie_name:"_sp",cookie_expiration:365,cookie_domain:(host=location.hostname.match(/[a-z0-9][a-z0-9\-]+\.[a-z\.]{2,6}$/i))?host[0]:""};return e.Utils={init:function(){this.polyFills(),this.checkLocalStorage(),this.SetUID(),this.storeReferralData()},polyFills:function(){window.console||(window.console={});for(var e=["log","info","warn","error","debug","trace","dir","group","groupCollapsed","groupEnd","time","timeEnd","profile","profileEnd","dirxml","assert","count","markTimeline","timeStamp","clear"],t=0;t<e.length;t++)window.console[e[t]]||(window.console[e[t]]=function(){});Date.prototype.toISOString||!function(){function e(e){var t=String(e);return 1===t.length&&(t="0"+t),t}Date.prototype.toISOString=function(){return this.getUTCFullYear()+"-"+e(this.getUTCMonth()+1)+"-"+e(this.getUTCDate())+"T"+e(this.getUTCHours())+":"+e(this.getUTCMinutes())+":"+e(this.getUTCSeconds())+"."+String((this.getUTCMilliseconds()/1e3).toFixed(3)).slice(2,5)+"Z"}}();try{new CustomEvent("?")}catch(n){this.CustomEvent=function(e,t){function n(n,o){var a=document.createEvent(e);return null!==n?i.call(a,n,(o||(o=t)).bubbles,o.cancelable,o.detail):a.initCustomEvent=i,a}function i(t,n,i,o){this["init"+e](t,n,i,o),"detail"in this||(this.detail=o)}return n}(this.CustomEvent?"CustomEvent":"Event",{bubbles:!1,cancelable:!1,detail:null})}document.querySelectorAll||(document.querySelectorAll=function(e){var t,n=document.createElement("style"),i=[];for(document.documentElement.firstChild.appendChild(n),document._qsa=[],n.styleSheet.cssText=e+"{x-qsa:expression(document._qsa && document._qsa.push(this))}",window.scrollBy(0,0),n.parentNode.removeChild(n);document._qsa.length;)t=document._qsa.shift(),t.style.removeAttribute("x-qsa"),i.push(t);return document._qsa=null,i}),document.querySelector||(document.querySelector=function(e){var t=document.querySelectorAll(e);return t.length?t[0]:null}),!("innerText"in document.createElement("a"))&&"getSelection"in window&&HTMLElement.prototype.__defineGetter__("innerText",function(){for(var e,t=window.getSelection(),n=[],i=0;i<t.rangeCount;i++)n[i]=t.getRangeAt(i);t.removeAllRanges(),t.selectAllChildren(this),e=t.toString(),t.removeAllRanges();for(var i=0;i<n.length;i++)t.addRange(n[i]);return e})},createCookie:function(e,t,n){var i="";if(n){var o=new Date;o.setTime(o.getTime()+24*n*60*60*1e3),i="; expires="+o.toGMTString()}document.cookie=e+"="+t+i+"; path=/"},readCookie:function(e){for(var t=e+"=",n=document.cookie.split(";"),i=0;i<n.length;i++){for(var o=n[i];" "===o.charAt(0);)o=o.substring(1,o.length);if(0===o.indexOf(t))return o.substring(t.length,o.length)}return null},eraseCookie:function(e){this.createCookie(e,"",-1)},getAllCookies:function(){var t={};if(document.cookie&&""!==document.cookie)for(var n=document.cookie.split(";"),i=0;i<n.length;i++){var o=n[i].split("=");o[0]=o[0].replace(/^ /,""),t[decodeURIComponent(o[0])]=decodeURIComponent(o[1])}return e.totalStorage("inbound_cookies",t),t},setUrlParams:function(){var n={};!function(){for(var e,t=function(e){return decodeURIComponent(e).replace(/\+/g," ")},i=window.location.search.substring(1),o=/([^&=]+)=?([^&]*)/g;e=o.exec(i);)if("-1"==e[1].indexOf("["))n[t(e[1])]=t(e[2]);else{var a=e[1].indexOf("["),r=e[1].slice(a+1,e[1].indexOf("]",a)),s=t(e[1].slice(0,a));"object"!=typeof n[s]&&(n[t(s)]={},n[t(s)].length=0),r?n[t(s)][t(r)]=t(e[2]):Array.prototype.push.call(n[t(s)],t(e[2]))}}();for(var i in n)if("object"==typeof n[i])for(var o in n[i])this.createCookie(o,n[i][o],30);else this.createCookie(i,n[i],30);if(t){var a=e.totalStorage("inbound_url_params")||{},r=this.mergeObjs(a,n);e.totalStorage("inbound_url_params",r)}var s={option1:"yo",option2:"woooo"};e.trigger("url_parameters",n,s)},getAllUrlParams:function(){var n={};if(t)var n=e.totalStorage("inbound_url_params");return n},getParameterVal:function(e,t){return(RegExp(e+"=(.+?)(&|$)").exec(t)||[,!1])[1]},checkLocalStorage:function(){if("localStorage"in window)try{ls="undefined"==typeof window.localStorage?void 0:window.localStorage,t="undefined"==typeof ls||"undefined"==typeof window.JSON?!1:!0}catch(e){t=!1}return t},showLocalStorageSize:function(){function e(e){return 2*e.length}function t(e){return e/1024/1024}function n(t){return{name:t,size:e(localStorage[t])}}function i(e){return e.size=t(e.size).toFixed(2)+" MB",e}var o=Object.keys(localStorage).map(n).map(i);console.table(o)},addDays:function(e,t){return new Date(e.getTime()+24*t*60*60*1e3)},GetDate:function(){var e=new Date,t=e.getDate(),n=10>t?"0":"",i=e.getFullYear(),o=e.getHours(),a=10>o?"0":"",r=e.getMinutes(),s=10>r?"0":"",l=e.getSeconds(),u=10>l?"0":"",d=e.getMonth()+1,c=10>d?"0":"",m=i+"/"+c+d+"/"+n+t+" "+a+o+":"+s+r+":"+u+l;return m},SetSessionTimeout:function(){var e=(this.readCookie("lead_session_expire"),new Date);e.setTime(e.getTime()+18e5),this.createCookie("lead_session_expire",!0,e)},storeReferralData:function(){var t=new Date,n=document.referrer||"Direct Traffic",i=e.Utils.readCookie("inbound_referral_site"),o=e.totalStorage("inbound_original_referral");t.setTime(t.getTime()+18e5),i||this.createCookie("inbound_referral_site",n,t),o||e.totalStorage("inbound_original_referral",o)},CreateUID:function(e){var t="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".split(""),n="";e||(e=Math.floor(Math.random()*t.length));for(var i=0;e>i;i++)n+=t[Math.floor(Math.random()*t.length)];return n},generateGUID:function(e){return e?(e^16*Math.random()>>e/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,guid)},SetUID:function(e){if(!this.readCookie("wp_lead_uid")){var t=e||this.CreateUID(35);this.createCookie("wp_lead_uid",t)}},countProperties:function(e){var t=0;for(var n in e)e.hasOwnProperty(n)&&++t;return t},mergeObjs:function(e,t){var n={};for(var i in e)n[i]=e[i];for(var i in t)n[i]=t[i];return n},hasClass:function(e,t){var n;if("classList"in document.documentElement)var n=t.classList.contains(e);else var n=new RegExp("(^|\\s)"+e+"(\\s|$)").test(t.className);return n},addClass:function(e,t){"classList"in document.documentElement?t.classList.add(e):this.hasClass(t,e)||(t.className+=(t.className?" ":"")+e)},removeClass:function(e,t){"classList"in document.documentElement?t.classList.remove(e):this.hasClass(t,e)&&(t.className=t.className.replace(new RegExp("(^|\\s)*"+e+"(\\s|$)*","g"),""))},removeElement:function(e){e.parentNode.removeChild(e)},trim:function(e){return e=e.replace(/(^\s*)|(\s*$)/gi,""),e=e.replace(/[ ]{2,}/gi," "),e=e.replace(/\n /,"\n")},ajaxPolyFill:function(){if("undefined"!=typeof XMLHttpRequest)return new XMLHttpRequest;for(var e,t=["MSXML2.XmlHttp.5.0","MSXML2.XmlHttp.4.0","MSXML2.XmlHttp.3.0","MSXML2.XmlHttp.2.0","Microsoft.XmlHttp"],n=0;n<t.length;n++)try{e=new ActiveXObject(t[n]);break}catch(i){}return e},ajaxSendData:function(e,t,n,i){var o=this.ajaxPolyFill();setTimeout(function(){o.open(n,e,!0),o.onreadystatechange=function(){4==o.readyState&&t(o.responseText)},"POST"==n&&o.setRequestHeader("Content-type","application/x-www-form-urlencoded"),o.send(i)},100)},ajaxGet:function(e,t,n,i){var o=[];for(var a in t)o.push(encodeURIComponent(a)+"="+encodeURIComponent(t[a]));this.ajaxSendData(e+"?"+o.join("&"),n,"GET",null,i)},ajaxPost:function(e,t,n,i){var o=[];for(var a in t)o.push(encodeURIComponent(a)+"="+encodeURIComponent(t[a]));this.ajaxSendData(e,n,"POST",o.join("&"),i)},sendEvent:function(e,t,i){t=t||{},async=!0;var a=getCookie();if(a){var r;for(r in a)t[r]=a[r]}t.id||(t.id=getId());var s={e:e,t:(new Date).toISOString(),kv:t},l=o.api_host+"/track?data="+encodeURIComponent(JSON.stringify(s));if(n){var u=new XMLHttpRequest;u.open("GET",l,async),u.withCredentials=async,u.send(null)}else{var d=document.createElement("script");d.type="text/javascript",d.async=async,d.defer=async,d.src=l;var c=document.getElementsByTagName("script")[0];c.parentNode.insertBefore(d,c)}return action(i),self},domReady:function(e,t){var n=!1,i=!0,o=e.document,a=o.documentElement,r=o.addEventListener?"addEventListener":"attachEvent",s=o.addEventListener?"removeEventListener":"detachEvent",l=o.addEventListener?"":"on",u=function(i){("readystatechange"!=i.type||"complete"==o.readyState)&&(("load"==i.type?e:o)[s](l+i.type,u,!1),!n&&(n=!0)&&t.call(e,i.type||i))},d=function(){try{a.doScroll("left")}catch(e){return setTimeout(d,50),void 0}u("poll")};if("complete"==o.readyState)t.call(e,"lazy");else{if(o.createEventObject&&a.doScroll){try{i=!e.frameElement}catch(c){}i&&d()}o[r](l+"DOMContentLoaded",u,!1),o[r](l+"readystatechange",u,!1),e[r](l+"load",u,!1)}},addListener:function(e,t,n){e&&(e.addEventListener?e.addEventListener(t,n,!1):e.attachEvent?e.attachEvent("on"+t,n):e["on"+t]=n)},removeListener:function(e,t,n){e.removeEventListener?e.removeEventListener(t,n,!1):e.detachEvent?e.detachEvent("on"+t,n):e["on"+t]=null},throttle:function(e,t){var n,i,o,a=null,r=0,s=function(){r=new Date,a=null,o=e.apply(n,i)};return function(){var l=new Date;r||(r=l);var u=t-(l-r);return n=this,i=arguments,0>=u?(clearTimeout(a),a=null,r=l,o=e.apply(n,i)):a||(a=setTimeout(s,u)),o}},checkTypeofGA:function(){"function"==typeof ga&&(universalGA=!0),"undefined"!=typeof _gaq&&"function"==typeof _gaq.push&&(classicGA=!0),"undefined"!=typeof dataLayer&&"function"==typeof dataLayer.push&&(googleTagManager=!0)}},e}(_inbound||{}),InboundForms=function(e){var t=!1,n=e.Utils,o=[],a=[],r=[],s=e.Settings,l=["first name","last name","name","email","e-mail","phone","website","job title","your favorite food","company","tele","address","comment"];if(e.Forms={init:function(){e.Forms.runFieldMappingFilters(),e.Forms.assignTrackClass(),e.Forms.formTrackInit()},runFieldMappingFilters:function(){l=e.hooks.applyFilters("forms.field_map",l)},debug:function(e,n){if(t&&console){var e=e||!1;e&&"string"==typeof e&&console.log(e),n&&n instanceof Function&&n()}},formTrackInit:function(){for(var e=0;e<window.document.forms.length;e++){var t=!1,n=window.document.forms[e];n.dataset.formProcessed||(n.dataset.formProcessed=!0,t=this.checkTrackStatus(n),t&&(this.attachFormSubmitEvent(n),this.initFormMapping(n)))}},checkTrackStatus:function(t){var n=t.getAttribute("class");return""!==n&&null!==n?n.toLowerCase().indexOf("wpl-track-me")>-1?!0:n.toLowerCase().indexOf("inbound-track")>-1?!0:(cb=function(){console.log(t)},e.deBugger("forms","This form not tracked. Please assign on in settings...",cb),!1):void 0},assignTrackClass:function(){if(window.inbound_settings){if(inbound_settings.inbound_track_include){var t=inbound_settings.inbound_track_include.split(","),n="add selectors "+inbound_settings.inbound_track_include;e.deBugger("forms",n),this.loopClassSelectors(t,"add")}if(inbound_settings.inbound_track_exclude){var t=inbound_settings.inbound_track_exclude.split(","),n="remove selectors "+inbound_settings.inbound_track_exclude;e.deBugger("forms",n),this.loopClassSelectors(t,"remove")}}},loopClassSelectors:function(t,i){for(var o=t.length-1;o>=0;o--){var a=n.trim(t[o]);-1===a.indexOf("#")&&-1===a.indexOf(".")&&(a="#"+a),a=document.querySelector(a),a&&("add"===i?(e.Utils.addClass("wpl-track-me",a),e.Utils.addClass("inbound-track",a)):(e.Utils.removeClass("wpl-track-me",a),e.Utils.removeClass("inbound-track",a)))}},initFormMapping:function(t){for(var n=[],i=0;i<t.elements.length;i++)formInput=t.elements[i],"hidden"!==formInput.type?(this.mapField(formInput),this.rememberInputValues(formInput),s.formAutoPopulation&&!e.Utils.hasClass("nopopulate",t)&&this.fillInputValues(formInput)):n.push(formInput);for(var o=n.length-1;o>=0;o--)formInput=n[o],this.mapField(formInput)},mapField:function(t){var a=t.id||!1,r=t.name||!1,s=this.getInputLabel(t);if(s){var u=this.ignoreFieldByLabel(s[0].innerText);if(u)return t.dataset.ignoreFormField=!0,!1}for(i=0;i<l.length;i++){var d=!1,c=l[i],m=n.trim(c),f=m.replace(/ /g,"_");r&&r.toLowerCase().indexOf(m)>-1?(d=!0,e.deBugger("forms","Found matching name attribute for -> "+m)):a&&a.toLowerCase().indexOf(m)>-1?(d=!0,e.deBugger("forms","Found matching ID attribute for ->"+m)):s?s[0].innerText.toLowerCase().indexOf(m)>-1&&(d=!0,e.deBugger("forms","Found matching sibling label for -> "+m)):o.push(m),d&&(this.addDataAttr(t,f),this.removeArrayItem(l,m),i--)}return inbound_data},formListener:function(t){t.preventDefault(),e.Forms.saveFormData(t.target),document.body.style.cursor="wait"},attachFormSubmitEvent:function(e){n.addListener(e,"submit",this.formListener);var t=document.querySelector(".inbound-email");n.addListener(t,"blur",this.mailCheck)},ignoreFieldByLabel:function(t){var n=!1;return t?((-1!=t.toLowerCase().indexOf("credit card")||-1!=t.toLowerCase().indexOf("card number"))&&(n=!0),(-1!=t.toLowerCase().indexOf("expiration")||-1!=t.toLowerCase().indexOf("expiry"))&&(n=!0),("month"==t.toLowerCase()||"mm"==t.toLowerCase()||"yy"==t.toLowerCase()||"yyyy"==t.toLowerCase()||"year"==t.toLowerCase())&&(n=!0),(-1!=t.toLowerCase().indexOf("cvv")||-1!=t.toLowerCase().indexOf("cvc")||-1!=t.toLowerCase().indexOf("secure code")||-1!=t.toLowerCase().indexOf("security code"))&&(n=!0),n&&e.deBugger("forms","ignore "+t),n):!1},ignoreFieldByValue:function(e){var t=!1;if(!e)return!1;("visa"==e.toLowerCase()||"mastercard"==e.toLowerCase()||"american express"==e.toLowerCase()||"amex"==e.toLowerCase()||"discover"==e.toLowerCase())&&(t=!0);var n=new RegExp("/^[0-9]+$/");if(n.test(e)){var i=e.replace(" ","");this.isInt(i)&&i.length>=16&&(t=!0)}return t},isInt:function(e){return"number"==typeof e&&isFinite(e)&&e%1===0},releaseFormSubmit:function(e){document.body.style.cursor="default",n.removeClass("wpl-track-me",e),n.removeListener(e,"submit",this.formListener);var t=e.getAttribute("class");return""!==t&&null!==t&&-1!=t.toLowerCase().indexOf("wpcf7-form")?(setTimeout(function(){document.body.style.cursor="default"},300),!0):(e.submit(),setTimeout(function(){for(var t=0;t<e.elements.length;t++)formInput=e.elements[t],type=formInput.type||!1,"submit"===type&&"submit"===formInput.name&&e.elements[t].click()},2e3),void 0)},saveFormData:function(t){for(var i=i||{},o=0;o<t.elements.length;o++)if(formInput=t.elements[o],multiple=!1,formInput.name){if(formInput.dataset.ignoreFormField){e.deBugger("forms","ignore "+formInput.name);continue}switch(inputName=formInput.name.replace(/\[([^\[]*)\]/g,"%5B%5D$1"),i[inputName]||(i[inputName]={}),formInput.type&&(i[inputName].type=formInput.type),i[inputName].name||(i[inputName].name=formInput.name),formInput.dataset.mapFormField&&(i[inputName].map=formInput.dataset.mapFormField),formInput.nodeName){case"INPUT":if(l=this.getInputValue(formInput),l===!1)continue;break;case"TEXTAREA":l=formInput.value;break;case"SELECT":if(formInput.multiple){values=[],multiple=!0;for(var s=0;s<formInput.length;s++)formInput[s].selected&&values.push(encodeURIComponent(formInput[s].value))}else l=formInput.value}if(e.deBugger("forms","Input Value = "+l),l){i[inputName].value||(i[inputName].value=[]),i[inputName].value.push(multiple?values.join(","):encodeURIComponent(l));var l=multiple?values.join(","):encodeURIComponent(l)}}e.deBugger("forms",i);for(var u in i){var d=i[u].value,c=i[u].map;if("undefined"!=typeof d&&null!=d&&""!=d&&a.push(u+"="+i[u].value.join(",")),"undefined"!=typeof c&&null!=c&&i[u].value&&(r.push(c+"="+i[u].value.join(",")),"email"===u))var m=i[u].value.join(",")}var f=a.join("&");e.deBugger("forms","Stringified Raw Form PARAMS: "+f);var g=r.join("&");e.deBugger("forms","Stringified Mapped PARAMS"+g);var m=n.getParameterVal("email",g)||n.readCookie("wp_lead_email");m||(m=n.getParameterVal("wpleads_email_address",g));var p=n.getParameterVal("name",g),v=n.getParameterVal("first_name",g),h=n.getParameterVal("last_name",g);if(!h&&v){var _=decodeURI(v).split(" ");_.length>0&&(v=_[0],h=_[1])}if(p&&!h&&!v){var _=decodeURI(p).split(" ");_.length>0&&(v=_[0],h=_[1])}p=v&&h?v+" "+h:p,v||(v=""),h||(h=""),e.deBugger("forms","fName = "+v),e.deBugger("forms","lName = "+h),e.deBugger("forms","fullName = "+p);var b=e.totalStorage("page_views")||{},y=e.totalStorage("inbound_url_params")||{};if("undefined"!=typeof landing_path_info)var w=landing_path_info.variation;else if("undefined"!=typeof cta_path_info)var w=cta_path_info.variation;else var w=inbound_settings.variation_id;var k=inbound_settings.post_type||"page",C=inbound_settings.post_id||0;search_data={},formData={action:"inbound_lead_store",email:m,full_name:p,first_name:v,last_name:h,raw_params:f,mapped_params:g,url_params:JSON.stringify(y),search_data:"test",page_views:JSON.stringify(b),post_type:k,page_id:C,variation:w,source:n.readCookie("inbound_referral_site")},callback=function(i){e.deBugger("forms","Lead Created with ID: "+i),i=parseInt(i,10),formData.leadID=i,i&&(n.createCookie("wp_lead_id",i),e.totalStorage.deleteItem("page_views"),e.totalStorage.deleteItem("tracking_events")),e.trigger("form_after_submission",formData),e.Forms.releaseFormSubmit(t)},e.trigger("form_before_submission",formData),n.ajaxPost(inbound_settings.admin_url,formData,callback)},rememberInputValues:function(t){var i=(t.name?"inbound_"+t.name:"",t.type?t.type:"text");return"submit"===i||"hidden"===i||"file"===i||"password"===i||t.dataset.ignoreFormField?!1:(n.addListener(t,"change",function(t){if(t.target.name){if("checkbox"!==i)var o=t.target.value;else for(var a=[],r=document.querySelectorAll('input[name="'+t.target.name+'"]'),s=0;s<r.length;s++){var l=r[s].checked;l&&a.push(r[s].value),o=a.join(",")}inputData={name:t.target.name,node:t.target.nodeName.toLowerCase(),type:i,value:o,mapping:t.target.dataset.mapFormField},e.trigger("form_input_change",inputData),n.createCookie("inbound_"+t.target.name,encodeURIComponent(o))}}),void 0)},fillInputValues:function(e){var t=e.name?"inbound_"+e.name:"",i=e.type?e.type:"text";if("submit"===i||"hidden"===i||"file"===i||"password"===i)return!1;if(n.readCookie(t)&&"comment"!=t)if(value=decodeURIComponent(n.readCookie(t)),"checkbox"===i||"radio"===i)for(var o=value.split(","),a=0;a<o.length;a++)e.value.indexOf(o[a])>-1&&(e.checked=!0);else"undefined"!==value&&(e.value=value)},getInputLabel:function(e){var t;return(t=this.siblingsIsLabel(e))?t:(t=this.CheckParentForLabel(e))?t:!1},getInputValue:function(e){var t=!1;switch(e.type){case"radio":case"checkbox":e.checked&&(t=e.value);break;case"text":case"hidden":default:t=e.value}return t},addDataAttr:function(e,t){for(var n=document.getElementsByName(e.name),i=n.length-1;i>=0;i--)e.dataset.mapFormField||(n[i].dataset.mapFormField=t)},removeArrayItem:function(e,t){if(e.indexOf)index=e.indexOf(t);else for(index=e.length-1;index>=0&&e[index]!==t;--index);index>=0&&e.splice(index,1)},siblingsIsLabel:function(e){for(var t=this.getSiblings(e),n=[],i=t.length-1;i>=0;i--)"label"===t[i].nodeName.toLowerCase()&&n.push(t[i]);return n.length>0&&n.length<2?n:!1},getChildren:function(e,t){for(var n=[];e;e=e.nextSibling)1==e.nodeType&&e!=t&&n.push(e);return n},getSiblings:function(e){return this.getChildren(e.parentNode.firstChild,e)},CheckParentForLabel:function(e){if("FORM"===e.nodeName)return null;do{var t=e.getElementsByTagName("label");if(t.length>0&&t.length<2)return e.getElementsByTagName("label")}while(e=e.parentNode);return null},mailCheck:function(){var e=document.querySelector(".inbound-email");e&&(n.addListener(e,"blur",this.mailCheck),u.run({email:document.querySelector(".inbound-email").value,suggested:function(t){var i=document.querySelector(".email_suggestion");i&&n.removeElement(i);var o=document.createElement("span");o.innerHTML="<span class=\"email_suggestion\">Did you mean <b><i id='email_correction' style='cursor: pointer;' title=\"click to update\">"+t.full+"</b></i>?</span>",e.parentNode.insertBefore(o,e.nextSibling);var a=document.getElementById("email_correction");n.addListener(a,"click",function(){e.value=a.innerHTML,a.parentNode.parentNode.innerHTML="Fixed!"})},empty:function(){}}))}},"undefined"==typeof u)var u={domainThreshold:1,topLevelThreshold:3,defaultDomains:["yahoo.com","google.com","hotmail.com","gmail.com","me.com","aol.com","mac.com","live.com","comcast.net","googlemail.com","msn.com","hotmail.co.uk","yahoo.co.uk","facebook.com","verizon.net","sbcglobal.net","att.net","gmx.com","mail.com","outlook.com","icloud.com"],defaultTopLevelDomains:["co.jp","co.uk","com","net","org","info","edu","gov","mil","ca","de"],run:function(e){e.domains=e.domains||u.defaultDomains,e.topLevelDomains=e.topLevelDomains||u.defaultTopLevelDomains,e.distanceFunction=e.distanceFunction||u.sift3Distance;var t=function(e){return e},n=e.suggested||t,i=e.empty||t,o=u.suggest(u.encodeEmail(e.email),e.domains,e.topLevelDomains,e.distanceFunction);return o?n(o):i()},suggest:function(e,t,n,i){e=e.toLowerCase();var o=this.splitEmail(e),a=this.findClosestDomain(o.domain,t,i,this.domainThreshold);if(a){if(a!=o.domain)return{address:o.address,domain:a,full:o.address+"@"+a}}else{var r=this.findClosestDomain(o.topLevelDomain,n,i,this.topLevelThreshold);if(o.domain&&r&&r!=o.topLevelDomain){var s=o.domain;return a=s.substring(0,s.lastIndexOf(o.topLevelDomain))+r,{address:o.address,domain:a,full:o.address+"@"+a}}}return!1},findClosestDomain:function(e,t,n,i){i=i||this.topLevelThreshold;var o,a=99,r=null;if(!e||!t)return!1;n||(n=this.sift3Distance);for(var s=0;s<t.length;s++){if(e===t[s])return e;o=n(e,t[s]),a>o&&(a=o,r=t[s])}return i>=a&&null!==r?r:!1},sift3Distance:function(e,t){if(null===e||0===e.length)return null===t||0===t.length?0:t.length;if(null===t||0===t.length)return e.length;for(var n=0,i=0,o=0,a=0,r=5;n+i<e.length&&n+o<t.length;){if(e.charAt(n+i)==t.charAt(n+o))a++;else{i=0,o=0;for(var s=0;r>s;s++){if(n+s<e.length&&e.charAt(n+s)==t.charAt(n)){i=s;break}if(n+s<t.length&&e.charAt(n)==t.charAt(n+s)){o=s;break}}}n++}return(e.length+t.length)/2-a},splitEmail:function(e){var t=e.trim().split("@");if(t.length<2)return!1;for(var n=0;n<t.length;n++)if(""===t[n])return!1;var i=t.pop(),o=i.split("."),a="";if(0===o.length)return!1;if(1==o.length)a=o[0];else{for(var n=1;n<o.length;n++)a+=o[n]+".";o.length>=2&&(a=a.substring(0,a.length-1))}return{topLevelDomain:a,domain:i,address:t.join("@")}},encodeEmail:function(e){var t=encodeURI(e);return t=t.replace("%20"," ").replace("%25","%").replace("%5E","^").replace("%60","`").replace("%7B","{").replace("%7C","|").replace("%7D","}")}};return e}(_inbound||{}),_inboundEvents=function(e){function t(t,i,o){var i=i||{};o=o||{},o.bubbles=o.bubbles||!0,o.cancelable=o.cancelable||!0,i=e.apply_filters("filter_"+t,i);!window.ActiveXObject&&"ActiveXObject"in window;if("function"==typeof CustomEvent)var a=new CustomEvent(t,{detail:i,bubbles:o.bubbles,cancelable:o.cancelable});else{var a=document.createEvent("Event");a.initEvent(t,!0,!0)}window.dispatchEvent(a),e.do_action(t,i),n(t,i)}function n(e,t){if(window.jQuery){var t=t||{};jQuery(document).trigger(e,t)}}e.trigger=function(t,n){e.Events[t](n)};return e.Events={analytics_ready:function(){var e={opt1:!0},n={data:"xyxy"};t("analytics_ready",n,e)},url_parameters:function(e){t("url_parameters",e)},session_start:function(){console.log(""),t("session_start")},session_end:function(e){t("session_end",e),console.log("Session End")},session_active:function(){t("session_active")},session_idle:function(e){t("session_idle",e)},session_resume:function(){t("session_resume")},session_heartbeat:function(e){var n={clock:e,leadData:InboundLeadData};t("session_heartbeat",n)},page_visit:function(e){t("page_view",e)},page_first_visit:function(){t("page_first_visit"),e.deBugger("pages","First Ever Page View of this Page")},page_revisit:function(n){t("page_revisit",n);var i=function(){console.log("pageData",n),console.log("Page Revisit viewed "+n+" times")};e.deBugger("pages",status,i)},tab_hidden:function(){e.deBugger("pages","Tab Hidden"),t("tab_hidden")},tab_visible:function(){e.deBugger("pages","Tab Visible"),t("tab_visible")},tab_mouseout:function(){e.deBugger("pages","Tab Mouseout"),t("tab_mouseout")},form_input_change:function(n){var i=function(){console.log(n)};e.deBugger("forms","inputData change. Data=",i),t("form_input_change",n)},form_before_submission:function(e){t("form_before_submission",e)},form_after_submission:function(e){t("form_after_submission",e)},analyticsError:function(e,t,n){var i=new CustomEvent("inbound_analytics_error",{detail:{MLHttpRequest:e,textStatus:t,errorThrown:n}});window.dispatchEvent(i),console.log("Page Save Error")}},e}(_inbound||{}),InboundTotalStorage=function(e){var t,n,i="_inbound";if("localStorage"in window)try{n="undefined"==typeof window.localStorage?void 0:window.localStorage,t="undefined"==typeof n||"undefined"==typeof window.JSON?!1:!0,window.localStorage.setItem(i,"1"),window.localStorage.removeItem(i)}catch(o){t=!1}e.totalStorage=function(t,n){return e.totalStorage.impl.init(t,n)},e.totalStorage.setItem=function(t,n){return e.totalStorage.impl.setItem(t,n)},e.totalStorage.getItem=function(t){return e.totalStorage.impl.getItem(t)},e.totalStorage.getAll=function(){return e.totalStorage.impl.getAll()},e.totalStorage.deleteItem=function(t){return e.totalStorage.impl.deleteItem(t)},e.totalStorage.impl={init:function(e,t){return"undefined"!=typeof t?this.setItem(e,t):this.getItem(e)},setItem:function(i,o){if(!t)try{return e.Utils.createCookie(i,o),o}catch(a){console.log("Local Storage not supported by this browser. Install the cookie plugin on your site to take advantage of the same functionality. You can get it at https://github.com/carhartl/jquery-cookie")}var r=JSON.stringify(o);return n.setItem(i,r),this.parseResult(r)},getItem:function(i){if(!t)try{return this.parseResult(e.Utils.readCookie(i))}catch(o){return null}var a=n.getItem(i);return this.parseResult(a)},deleteItem:function(i){if(!t)try{return e.Utils.eraseCookie(i,null),!0}catch(o){return!1}return n.removeItem(i),!0},getAll:function(){var i=[];if(t)for(var o in n)o.length&&i.push({key:o,value:this.parseResult(n.getItem(o))});else try{for(var a=document.cookie.split(";"),r=0;r<a.length;r++){var s=a[r].split("="),l=s[0];i.push({key:l,value:this.parseResult(e.Utils.readCookie(l))})}}catch(u){return null}return i},parseResult:function(e){var t;try{t=JSON.parse(e),"undefined"==typeof t&&(t=e),"true"==t&&(t=!0),"false"==t&&(t=!1),parseFloat(t)==t&&"object"!=typeof t&&(t=parseFloat(t))}catch(n){t=e}return t}}}(_inbound||{}),_inboundLeadsAPI=function(e){return e.LeadsAPI={init:function(){var t=e.Utils,n=(t.readCookie("wp_lead_uid"),t.readCookie("wp_lead_id")),i=t.readCookie("lead_session_expire");i||(e.deBugger("leads","expired vistor. Run Processes"),n&&e.LeadsAPI.getAllLeadData())},setGlobalLeadData:function(e){InboundLeadData=e},getAllLeadData:function(){var t=e.Utils.readCookie("wp_lead_id"),n=e.totalStorage("inbound_lead_data"),i=e.Utils.readCookie("lead_data_expire");data={action:"inbound_get_all_lead_data",wp_lead_id:t},success=function(t){var n=JSON.parse(t);e.LeadsAPI.setGlobalLeadData(n),e.totalStorage("inbound_lead_data",n);var i=new Date;i.setTime(i.getTime()+18e5);var o=e.Utils.addDays(i,3);e.Utils.createCookie("lead_data_expire",!0,o)},n?(e.LeadsAPI.setGlobalLeadData(n),e.deBugger("lead","Set Global Lead Data from Localstorage"),i||(e.Utils.ajaxPost(inbound_settings.admin_url,data,success),e.deBugger("lead","localized data old. Pull new from DB"))):e.Utils.ajaxPost(inbound_settings.admin_url,data,success)},getLeadLists:function(){var t=e.Utils.readCookie("wp_lead_id"),n={action:"wpl_check_lists",wp_lead_id:t},i=function(){e.Utils.createCookie("lead_session_list_check",!0,{path:"/",expires:1}),e.deBugger("lead","Lists checked")};e.Utils.ajaxPost(inbound_settings.admin_url,n,i)}},e}(_inbound||{}),_inboundPageTracking=function(e){var t,n,i=!1,o=!1,a=!1,r=parseInt(e.Utils.readCookie("lead_session"),10)||0,s=0,l=(new Date,null),u=null,d=null,c=e.Utils,m=e.Utils.GetDate(),f="page_views",g=e.totalStorage(f)||{},p=inbound_settings.post_id||window.location.pathname,v=e.Settings.timeout||1e4;return e.PageTracking={init:function(i){return"page_views"!==f?!1:(this.CheckTimeOut(),i=i||{},t=parseInt(i.reportInterval,10)||10,n=parseInt(i.idleTimeout,10)||3,c.addListener(document,"keydown",c.throttle(e.PageTracking.pingSession,1e3)),c.addListener(document,"click",c.throttle(e.PageTracking.pingSession,1e3)),c.addListener(window,"mousemove",c.throttle(e.PageTracking.pingSession,1e3)),e.PageTracking.checkVisibility(),this.startSession(),void 0)},setIdle:function(t){var t=t||"No Movement",n="Session IDLE. Activity Timeout due to "+t;
3
- e.deBugger("pages",n),clearTimeout(e.PageTracking.idleTimer),e.PageTracking.stopClock(),e.trigger("session_idle")},checkVisibility:function(){var t,n,i;"undefined"!=typeof document.hidden?(t="hidden",i="visibilitychange",n="visibilityState"):"undefined"!=typeof document.mozHidden?(t="mozHidden",i="mozvisibilitychange",n="mozVisibilityState"):"undefined"!=typeof document.msHidden?(t="msHidden",i="msvisibilitychange",n="msVisibilityState"):"undefined"!=typeof document.webkitHidden&&(t="webkitHidden",i="webkitvisibilitychange",n="webkitVisibilityState");var o=document[t];e.Utils.addListener(document,i,function(){o!=document[t]&&(document[t]?(e.trigger("tab_hidden"),e.PageTracking.setIdle("browser tab switch")):(e.trigger("tab_visible"),e.PageTracking.pingSession()),o=document[t])})},clock:function(){r+=1;var n=r/60,i="Total time spent on Page in this Session: "+n.toFixed(2)+" min";if(e.deBugger("pages",i),r>0&&r%t===0){var o=new Date;o.setTime(o.getTime()+18e5),c.createCookie("lead_session",r,o),e.trigger("session_heartbeat",r)}},inactiveClock:function(){s+=1;var t=(1800-s)/60,n="Time until Session Timeout: "+t.toFixed(2)+" min";e.deBugger("pages",n),s>1800&&(e.trigger("session_end",InboundLeadData),e.Utils.eraseCookie("lead_session"),s=0,clearTimeout(u))},stopClock:function(){o=!0,clearTimeout(l),clearTimeout(u),u=setInterval(e.PageTracking.inactiveClock,1e3)},restartClock:function(){o=!1,e.trigger("session_resume"),e.deBugger("pages","Activity resumed. Session Active"),clearTimeout(l),s=0,clearTimeout(u),l=setInterval(e.PageTracking.clock,1e3)},turnOff:function(){e.PageTracking.setIdle(),a=!0},turnOn:function(){a=!1},startSession:function(){new Date;i=!0,l=setInterval(e.PageTracking.clock,1e3);var t=c.readCookie("lead_session");if(t)e.trigger("session_active");else{e.trigger("session_start");var n=new Date;n.setTime(n.getTime()+18e5),e.Utils.createCookie("lead_session",1,n)}this.pingSession()},resetInactiveFunc:function(){s=0,clearTimeout(u)},pingSession:function(t){a||(i||e.PageTracking.startSession(),o&&e.PageTracking.restartClock(),clearTimeout(d),d=setTimeout(e.PageTracking.setIdle,1e3*n+100),"undefined"!=typeof t&&"mousemove"===t.type&&e.PageTracking.mouseEvents(t))},mouseEvents:function(t){t.pageY<=5&&e.trigger("tab_mouseout")},getPageViews:function(){var t=e.Utils.checkLocalStorage();if(t){var n=localStorage.getItem(f),i=JSON.parse(n);return i}},isRevisit:function(e){var t=!1,e=e||{},n=e[p];return"undefined"!=typeof n&&null!==n&&(t=!0),t},triggerPageView:function(t){var n={title:document.title,url:document.location.href,path:document.location.pathname,count:1};t?(g[p].push(m),n.count=g[p].length,e.trigger("page_revisit",n)):(g[p]=[],g[p].push(m),e.trigger("page_first_visit",n)),e.trigger("page_visit",n),e.totalStorage(f,g),this.storePageView()},CheckTimeOut:function(){var t,n,i=this.isRevisit(g);if(i){var o=g[p].length-1,a=g[p][o],r=Math.abs(new Date(a).getTime()-new Date(m).getTime());n=r>v,n?(t="Timeout Happened. Page view fired",this.triggerPageView(i)):(time_left=.001*Math.abs(v-r),t=v/1e3+" sec timeout not done: "+time_left+" seconds left")}else this.triggerPageView(i);e.deBugger("pages",t)},storePageView:function(){if("off"!=inbound_settings.page_tracking){var t=e.Utils.readCookie("wp_lead_id")?e.Utils.readCookie("wp_lead_id"):"",n=e.Utils.readCookie("wp_lead_uid")?e.Utils.readCookie("wp_lead_uid"):"",i={action:"inbound_track_lead",wp_lead_uid:n,wp_lead_id:t,page_id:inbound_settings.post_id,variation_id:inbound_settings.variation_id,post_type:inbound_settings.post_type,current_url:window.location.href,json:"0"},o=function(){};e.Utils.ajaxPost(inbound_settings.admin_url,i,o)}}},e}(_inbound||{});_inbound.init(),InboundLeadData=_inbound.totalStorage("inbound_lead_data")||null;
1
  /*! Inbound Analyticsv1.0.0 | (c) 2015 Inbound Now | https://github.com/inboundnow/cta */
2
+ var inbound_data=inbound_data||{},_inboundOptions=_inboundOptions||{},_gaq=_gaq||[],_inbound=function(e){var t={timeout:inbound_settings.is_admin?500:1e4,formAutoTracking:!0,formAutoPopulation:!0},n={init:function(){_inbound.Utils.init(),_inbound.Utils.domReady(window,function(){_inbound.DomLoaded()})},DomLoaded:function(){_inbound.PageTracking.init(),_inbound.Forms.init(),_inbound.Utils.setUrlParams(),_inbound.LeadsAPI.init(),setTimeout(function(){_inbound.Forms.init()},2e3),_inbound.trigger("analytics_ready")},extend:function(e,t){var n,i={};for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&(i[n]=e[n]);for(n in t)Object.prototype.hasOwnProperty.call(t,n)&&(i[n]=t[n]);return i},debug:function(){},deBugger:function(e,t,n){if(console){var i,o,a,r=document.location.hash?document.location.hash:"",s=r.indexOf("#debug")>-1,t=t||!1;r&&r.match(/debug/)&&(r=r.split("-"),a=r[1]),o="true"===_inbound.Utils.readCookie("inbound_debug")?!0:!1,i="true"===_inbound.Utils.readCookie("inbound_debug_"+e)?!0:!1,(i||s||o)&&(t&&"string"==typeof t&&(o||"all"===a?console.log('logAll "'+e+'" =>',t):i?console.log('log "'+e+'" =>',t):e===a&&console.log('#log "'+e+'" =>',t)),n&&n instanceof Function&&n())}}},i=n.extend(t,e);return n.Settings=i||{},n}(_inboundOptions),_inboundHooks=function(e){var t=function(){function e(e,t,n,i){return"string"==typeof e&&"function"==typeof t&&(n=parseInt(n||10,10),s("actions",e,t,n,i)),d}function t(){var e=Array.prototype.slice.call(arguments),t=e.shift();return"string"==typeof t&&u("actions",t,e),d}function n(e,t){return"string"==typeof e&&r("actions",e,t),d}function i(e,t,n){return"string"==typeof e&&"function"==typeof t&&(n=parseInt(n||10,10),s("filters",e,t,n)),d}function o(){var e=Array.prototype.slice.call(arguments),t=e.shift();return"string"==typeof t?u("filters",t,e):d}function a(e,t){return"string"==typeof e&&r("filters",e,t),d}function r(e,t,n,i){if(c[e][t])if(n){var o,a=c[e][t];if(i)for(o=a.length;o--;){var r=a[o];r.callback===n&&r.context===i&&a.splice(o,1)}else for(o=a.length;o--;)a[o].callback===n&&a.splice(o,1)}else c[e][t]=[]}function s(e,t,n,i,o){var a={callback:n,priority:i,context:o},r=c[e][t];r?(r.push(a),r=l(r)):r=[a],c[e][t]=r}function l(e){for(var t,n,i,o=1,a=e.length;a>o;o++){for(t=e[o],n=o;(i=e[n-1])&&i.priority>t.priority;)e[n]=e[n-1],--n;e[n]=t}return e}function u(e,t,n){var i=c[e][t];if(!i)return"filters"===e?n[0]:!1;var o=0,a=i.length;if("filters"===e)for(;a>o;o++)n[0]=i[o].callback.apply(i[o].context,n);else for(;a>o;o++)i[o].callback.apply(i[o].context,n);return"filters"===e?n[0]:!0}var d={removeFilter:a,applyFilters:o,addFilter:i,removeAction:n,doAction:t,addAction:e},c={actions:{},filters:{}};return d};return e.hooks=new t,e.add_action=function(){var t=arguments[0].split(" ");for(k in t)arguments[0]="inbound."+t[k],e.hooks.addAction.apply(this,arguments);return this},e.remove_action=function(){return arguments[0]="inbound."+arguments[0],e.hooks.removeAction.apply(this,arguments),this},e.do_action=function(){return arguments[0]="inbound."+arguments[0],e.hooks.doAction.apply(this,arguments),this},e.add_filter=function(){return arguments[0]="inbound."+arguments[0],e.hooks.addFilter.apply(this,arguments),this},e.remove_filter=function(){return arguments[0]="inbound."+arguments[0],e.hooks.removeFilter.apply(this,arguments),this},e.apply_filters=function(){return arguments[0]="inbound."+arguments[0],e.hooks.applyFilters.apply(this,arguments)},e}(_inbound||{}),_inboundUtils=function(e){var t,n=window.XMLHttpRequest&&"withCredentials"in new XMLHttpRequest,i=(Object.prototype.toString,("https:"==location.protocol?"https://":"http://")+location.hostname+location.pathname.replace(/\/$/,"")),o={api_host:i,track_pageview:!0,track_links_timeout:300,cookie_name:"_sp",cookie_expiration:365,cookie_domain:(host=location.hostname.match(/[a-z0-9][a-z0-9\-]+\.[a-z\.]{2,6}$/i))?host[0]:""};return e.Utils={init:function(){this.polyFills(),this.checkLocalStorage(),this.SetUID(),this.storeReferralData()},polyFills:function(){window.console||(window.console={});for(var e=["log","info","warn","error","debug","trace","dir","group","groupCollapsed","groupEnd","time","timeEnd","profile","profileEnd","dirxml","assert","count","markTimeline","timeStamp","clear"],t=0;t<e.length;t++)window.console[e[t]]||(window.console[e[t]]=function(){});Date.prototype.toISOString||!function(){function e(e){var t=String(e);return 1===t.length&&(t="0"+t),t}Date.prototype.toISOString=function(){return this.getUTCFullYear()+"-"+e(this.getUTCMonth()+1)+"-"+e(this.getUTCDate())+"T"+e(this.getUTCHours())+":"+e(this.getUTCMinutes())+":"+e(this.getUTCSeconds())+"."+String((this.getUTCMilliseconds()/1e3).toFixed(3)).slice(2,5)+"Z"}}();try{new CustomEvent("?")}catch(n){this.CustomEvent=function(e,t){function n(n,o){var a=document.createEvent(e);return null!==n?i.call(a,n,(o||(o=t)).bubbles,o.cancelable,o.detail):a.initCustomEvent=i,a}function i(t,n,i,o){this["init"+e](t,n,i,o),"detail"in this||(this.detail=o)}return n}(this.CustomEvent?"CustomEvent":"Event",{bubbles:!1,cancelable:!1,detail:null})}document.querySelectorAll||(document.querySelectorAll=function(e){var t,n=document.createElement("style"),i=[];for(document.documentElement.firstChild.appendChild(n),document._qsa=[],n.styleSheet.cssText=e+"{x-qsa:expression(document._qsa && document._qsa.push(this))}",window.scrollBy(0,0),n.parentNode.removeChild(n);document._qsa.length;)t=document._qsa.shift(),t.style.removeAttribute("x-qsa"),i.push(t);return document._qsa=null,i}),document.querySelector||(document.querySelector=function(e){var t=document.querySelectorAll(e);return t.length?t[0]:null}),!("innerText"in document.createElement("a"))&&"getSelection"in window&&HTMLElement.prototype.__defineGetter__("innerText",function(){for(var e,t=window.getSelection(),n=[],i=0;i<t.rangeCount;i++)n[i]=t.getRangeAt(i);t.removeAllRanges(),t.selectAllChildren(this),e=t.toString(),t.removeAllRanges();for(var i=0;i<n.length;i++)t.addRange(n[i]);return e})},createCookie:function(e,t,n){var i="";if(n){var o=new Date;o.setTime(o.getTime()+24*n*60*60*1e3),i="; expires="+o.toGMTString()}document.cookie=e+"="+t+i+"; path=/"},readCookie:function(e){for(var t=e+"=",n=document.cookie.split(";"),i=0;i<n.length;i++){for(var o=n[i];" "===o.charAt(0);)o=o.substring(1,o.length);if(0===o.indexOf(t))return o.substring(t.length,o.length)}return null},eraseCookie:function(e){this.createCookie(e,"",-1)},getAllCookies:function(){var t={};if(document.cookie&&""!==document.cookie)for(var n=document.cookie.split(";"),i=0;i<n.length;i++){var o=n[i].split("=");o[0]=o[0].replace(/^ /,""),t[decodeURIComponent(o[0])]=decodeURIComponent(o[1])}return e.totalStorage("inbound_cookies",t),t},setUrlParams:function(){var n={};!function(){for(var e,t=function(e){return decodeURIComponent(e).replace(/\+/g," ")},i=window.location.search.substring(1),o=/([^&=]+)=?([^&]*)/g;e=o.exec(i);)if("-1"==e[1].indexOf("["))n[t(e[1])]=t(e[2]);else{var a=e[1].indexOf("["),r=e[1].slice(a+1,e[1].indexOf("]",a)),s=t(e[1].slice(0,a));"object"!=typeof n[s]&&(n[t(s)]={},n[t(s)].length=0),r?n[t(s)][t(r)]=t(e[2]):Array.prototype.push.call(n[t(s)],t(e[2]))}}();for(var i in n)if("object"==typeof n[i])for(var o in n[i])this.createCookie(o,n[i][o],30);else this.createCookie(i,n[i],30);if(t){var a=e.totalStorage("inbound_url_params")||{},r=this.mergeObjs(a,n);e.totalStorage("inbound_url_params",r)}var s={option1:"yo",option2:"woooo"};e.trigger("url_parameters",n,s)},getAllUrlParams:function(){var n={};if(t)var n=e.totalStorage("inbound_url_params");return n},getParameterVal:function(e,t){return(RegExp(e+"=(.+?)(&|$)").exec(t)||[,!1])[1]},checkLocalStorage:function(){if("localStorage"in window)try{ls="undefined"==typeof window.localStorage?void 0:window.localStorage,t="undefined"==typeof ls||"undefined"==typeof window.JSON?!1:!0}catch(e){t=!1}return t},showLocalStorageSize:function(){function e(e){return 2*e.length}function t(e){return e/1024/1024}function n(t){return{name:t,size:e(localStorage[t])}}function i(e){return e.size=t(e.size).toFixed(2)+" MB",e}var o=Object.keys(localStorage).map(n).map(i);console.table(o)},addDays:function(e,t){return new Date(e.getTime()+24*t*60*60*1e3)},GetDate:function(){var e=new Date,t=e.getDate(),n=10>t?"0":"",i=e.getFullYear(),o=e.getHours(),a=10>o?"0":"",r=e.getMinutes(),s=10>r?"0":"",l=e.getSeconds(),u=10>l?"0":"",d=e.getMonth()+1,c=10>d?"0":"",m=i+"/"+c+d+"/"+n+t+" "+a+o+":"+s+r+":"+u+l;return m},SetSessionTimeout:function(){var e=(this.readCookie("lead_session_expire"),new Date);e.setTime(e.getTime()+18e5),this.createCookie("lead_session_expire",!0,e)},storeReferralData:function(){var t=new Date,n=document.referrer||"Direct Traffic",i=e.Utils.readCookie("inbound_referral_site"),o=e.totalStorage("inbound_original_referral");t.setTime(t.getTime()+18e5),i||this.createCookie("inbound_referral_site",n,t),o||e.totalStorage("inbound_original_referral",o)},CreateUID:function(e){var t="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".split(""),n="";e||(e=Math.floor(Math.random()*t.length));for(var i=0;e>i;i++)n+=t[Math.floor(Math.random()*t.length)];return n},generateGUID:function(e){return e?(e^16*Math.random()>>e/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,guid)},SetUID:function(e){if(!this.readCookie("wp_lead_uid")){var t=e||this.CreateUID(35);this.createCookie("wp_lead_uid",t)}},countProperties:function(e){var t=0;for(var n in e)e.hasOwnProperty(n)&&++t;return t},mergeObjs:function(e,t){var n={};for(var i in e)n[i]=e[i];for(var i in t)n[i]=t[i];return n},hasClass:function(e,t){var n;if("classList"in document.documentElement)var n=t.classList.contains(e);else var n=new RegExp("(^|\\s)"+e+"(\\s|$)").test(t.className);return n},addClass:function(e,t){"classList"in document.documentElement?t.classList.add(e):this.hasClass(t,e)||(t.className+=(t.className?" ":"")+e)},removeClass:function(e,t){"classList"in document.documentElement?t.classList.remove(e):this.hasClass(t,e)&&(t.className=t.className.replace(new RegExp("(^|\\s)*"+e+"(\\s|$)*","g"),""))},removeElement:function(e){e.parentNode.removeChild(e)},trim:function(e){return e=e.replace(/(^\s*)|(\s*$)/gi,""),e=e.replace(/[ ]{2,}/gi," "),e=e.replace(/\n /,"\n")},ajaxPolyFill:function(){if("undefined"!=typeof XMLHttpRequest)return new XMLHttpRequest;for(var e,t=["MSXML2.XmlHttp.5.0","MSXML2.XmlHttp.4.0","MSXML2.XmlHttp.3.0","MSXML2.XmlHttp.2.0","Microsoft.XmlHttp"],n=0;n<t.length;n++)try{e=new ActiveXObject(t[n]);break}catch(i){}return e},ajaxSendData:function(e,t,n,i){var o=this.ajaxPolyFill();setTimeout(function(){o.open(n,e,!0),o.onreadystatechange=function(){4==o.readyState&&t(o.responseText)},"POST"==n&&o.setRequestHeader("Content-type","application/x-www-form-urlencoded"),o.send(i)},100)},ajaxGet:function(e,t,n,i){var o=[];for(var a in t)o.push(encodeURIComponent(a)+"="+encodeURIComponent(t[a]));this.ajaxSendData(e+"?"+o.join("&"),n,"GET",null,i)},ajaxPost:function(e,t,n,i){var o=[];for(var a in t)o.push(encodeURIComponent(a)+"="+encodeURIComponent(t[a]));this.ajaxSendData(e,n,"POST",o.join("&"),i)},sendEvent:function(e,t,i){t=t||{},async=!0;var a=getCookie();if(a){var r;for(r in a)t[r]=a[r]}t.id||(t.id=getId());var s={e:e,t:(new Date).toISOString(),kv:t},l=o.api_host+"/track?data="+encodeURIComponent(JSON.stringify(s));if(n){var u=new XMLHttpRequest;u.open("GET",l,async),u.withCredentials=async,u.send(null)}else{var d=document.createElement("script");d.type="text/javascript",d.async=async,d.defer=async,d.src=l;var c=document.getElementsByTagName("script")[0];c.parentNode.insertBefore(d,c)}return action(i),self},domReady:function(e,t){var n=!1,i=!0,o=e.document,a=o.documentElement,r=o.addEventListener?"addEventListener":"attachEvent",s=o.addEventListener?"removeEventListener":"detachEvent",l=o.addEventListener?"":"on",u=function(i){("readystatechange"!=i.type||"complete"==o.readyState)&&(("load"==i.type?e:o)[s](l+i.type,u,!1),!n&&(n=!0)&&t.call(e,i.type||i))},d=function(){try{a.doScroll("left")}catch(e){return setTimeout(d,50),void 0}u("poll")};if("complete"==o.readyState)t.call(e,"lazy");else{if(o.createEventObject&&a.doScroll){try{i=!e.frameElement}catch(c){}i&&d()}o[r](l+"DOMContentLoaded",u,!1),o[r](l+"readystatechange",u,!1),e[r](l+"load",u,!1)}},addListener:function(e,t,n){e&&(e.addEventListener?e.addEventListener(t,n,!1):e.attachEvent?e.attachEvent("on"+t,n):e["on"+t]=n)},removeListener:function(e,t,n){e.removeEventListener?e.removeEventListener(t,n,!1):e.detachEvent?e.detachEvent("on"+t,n):e["on"+t]=null},throttle:function(e,t){var n,i,o,a=null,r=0,s=function(){r=new Date,a=null,o=e.apply(n,i)};return function(){var l=new Date;r||(r=l);var u=t-(l-r);return n=this,i=arguments,0>=u?(clearTimeout(a),a=null,r=l,o=e.apply(n,i)):a||(a=setTimeout(s,u)),o}},checkTypeofGA:function(){"function"==typeof ga&&(universalGA=!0),"undefined"!=typeof _gaq&&"function"==typeof _gaq.push&&(classicGA=!0),"undefined"!=typeof dataLayer&&"function"==typeof dataLayer.push&&(googleTagManager=!0)}},e}(_inbound||{}),InboundForms=function(e){var t=!1,n=e.Utils,o=[],a=[],r=[],s=e.Settings,l=["first name","last name","name","email","e-mail","phone","website","job title","your favorite food","company","tele","address","comment"];if(e.Forms={init:function(){e.Forms.runFieldMappingFilters(),e.Forms.assignTrackClass(),e.Forms.formTrackInit()},runFieldMappingFilters:function(){l=e.hooks.applyFilters("forms.field_map",l)},debug:function(e,n){if(t&&console){var e=e||!1;e&&"string"==typeof e&&console.log(e),n&&n instanceof Function&&n()}},formTrackInit:function(){for(var e=0;e<window.document.forms.length;e++){var t=!1,n=window.document.forms[e];n.dataset.formProcessed||(n.dataset.formProcessed=!0,t=this.checkTrackStatus(n),t&&(this.attachFormSubmitEvent(n),this.initFormMapping(n)))}},checkTrackStatus:function(t){var n=t.getAttribute("class");return""!==n&&null!==n?n.toLowerCase().indexOf("wpl-track-me")>-1?!0:n.toLowerCase().indexOf("inbound-track")>-1?!0:(cb=function(){console.log(t)},e.deBugger("forms","This form not tracked. Please assign on in settings...",cb),!1):void 0},assignTrackClass:function(){if(window.inbound_settings){if(inbound_settings.inbound_track_include){var t=inbound_settings.inbound_track_include.split(","),n="add selectors "+inbound_settings.inbound_track_include;e.deBugger("forms",n),this.loopClassSelectors(t,"add")}if(inbound_settings.inbound_track_exclude){var t=inbound_settings.inbound_track_exclude.split(","),n="remove selectors "+inbound_settings.inbound_track_exclude;e.deBugger("forms",n),this.loopClassSelectors(t,"remove")}}},loopClassSelectors:function(t,i){for(var o=t.length-1;o>=0;o--){var a=n.trim(t[o]);-1===a.indexOf("#")&&-1===a.indexOf(".")&&(a="#"+a),a=document.querySelector(a),a&&("add"===i?(e.Utils.addClass("wpl-track-me",a),e.Utils.addClass("inbound-track",a)):(e.Utils.removeClass("wpl-track-me",a),e.Utils.removeClass("inbound-track",a)))}},initFormMapping:function(t){for(var n=[],i=0;i<t.elements.length;i++)formInput=t.elements[i],"hidden"!==formInput.type?(this.mapField(formInput),this.rememberInputValues(formInput),s.formAutoPopulation&&!e.Utils.hasClass("nopopulate",t)&&this.fillInputValues(formInput)):n.push(formInput);for(var o=n.length-1;o>=0;o--)formInput=n[o],this.mapField(formInput)},mapField:function(t){var a=t.id||!1,r=t.name||!1,s=this.getInputLabel(t);if(s){var u=this.ignoreFieldByLabel(s[0].innerText);if(u)return t.dataset.ignoreFormField=!0,!1}for(i=0;i<l.length;i++){var d=!1,c=l[i],m=n.trim(c),f=m.replace(/ /g,"_");r&&r.toLowerCase().indexOf(m)>-1?(d=!0,e.deBugger("forms","Found matching name attribute for -> "+m)):a&&a.toLowerCase().indexOf(m)>-1?(d=!0,e.deBugger("forms","Found matching ID attribute for ->"+m)):s?s[0].innerText.toLowerCase().indexOf(m)>-1&&(d=!0,e.deBugger("forms","Found matching sibling label for -> "+m)):o.push(m),d&&(this.addDataAttr(t,f),this.removeArrayItem(l,m),i--)}return inbound_data},formListener:function(t){t.preventDefault(),e.Forms.saveFormData(t.target),document.body.style.cursor="wait"},attachFormSubmitEvent:function(e){n.addListener(e,"submit",this.formListener);document.querySelector(".inbound-email")},ignoreFieldByLabel:function(t){var n=!1;return t?((-1!=t.toLowerCase().indexOf("credit card")||-1!=t.toLowerCase().indexOf("card number"))&&(n=!0),(-1!=t.toLowerCase().indexOf("expiration")||-1!=t.toLowerCase().indexOf("expiry"))&&(n=!0),("month"==t.toLowerCase()||"mm"==t.toLowerCase()||"yy"==t.toLowerCase()||"yyyy"==t.toLowerCase()||"year"==t.toLowerCase())&&(n=!0),(-1!=t.toLowerCase().indexOf("cvv")||-1!=t.toLowerCase().indexOf("cvc")||-1!=t.toLowerCase().indexOf("secure code")||-1!=t.toLowerCase().indexOf("security code"))&&(n=!0),n&&e.deBugger("forms","ignore "+t),n):!1},ignoreFieldByValue:function(e){var t=!1;if(!e)return!1;("visa"==e.toLowerCase()||"mastercard"==e.toLowerCase()||"american express"==e.toLowerCase()||"amex"==e.toLowerCase()||"discover"==e.toLowerCase())&&(t=!0);var n=new RegExp("/^[0-9]+$/");if(n.test(e)){var i=e.replace(" ","");this.isInt(i)&&i.length>=16&&(t=!0)}return t},isInt:function(e){return"number"==typeof e&&isFinite(e)&&e%1===0},releaseFormSubmit:function(e){document.body.style.cursor="default",n.removeClass("wpl-track-me",e),n.removeListener(e,"submit",this.formListener);var t=e.getAttribute("class");return""!==t&&null!==t&&-1!=t.toLowerCase().indexOf("wpcf7-form")?(setTimeout(function(){document.body.style.cursor="default"},300),!0):(e.submit(),setTimeout(function(){for(var t=0;t<e.elements.length;t++)formInput=e.elements[t],type=formInput.type||!1,"submit"===type&&"submit"===formInput.name&&e.elements[t].click()},2e3),void 0)},saveFormData:function(t){for(var i=i||{},o=0;o<t.elements.length;o++)if(formInput=t.elements[o],multiple=!1,formInput.name){if(formInput.dataset.ignoreFormField){e.deBugger("forms","ignore "+formInput.name);continue}switch(inputName=formInput.name.replace(/\[([^\[]*)\]/g,"%5B%5D$1"),i[inputName]||(i[inputName]={}),formInput.type&&(i[inputName].type=formInput.type),i[inputName].name||(i[inputName].name=formInput.name),formInput.dataset.mapFormField&&(i[inputName].map=formInput.dataset.mapFormField),formInput.nodeName){case"INPUT":if(l=this.getInputValue(formInput),l===!1)continue;break;case"TEXTAREA":l=formInput.value;break;case"SELECT":if(formInput.multiple){values=[],multiple=!0;for(var s=0;s<formInput.length;s++)formInput[s].selected&&values.push(encodeURIComponent(formInput[s].value))}else l=formInput.value}if(e.deBugger("forms","Input Value = "+l),l){i[inputName].value||(i[inputName].value=[]),i[inputName].value.push(multiple?values.join(","):encodeURIComponent(l));var l=multiple?values.join(","):encodeURIComponent(l)}}e.deBugger("forms",i);for(var u in i){var d=i[u].value,c=i[u].map;if("undefined"!=typeof d&&null!=d&&""!=d&&a.push(u+"="+i[u].value.join(",")),"undefined"!=typeof c&&null!=c&&i[u].value&&(r.push(c+"="+i[u].value.join(",")),"email"===u))var m=i[u].value.join(",")}var f=a.join("&");e.deBugger("forms","Stringified Raw Form PARAMS: "+f);var g=r.join("&");e.deBugger("forms","Stringified Mapped PARAMS"+g);var m=n.getParameterVal("email",g)||n.readCookie("wp_lead_email");m||(m=n.getParameterVal("wpleads_email_address",g));var p=n.getParameterVal("name",g),v=n.getParameterVal("first_name",g),h=n.getParameterVal("last_name",g);if(!h&&v){var _=decodeURI(v).split(" ");_.length>0&&(v=_[0],h=_[1])}if(p&&!h&&!v){var _=decodeURI(p).split(" ");_.length>0&&(v=_[0],h=_[1])}p=v&&h?v+" "+h:p,v||(v=""),h||(h=""),e.deBugger("forms","fName = "+v),e.deBugger("forms","lName = "+h),e.deBugger("forms","fullName = "+p);var b=e.totalStorage("page_views")||{},y=e.totalStorage("inbound_url_params")||{};if("undefined"!=typeof landing_path_info)var w=landing_path_info.variation;else if("undefined"!=typeof cta_path_info)var w=cta_path_info.variation;else var w=inbound_settings.variation_id;var k=inbound_settings.post_type||"page",C=inbound_settings.post_id||0;search_data={},formData={action:"inbound_lead_store",email:m,full_name:p,first_name:v,last_name:h,raw_params:f,mapped_params:g,url_params:JSON.stringify(y),search_data:"test",page_views:JSON.stringify(b),post_type:k,page_id:C,variation:w,source:n.readCookie("inbound_referral_site")},callback=function(i){e.deBugger("forms","Lead Created with ID: "+i),i=parseInt(i,10),formData.leadID=i,i&&(n.createCookie("wp_lead_id",i),e.totalStorage.deleteItem("page_views"),e.totalStorage.deleteItem("tracking_events")),e.trigger("form_after_submission",formData),e.Forms.releaseFormSubmit(t)},e.trigger("form_before_submission",formData),n.ajaxPost(inbound_settings.admin_url,formData,callback)},rememberInputValues:function(t){var i=(t.name?"inbound_"+t.name:"",t.type?t.type:"text");return"submit"===i||"hidden"===i||"file"===i||"password"===i||t.dataset.ignoreFormField?!1:(n.addListener(t,"change",function(t){if(t.target.name){if("checkbox"!==i)var o=t.target.value;else for(var a=[],r=document.querySelectorAll('input[name="'+t.target.name+'"]'),s=0;s<r.length;s++){var l=r[s].checked;l&&a.push(r[s].value),o=a.join(",")}inputData={name:t.target.name,node:t.target.nodeName.toLowerCase(),type:i,value:o,mapping:t.target.dataset.mapFormField},e.trigger("form_input_change",inputData),n.createCookie("inbound_"+t.target.name,encodeURIComponent(o))}}),void 0)},fillInputValues:function(e){var t=e.name?"inbound_"+e.name:"",i=e.type?e.type:"text";if("submit"===i||"hidden"===i||"file"===i||"password"===i)return!1;if(n.readCookie(t)&&"comment"!=t)if(value=decodeURIComponent(n.readCookie(t)),"checkbox"===i||"radio"===i)for(var o=value.split(","),a=0;a<o.length;a++)e.value.indexOf(o[a])>-1&&(e.checked=!0);else"undefined"!==value&&(e.value=value)},getInputLabel:function(e){var t;return(t=this.siblingsIsLabel(e))?t:(t=this.CheckParentForLabel(e))?t:!1},getInputValue:function(e){var t=!1;switch(e.type){case"radio":case"checkbox":e.checked&&(t=e.value);break;case"text":case"hidden":default:t=e.value}return t},addDataAttr:function(e,t){for(var n=document.getElementsByName(e.name),i=n.length-1;i>=0;i--)e.dataset.mapFormField||(n[i].dataset.mapFormField=t)},removeArrayItem:function(e,t){if(e.indexOf)index=e.indexOf(t);else for(index=e.length-1;index>=0&&e[index]!==t;--index);index>=0&&e.splice(index,1)},siblingsIsLabel:function(e){for(var t=this.getSiblings(e),n=[],i=t.length-1;i>=0;i--)"label"===t[i].nodeName.toLowerCase()&&n.push(t[i]);return n.length>0&&n.length<2?n:!1},getChildren:function(e,t){for(var n=[];e;e=e.nextSibling)1==e.nodeType&&e!=t&&n.push(e);return n},getSiblings:function(e){return this.getChildren(e.parentNode.firstChild,e)},CheckParentForLabel:function(e){if("FORM"===e.nodeName)return null;do{var t=e.getElementsByTagName("label");if(t.length>0&&t.length<2)return e.getElementsByTagName("label")}while(e=e.parentNode);return null},mailCheck:function(){var e=document.querySelector(".inbound-email");e&&(n.addListener(e,"blur",this.mailCheck),u.run({email:document.querySelector(".inbound-email").value,suggested:function(t){var i=document.querySelector(".email_suggestion");i&&n.removeElement(i);var o=document.createElement("span");o.innerHTML="<span class=\"email_suggestion\">Did youu mean <b><i id='email_correction' style='cursor: pointer;' title=\"click to update\">"+t.full+"</b></i>?</span>",e.parentNode.insertBefore(o,e.nextSibling);var a=document.getElementById("email_correction");n.addListener(a,"click",function(){e.value=a.innerHTML,a.parentNode.parentNode.innerHTML="Fixed!"})},empty:function(){}}))}},"undefined"==typeof u)var u={domainThreshold:1,topLevelThreshold:3,defaultDomains:["yahoo.com","google.com","hotmail.com","gmail.com","me.com","aol.com","mac.com","live.com","comcast.net","googlemail.com","msn.com","hotmail.co.uk","yahoo.co.uk","facebook.com","verizon.net","sbcglobal.net","att.net","gmx.com","mail.com","outlook.com","icloud.com"],defaultTopLevelDomains:["co.jp","co.uk","com","net","org","info","edu","gov","mil","ca","de"],run:function(e){e.domains=e.domains||u.defaultDomains,e.topLevelDomains=e.topLevelDomains||u.defaultTopLevelDomains,e.distanceFunction=e.distanceFunction||u.sift3Distance;var t=function(e){return e},n=e.suggested||t,i=e.empty||t,o=u.suggest(u.encodeEmail(e.email),e.domains,e.topLevelDomains,e.distanceFunction);return o?n(o):i()},suggest:function(e,t,n,i){e=e.toLowerCase();var o=this.splitEmail(e),a=this.findClosestDomain(o.domain,t,i,this.domainThreshold);if(a){if(a!=o.domain)return{address:o.address,domain:a,full:o.address+"@"+a}}else{var r=this.findClosestDomain(o.topLevelDomain,n,i,this.topLevelThreshold);if(o.domain&&r&&r!=o.topLevelDomain){var s=o.domain;return a=s.substring(0,s.lastIndexOf(o.topLevelDomain))+r,{address:o.address,domain:a,full:o.address+"@"+a}}}return!1},findClosestDomain:function(e,t,n,i){i=i||this.topLevelThreshold;var o,a=99,r=null;if(!e||!t)return!1;n||(n=this.sift3Distance);for(var s=0;s<t.length;s++){if(e===t[s])return e;o=n(e,t[s]),a>o&&(a=o,r=t[s])}return i>=a&&null!==r?r:!1},sift3Distance:function(e,t){if(null===e||0===e.length)return null===t||0===t.length?0:t.length;if(null===t||0===t.length)return e.length;for(var n=0,i=0,o=0,a=0,r=5;n+i<e.length&&n+o<t.length;){if(e.charAt(n+i)==t.charAt(n+o))a++;else{i=0,o=0;for(var s=0;r>s;s++){if(n+s<e.length&&e.charAt(n+s)==t.charAt(n)){i=s;break}if(n+s<t.length&&e.charAt(n)==t.charAt(n+s)){o=s;break}}}n++}return(e.length+t.length)/2-a},splitEmail:function(e){var t=e.trim().split("@");if(t.length<2)return!1;for(var n=0;n<t.length;n++)if(""===t[n])return!1;var i=t.pop(),o=i.split("."),a="";if(0===o.length)return!1;if(1==o.length)a=o[0];else{for(var n=1;n<o.length;n++)a+=o[n]+".";o.length>=2&&(a=a.substring(0,a.length-1))}return{topLevelDomain:a,domain:i,address:t.join("@")}},encodeEmail:function(e){var t=encodeURI(e);return t=t.replace("%20"," ").replace("%25","%").replace("%5E","^").replace("%60","`").replace("%7B","{").replace("%7C","|").replace("%7D","}")}};return e}(_inbound||{}),_inboundEvents=function(e){function t(t,i,o){var i=i||{};o=o||{},o.bubbles=o.bubbles||!0,o.cancelable=o.cancelable||!0,i=e.apply_filters("filter_"+t,i);!window.ActiveXObject&&"ActiveXObject"in window;if("function"==typeof CustomEvent)var a=new CustomEvent(t,{detail:i,bubbles:o.bubbles,cancelable:o.cancelable});else{var a=document.createEvent("Event");a.initEvent(t,!0,!0)}window.dispatchEvent(a),e.do_action(t,i),n(t,i)}function n(e,t){if(window.jQuery){var t=t||{};jQuery(document).trigger(e,t)}}e.trigger=function(t,n){e.Events[t](n)};return e.Events={analytics_ready:function(){var e={opt1:!0},n={data:"xyxy"};t("analytics_ready",n,e)},url_parameters:function(e){t("url_parameters",e)},session_start:function(){console.log(""),t("session_start")},session_end:function(e){t("session_end",e),console.log("Session End")},session_active:function(){t("session_active")},session_idle:function(e){t("session_idle",e)},session_resume:function(){t("session_resume")},session_heartbeat:function(e){var n={clock:e,leadData:InboundLeadData};t("session_heartbeat",n)},page_visit:function(e){t("page_view",e)},page_first_visit:function(){t("page_first_visit"),e.deBugger("pages","First Ever Page View of this Page")},page_revisit:function(n){t("page_revisit",n);var i=function(){console.log("pageData",n),console.log("Page Revisit viewed "+n+" times")};e.deBugger("pages",status,i)},tab_hidden:function(){e.deBugger("pages","Tab Hidden"),t("tab_hidden")},tab_visible:function(){e.deBugger("pages","Tab Visible"),t("tab_visible")},tab_mouseout:function(){e.deBugger("pages","Tab Mouseout"),t("tab_mouseout")},form_input_change:function(n){var i=function(){console.log(n)};e.deBugger("forms","inputData change. Data=",i),t("form_input_change",n)},form_before_submission:function(e){t("form_before_submission",e)},form_after_submission:function(e){t("form_after_submission",e)},analyticsError:function(e,t,n){var i=new CustomEvent("inbound_analytics_error",{detail:{MLHttpRequest:e,textStatus:t,errorThrown:n}});window.dispatchEvent(i),console.log("Page Save Error")}},e}(_inbound||{}),InboundTotalStorage=function(e){var t,n,i="_inbound";if("localStorage"in window)try{n="undefined"==typeof window.localStorage?void 0:window.localStorage,t="undefined"==typeof n||"undefined"==typeof window.JSON?!1:!0,window.localStorage.setItem(i,"1"),window.localStorage.removeItem(i)}catch(o){t=!1}e.totalStorage=function(t,n){return e.totalStorage.impl.init(t,n)},e.totalStorage.setItem=function(t,n){return e.totalStorage.impl.setItem(t,n)},e.totalStorage.getItem=function(t){return e.totalStorage.impl.getItem(t)},e.totalStorage.getAll=function(){return e.totalStorage.impl.getAll()},e.totalStorage.deleteItem=function(t){return e.totalStorage.impl.deleteItem(t)},e.totalStorage.impl={init:function(e,t){return"undefined"!=typeof t?this.setItem(e,t):this.getItem(e)},setItem:function(i,o){if(!t)try{return e.Utils.createCookie(i,o),o}catch(a){console.log("Local Storage not supported by this browser. Install the cookie plugin on your site to take advantage of the same functionality. You can get it at https://github.com/carhartl/jquery-cookie")}var r=JSON.stringify(o);return n.setItem(i,r),this.parseResult(r)},getItem:function(i){if(!t)try{return this.parseResult(e.Utils.readCookie(i))}catch(o){return null}var a=n.getItem(i);return this.parseResult(a)},deleteItem:function(i){if(!t)try{return e.Utils.eraseCookie(i,null),!0}catch(o){return!1}return n.removeItem(i),!0},getAll:function(){var i=[];if(t)for(var o in n)o.length&&i.push({key:o,value:this.parseResult(n.getItem(o))});else try{for(var a=document.cookie.split(";"),r=0;r<a.length;r++){var s=a[r].split("="),l=s[0];i.push({key:l,value:this.parseResult(e.Utils.readCookie(l))})}}catch(u){return null}return i},parseResult:function(e){var t;try{t=JSON.parse(e),"undefined"==typeof t&&(t=e),"true"==t&&(t=!0),"false"==t&&(t=!1),parseFloat(t)==t&&"object"!=typeof t&&(t=parseFloat(t))}catch(n){t=e}return t}}}(_inbound||{}),_inboundLeadsAPI=function(e){return e.LeadsAPI={init:function(){var t=e.Utils,n=(t.readCookie("wp_lead_uid"),t.readCookie("wp_lead_id")),i=t.readCookie("lead_session_expire");i||(e.deBugger("leads","expired vistor. Run Processes"),n&&e.LeadsAPI.getAllLeadData())},setGlobalLeadData:function(e){InboundLeadData=e},getAllLeadData:function(){var t=e.Utils.readCookie("wp_lead_id"),n=e.totalStorage("inbound_lead_data"),i=e.Utils.readCookie("lead_data_expire");data={action:"inbound_get_all_lead_data",wp_lead_id:t},success=function(t){var n=JSON.parse(t);e.LeadsAPI.setGlobalLeadData(n),e.totalStorage("inbound_lead_data",n);var i=new Date;i.setTime(i.getTime()+18e5);var o=e.Utils.addDays(i,3);e.Utils.createCookie("lead_data_expire",!0,o)},n?(e.LeadsAPI.setGlobalLeadData(n),e.deBugger("lead","Set Global Lead Data from Localstorage"),i||(e.Utils.ajaxPost(inbound_settings.admin_url,data,success),e.deBugger("lead","localized data old. Pull new from DB"))):e.Utils.ajaxPost(inbound_settings.admin_url,data,success)},getLeadLists:function(){var t=e.Utils.readCookie("wp_lead_id"),n={action:"wpl_check_lists",wp_lead_id:t},i=function(){e.Utils.createCookie("lead_session_list_check",!0,{path:"/",expires:1}),e.deBugger("lead","Lists checked")};e.Utils.ajaxPost(inbound_settings.admin_url,n,i)}},e}(_inbound||{}),_inboundPageTracking=function(e){var t,n,i=!1,o=!1,a=!1,r=parseInt(e.Utils.readCookie("lead_session"),10)||0,s=0,l=(new Date,null),u=null,d=null,c=e.Utils,m=e.Utils.GetDate(),f="page_views",g=e.totalStorage(f)||{},p=inbound_settings.post_id||window.location.pathname,v=e.Settings.timeout||1e4;return e.PageTracking={init:function(i){return"page_views"!==f?!1:(this.CheckTimeOut(),i=i||{},t=parseInt(i.reportInterval,10)||10,n=parseInt(i.idleTimeout,10)||3,c.addListener(document,"keydown",c.throttle(e.PageTracking.pingSession,1e3)),c.addListener(document,"click",c.throttle(e.PageTracking.pingSession,1e3)),c.addListener(window,"mousemove",c.throttle(e.PageTracking.pingSession,1e3)),e.PageTracking.checkVisibility(),this.startSession(),void 0)},setIdle:function(t){var t=t||"No Movement",n="Session IDLE. Activity Timeout due to "+t;e.deBugger("pages",n),clearTimeout(e.PageTracking.idleTimer),e.PageTracking.stopClock(),e.trigger("session_idle")
3
+ },checkVisibility:function(){var t,n,i;"undefined"!=typeof document.hidden?(t="hidden",i="visibilitychange",n="visibilityState"):"undefined"!=typeof document.mozHidden?(t="mozHidden",i="mozvisibilitychange",n="mozVisibilityState"):"undefined"!=typeof document.msHidden?(t="msHidden",i="msvisibilitychange",n="msVisibilityState"):"undefined"!=typeof document.webkitHidden&&(t="webkitHidden",i="webkitvisibilitychange",n="webkitVisibilityState");var o=document[t];e.Utils.addListener(document,i,function(){o!=document[t]&&(document[t]?(e.trigger("tab_hidden"),e.PageTracking.setIdle("browser tab switch")):(e.trigger("tab_visible"),e.PageTracking.pingSession()),o=document[t])})},clock:function(){r+=1;var n=r/60,i="Total time spent on Page in this Session: "+n.toFixed(2)+" min";if(e.deBugger("pages",i),r>0&&r%t===0){var o=new Date;o.setTime(o.getTime()+18e5),c.createCookie("lead_session",r,o),e.trigger("session_heartbeat",r)}},inactiveClock:function(){s+=1;var t=(1800-s)/60,n="Time until Session Timeout: "+t.toFixed(2)+" min";e.deBugger("pages",n),s>1800&&(e.trigger("session_end",InboundLeadData),e.Utils.eraseCookie("lead_session"),s=0,clearTimeout(u))},stopClock:function(){o=!0,clearTimeout(l),clearTimeout(u),u=setInterval(e.PageTracking.inactiveClock,1e3)},restartClock:function(){o=!1,e.trigger("session_resume"),e.deBugger("pages","Activity resumed. Session Active"),clearTimeout(l),s=0,clearTimeout(u),l=setInterval(e.PageTracking.clock,1e3)},turnOff:function(){e.PageTracking.setIdle(),a=!0},turnOn:function(){a=!1},startSession:function(){new Date;i=!0,l=setInterval(e.PageTracking.clock,1e3);var t=c.readCookie("lead_session");if(t)e.trigger("session_active");else{e.trigger("session_start");var n=new Date;n.setTime(n.getTime()+18e5),e.Utils.createCookie("lead_session",1,n)}this.pingSession()},resetInactiveFunc:function(){s=0,clearTimeout(u)},pingSession:function(t){a||(i||e.PageTracking.startSession(),o&&e.PageTracking.restartClock(),clearTimeout(d),d=setTimeout(e.PageTracking.setIdle,1e3*n+100),"undefined"!=typeof t&&"mousemove"===t.type&&e.PageTracking.mouseEvents(t))},mouseEvents:function(t){t.pageY<=5&&e.trigger("tab_mouseout")},getPageViews:function(){var t=e.Utils.checkLocalStorage();if(t){var n=localStorage.getItem(f),i=JSON.parse(n);return i}},isRevisit:function(e){var t=!1,e=e||{},n=e[p];return"undefined"!=typeof n&&null!==n&&(t=!0),t},triggerPageView:function(t){var n={title:document.title,url:document.location.href,path:document.location.pathname,count:1};t?(g[p].push(m),n.count=g[p].length,e.trigger("page_revisit",n)):(g[p]=[],g[p].push(m),e.trigger("page_first_visit",n)),e.trigger("page_visit",n),e.totalStorage(f,g),this.storePageView()},CheckTimeOut:function(){var t,n,i=this.isRevisit(g);if(i){var o=g[p].length-1,a=g[p][o],r=Math.abs(new Date(a).getTime()-new Date(m).getTime());n=r>v,n?(t="Timeout Happened. Page view fired",this.triggerPageView(i)):(time_left=.001*Math.abs(v-r),t=v/1e3+" sec timeout not done: "+time_left+" seconds left")}else this.triggerPageView(i);e.deBugger("pages",t)},storePageView:function(){if("off"!=inbound_settings.page_tracking){var t=e.Utils.readCookie("wp_lead_id")?e.Utils.readCookie("wp_lead_id"):"",n=e.Utils.readCookie("wp_lead_uid")?e.Utils.readCookie("wp_lead_uid"):"",i={action:"inbound_track_lead",wp_lead_uid:n,wp_lead_id:t,page_id:inbound_settings.post_id,variation_id:inbound_settings.variation_id,post_type:inbound_settings.post_type,current_url:window.location.href,json:"0"},o=function(){};e.Utils.ajaxPost(inbound_settings.admin_url,i,o)}}},e}(_inbound||{});_inbound.init(),InboundLeadData=_inbound.totalStorage("inbound_lead_data")||null;
shared/classes/class.feedback.php CHANGED
@@ -251,7 +251,7 @@ if (!class_exists('Inbound_Feedback')) {
251
  } else if (in_array($screen->id, $cta_page_array)) {
252
  $plugin_name = __( 'Calls to Action plugin' , INBOUNDNOW_TEXT_DOMAIN );
253
  } else if (in_array($screen->id, $leads_page_array)) {
254
- $plugin_name = __( 'Leads Pages plugin' , INBOUNDNOW_TEXT_DOMAIN );
255
  }
256
 
257
  ?>
251
  } else if (in_array($screen->id, $cta_page_array)) {
252
  $plugin_name = __( 'Calls to Action plugin' , INBOUNDNOW_TEXT_DOMAIN );
253
  } else if (in_array($screen->id, $leads_page_array)) {
254
+ $plugin_name = __( 'Leads plugin' , INBOUNDNOW_TEXT_DOMAIN );
255
  }
256
 
257
  ?>
shared/classes/class.form.php CHANGED
@@ -411,12 +411,13 @@ if (!class_exists('Inbound_Forms')) {
411
  $form .= '<input type="email" class="inbound-input inbound-input-email '.$formatted_label . $input_classes.' '.$field_input_class.'" name="'.$field_name.'" '.$form_placeholder.' id="'.$field_name.'" value="'.$fill_value.'" '.$data_mapping_attr.$et_output.' '.$req.'/>';
412
 
413
  } else if ($type === 'range') {
414
-
 
415
  $hidden_param = (isset($matches[3][$i]['dynamic'])) ? $matches[3][$i]['dynamic'] : '';
416
  $fill_value = (isset($matches[3][$i]['default'])) ? $matches[3][$i]['default'] : '';
417
  $dynamic_value = (isset($_GET[$hidden_param])) ? $_GET[$hidden_param] : '';
418
 
419
- $form .= '<input type="range" class="inbound-input inbound-input-range '.$formatted_label . $input_classes.' '.$field_input_class.'" name="'.$field_name.'" '.$form_placeholder.' id="'.$field_name.'" value="'.$fill_value.'" '.$data_mapping_attr.$et_output.' '.$req.'/>';
420
 
421
  } else if ($type === 'text') {
422
 
@@ -850,6 +851,11 @@ if (!class_exists('Inbound_Forms')) {
850
  return;
851
  }
852
 
 
 
 
 
 
853
  /* Get Lead Email Address */
854
  $lead_email = false;
855
  foreach ($form_post_data as $key => $value) {
@@ -878,35 +884,26 @@ if (!class_exists('Inbound_Forms')) {
878
 
879
  $Inbound_Templating_Engine = Inbound_Templating_Engine();
880
  $form_id = $form_meta_data['post_id']; //This is page id or post id
881
- $template_id = $form_meta_data['inbound_email_send_notification_template'][0];
882
 
883
  /* Rebuild Form Meta Data to Load Single Values */
884
  foreach( $form_meta_data as $key => $value ) {
885
  $form_meta_data[$key] = $value[0];
886
  }
887
 
888
- /* If Email Template Selected Use That */
889
- if ( $template_id && $template_id != 'custom' ) {
890
-
891
- $template_array = self::get_email_template( $template_id );
892
- $confirm_subject = $template_array['subject'];
893
- $confirm_email_message = $template_array['body'];
894
 
895
- /* Else Use Custom Template */
896
- } else {
897
-
898
- $template = get_post($form_id);
899
- $content = $template->post_content;
900
- $confirm_subject = get_post_meta( $form_id, 'inbound_confirmation_subject', TRUE );
901
- $content = apply_filters('the_content', $content);
902
- $content = str_replace(']]>', ']]&gt;', $content);
903
 
904
- $confirm_email_message = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head><meta http-equiv="Content-Type" content="text/html;' . get_option('blog_charset') . '" /></head><body style="margin: 0px; background-color: #F4F3F4; font-family: Helvetica, Arial, sans-serif; font-size:12px;" text="#444444" bgcolor="#F4F3F4" link="#21759B" alink="#21759B" vlink="#21759B" marginheight="0" topmargin="0" marginwidth="0" leftmargin="0"><table cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff" border="0"><tr>';
905
- $confirm_email_message .= $content;
906
- $confirm_email_message .= '</tr></table></body></html>';
907
- }
908
 
909
 
 
 
910
 
911
  $confirm_subject = $Inbound_Templating_Engine->replace_tokens( $confirm_subject, array($form_post_data, $form_meta_data ));
912
 
@@ -933,36 +930,177 @@ if (!class_exists('Inbound_Forms')) {
933
  */
934
  static function get_new_lead_email_template( ) {
935
 
936
- $email_template = array();
937
-
938
- $templates = get_posts(array(
939
- 'post_type' => 'email-template',
940
- 'posts_per_page' => 1,
941
- 'meta_key' => '_inbound_template_id',
942
- 'meta_value' => 'inbound-new-lead-notification'
943
- ));
944
-
945
- foreach ( $templates as $template ) {
946
- $email_template['ID'] = $template->ID;
947
- $email_template['subject'] = get_post_meta( $template->ID , 'inbound_email_subject_template' , true );
948
- $email_template['body'] = get_post_meta( $template->ID , 'inbound_email_body_template' , true );
949
- }
950
-
951
- return $email_template;
952
- }
953
-
954
- /**
955
- * Get Email Template by ID
956
- */
957
- public static function get_email_template( $ID ) {
958
-
959
- $email_template = array();
960
-
961
- $template = get_post($ID);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
962
 
963
- $email_template['ID'] = $template->ID;
964
- $email_template['subject'] = get_post_meta( $template->ID , 'inbound_email_subject_template' , true );
965
- $email_template['body'] = get_post_meta( $template->ID , 'inbound_email_body_template' , true );
966
 
967
  return $email_template;
968
  }
411
  $form .= '<input type="email" class="inbound-input inbound-input-email '.$formatted_label . $input_classes.' '.$field_input_class.'" name="'.$field_name.'" '.$form_placeholder.' id="'.$field_name.'" value="'.$fill_value.'" '.$data_mapping_attr.$et_output.' '.$req.'/>';
412
 
413
  } else if ($type === 'range') {
414
+ $range = $matches[3][$i]['range'];
415
+ $options = explode( '|' , $range );
416
  $hidden_param = (isset($matches[3][$i]['dynamic'])) ? $matches[3][$i]['dynamic'] : '';
417
  $fill_value = (isset($matches[3][$i]['default'])) ? $matches[3][$i]['default'] : '';
418
  $dynamic_value = (isset($_GET[$hidden_param])) ? $_GET[$hidden_param] : '';
419
 
420
+ $form .= '<input type="range" min="'.$options[0].'" max="'.$options[1].'" step="'.$options[2].'" class="inbound-input inbound-input-range '.$formatted_label . $input_classes.' '.$field_input_class.'" name="'.$field_name.'" '.$form_placeholder.' id="'.$field_name.'" value="'.$fill_value.'" '.$data_mapping_attr.$et_output.' '.$req.'/>';
421
 
422
  } else if ($type === 'text') {
423
 
851
  return;
852
  }
853
 
854
+ /* Listen for Inbound Mailer takeover */
855
+ if (apply_filters('inbound-forms/email-reponse-hijack' , false, $form_meta_data , $form_post_data ) ) {
856
+ return;
857
+ }
858
+
859
  /* Get Lead Email Address */
860
  $lead_email = false;
861
  foreach ($form_post_data as $key => $value) {
884
 
885
  $Inbound_Templating_Engine = Inbound_Templating_Engine();
886
  $form_id = $form_meta_data['post_id']; //This is page id or post id
 
887
 
888
  /* Rebuild Form Meta Data to Load Single Values */
889
  foreach( $form_meta_data as $key => $value ) {
890
  $form_meta_data[$key] = $value[0];
891
  }
892
 
893
+ $template = get_post($form_id);
894
+ $content = $template->post_content;
895
+ $confirm_subject = get_post_meta( $form_id, 'inbound_confirmation_subject', TRUE );
896
+ $content = apply_filters('the_content', $content);
897
+ $content = str_replace(']]>', ']]&gt;', $content);
 
898
 
899
+ $confirm_email_message = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><head><meta http-equiv="Content-Type" content="text/html;' . get_option('blog_charset') . '" /></head><body style="margin: 0px; background-color: #F4F3F4; font-family: Helvetica, Arial, sans-serif; font-size:12px;" text="#444444" bgcolor="#F4F3F4" link="#21759B" alink="#21759B" vlink="#21759B" marginheight="0" topmargin="0" marginwidth="0" leftmargin="0"><table cellpadding="0" cellspacing="0" width="100%" bgcolor="#ffffff" border="0"><tr>';
900
+ $confirm_email_message .= $content;
901
+ $confirm_email_message .= '</tr></table></body></html>';
 
 
 
 
 
902
 
 
 
 
 
903
 
904
 
905
+ $confirm_subject = apply_filters( 'inbound_email_response/subject' , $confirm_subject , $form_meta_data , $form_post_data );
906
+ $confirm_email_message = apply_filters( 'inbound_email_response/body' , $confirm_email_message , $form_meta_data , $form_post_data );
907
 
908
  $confirm_subject = $Inbound_Templating_Engine->replace_tokens( $confirm_subject, array($form_post_data, $form_meta_data ));
909
 
930
  */
931
  static function get_new_lead_email_template( ) {
932
 
933
+ $html = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
934
+ <html>
935
+ <head>
936
+ <meta http-equiv="Content-Type" content="text/html;" charset="UTF-8" />
937
+ <style type="text/css">
938
+ html {
939
+ background: #EEEDED;
940
+ }
941
+ </style>
942
+ </head>
943
+ <body style="margin: 0px; background-color: #FFFFFF; font-family: Helvetica, Arial, sans-serif; font-size:12px;" text="#444444" bgcolor="#FFFFFF" link="#21759B" alink="#21759B" vlink="#21759B" marginheight="0" topmargin="0" marginwidth="0" leftmargin="0">
944
+
945
+ <table cellpadding="0" width="600" bgcolor="#FFFFFF" cellspacing="0" border="0" align="center" style="width:100%!important;line-height:100%!important;border-collapse:collapse;margin-top:0;margin-right:0;margin-bottom:0;margin-left:0;padding-top:0;padding-right:0;padding-bottom:0;padding-left:0">
946
+ <tbody><tr>
947
+ <td valign="top" height="20">&nbsp;</td>
948
+ </tr>
949
+ <tr>
950
+ <td valign="top">
951
+ <table cellpadding="0" bgcolor="#ffffff" cellspacing="0" border="0" align="center" style="border-collapse:collapse;width:600px;font-size:13px;line-height:20px;color:#545454;font-family:Arial,sans-serif;border-radius:3px;margin-top:0;margin-right:auto;margin-bottom:0;margin-left:auto">
952
+ <tbody><tr>
953
+ <td valign="top">
954
+ <table cellpadding="0" cellspacing="0" border="0" style="border-collapse:separate;width:100%;border-radius:3px 3px 0 0;font-size:1px;line-height:3px;height:3px;border-top-color:#0298e3;border-right-color:#0298e3;border-bottom-color:#0298e3;border-left-color:#0298e3;border-top-style:solid;border-right-style:solid;border-bottom-style:solid;border-left-style:solid;border-top-width:1px;border-right-width:1px;border-bottom-width:1px;border-left-width:1px">
955
+ <tbody><tr>
956
+ <td valign="top" style="font-family:Arial,sans-serif;background-color:#5ab8e7;border-top-width:1px;border-top-color:#8ccae9;border-top-style:solid" bgcolor="#5ab8e7">&nbsp;</td>
957
+ </tr>
958
+ </tbody></table>
959
+ <table cellpadding="0" cellspacing="0" border="0" style="border-collapse:separate;width:600px;border-radius:0 0 3px 3px;border-top-color:#8c8c8c;border-right-color:#8c8c8c;border-bottom-color:#8c8c8c;border-left-color:#8c8c8c;border-top-style:solid;border-right-style:solid;border-bottom-style:solid;border-left-style:solid;border-top-width:0;border-right-width:1px;border-bottom-width:1px;border-left-width:1px">
960
+ <tbody><tr>
961
+ <td valign="top" style="font-size:13px;line-height:20px;color:#545454;font-family:Arial,sans-serif;border-radius:0 0 3px 3px;padding-top:3px;padding-right:30px;padding-bottom:15px;padding-left:30px">
962
+
963
+ <h1 style="margin-top:20px;margin-right:0;margin-bottom:20px;margin-left:0; font-size:28px; line-height: 28px; color:#000;"> '. __('New Lead on {{form-name}}' , 'ma' ) .'</h1>
964
+ <p style="margin-top:20px;margin-right:0;margin-bottom:20px;margin-left:0">'. __('There is a new lead that just converted on <strong>{{date-time}}</strong> from page: <a href="{{source}}">{{source}}</a> {{redirect-message}}' , 'ma' ) .'</p>
965
+
966
+ <!-- NEW TABLE -->
967
+ <table class="heavyTable" style="width: 100%;
968
+ max-width: 600px;
969
+ border-collapse: collapse;
970
+ border: 1px solid #cccccc;
971
+ background: white;
972
+ margin-bottom: 20px;">
973
+ <tbody>
974
+ <tr style="background: #3A9FD1; height: 54px; font-weight: lighter; color: #fff;border: 1px solid #3A9FD1;text-align: left; padding-left: 10px;">
975
+ <td align="left" width="600" style="-webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; color: #fff; font-weight: bold; text-decoration: none; font-family: Helvetica, Arial, sans-serif; display: block;">
976
+ <h1 style="font-size: 30px; display: inline-block;margin-top: 15px;margin-left: 10px; margin-bottom: 0px; letter-spacing: 0px; word-spacing: 0px; font-weight: 300;">'. __('Lead Information' , 'ma' ) .'</h1>
977
+ <div style="float:right; margin-top: 5px; margin-right: 15px;"><!--[if mso]>
978
+ <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="{{admin-url}}edit.php?post_type=wp-lead&lead-email-redirect={{lead-email-address}}" style="height:40px;v-text-anchor:middle;width:130px;font-size:18px;" arcsize="10%" stroke="f" fillcolor="#ffffff">
979
+ <w:anchorlock/>
980
+ <center>
981
+ <![endif]-->
982
+ <a href="{{admin-url}}edit.php?post_type=wp-lead&lead-email-redirect={{lead-email-address}}"
983
+ style="background-color:#ffffff;border-radius:4px;color:#3A9FD1;display:inline-block;font-family:sans-serif;font-size:18px;font-weight:bold;line-height:40px;text-align:center;text-decoration:none;width:130px;-webkit-text-size-adjust:none;">'. __('View Lead' , 'ma' ) .'</a>
984
+ <!--[if mso]>
985
+ </center>
986
+ </v:roundrect>
987
+ <![endif]-->
988
+ </div>
989
+ </td>
990
+ </tr>
991
+
992
+ <!-- LOOP THROUGH POST PARAMS -->
993
+ [inbound-email-post-params]
994
+
995
+ <!-- END LOOP -->
996
+
997
+ <!-- IF CHAR COUNT OVER 50 make label display block -->
998
+
999
+ </tbody>
1000
+ </table>
1001
+ <!-- END NEW TABLE -->
1002
+ <!-- Start 3 col -->
1003
+ <table style="margin-bottom: 20px; border: 1px solid #cccccc; border-collapse: collapse;" width="100%" border="1" BORDERWIDTH="1" BORDERCOLOR="CCCCCC" cellspacing="0" cellpadding="5" align="left" valign="top" borderspacing="0" >
1004
+
1005
+ <tbody valign="top">
1006
+ <tr valign="top" border="0">
1007
+ <td width="160" height="50" align="center" valign="top" border="0">
1008
+ <h3 style="color:#2e2e2e;font-size:15px;"><a style="text-decoration: none;" href="{{admin-url}}edit.php?post_type=wp-lead&lead-email-redirect={{lead-email-address}}&tab=tabs-wpleads_lead_tab_conversions">'. __( 'View Lead Activity' , 'ma' ) .'</a></h3>
1009
+ </td>
1010
+
1011
+ <td width="160" height="50" align="center" valign="top" border="0">
1012
+ <h3 style="color:#2e2e2e;font-size:15px;"><a style="text-decoration: none;" href="{{admin-url}}edit.php?post_type=wp-lead&lead-email-redirect={{lead-email-address}}&scroll-to=wplead_metabox_conversion">'. __( 'Pages Viewed' , 'ma' ) .'</a></h3>
1013
+ </td>
1014
+
1015
+ <td width="160" height="50" align="center" valign="top" border="0">
1016
+ <h3 style="color:#2e2e2e;font-size:15px;"><a style="text-decoration: none;" href="{{admin-url}}edit.php?post_type=wp-lead&lead-email-redirect={{lead-email-address}}&tab=tabs-wpleads_lead_tab_raw_form_data">'. __( 'View Form Data' , 'ma' ) .'</a></h3>
1017
+ </td>
1018
+ </tr>
1019
+ </tbody></table>
1020
+ <!-- end 3 col -->
1021
+ <!-- Start half/half -->
1022
+ <table width="100%" border="0" cellspacing="0" cellpadding="0" style="margin-bottom:10px;">
1023
+ <tbody><tr>
1024
+ <td align="center" width="250" height="30" cellpadding="5">
1025
+ <div><!--[if mso]>
1026
+ <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="{{admin-url}}edit.php?post_type=wp-lead&lead-email-redirect={{lead-email-address}}" style="height:40px;v-text-anchor:middle;width:250px;" arcsize="10%" strokecolor="#7490af" fillcolor="#3A9FD1">
1027
+ <w:anchorlock/>
1028
+ <center style="color:#ffffff;font-family:sans-serif;font-size:13px;font-weight:bold;">'. __( 'View Lead' , 'ma' ) .'</center>
1029
+ </v:roundrect>
1030
+ <![endif]--><a href="{{admin-url}}edit.php?post_type=wp-lead&lead-email-redirect={{lead-email-address}}"
1031
+ style="background-color:#3A9FD1;border:1px solid #7490af;border-radius:4px;color:#ffffff;display:inline-block;font-family:sans-serif;font-size:18px;font-weight:bold;line-height:40px;text-align:center;text-decoration:none;width:250px;-webkit-text-size-adjust:none;mso-hide:all;" title="'. __( 'View the full Lead details in WordPress' , 'ma' ) .'">'. __( 'View Full Lead Details' ,'ma' ) .'</a>
1032
+ </div>
1033
+ </td>
1034
+
1035
+ <td align="center" width="250" height="30" cellpadding="5">
1036
+ <div><!--[if mso]>
1037
+ <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="mailto:{{lead-email-address}}?subject=RE:{{form-name}}&body='. __( 'Thanks for filling out our form.' , 'ma' ) .'" style="height:40px;v-text-anchor:middle;width:250px;" arcsize="10%" strokecolor="#558939" fillcolor="#59b329">
1038
+ <w:anchorlock/>
1039
+ <center style="color:#ffffff;font-family:sans-serif;font-size:13px;font-weight:bold;">'. __( 'Reply to Lead Now' ,'ma' ) .'</center>
1040
+ </v:roundrect>
1041
+ <![endif]--><a href="mailto:{{lead-email-address}}?subject=RE:{{form-name}}&body='. __( 'Thanks for filling out our form on {{current-page-url}}' , 'ma' ).'"
1042
+ style="background-color:#59b329;border:1px solid #558939;border-radius:4px;color:#ffffff;display:inline-block;font-family:sans-serif;font-size:18px;font-weight:bold;line-height:40px;text-align:center;text-decoration:none;width:250px;-webkit-text-size-adjust:none;mso-hide:all;" title="'. __( 'Email This Lead now' , 'ma' ) .'">'. __( 'Reply to Lead Now' , 'ma' ).'</a></div>
1043
+
1044
+ </td>
1045
+ </tr>
1046
+ </tbody>
1047
+ </table>
1048
+ <!-- End half/half -->
1049
+
1050
+ </td>
1051
+ </tr>
1052
+ </tbody></table>
1053
+ </td>
1054
+ </tr>
1055
+ </tbody></table>
1056
+ <table cellpadding="0" cellspacing="0" border="0" align="center" style="border-collapse:collapse;width:600px;font-size:13px;line-height:20px;color:#545454;font-family:Arial,sans-serif;margin-top:0;margin-right:auto;margin-bottom:0;margin-left:auto">
1057
+ <tbody><tr>
1058
+ <td valign="top" width="30" style="color:#272727">&nbsp;</td>
1059
+ <td valign="top" height="18" style="height:18px;color:#272727"></td>
1060
+ <td style="color:#272727">&nbsp;</td>
1061
+ <td style="color:#545454;text-align:right" align="right">&nbsp;</td>
1062
+ <td valign="middle" width="30" style="color:#272727">&nbsp;</td>
1063
+ </tr>
1064
+ <tr>
1065
+ <td valign="middle" width="30" style="color:#272727">&nbsp;</td>
1066
+ <td width="50" height="40" valign="middle" align="left" style="color:#272727">
1067
+ <a href="http://www.inboundnow.com" target="_blank"><img src="{{leads-urlpath}}images/inbound-email.png" height="40" width="40" alt=" " style="outline:none;text-decoration:none;max-width:100%;display:block;width:40px;min-height:40px;border-radius:20px"></a>
1068
+ </td>
1069
+ <td style="color:#272727"><a style="color:#272727;text-decoration:none;" href="http://www.inboundnow.com" target="_blank">
1070
+ '. __( '<b>Leads</b>
1071
+ from Inbound Now' , 'ma' ) .'</a>
1072
+ </td>
1073
+ <td valign="middle" align="left" style="color:#545454;text-align:right">{{date-time}}</td>
1074
+ <td valign="middle" width="30" style="color:#272727">&nbsp;</td>
1075
+ </tr>
1076
+ <tr>
1077
+ <td valign="top" height="6" style="color:#272727;line-height:1px">&nbsp;</td>
1078
+ <td style="color:#272727;line-height:1px">&nbsp;</td>
1079
+ <td style="color:#272727;line-height:1px">&nbsp;</td>
1080
+ <td style="color:#545454;text-align:right;line-height:1px" align="right">&nbsp;</td>
1081
+ <td valign="middle" width="30" style="color:#272727;line-height:1px">&nbsp;</td>
1082
+ </tr>
1083
+ </tbody></table>
1084
+
1085
+ <table cellpadding="0" cellspacing="0" border="0" align="center" style="border-collapse:collapse;width:600px">
1086
+ <tbody><tr>
1087
+ <td valign="top" style="color:#b1b1b1;font-size:11px;line-height:16px;font-family:Arial,sans-serif;text-align:center" align="center">
1088
+ <p style="margin-top:1em;margin-right:0;margin-bottom:1em;margin-left:0"></p>
1089
+ </td>
1090
+ </tr>
1091
+ </tbody></table>
1092
+ </td>
1093
+ </tr>
1094
+ <tr>
1095
+ <td valign="top" height="20">&nbsp;</td>
1096
+ </tr>
1097
+ </tbody></table>
1098
+ </body>';
1099
+
1100
+
1101
+ $email_template['subject'] = apply_filters( 'inbound_new_lead_notification/subject' , '' );
1102
+ $email_template['body'] = apply_filters( 'inbound_new_lead_notification/body' , $html );
1103
 
 
 
 
1104
 
1105
  return $email_template;
1106
  }
shared/classes/class.inbound-forms.akismet.php CHANGED
@@ -156,8 +156,6 @@ if ( !class_exists('Inbound_Akismet') ) {
156
  }
157
  }
158
 
159
-
160
-
161
  return '';
162
  }
163
 
156
  }
157
  }
158
 
 
 
159
  return '';
160
  }
161
 
shared/classes/class.lead-storage.php CHANGED
@@ -1,714 +1,713 @@
1
- <?php
2
- /**
3
- * Inbound Lead Storage
4
- *
5
- * - Handles lead creation and data storage
6
- */
7
- if (!class_exists('LeadStorage')) {
8
- class LeadStorage {
9
- static $mapped_fields;
10
- static $is_ajax;
11
-
12
- /**
13
- * Initialize class
14
- */
15
- static function init() {
16
- /* determines if in ajax mode */
17
- self::set_mode();
18
-
19
- /* sets up ajax listeners */
20
- add_action('wp_ajax_inbound_lead_store', array(__CLASS__, 'inbound_lead_store'), 10, 1);
21
- add_action('wp_ajax_nopriv_inbound_lead_store', array(__CLASS__, 'inbound_lead_store'), 10, 1);
22
-
23
- /* filters name data to build a more comprehensive data set */
24
- add_filter( 'inboundnow_store_lead_pre_filter_data', array(__CLASS__, 'improve_lead_name'), 10 , 1);
25
- }
26
-
27
- /**
28
- * Checks if running in ajax mode
29
- */
30
- static function set_mode( $mode = 'auto' ) {
31
- // http://davidwalsh.name/detect-ajax
32
- switch( $mode ) {
33
- case 'auto':
34
- self::$is_ajax = ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ? true : false;
35
- BREAK;
36
- case 'ajax':
37
- self::$is_ajax = true;
38
- BREAK;
39
- case 'return':
40
- self::$is_ajax = false;
41
- BREAK;
42
- }
43
- }
44
-
45
- /**
46
- * Stores lead
47
- */
48
- static function inbound_lead_store( $args ) {
49
- global $user_ID, $wpdb;
50
- if (!is_array($args)) { $args = array(); }
51
-
52
- /* Mergs $args with POST request for support of ajax and direct calls */
53
- if(isset($_POST)){
54
- $args = array_merge( $args, $_POST );
55
- }
56
-
57
- $lead = array();
58
- if(isset($user_ID)){
59
- $lead['user_ID'] = $user_ID;
60
- }
61
- /* Current wordpress time from settings */
62
- $time = current_time( 'timestamp', 0 );
63
- $lead['wordpress_date_time'] = date("Y-m-d G:i:s T", $time);
64
-
65
- $lead['email'] = str_replace("%40", "@", self::check_val('email', $args));
66
- $lead['name'] = str_replace("%20", " ", self::check_val('full_name', $args));
67
- $lead['first_name'] = str_replace("%20", "", self::check_val('first_name', $args));
68
- $lead['last_name'] = str_replace("%20", "", self::check_val('last_name', $args));
69
- $lead['page_id'] = self::check_val('page_id', $args);
70
- $lead['page_views'] = self::check_val('page_views', $args);
71
- $lead['raw_params'] = self::check_val('raw_params', $args);
72
-
73
- $lead['mapped_params'] = self::check_val('mapped_params', $args);
74
- $lead['url_params'] = self::check_val('url_params', $args);
75
- $lead['variation'] = self::check_val('variation', $args);
76
- $lead['source'] = self::check_val('source', $args);
77
- $lead['ip_address'] = self::lookup_ip_address();
78
-
79
- if($lead['mapped_params']){
80
- parse_str($lead['mapped_params'], $mappedData);
81
- } else {
82
- $mappedData = array();
83
- }
84
-
85
- $mappedData = self::improve_mapping($mappedData, $lead);
86
-
87
- /* prepate lead lists */
88
- $lead['lead_lists'] = (isset($args['lead_lists'])) ? $args['lead_lists'] : null;
89
- if ( !$lead['lead_lists'] && array_key_exists('inbound_form_lists', $mappedData) ) {
90
- $lead['lead_lists'] = explode(",", $mappedData['inbound_form_lists']);
91
- }
92
-
93
- /* Look for direct key matches & clean up $lead_data */
94
- $lead = apply_filters( 'inboundnow_store_lead_pre_filter_data', $lead, $args);
95
-
96
- /* TODO have fallbacks for existing lead ID or Lead UID lookups*/
97
- /* check for set email */
98
- if ( (isset($lead['email']) && !empty($lead['email']) && strstr($lead['email'] ,'@'))) {
99
-
100
-
101
- $leadExists = self::lookup_lead_by_email($lead['email']);
102
-
103
- /* Update Lead if Exists else Create New Lead */
104
- if ( $leadExists ) {
105
- $lead['id'] = $leadExists;
106
- /* action hook on existing leads only */
107
- do_action('wpleads_existing_lead_update', $lead);
108
- } else {
109
- /* Create new lead if one doesnt exist */
110
- $lead['id'] = self::store_new_lead($lead);
111
- }
112
-
113
- /* do everything else for lead storage */
114
- self::update_common_meta($lead);
115
-
116
- do_action('wpleads_after_conversion_lead_insert', $lead['id']); // action hook on all lead inserts
117
-
118
- /* Add Leads to List on creation */
119
- if(!empty($lead['lead_lists']) && is_array($lead['lead_lists'])){
120
- global $Inbound_Leads;
121
- $Inbound_Leads->add_lead_to_list($lead['id'], $lead['lead_lists'], 'wplead_list_category');
122
- }
123
-
124
- /* Store page views for people with ajax tracking off */
125
- $ajax_tracking_off = false; // get_option
126
- if($lead['page_views'] && $ajax_tracking_off ) {
127
- self::store_page_views($lead);
128
- }
129
-
130
- /* Store Mapped Form Data */
131
- if(!empty($mappedData)){
132
- self::store_mapped_data($lead, $mappedData);
133
- }
134
-
135
- /* Store past search history */
136
- if(isset($lead['search_data'])){
137
- self::store_search_history($lead);
138
- }
139
-
140
- /* Store ConversionData */
141
- if ( isset($lead['page_id']) && $lead['page_id'] ) {
142
- self::store_conversion_data($lead);
143
- }
144
-
145
- /* Store Lead Source */
146
- if ( isset($lead['source']) ) {
147
- self::store_referral_data($lead);
148
- }
149
-
150
- /* Store URL Params */
151
- if($lead['url_params']) {
152
- $param_array = json_decode(stripslashes($lead['url_params']));
153
- //print_r($param_array); exit;
154
- if(is_array($param_array)){
155
-
156
- }
157
- }
158
-
159
- /* Store Conversion Data to LANDING PAGE/CTA DATA */
160
- if (isset($lead['page_id'])) {
161
- self::store_conversion_stats($lead);
162
- }
163
-
164
- /* Store IP addresss & Store GEO Data */
165
- if ($lead['ip_address']) {
166
- update_post_meta( $lead['id'], 'wpleads_ip_address', $ip_addresses );
167
- //self::store_geolocation_data($lead);
168
- }
169
-
170
-
171
- if ( self::$is_ajax ) {
172
- echo $lead['id'];
173
- do_action('inbound_store_lead_post', $lead );
174
- exit;
175
- } else {
176
- do_action('inbound_store_lead_post', $lead );
177
- return $lead['id'];
178
- }
179
-
180
- }
181
- }
182
-
183
- /**
184
- * Creates new lead in wp-lead post type
185
- */
186
- static function store_new_lead($lead){
187
- /* Create New Lead */
188
- $post = array(
189
- 'post_title' => $lead['email'],
190
- //'post_content' => $json,
191
- 'post_status' => 'publish',
192
- 'post_type' => 'wp-lead',
193
- 'post_author' => 1
194
- );
195
-
196
- //$post = add_filter('lp_leads_post_vars',$post);
197
- $id = wp_insert_post($post);
198
- /* specific updates for new leads */
199
- update_post_meta( $id, 'wpleads_email_address', $lead['email'] );
200
- /* new lead run simple page_view storage */
201
- update_post_meta( $id, 'page_views', $lead['page_views']);
202
- /* dont need update_post_meta( $id, 'wpleads_page_view_count', $lead['page_view_count']); */
203
-
204
- do_action('wpleads_new_lead_insert', $lead ); // action hook on new leads only
205
- return $id;
206
- }
207
-
208
- /**
209
- * Updates pages viewed object
210
- */
211
- static function store_page_views($lead){
212
- $page_view_data = get_post_meta( $lead['id'], 'page_views', TRUE );
213
- $page_view_data = json_decode($page_view_data,true);
214
-
215
- // If page_view meta exists do this
216
- if (is_array($page_view_data)) {
217
- $new_page_views = self::json_array_merge( $page_view_data, $lead['page_views']);
218
- $page_views = json_encode($new_page_views);
219
- } else {
220
- // Create page_view meta if it doesn't exist
221
- $page_views = $lead['page_views'];
222
- $page_views = json_encode($page_views);
223
- }
224
- update_post_meta($lead['id'], 'page_views', $page_views );
225
- }
226
-
227
- /**
228
- * Prefixes keys with wpleads_ if key is not prepended with wpleads_
229
- */
230
- static function store_mapped_data($lead, $mappedData){
231
-
232
- foreach ($mappedData as $key => $value) {
233
-
234
- if (!$value) {
235
- continue;
236
- }
237
-
238
- /* sanitise inputs */
239
- if (is_string($value)) {
240
- $value = strip_tags( $value );
241
- }
242
-
243
- update_post_meta($lead['id'], $key, $value);
244
-
245
- /* Old convention with wpleads_ prefix */
246
- if( !strstr($key,'wpleads_') ) {
247
- update_post_meta($lead['id'], 'wpleads_'.$key, $value);
248
- } else {
249
- update_post_meta($lead['id'], $key, $value);
250
- }
251
-
252
- }
253
- }
254
-
255
- /**
256
- * Updates search history object
257
- */
258
- static function store_search_history($lead){
259
- $search = $lead['search_data'];
260
- $search_data = get_post_meta( $lead['id'], 'wpleads_search_data', TRUE );
261
- $search_data = json_decode($search_data,true);
262
- if (is_array($search_data)){
263
- $s_count = count($search_data) + 1;
264
- $loop_count = 1;
265
- foreach ($search as $key => $value) {
266
- $search_data[$s_count]['date'] = $search[$loop_count]['date'];
267
- $search_data[$s_count]['value'] = $search[$loop_count]['value'];
268
- $s_count++; $loop_count++;
269
- }
270
- } else {
271
- // Create search obj
272
- $s_count = 1;
273
- $loop_count = 1;
274
- foreach ($search as $key => $value) {
275
- $search_data[$s_count]['date'] = $search[$loop_count]['date'];
276
- $search_data[$s_count]['value'] = $search[$loop_count]['value'];
277
- $s_count++; $loop_count++;
278
- }
279
- }
280
- $search_data = json_encode($search_data);
281
- update_post_meta($lead['id'], 'wpleads_search_data', $search_data); // Store search object
282
- }
283
-
284
- /**
285
- * updates conversion data object
286
- */
287
- static function store_conversion_data( $lead ) {
288
-
289
- $conversion_data = get_post_meta( $lead['id'], 'wpleads_conversion_data', TRUE );
290
- $conversion_data = json_decode($conversion_data,true);
291
- $variation = $lead['variation'];
292
-
293
- if ( is_array($conversion_data)) {
294
- $c_count = count($conversion_data) + 1;
295
- $conversion_data[$c_count]['id'] = $lead['page_id'];
296
- $conversion_data[$c_count]['variation'] = $variation;
297
- $conversion_data[$c_count]['datetime'] = $lead['wordpress_date_time'];
298
- } else {
299
- $c_count = 1;
300
- $conversion_data[1]['id'] = $lead['page_id'];
301
- $conversion_data[1]['variation'] = $variation;
302
- $conversion_data[1]['datetime'] = $lead['wordpress_date_time'];
303
- $conversion_data[1]['first_time'] = 1;
304
- }
305
-
306
- $lead['conversion_data'] = json_encode($conversion_data);
307
- update_post_meta($lead['id'],'wpleads_conversion_count', $c_count); // Store conversions count
308
- update_post_meta($lead['id'], 'wpleads_conversion_data', $lead['conversion_data']);// Store conversion obj
309
-
310
- }
311
- /**
312
- * Store Conversion Data to LANDING PAGE/CTA DATA
313
- */
314
- static function store_conversion_stats($lead){
315
- $page_conversion_data = get_post_meta( $lead['page_id'], '_inbound_conversion_data', TRUE );
316
- $page_conversion_data = json_decode($page_conversion_data,true);
317
- $version = ($lead['variation'] != 'default') ? $lead['variation'] : '0';
318
- if (is_array($page_conversion_data)) {
319
- $convert_count = count($page_conversion_data) + 1;
320
- $page_conversion_data[$convert_count]['lead_id'] = $lead['id'];
321
- $page_conversion_data[$convert_count]['variation'] = $version;
322
- $page_conversion_data[$convert_count]['datetime'] = $lead['wordpress_date_time'];
323
- } else {
324
- $page_conversion_data[1]['lead_id'] = $lead['id'];
325
- $page_conversion_data[1]['variation'] = $version;
326
- $page_conversion_data[1]['datetime'] = $lead['wordpress_date_time'];
327
- }
328
- $page_conversion_data = json_encode($page_conversion_data);
329
- update_post_meta($lead['page_id'], '_inbound_conversion_data', $page_conversion_data);
330
- }
331
-
332
- /**
333
- * Stores referral data
334
- */
335
- static function store_referral_data($lead) {
336
- $referral_data = get_post_meta( $lead['id'], 'wpleads_referral_data', TRUE );
337
-
338
- // Parse referral for additional data
339
- include_once( INBOUNDNOW_SHARED_PATH. 'assets/includes/Snowplow/RefererParser/INBOUND_Parser.php');
340
- include_once( INBOUNDNOW_SHARED_PATH .'assets/includes/Snowplow/RefererParser/INBOUND_Referer.php');
341
- include_once(INBOUNDNOW_SHARED_PATH . 'assets/includes/Snowplow/RefererParser/INBOUND_Medium.php');
342
- // intialized the parser class
343
- $parser = new INBOUND_Parser();
344
- //$array = array('http://google.com', 'http://twitter.com', 'http://tumblr.com?query=test', '');
345
- $referer = $parser->parse($lead['source']);
346
-
347
- if ( $referer->isKnown() ) {
348
- $ref_type = $referer->getMedium();
349
-
350
- } else {
351
- // check if ref exists
352
- $ref_type = ($lead['source'] === "Direct Traffic") ? 'Direct Traffic' : 'referral';
353
- }
354
-
355
- $referral_data = json_decode($referral_data,true);
356
- if (is_array($referral_data)){
357
- $r_count = count($referral_data) + 1;
358
- $referral_data[$r_count]['source'] = $lead['source'];
359
- $referral_data[$r_count]['type'] = $ref_type;
360
- $referral_data[$r_count]['datetime'] = $lead['wordpress_date_time'];
361
- } else {
362
- $referral_data[1]['source'] = $lead['source'];
363
- $referral_data[1]['type'] = $ref_type;
364
- $referral_data[1]['datetime'] = $lead['wordpress_date_time'];
365
- $referral_data[1]['original_source'] = 1;
366
- }
367
-
368
- $lead['referral_data'] = json_encode($referral_data);
369
- //echo $lead['referral_data']; exit;
370
- update_post_meta($lead['id'], 'wpleads_referral_data', $lead['referral_data']); // Store referral object
371
- update_post_meta($lead['id'], 'wpleads_referral_type', $ref_type); // Store referral object
372
- }
373
-
374
- /**
375
- * Loop trough lead_data array and update post meta
376
- */
377
- static function update_common_meta($lead) {
378
-
379
- if (!empty($lead['user_ID'])) {
380
- /* Update user_ID if exists */
381
- update_post_meta( $lead['id'], 'wpleads_wordpress_user_id', $lead['user_ID'] );
382
- }
383
-
384
- /* Update wp_lead_uid if exist */
385
- if (!empty($lead['wp_lead_uid'])) {
386
- update_post_meta( $lead['id'], 'wp_leads_uid', $lead['wp_lead_uid'] );
387
- }
388
-
389
- /* Update email address */
390
- if (!empty($lead['email'])) {
391
- update_post_meta( $lead['id'], 'wplead_emails_address', $lead['email'] );
392
- }
393
-
394
- /* Update mappable fields that have a value associated with them */
395
- $lead_fields = Leads_Field_Map::build_map_array();
396
- foreach ( $lead_fields as $key => $value ) {
397
- $shortkey = str_replace('wpleads_' , '' , $key );
398
- if (!empty($lead[$shortkey]) && $lead[$shortkey] !== 0 ) {
399
- update_post_meta( $lead['id'], $key, $lead[$shortkey] );
400
- }
401
- }
402
- }
403
-
404
- /**
405
- * Connects to geoplugin.net and gets data on IP address and sets it into historical log
406
- * @param ARRAY $lead_data
407
- */
408
- static function store_geolocation_data( $lead ) {
409
-
410
- $ip_addresses = get_post_meta( $lead['id'], 'wpleads_ip_address', true );
411
- $ip_addresses = json_decode( stripslashes($ip_addresses) , true);
412
-
413
- if (!$ip_addresses) {
414
- $ip_addresses = array();
415
- }
416
-
417
- $new_record[ $lead['ip_address'] ]['ip_address'] = $lead['ip_address'];
418
-
419
-
420
- /* ignore for local environments */
421
- if ($lead['ip_address']!= "127.0.0.1"){ // exclude localhost
422
- $response = wp_remote_get('http://www.geoplugin.net/php.gp?ip='.$lead['ip_address']);
423
- if ( !is_wp_error($response) && isset($response['body']) ) {
424
- $geo_array = @unserialize($response['body']);
425
- $new_record[ $lead['ip_address'] ]['geodata'] = $geo_array;
426
- }
427
-
428
- }
429
-
430
- $ip_addresses = array_merge( $new_record, $ip_addresses );
431
- $ip_addresses = json_encode( $ip_addresses );
432
-
433
- update_post_meta( $lead['id'], 'wpleads_ip_address', $ip_addresses );
434
- }
435
-
436
- /**
437
- * Updates raw form data object
438
- */
439
- static function store_raw_form_data($lead){
440
- /* Raw Form Values Store */
441
- if ($lead_data['form_input_values']) {
442
- $raw_post_data = get_post_meta($$lead['id'],'wpleads_raw_post_data', true);
443
- $a1 = json_decode( $raw_post_data, true );
444
- $a2 = json_decode( stripslashes($lead_data['form_input_values']), true );
445
- $exclude_array = array('card_number','card_cvc','card_exp_month','card_exp_year'); // add filter
446
- $lead_mapping_fields = Leads_Field_Map::build_map_array();
447
-
448
- foreach ($a2 as $key=>$value)
449
- {
450
- if (array_key_exists( $key , $exclude_array )) {
451
- unset($a2[$key]);
452
- continue;
453
- }
454
- if (preg_match("/\[\]/", $key)) {
455
- $key = str_replace("[]", "", $key); // fix array value keys
456
- }
457
- if (array_key_exists($key, $lead_mapping_fields)) {
458
- update_post_meta( $lead_id, $key, $value );
459
- }
460
-
461
- if (stristr($key,'company')) {
462
- update_post_meta( $lead_id, 'wpleads_company_name', $value );
463
- }
464
- else if (stristr($key,'website'))
465
- {
466
- $websites = get_post_meta( $lead_id, 'wpleads_websites', $value );
467
- if(is_array($websites)) {
468
- $array_websites = explode(';',$websites);
469
- }
470
- $array_websites[] = $value;
471
- $websites = implode(';',$array_websites);
472
- update_post_meta( $lead_id, 'wpleads_websites', $websites );
473
- }
474
- }
475
- // Merge form fields if exist
476
- if (is_array($a1)) {
477
- $new_raw_post_data = array_merge_recursive( $a1, $a2 );
478
- } else {
479
- $new_raw_post_data = $a2;
480
- }
481
- $new_raw_post_data = json_encode( $new_raw_post_data );
482
- update_post_meta( $lead_id,'wpleads_raw_post_data', $new_raw_post_data );
483
- }
484
- }
485
-
486
- /**
487
- * Parses & improves lead name
488
- */
489
- static function improve_lead_name( $lead ) {
490
- /* */
491
- $lead['name'] = (isset($lead['name'])) ? $lead['name'] : '';
492
-
493
- /* do not let names with 'false' pass */
494
- if ( !empty($lead['name']) && $lead['name'] == 'false' ) {
495
- $lead['name'] = '';
496
- }
497
- if ( !empty($lead['first_name']) && $lead['first_name'] == 'false' ) {
498
- $lead['first_name'] = '';
499
- }
500
-
501
- /* if last name empty and full name present */
502
- if ( empty($lead['last_name']) && $lead['name'] ) {
503
- $parts = explode(' ' , $lead['name']);
504
-
505
- /* Set first name */
506
- $lead['first_name'] = $parts[0];
507
-
508
- /* Set last name */
509
- if (isset($parts[1])) {
510
- $lead['last_name'] = $parts[1];
511
- }
512
- }
513
- /* if last name empty and first name present */
514
- else if (empty($lead['last_name']) && $lead['first_name'] ) {
515
- $parts = explode(' ' , $lead['first_name']);
516
-
517
- /* Set First Name */
518
- $lead['first_name'] = $parts[0];
519
-
520
- /* Set Last Name */
521
- if (isset($parts[1])) {
522
- $lead['last_name'] = $parts[1];
523
- }
524
- }
525
-
526
- /* set full name */
527
- if (!$lead['name'] && $lead['first_name'] && $lead['last_name'] ) {
528
- $lead['name'] = $lead['first_name'] .' '. $lead['last_name'];
529
- }
530
-
531
- return $lead;
532
- }
533
-
534
- /**
535
- * Uses mapped data if not programatically set
536
- */
537
- static function improve_mapping($mappedData, $lead) {
538
-
539
- /* check to see if there are any mapped values arriving through inbound_store_lead */
540
- $fields = Leads_Field_Map::build_map_array();
541
-
542
- foreach ($fields as $key => $label ) {
543
- if( isset( $lead[ $key ]) && !isset($mappedData[$key]) ) {
544
- $mappedData[$key] = $lead[ $key ];
545
- }
546
- }
547
-
548
- /* remove instances of wpleads_ */
549
- $newMap = array();
550
- foreach ($mappedData as $key=>$value) {
551
- $key = str_replace('wpleads_','',$key);
552
- $newMap[$key] = $value;
553
- }
554
-
555
- /* Set names if not mapped */
556
- $newMap['first_name'] = (!isset($newMap['first_name'])) ? $lead['first_name'] : $newMap['first_name'];
557
- $newMap['last_name'] = (!isset($newMap['last_name'])) ? $lead['last_name'] : $newMap['last_name'];
558
-
559
- /* improve mapped names */
560
- $newMap = self::improve_lead_name( $newMap );
561
-
562
- return $newMap;
563
- }
564
-
565
- /**
566
- * Search lead by email
567
- */
568
- static function lookup_lead_by_email($email){
569
- global $wpdb;
570
- $query = $wpdb->prepare(
571
- 'SELECT ID FROM ' . $wpdb->posts . '
572
- WHERE post_title = %s
573
- AND post_type = \'wp-lead\'',
574
- $email
575
- );
576
- $wpdb->query( $query );
577
- if ( $wpdb->num_rows ) {
578
- $lead_id = $wpdb->get_var( $query );
579
- return $lead_id;
580
- } else {
581
- return false;
582
- }
583
-
584
- }
585
-
586
- static function check_val($key, $args) {
587
- $val = (isset($args[$key])) ? $args[$key] : false;
588
- return $val;
589
- }
590
- static function json_array_merge( $arr1, $arr2 ) {
591
- $keys = array_keys( $arr2 );
592
- foreach( $keys as $key ) {
593
- if( isset( $arr1[$key] )
594
- && is_array( $arr1[$key] )
595
- && is_array( $arr2[$key] )
596
- ) {
597
- $arr1[$key] = my_merge( $arr1[$key], $arr2[$key] );
598
- } else {
599
- $arr1[$key] = $arr2[$key];
600
- }
601
- }
602
- return $arr1;
603
- }
604
-
605
- /**
606
- * Discover session IP address
607
- */
608
- static function lookup_ip_address() {
609
- if(isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
610
- if(isset($_SERVER["HTTP_CLIENT_IP"])) {
611
- $proxy = $_SERVER["HTTP_CLIENT_IP"];
612
- } else {
613
- $proxy = $_SERVER["REMOTE_ADDR"];
614
- }
615
- $ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
616
- } else {
617
- if(isset($_SERVER["HTTP_CLIENT_IP"])) {
618
- $ip = $_SERVER["HTTP_CLIENT_IP"];
619
- } else {
620
- $ip = $_SERVER["REMOTE_ADDR"];
621
- }
622
- }
623
- return $ip;
624
- }
625
-
626
- }
627
-
628
- LeadStorage::init();
629
- }
630
-
631
- /**
632
- * Legacy function used by some extensions
633
- * @param ARRAY $args legacy dataset of mapped lead fields
634
- * @param BOOL $return set to true to disable printing of lead id
635
- */
636
- if (!function_exists('inbound_store_lead')) {
637
- function inbound_store_lead( $args , $return = true ) {
638
- global $user_ID, $wpdb;
639
-
640
- if (!is_array($args)) {
641
- $args = array();
642
- }
643
-
644
- /* Mergs $args with POST request for support of ajax and direct calls */
645
- $args = array_merge( $args , $_POST );
646
-
647
- /* wpleads_email_address becomes wpleads_email */
648
- $args['email'] = $args['wpleads_email_address'];
649
-
650
- /* loop through and remove wpleads_ (we will add them back in the new method ) */
651
- foreach ($args as $key => $value) {
652
- $newkey = str_replace( 'wpleads_' , '' , $key );
653
- unset($args[$key]);
654
- $args[$newkey] = $value;
655
- }
656
-
657
- /* Send data through new method */
658
- $Leads = new LeadStorage();
659
- if ($return) {
660
- $Leads->set_mode('return');
661
- } else {
662
- $Leads->set_mode('ajax');
663
- }
664
-
665
- /* prepare lead lists as array */
666
- if (isset($args['lead_lists']) && !is_array($args['lead_lists'])) {
667
- $args['lead_lists'] = explode(',',$args['lead_lists']);
668
- }
669
-
670
- $lead_id = $Leads::inbound_lead_store( $args );
671
-
672
- return $lead_id;
673
-
674
-
675
- }
676
- }
677
-
678
-
679
- /**
680
- * Legacy functions for adding conversion to lead profile
681
- * @param INT $lead_id
682
- * @param ARRAY dataset of lead informaiton
683
- */
684
- if (!function_exists('inbound_add_conversion_to_lead')) {
685
- function inbound_add_conversion_to_lead( $lead_id , $lead_data ) {
686
-
687
-
688
- if ( $lead_data['page_id'] ) {
689
- $time = current_time( 'timestamp', 0 ); // Current wordpress time from settings
690
- $lead_data['wordpress_date_time'] = date("Y-m-d G:i:s T", $time);
691
- $conversion_data = get_post_meta( $lead_id, 'wpleads_conversion_data', TRUE );
692
- $conversion_data = json_decode($conversion_data,true);
693
- $variation = $lead_data['variation'];
694
-
695
- if ( is_array($conversion_data)) {
696
- $c_count = count($conversion_data) + 1;
697
- $conversion_data[$c_count]['id'] = $lead_data['page_id'];
698
- $conversion_data[$c_count]['variation'] = $variation;
699
- $conversion_data[$c_count]['datetime'] = $lead_data['wordpress_date_time'];
700
- } else {
701
- $c_count = 1;
702
- $conversion_data[$c_count]['id'] = $lead_data['page_id'];
703
- $conversion_data[$c_count]['variation'] = $variation;
704
- $conversion_data[$c_count]['datetime'] = $lead_data['wordpress_date_time'];
705
- $conversion_data[$c_count]['first_time'] = 1;
706
- }
707
-
708
- $lead_data['conversion_data'] = json_encode($conversion_data);
709
- update_post_meta($lead_id,'wpleads_conversion_count', $c_count); // Store conversions count
710
- update_post_meta($lead_id, 'wpleads_conversion_data', $lead_data['conversion_data']); // Store conversion object
711
-
712
- }
713
- }
714
- }
1
+ <?php
2
+ /**
3
+ * Inbound Lead Storage
4
+ *
5
+ * - Handles lead creation and data storage
6
+ */
7
+ if (!class_exists('LeadStorage')) {
8
+ class LeadStorage {
9
+ static $mapped_fields;
10
+ static $is_ajax;
11
+
12
+ /**
13
+ * Initialize class
14
+ */
15
+ static function init() {
16
+ /* determines if in ajax mode */
17
+ self::set_mode();
18
+
19
+ /* sets up ajax listeners */
20
+ add_action('wp_ajax_inbound_lead_store', array(__CLASS__, 'inbound_lead_store'), 10, 1);
21
+ add_action('wp_ajax_nopriv_inbound_lead_store', array(__CLASS__, 'inbound_lead_store'), 10, 1);
22
+
23
+ /* filters name data to build a more comprehensive data set */
24
+ add_filter( 'inboundnow_store_lead_pre_filter_data', array(__CLASS__, 'improve_lead_name'), 10 , 1);
25
+ }
26
+
27
+ /**
28
+ * Checks if running in ajax mode
29
+ */
30
+ static function set_mode( $mode = 'auto' ) {
31
+ // http://davidwalsh.name/detect-ajax
32
+ switch( $mode ) {
33
+ case 'auto':
34
+ self::$is_ajax = ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ? true : false;
35
+ BREAK;
36
+ case 'ajax':
37
+ self::$is_ajax = true;
38
+ BREAK;
39
+ case 'return':
40
+ self::$is_ajax = false;
41
+ BREAK;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Stores lead
47
+ */
48
+ static function inbound_lead_store( $args ) {
49
+ global $user_ID, $wpdb;
50
+ if (!is_array($args)) { $args = array(); }
51
+
52
+ /* Mergs $args with POST request for support of ajax and direct calls */
53
+ if(isset($_POST)){
54
+ $args = array_merge( $args, $_POST );
55
+ }
56
+
57
+ $lead = array();
58
+ if(isset($user_ID)){
59
+ $lead['user_ID'] = $user_ID;
60
+ }
61
+ /* Current wordpress time from settings */
62
+ $time = current_time( 'timestamp', 0 );
63
+ $lead['wordpress_date_time'] = date("Y-m-d G:i:s T", $time);
64
+
65
+ $lead['email'] = str_replace("%40", "@", self::check_val('email', $args));
66
+ $lead['name'] = str_replace("%20", " ", self::check_val('full_name', $args));
67
+ $lead['first_name'] = str_replace("%20", "", self::check_val('first_name', $args));
68
+ $lead['last_name'] = str_replace("%20", "", self::check_val('last_name', $args));
69
+ $lead['page_id'] = self::check_val('page_id', $args);
70
+ $lead['page_views'] = self::check_val('page_views', $args);
71
+ $lead['raw_params'] = self::check_val('raw_params', $args);
72
+
73
+ $lead['mapped_params'] = self::check_val('mapped_params', $args);
74
+ $lead['url_params'] = self::check_val('url_params', $args);
75
+ $lead['variation'] = self::check_val('variation', $args);
76
+ $lead['source'] = self::check_val('source', $args);
77
+ $lead['ip_address'] = self::lookup_ip_address();
78
+
79
+ if($lead['mapped_params']){
80
+ parse_str($lead['mapped_params'], $mappedData);
81
+ } else {
82
+ $mappedData = array();
83
+ }
84
+
85
+ $mappedData = self::improve_mapping($mappedData, $lead);
86
+
87
+ /* prepate lead lists */
88
+ $lead['lead_lists'] = (isset($args['lead_lists'])) ? $args['lead_lists'] : null;
89
+ if ( !$lead['lead_lists'] && array_key_exists('inbound_form_lists', $mappedData) ) {
90
+ $lead['lead_lists'] = explode(",", $mappedData['inbound_form_lists']);
91
+ }
92
+
93
+ /* Look for direct key matches & clean up $lead_data */
94
+ $lead = apply_filters( 'inboundnow_store_lead_pre_filter_data', $lead, $args);
95
+
96
+ /* TODO have fallbacks for existing lead ID or Lead UID lookups*/
97
+ /* check for set email */
98
+ if ( (isset($lead['email']) && !empty($lead['email']) && strstr($lead['email'] ,'@'))) {
99
+
100
+
101
+ $leadExists = self::lookup_lead_by_email($lead['email']);
102
+
103
+ /* Update Lead if Exists else Create New Lead */
104
+ if ( $leadExists ) {
105
+ $lead['id'] = $leadExists;
106
+ /* action hook on existing leads only */
107
+ do_action('wpleads_existing_lead_update', $lead);
108
+ } else {
109
+ /* Create new lead if one doesnt exist */
110
+ $lead['id'] = self::store_new_lead($lead);
111
+ }
112
+
113
+ /* do everything else for lead storage */
114
+ self::update_common_meta($lead);
115
+
116
+ do_action('wpleads_after_conversion_lead_insert', $lead['id']); // action hook on all lead inserts
117
+
118
+ /* Add Leads to List on creation */
119
+ if(!empty($lead['lead_lists']) && is_array($lead['lead_lists'])){
120
+ global $Inbound_Leads;
121
+ $Inbound_Leads->add_lead_to_list($lead['id'], $lead['lead_lists'], 'wplead_list_category');
122
+ }
123
+
124
+ /* Store page views for people with ajax tracking off */
125
+ $ajax_tracking_off = false; // get_option
126
+ if($lead['page_views'] && $ajax_tracking_off ) {
127
+ self::store_page_views($lead);
128
+ }
129
+
130
+ /* Store Mapped Form Data */
131
+ if(!empty($mappedData)){
132
+ self::store_mapped_data($lead, $mappedData);
133
+ }
134
+
135
+ /* Store past search history */
136
+ if(isset($lead['search_data'])){
137
+ self::store_search_history($lead);
138
+ }
139
+
140
+ /* Store ConversionData */
141
+ if ( isset($lead['page_id']) && $lead['page_id'] ) {
142
+ self::store_conversion_data($lead);
143
+ }
144
+
145
+ /* Store Lead Source */
146
+ if ( isset($lead['source']) ) {
147
+ self::store_referral_data($lead);
148
+ }
149
+
150
+ /* Store URL Params */
151
+ if($lead['url_params']) {
152
+ $param_array = json_decode(stripslashes($lead['url_params']));
153
+ //print_r($param_array); exit;
154
+ if(is_array($param_array)){
155
+
156
+ }
157
+ }
158
+
159
+ /* Store Conversion Data to LANDING PAGE/CTA DATA */
160
+ if (isset($lead['page_id'])) {
161
+ self::store_conversion_stats($lead);
162
+ }
163
+
164
+ /* Store IP addresss & Store GEO Data */
165
+ if ($lead['ip_address']) {
166
+ update_post_meta( $lead['id'], 'wpleads_ip_address', $lead['ip_address'] );
167
+ //self::store_geolocation_data($lead);
168
+ }
169
+
170
+
171
+ if ( self::$is_ajax ) {
172
+ echo $lead['id'];
173
+ do_action('inbound_store_lead_post', $lead );
174
+ exit;
175
+ } else {
176
+ do_action('inbound_store_lead_post', $lead );
177
+ return $lead['id'];
178
+ }
179
+
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Creates new lead in wp-lead post type
185
+ */
186
+ static function store_new_lead($lead){
187
+ /* Create New Lead */
188
+ $post = array(
189
+ 'post_title' => $lead['email'],
190
+ //'post_content' => $json,
191
+ 'post_status' => 'publish',
192
+ 'post_type' => 'wp-lead',
193
+ 'post_author' => 1
194
+ );
195
+
196
+ //$post = add_filter('lp_leads_post_vars',$post);
197
+ $id = wp_insert_post($post);
198
+ /* specific updates for new leads */
199
+ update_post_meta( $id, 'wpleads_email_address', $lead['email'] );
200
+ /* new lead run simple page_view storage */
201
+ update_post_meta( $id, 'page_views', $lead['page_views']);
202
+ /* dont need update_post_meta( $id, 'wpleads_page_view_count', $lead['page_view_count']); */
203
+
204
+ do_action('wpleads_new_lead_insert', $lead ); // action hook on new leads only
205
+ return $id;
206
+ }
207
+
208
+ /**
209
+ * Updates pages viewed object
210
+ */
211
+ static function store_page_views($lead){
212
+ $page_view_data = get_post_meta( $lead['id'], 'page_views', TRUE );
213
+ $page_view_data = json_decode($page_view_data,true);
214
+
215
+ // If page_view meta exists do this
216
+ if (is_array($page_view_data)) {
217
+ $new_page_views = self::json_array_merge( $page_view_data, $lead['page_views']);
218
+ $page_views = json_encode($new_page_views);
219
+ } else {
220
+ // Create page_view meta if it doesn't exist
221
+ $page_views = $lead['page_views'];
222
+ $page_views = json_encode($page_views);
223
+ }
224
+ update_post_meta($lead['id'], 'page_views', $page_views );
225
+ }
226
+
227
+ /**
228
+ * Prefixes keys with wpleads_ if key is not prepended with wpleads_
229
+ */
230
+ static function store_mapped_data($lead, $mappedData){
231
+
232
+ foreach ($mappedData as $key => $value) {
233
+
234
+ if (!$value) {
235
+ continue;
236
+ }
237
+
238
+ /* sanitise inputs */
239
+ if (is_string($value)) {
240
+ $value = strip_tags( $value );
241
+ }
242
+
243
+ update_post_meta($lead['id'], $key, $value);
244
+
245
+ /* Old convention with wpleads_ prefix */
246
+ if( !strstr($key,'wpleads_') ) {
247
+ update_post_meta($lead['id'], 'wpleads_'.$key, $value);
248
+ } else {
249
+ update_post_meta($lead['id'], $key, $value);
250
+ }
251
+
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Updates search history object
257
+ */
258
+ static function store_search_history($lead){
259
+ $search = $lead['search_data'];
260
+ $search_data = get_post_meta( $lead['id'], 'wpleads_search_data', TRUE );
261
+ $search_data = json_decode($search_data,true);
262
+ if (is_array($search_data)){
263
+ $s_count = count($search_data) + 1;
264
+ $loop_count = 1;
265
+ foreach ($search as $key => $value) {
266
+ $search_data[$s_count]['date'] = $search[$loop_count]['date'];
267
+ $search_data[$s_count]['value'] = $search[$loop_count]['value'];
268
+ $s_count++; $loop_count++;
269
+ }
270
+ } else {
271
+ // Create search obj
272
+ $s_count = 1;
273
+ $loop_count = 1;
274
+ foreach ($search as $key => $value) {
275
+ $search_data[$s_count]['date'] = $search[$loop_count]['date'];
276
+ $search_data[$s_count]['value'] = $search[$loop_count]['value'];
277
+ $s_count++; $loop_count++;
278
+ }
279
+ }
280
+ $search_data = json_encode($search_data);
281
+ update_post_meta($lead['id'], 'wpleads_search_data', $search_data); // Store search object
282
+ }
283
+
284
+ /**
285
+ * updates conversion data object
286
+ */
287
+ static function store_conversion_data( $lead ) {
288
+
289
+ $conversion_data = get_post_meta( $lead['id'], 'wpleads_conversion_data', TRUE );
290
+ $conversion_data = json_decode($conversion_data,true);
291
+ $variation = $lead['variation'];
292
+
293
+ if ( is_array($conversion_data)) {
294
+ $c_count = count($conversion_data) + 1;
295
+ $conversion_data[$c_count]['id'] = $lead['page_id'];
296
+ $conversion_data[$c_count]['variation'] = $variation;
297
+ $conversion_data[$c_count]['datetime'] = $lead['wordpress_date_time'];
298
+ } else {
299
+ $c_count = 1;
300
+ $conversion_data[1]['id'] = $lead['page_id'];
301
+ $conversion_data[1]['variation'] = $variation;
302
+ $conversion_data[1]['datetime'] = $lead['wordpress_date_time'];
303
+ $conversion_data[1]['first_time'] = 1;
304
+ }
305
+
306
+ $lead['conversion_data'] = json_encode($conversion_data);
307
+ update_post_meta($lead['id'],'wpleads_conversion_count', $c_count); // Store conversions count
308
+ update_post_meta($lead['id'], 'wpleads_conversion_data', $lead['conversion_data']);// Store conversion obj
309
+
310
+ }
311
+ /**
312
+ * Store Conversion Data to LANDING PAGE/CTA DATA
313
+ */
314
+ static function store_conversion_stats($lead){
315
+ $page_conversion_data = get_post_meta( $lead['page_id'], '_inbound_conversion_data', TRUE );
316
+ $page_conversion_data = json_decode($page_conversion_data,true);
317
+ $version = ($lead['variation'] != 'default') ? $lead['variation'] : '0';
318
+ if (is_array($page_conversion_data)) {
319
+ $convert_count = count($page_conversion_data) + 1;
320
+ $page_conversion_data[$convert_count]['lead_id'] = $lead['id'];
321
+ $page_conversion_data[$convert_count]['variation'] = $version;
322
+ $page_conversion_data[$convert_count]['datetime'] = $lead['wordpress_date_time'];
323
+ } else {
324
+ $page_conversion_data[1]['lead_id'] = $lead['id'];
325
+ $page_conversion_data[1]['variation'] = $version;
326
+ $page_conversion_data[1]['datetime'] = $lead['wordpress_date_time'];
327
+ }
328
+ $page_conversion_data = json_encode($page_conversion_data);
329
+ update_post_meta($lead['page_id'], '_inbound_conversion_data', $page_conversion_data);
330
+ }
331
+
332
+ /**
333
+ * Stores referral data
334
+ */
335
+ static function store_referral_data($lead) {
336
+ $referral_data = get_post_meta( $lead['id'], 'wpleads_referral_data', TRUE );
337
+
338
+ // Parse referral for additional data
339
+ include_once( INBOUNDNOW_SHARED_PATH. 'assets/includes/Snowplow/RefererParser/INBOUND_Parser.php');
340
+ include_once( INBOUNDNOW_SHARED_PATH .'assets/includes/Snowplow/RefererParser/INBOUND_Referer.php');
341
+ include_once(INBOUNDNOW_SHARED_PATH . 'assets/includes/Snowplow/RefererParser/INBOUND_Medium.php');
342
+ // intialized the parser class
343
+ $parser = new INBOUND_Parser();
344
+ //$array = array('http://google.com', 'http://twitter.com', 'http://tumblr.com?query=test', '');
345
+ $referer = $parser->parse($lead['source']);
346
+
347
+ if ( $referer->isKnown() ) {
348
+ $ref_type = $referer->getMedium();
349
+
350
+ } else {
351
+ // check if ref exists
352
+ $ref_type = ($lead['source'] === "Direct Traffic") ? 'Direct Traffic' : 'referral';
353
+ }
354
+
355
+ $referral_data = json_decode($referral_data,true);
356
+ if (is_array($referral_data)){
357
+ $r_count = count($referral_data) + 1;
358
+ $referral_data[$r_count]['source'] = $lead['source'];
359
+ $referral_data[$r_count]['type'] = $ref_type;
360
+ $referral_data[$r_count]['datetime'] = $lead['wordpress_date_time'];
361
+ } else {
362
+ $referral_data[1]['source'] = $lead['source'];
363
+ $referral_data[1]['type'] = $ref_type;
364
+ $referral_data[1]['datetime'] = $lead['wordpress_date_time'];
365
+ $referral_data[1]['original_source'] = 1;
366
+ }
367
+
368
+ $lead['referral_data'] = json_encode($referral_data);
369
+ //echo $lead['referral_data']; exit;
370
+ update_post_meta($lead['id'], 'wpleads_referral_data', $lead['referral_data']); // Store referral object
371
+ update_post_meta($lead['id'], 'wpleads_referral_type', $ref_type); // Store referral object
372
+ }
373
+
374
+ /**
375
+ * Loop trough lead_data array and update post meta
376
+ */
377
+ static function update_common_meta($lead) {
378
+
379
+ if (!empty($lead['user_ID'])) {
380
+ /* Update user_ID if exists */
381
+ update_post_meta( $lead['id'], 'wpleads_wordpress_user_id', $lead['user_ID'] );
382
+ }
383
+
384
+ /* Update wp_lead_uid if exist */
385
+ if (!empty($lead['wp_lead_uid'])) {
386
+ update_post_meta( $lead['id'], 'wp_leads_uid', $lead['wp_lead_uid'] );
387
+ }
388
+
389
+ /* Update email address */
390
+ if (!empty($lead['email'])) {
391
+ update_post_meta( $lead['id'], 'wplead_emails_address', $lead['email'] );
392
+ }
393
+
394
+ /* Update mappable fields that have a value associated with them */
395
+ $lead_fields = Leads_Field_Map::build_map_array();
396
+ foreach ( $lead_fields as $key => $value ) {
397
+ $shortkey = str_replace('wpleads_' , '' , $key );
398
+ if (!empty($lead[$shortkey]) && $lead[$shortkey] !== 0 ) {
399
+ update_post_meta( $lead['id'], $key, $lead[$shortkey] );
400
+ }
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Connects to geoplugin.net and gets data on IP address and sets it into historical log
406
+ * @param ARRAY $lead_data
407
+ */
408
+ static function store_geolocation_data( $lead ) {
409
+
410
+ $ip_addresses = get_post_meta( $lead['id'], 'wpleads_ip_address', true );
411
+ $ip_addresses = json_decode( stripslashes($ip_addresses) , true);
412
+
413
+ if (!$ip_addresses) {
414
+ $ip_addresses = array();
415
+ }
416
+
417
+ $new_record[ $lead['ip_address'] ]['ip_address'] = $lead['ip_address'];
418
+
419
+ /* ignore for local environments */
420
+ if ($lead['ip_address']!= "127.0.0.1"){ // exclude localhost
421
+ $response = wp_remote_get('http://www.geoplugin.net/php.gp?ip='.$lead['ip_address']);
422
+ if ( !is_wp_error($response) && isset($response['body']) ) {
423
+ $geo_array = @unserialize($response['body']);
424
+ $new_record[ $lead['ip_address'] ]['geodata'] = $geo_array;
425
+ }
426
+
427
+ }
428
+
429
+ $ip_addresses = array_merge( $new_record, $ip_addresses );
430
+ $ip_addresses = json_encode( $ip_addresses );
431
+
432
+ update_post_meta( $lead['id'], 'wpleads_ip_address', $ip_addresses );
433
+ }
434
+
435
+ /**
436
+ * Updates raw form data object
437
+ */
438
+ static function store_raw_form_data($lead){
439
+ /* Raw Form Values Store */
440
+ if ($lead_data['form_input_values']) {
441
+ $raw_post_data = get_post_meta($$lead['id'],'wpleads_raw_post_data', true);
442
+ $a1 = json_decode( $raw_post_data, true );
443
+ $a2 = json_decode( stripslashes($lead_data['form_input_values']), true );
444
+ $exclude_array = array('card_number','card_cvc','card_exp_month','card_exp_year'); // add filter
445
+ $lead_mapping_fields = Leads_Field_Map::build_map_array();
446
+
447
+ foreach ($a2 as $key=>$value)
448
+ {
449
+ if (array_key_exists( $key , $exclude_array )) {
450
+ unset($a2[$key]);
451
+ continue;
452
+ }
453
+ if (preg_match("/\[\]/", $key)) {
454
+ $key = str_replace("[]", "", $key); // fix array value keys
455
+ }
456
+ if (array_key_exists($key, $lead_mapping_fields)) {
457
+ update_post_meta( $lead_id, $key, $value );
458
+ }
459
+
460
+ if (stristr($key,'company')) {
461
+ update_post_meta( $lead_id, 'wpleads_company_name', $value );
462
+ }
463
+ else if (stristr($key,'website'))
464
+ {
465
+ $websites = get_post_meta( $lead_id, 'wpleads_websites', $value );
466
+ if(is_array($websites)) {
467
+ $array_websites = explode(';',$websites);
468
+ }
469
+ $array_websites[] = $value;
470
+ $websites = implode(';',$array_websites);
471
+ update_post_meta( $lead_id, 'wpleads_websites', $websites );
472
+ }
473
+ }
474
+ // Merge form fields if exist
475
+ if (is_array($a1)) {
476
+ $new_raw_post_data = array_merge_recursive( $a1, $a2 );
477
+ } else {
478
+ $new_raw_post_data = $a2;
479
+ }
480
+ $new_raw_post_data = json_encode( $new_raw_post_data );
481
+ update_post_meta( $lead_id,'wpleads_raw_post_data', $new_raw_post_data );
482
+ }
483
+ }
484
+
485
+ /**
486
+ * Parses & improves lead name
487
+ */
488
+ static function improve_lead_name( $lead ) {
489
+ /* */
490
+ $lead['name'] = (isset($lead['name'])) ? $lead['name'] : '';
491
+
492
+ /* do not let names with 'false' pass */
493
+ if ( !empty($lead['name']) && $lead['name'] == 'false' ) {
494
+ $lead['name'] = '';
495
+ }
496
+ if ( !empty($lead['first_name']) && $lead['first_name'] == 'false' ) {
497
+ $lead['first_name'] = '';
498
+ }
499
+
500
+ /* if last name empty and full name present */
501
+ if ( empty($lead['last_name']) && $lead['name'] ) {
502
+ $parts = explode(' ' , $lead['name']);
503
+
504
+ /* Set first name */
505
+ $lead['first_name'] = $parts[0];
506
+
507
+ /* Set last name */
508
+ if (isset($parts[1])) {
509
+ $lead['last_name'] = $parts[1];
510
+ }
511
+ }
512
+ /* if last name empty and first name present */
513
+ else if (empty($lead['last_name']) && $lead['first_name'] ) {
514
+ $parts = explode(' ' , $lead['first_name']);
515
+
516
+ /* Set First Name */
517
+ $lead['first_name'] = $parts[0];
518
+
519
+ /* Set Last Name */
520
+ if (isset($parts[1])) {
521
+ $lead['last_name'] = $parts[1];
522
+ }
523
+ }
524
+
525
+ /* set full name */
526
+ if (!$lead['name'] && $lead['first_name'] && $lead['last_name'] ) {
527
+ $lead['name'] = $lead['first_name'] .' '. $lead['last_name'];
528
+ }
529
+
530
+ return $lead;
531
+ }
532
+
533
+ /**
534
+ * Uses mapped data if not programatically set
535
+ */
536
+ static function improve_mapping($mappedData, $lead) {
537
+
538
+ /* check to see if there are any mapped values arriving through inbound_store_lead */
539
+ $fields = Leads_Field_Map::build_map_array();
540
+
541
+ foreach ($fields as $key => $label ) {
542
+ if( isset( $lead[ $key ]) && !isset($mappedData[$key]) ) {
543
+ $mappedData[$key] = $lead[ $key ];
544
+ }
545
+ }
546
+
547
+ /* remove instances of wpleads_ */
548
+ $newMap = array();
549
+ foreach ($mappedData as $key=>$value) {
550
+ $key = str_replace('wpleads_','',$key);
551
+ $newMap[$key] = $value;
552
+ }
553
+
554
+ /* Set names if not mapped */
555
+ $newMap['first_name'] = (!isset($newMap['first_name'])) ? $lead['first_name'] : $newMap['first_name'];
556
+ $newMap['last_name'] = (!isset($newMap['last_name'])) ? $lead['last_name'] : $newMap['last_name'];
557
+
558
+ /* improve mapped names */
559
+ $newMap = self::improve_lead_name( $newMap );
560
+
561
+ return $newMap;
562
+ }
563
+
564
+ /**
565
+ * Search lead by email
566
+ */
567
+ static function lookup_lead_by_email($email){
568
+ global $wpdb;
569
+ $query = $wpdb->prepare(
570
+ 'SELECT ID FROM ' . $wpdb->posts . '
571
+ WHERE post_title = %s
572
+ AND post_type = \'wp-lead\'',
573
+ $email
574
+ );
575
+ $wpdb->query( $query );
576
+ if ( $wpdb->num_rows ) {
577
+ $lead_id = $wpdb->get_var( $query );
578
+ return $lead_id;
579
+ } else {
580
+ return false;
581
+ }
582
+
583
+ }
584
+
585
+ static function check_val($key, $args) {
586
+ $val = (isset($args[$key])) ? $args[$key] : false;
587
+ return $val;
588
+ }
589
+ static function json_array_merge( $arr1, $arr2 ) {
590
+ $keys = array_keys( $arr2 );
591
+ foreach( $keys as $key ) {
592
+ if( isset( $arr1[$key] )
593
+ && is_array( $arr1[$key] )
594
+ && is_array( $arr2[$key] )
595
+ ) {
596
+ $arr1[$key] = my_merge( $arr1[$key], $arr2[$key] );
597
+ } else {
598
+ $arr1[$key] = $arr2[$key];
599
+ }
600
+ }
601
+ return $arr1;
602
+ }
603
+
604
+ /**
605
+ * Discover session IP address
606
+ */
607
+ static function lookup_ip_address() {
608
+ if(isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
609
+ if(isset($_SERVER["HTTP_CLIENT_IP"])) {
610
+ $proxy = $_SERVER["HTTP_CLIENT_IP"];
611
+ } else {
612
+ $proxy = $_SERVER["REMOTE_ADDR"];
613
+ }
614
+ $ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
615
+ } else {
616
+ if(isset($_SERVER["HTTP_CLIENT_IP"])) {
617
+ $ip = $_SERVER["HTTP_CLIENT_IP"];
618
+ } else {
619
+ $ip = $_SERVER["REMOTE_ADDR"];
620
+ }
621
+ }
622
+ return $ip;
623
+ }
624
+
625
+ }
626
+
627
+ LeadStorage::init();
628
+ }
629
+
630
+ /**
631
+ * Legacy function used by some extensions
632
+ * @param ARRAY $args legacy dataset of mapped lead fields
633
+ * @param BOOL $return set to true to disable printing of lead id
634
+ */
635
+ if (!function_exists('inbound_store_lead')) {
636
+ function inbound_store_lead( $args , $return = true ) {
637
+ global $user_ID, $wpdb;
638
+
639
+ if (!is_array($args)) {
640
+ $args = array();
641
+ }
642
+
643
+ /* Mergs $args with POST request for support of ajax and direct calls */
644
+ $args = array_merge( $args , $_POST );
645
+
646
+ /* wpleads_email_address becomes wpleads_email */
647
+ $args['email'] = $args['wpleads_email_address'];
648
+
649
+ /* loop through and remove wpleads_ (we will add them back in the new method ) */
650
+ foreach ($args as $key => $value) {
651
+ $newkey = str_replace( 'wpleads_' , '' , $key );
652
+ unset($args[$key]);
653
+ $args[$newkey] = $value;
654
+ }
655
+
656
+ /* Send data through new method */
657
+ $Leads = new LeadStorage();
658
+ if ($return) {
659
+ $Leads->set_mode('return');
660
+ } else {
661
+ $Leads->set_mode('ajax');
662
+ }
663
+
664
+ /* prepare lead lists as array */
665
+ if (isset($args['lead_lists']) && !is_array($args['lead_lists'])) {
666
+ $args['lead_lists'] = explode(',',$args['lead_lists']);
667
+ }
668
+
669
+ $lead_id = $Leads::inbound_lead_store( $args );
670
+
671
+ return $lead_id;
672
+
673
+
674
+ }
675
+ }
676
+
677
+
678
+ /**
679
+ * Legacy functions for adding conversion to lead profile
680
+ * @param INT $lead_id
681
+ * @param ARRAY dataset of lead informaiton
682
+ */
683
+ if (!function_exists('inbound_add_conversion_to_lead')) {
684
+ function inbound_add_conversion_to_lead( $lead_id , $lead_data ) {
685
+
686
+
687
+ if ( $lead_data['page_id'] ) {
688
+ $time = current_time( 'timestamp', 0 ); // Current wordpress time from settings
689
+ $lead_data['wordpress_date_time'] = date("Y-m-d G:i:s T", $time);
690
+ $conversion_data = get_post_meta( $lead_id, 'wpleads_conversion_data', TRUE );
691
+ $conversion_data = json_decode($conversion_data,true);
692
+ $variation = $lead_data['variation'];
693
+
694
+ if ( is_array($conversion_data)) {
695
+ $c_count = count($conversion_data) + 1;
696
+ $conversion_data[$c_count]['id'] = $lead_data['page_id'];
697
+ $conversion_data[$c_count]['variation'] = $variation;
698
+ $conversion_data[$c_count]['datetime'] = $lead_data['wordpress_date_time'];
699
+ } else {
700
+ $c_count = 1;
701
+ $conversion_data[$c_count]['id'] = $lead_data['page_id'];
702
+ $conversion_data[$c_count]['variation'] = $variation;
703
+ $conversion_data[$c_count]['datetime'] = $lead_data['wordpress_date_time'];
704
+ $conversion_data[$c_count]['first_time'] = 1;
705
+ }
706
+
707
+ $lead_data['conversion_data'] = json_encode($conversion_data);
708
+ update_post_meta($lead_id,'wpleads_conversion_count', $c_count); // Store conversions count
709
+ update_post_meta($lead_id, 'wpleads_conversion_data', $lead_data['conversion_data']); // Store conversion object
710
+
711
+ }
712
+ }
713
+ }
 
shared/classes/class.licensing.php CHANGED
@@ -51,8 +51,9 @@ if ( ! class_exists( 'Inbound_License' ) )
51
  add_filter( 'wpleads_define_global_settings', array( $this, 'wpleads_settings' ), 2 );
52
 
53
  /* save license key data / activate license keys */
54
- if (is_admin())
55
  $this->save_license_field();
 
56
 
57
  /* render license key settings in license keys tab */
58
  add_action('lp_render_global_settings', array( $this, 'display_license_field' ) );
@@ -174,8 +175,7 @@ if ( ! class_exists( 'Inbound_License' ) )
174
  echo "<br>";
175
  */
176
 
177
- if (isset($cache_date)&&($date<$cache_date)&&$license_status=='valid')
178
- {
179
  return "valid";
180
  }
181
 
@@ -217,8 +217,9 @@ if ( ! class_exists( 'Inbound_License' ) )
217
  public function save_license_field()
218
  {
219
 
220
- if (!isset($_POST['inboundnow_master_license_key']))
221
  return;
 
222
 
223
  $field_id = "inboundnow-license-keys-".$this->plugin_slug;
224
 
@@ -240,8 +241,7 @@ if ( ! class_exists( 'Inbound_License' ) )
240
  if ($license_status=='valid' && $master_license_key == $this->master_license_key )
241
  return;
242
 
243
- if ( $master_license_key )
244
- {
245
  update_option($field_id ,$master_license_key);
246
 
247
  // data to send in our API request
@@ -289,15 +289,16 @@ if ( ! class_exists( 'Inbound_License' ) )
289
  public function pre_set_site_transient_update_plugins_filter( $_transient_data )
290
  {
291
 
292
- if( empty( $_transient_data ) ) return $_transient_data;
 
 
293
 
294
  $to_send = array( 'slug' => $this->plugin_slug );
295
 
296
  $api_response = $this->api_request( );
297
 
298
 
299
- if( false !== $api_response && is_object( $api_response ) )
300
- {
301
  if( version_compare( $this->plugin_version, $api_response->new_version, '<' ) )
302
  $_transient_data->response[$this->plugin_basename] = $api_response;
303
  }
@@ -319,7 +320,7 @@ if ( ! class_exists( 'Inbound_License' ) )
319
  }
320
 
321
  /*** Calls the API and, if successfull, returns the object delivered by the API. */
322
- public function api_request( ) {
323
 
324
  $api_params = array(
325
  'edd_action' => 'get_version',
51
  add_filter( 'wpleads_define_global_settings', array( $this, 'wpleads_settings' ), 2 );
52
 
53
  /* save license key data / activate license keys */
54
+ if (is_admin()) {
55
  $this->save_license_field();
56
+ }
57
 
58
  /* render license key settings in license keys tab */
59
  add_action('lp_render_global_settings', array( $this, 'display_license_field' ) );
175
  echo "<br>";
176
  */
177
 
178
+ if (isset($cache_date)&&($date<$cache_date)&&$license_status=='valid') {
 
179
  return "valid";
180
  }
181
 
217
  public function save_license_field()
218
  {
219
 
220
+ if (!isset($_POST['inboundnow_master_license_key'])) {
221
  return;
222
+ }
223
 
224
  $field_id = "inboundnow-license-keys-".$this->plugin_slug;
225
 
241
  if ($license_status=='valid' && $master_license_key == $this->master_license_key )
242
  return;
243
 
244
+ if ( $master_license_key ) {
 
245
  update_option($field_id ,$master_license_key);
246
 
247
  // data to send in our API request
289
  public function pre_set_site_transient_update_plugins_filter( $_transient_data )
290
  {
291
 
292
+ if( empty( $_transient_data ) ) {
293
+ return $_transient_data;
294
+ }
295
 
296
  $to_send = array( 'slug' => $this->plugin_slug );
297
 
298
  $api_response = $this->api_request( );
299
 
300
 
301
+ if( false !== $api_response && is_object( $api_response ) ) {
 
302
  if( version_compare( $this->plugin_version, $api_response->new_version, '<' ) )
303
  $_transient_data->response[$this->plugin_basename] = $api_response;
304
  }
320
  }
321
 
322
  /*** Calls the API and, if successfull, returns the object delivered by the API. */
323
+ public function api_request() {
324
 
325
  $api_params = array(
326
  'edd_action' => 'get_version',
shared/classes/class.load-shared.php CHANGED
@@ -41,7 +41,7 @@ if (!class_exists('Inbound_Load_Shared')) {
41
  public static function load_files() {
42
 
43
  include_once( INBOUNDNOW_SHARED_PATH . 'classes/class.post-type.wp-lead.php');
44
- include_once( INBOUNDNOW_SHARED_PATH . 'classes/class.post-type.email-template.php');
45
  include_once( INBOUNDNOW_SHARED_PATH . 'classes/class.form.php');
46
  include_once( INBOUNDNOW_SHARED_PATH . 'classes/class.menus.adminbar.php');
47
  include_once( INBOUNDNOW_SHARED_PATH . 'classes/class.debug.php');
41
  public static function load_files() {
42
 
43
  include_once( INBOUNDNOW_SHARED_PATH . 'classes/class.post-type.wp-lead.php');
44
+ /* include_once( INBOUNDNOW_SHARED_PATH . 'classes/class.post-type.email-template.php'); LEGACY */
45
  include_once( INBOUNDNOW_SHARED_PATH . 'classes/class.form.php');
46
  include_once( INBOUNDNOW_SHARED_PATH . 'classes/class.menus.adminbar.php');
47
  include_once( INBOUNDNOW_SHARED_PATH . 'classes/class.debug.php');
shared/classes/class.magic.php CHANGED
@@ -41,7 +41,7 @@ if ( ! class_exists( 'Inbound_Magic' ) ) {
41
  */
42
  public static function buffer_callback( $content ) {
43
 
44
-
45
  $main = "#/jquery\.js(.*?)</script>#";
46
  $patternFrontEnd = "#wp-includes/js/jquery/jquery\.js\?ver=([^']+)'></script>#";
47
  $patternFrontTwo = "#wp-includes/js/jquery/jquery\.js'></script>#";
@@ -56,7 +56,7 @@ if ( ! class_exists( 'Inbound_Magic' ) ) {
56
  $content = preg_replace( $main, '$0<script>jQuery = jQuery;</script>', $content );
57
  return $content;
58
 
59
- }else if ( preg_match( $patternFrontEnd, $content ) ) {
60
  //jQuery = (typeof jQuery !== "undefined") ? jQuery : false;
61
  $content = preg_replace( $patternFrontEnd, '$0<script>jQuery = jQuery;</script>', $content );
62
  return $content;
41
  */
42
  public static function buffer_callback( $content ) {
43
 
44
+
45
  $main = "#/jquery\.js(.*?)</script>#";
46
  $patternFrontEnd = "#wp-includes/js/jquery/jquery\.js\?ver=([^']+)'></script>#";
47
  $patternFrontTwo = "#wp-includes/js/jquery/jquery\.js'></script>#";
56
  $content = preg_replace( $main, '$0<script>jQuery = jQuery;</script>', $content );
57
  return $content;
58
 
59
+ } else if ( preg_match( $patternFrontEnd, $content ) ) {
60
  //jQuery = (typeof jQuery !== "undefined") ? jQuery : false;
61
  $content = preg_replace( $patternFrontEnd, '$0<script>jQuery = jQuery;</script>', $content );
62
  return $content;
shared/classes/class.master-license.php CHANGED
@@ -22,16 +22,16 @@ if (!function_exists('inboundnow_add_master_license'))
22
  case 'wp_cta_define_global_settings':
23
  $key = 'wp-cta-license-keys';
24
  $text_domain = 'cta';
25
- break;
26
  }
27
-
28
  $global_settings[$key]['settings']['master-key'] = array(
29
  'id' => 'extensions-license-keys-master-key-header',
30
  'description' => __( "Head to http://www.inboundnow.com/ to retrieve your extension-ready license key." , $text_domain ),
31
  'type' => 'header',
32
  'default' => '<h3 class="lp_global_settings_header">'. __( 'InboundNow Master Key' , $text_domain ) .'</h3>'
33
  );
34
-
35
  $global_settings[$key]['settings']['master-key'] = array(
36
  'id' => 'inboundnow_master_license_key',
37
  'option_name' => 'inboundnow_master_license_key',
@@ -40,7 +40,7 @@ if (!function_exists('inboundnow_add_master_license'))
40
  'type' => 'text',
41
  'default' => ''
42
  );
43
-
44
  return $global_settings;
45
  }
46
  }
22
  case 'wp_cta_define_global_settings':
23
  $key = 'wp-cta-license-keys';
24
  $text_domain = 'cta';
25
+ break;
26
  }
27
+
28
  $global_settings[$key]['settings']['master-key'] = array(
29
  'id' => 'extensions-license-keys-master-key-header',
30
  'description' => __( "Head to http://www.inboundnow.com/ to retrieve your extension-ready license key." , $text_domain ),
31
  'type' => 'header',
32
  'default' => '<h3 class="lp_global_settings_header">'. __( 'InboundNow Master Key' , $text_domain ) .'</h3>'
33
  );
34
+
35
  $global_settings[$key]['settings']['master-key'] = array(
36
  'id' => 'inboundnow_master_license_key',
37
  'option_name' => 'inboundnow_master_license_key',
40
  'type' => 'text',
41
  'default' => ''
42
  );
43
+
44
  return $global_settings;
45
  }
46
  }
shared/classes/class.menu.php CHANGED
@@ -107,16 +107,15 @@ if (!class_exists('Inbound_Menu')) {
107
  'meta' => array( 'class' => 'ab-sub-secondary' )
108
  ) );
109
 
110
- foreach ( $secondary_menu_items as $id => $menu_item )
111
- {
112
  $menu_item['id'] = $id;
113
 
114
  if ( ! isset( $menu_item['meta']['target'] ) ) {
115
  $menu_item['meta']['target'] = '_blank';
116
  }
117
 
118
- if ( '_blank' === $menu_item['meta']['target'] )
119
- {
120
  if ( ! isset( $menu_item['meta']['class'] ) ) {
121
  $menu_item['meta']['class'] = '';
122
  }
@@ -414,8 +413,7 @@ if (!class_exists('Inbound_Menu')) {
414
 
415
  /* 1.1.x Get Forms and List */
416
  $forms = get_posts(array('post_type'=>'inbound-forms','post_status'=>'published'));
417
- foreach ($forms as $form)
418
- {
419
  $menu_items['inbound-form-'.$form->ID] = array(
420
  'parent' => 'inbound-forms-view',
421
  'title' => $form->post_title,
@@ -690,4 +688,4 @@ if (!class_exists('Inbound_Menu')) {
690
  }
691
 
692
  add_action('init' , array( 'Inbound_Menu' , 'init' ) );
693
- }
107
  'meta' => array( 'class' => 'ab-sub-secondary' )
108
  ) );
109
 
110
+ foreach ( $secondary_menu_items as $id => $menu_item ) {
 
111
  $menu_item['id'] = $id;
112
 
113
  if ( ! isset( $menu_item['meta']['target'] ) ) {
114
  $menu_item['meta']['target'] = '_blank';
115
  }
116
 
117
+ if ( '_blank' === $menu_item['meta']['target'] ) {
118
+
119
  if ( ! isset( $menu_item['meta']['class'] ) ) {
120
  $menu_item['meta']['class'] = '';
121
  }
413
 
414
  /* 1.1.x Get Forms and List */
415
  $forms = get_posts(array('post_type'=>'inbound-forms','post_status'=>'published'));
416
+ foreach ($forms as $form) {
 
417
  $menu_items['inbound-form-'.$form->ID] = array(
418
  'parent' => 'inbound-forms-view',
419
  'title' => $form->post_title,
688
  }
689
 
690
  add_action('init' , array( 'Inbound_Menu' , 'init' ) );
691
+ }
shared/classes/class.menus.adminbar.php CHANGED
@@ -111,16 +111,14 @@ if (!class_exists('Inbound_Menus_Adminbar')) {
111
  'meta' => array( 'class' => 'ab-sub-secondary' )
112
  ) );
113
 
114
- foreach ( $secondary_menu_items as $id => $menu_item )
115
- {
116
  $menu_item['id'] = $id;
117
 
118
  if ( ! isset( $menu_item['meta']['target'] ) ) {
119
  $menu_item['meta']['target'] = '_blank';
120
  }
121
 
122
- if ( '_blank' === $menu_item['meta']['target'] )
123
- {
124
  if ( ! isset( $menu_item['meta']['class'] ) ) {
125
  $menu_item['meta']['class'] = '';
126
  }
111
  'meta' => array( 'class' => 'ab-sub-secondary' )
112
  ) );
113
 
114
+ foreach ( $secondary_menu_items as $id => $menu_item ) {
 
115
  $menu_item['id'] = $id;
116
 
117
  if ( ! isset( $menu_item['meta']['target'] ) ) {
118
  $menu_item['meta']['target'] = '_blank';
119
  }
120
 
121
+ if ( '_blank' === $menu_item['meta']['target'] ) {
 
122
  if ( ! isset( $menu_item['meta']['class'] ) ) {
123
  $menu_item['meta']['class'] = '';
124
  }
shared/classes/class.welcome.php CHANGED
@@ -101,9 +101,9 @@ class Inbound_Now_Welcome {
101
  foreach ($page_array as $key => $value) {
102
  $active = ($current_view === $key) ? 'nav-tab-active' : '';
103
 
104
- echo '<a class="nav-tab '.$active.'" href="'.esc_url( admin_url( add_query_arg( array( 'page' => $key ), 'index.php' ) ) ).'">';
105
- echo _e( $value, 'inbound-now');
106
- echo '</a>';
107
 
108
  }
109
  echo '</h2>';
101
  foreach ($page_array as $key => $value) {
102
  $active = ($current_view === $key) ? 'nav-tab-active' : '';
103
 
104
+ echo '<a class="nav-tab '.$active.'" href="'.esc_url( admin_url( add_query_arg( array( 'page' => $key ), 'index.php' ) ) ).'">';
105
+ echo _e( $value, 'inbound-now');
106
+ echo '</a>';
107
 
108
  }
109
  echo '</h2>';
shared/shortcodes/css/form-cpt.css CHANGED
@@ -1,10 +1,13 @@
1
- #side-sortables, #cpt-form-serialize, #inbound-shortcodes-form-wrap #inbound-shortcodes-form-head, #inbound_insert_shortcode_two, #shortcode_cancel, #entire-form-area, .inbound_tbody.parent-inbound_shortcode_helper-block-one, #postdivrich, #inbound-email-response, #postcustom, #cpt-form-serialize-default {
2
  display: none;
3
  }
4
  #inbound-email-response h2{
5
  margin-bottom: 0px;
6
  margin-top: 5px;
7
  }
 
 
 
8
  .inbound_tbody.parent-inbound_shortcode_helper-block-one, #local-storage-notice, #setting-error-tgmpa, #screen-options-link-wrap, #notice, .updated.inbound-shortcode-trigger {
9
  display: none !important;
10
  }
@@ -13,6 +16,8 @@
13
  }
14
  #short_shortcode_form {
15
  margin-top:10px;
 
 
16
  }
17
  #view-email-response {
18
  margin-left: 10px;
@@ -24,7 +29,7 @@
24
  width: 100%;
25
  }
26
  #email-token-list li {
27
- width: 50%;
28
  float: left;
29
  display: inline;
30
  }
@@ -61,8 +66,8 @@ height: 25px;}
61
  -moz-box-shadow: 0px 0px 15px rgba(0,0,0,0.1);
62
  box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1);
63
  z-index: 200;
64
- width: 97%;
65
- margin-left: 8px;
66
  }
67
  #form-leads-list h2 {
68
  font-size: 20px;
1
+ #side-sortables, #cpt-form-serialize, #inbound-shortcodes-form-wrap #inbound-shortcodes-form-head, #inbound_insert_shortcode_two, #shortcode_cancel, #entire-form-area, .inbound_tbody.parent-inbound_shortcode_helper-block-one, #postdivrich, #inbound-email-response, #postcustom, #cpt-form-serialize-default, .add-new-h2 {
2
  display: none;
3
  }
4
  #inbound-email-response h2{
5
  margin-bottom: 0px;
6
  margin-top: 5px;
7
  }
8
+ #view-form-builder {
9
+ margin-left: 10px;
10
+ }
11
  .inbound_tbody.parent-inbound_shortcode_helper-block-one, #local-storage-notice, #setting-error-tgmpa, #screen-options-link-wrap, #notice, .updated.inbound-shortcode-trigger {
12
  display: none !important;
13
  }
16
  }
17
  #short_shortcode_form {
18
  margin-top:10px;
19
+ font-weight: bold;
20
+ font-size: 16px;
21
  }
22
  #view-email-response {
23
  margin-left: 10px;
29
  width: 100%;
30
  }
31
  #email-token-list li {
32
+ width: 22%;
33
  float: left;
34
  display: inline;
35
  }
66
  -moz-box-shadow: 0px 0px 15px rgba(0,0,0,0.1);
67
  box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1);
68
  z-index: 200;
69
+ width: 63%;
70
+
71
  }
72
  #form-leads-list h2 {
73
  font-size: 20px;
shared/shortcodes/css/select2.png ADDED
Binary file
shared/shortcodes/css/shortcodes.css CHANGED
@@ -192,7 +192,7 @@ float: left;
192
  /* Form Table
193
  * ---------------------------------------- */
194
  #inbound-shortcodes-form-table {
195
- width:520px;
196
  position: relative;
197
  }
198
  #form-extra-controls {
@@ -213,8 +213,8 @@ float: left;
213
  border-bottom:none;
214
  }
215
  #inbound-shortcodes-form-table tbody tr.form-row td.label{
216
- min-width: 160px;
217
- /*max-width:120px;*/
218
  text-align:left;
219
  vertical-align:top;
220
  line-height:35px;
@@ -238,6 +238,7 @@ float: left;
238
  #inbound-shortcodes-form-table textarea{=
239
  padding:5px 6px;
240
  margin:0 0 0px 0;
 
241
  }
242
  #inbound-shortcodes-form-table textarea{
243
  max-width: 100%;
192
  /* Form Table
193
  * ---------------------------------------- */
194
  #inbound-shortcodes-form-table {
195
+ width:100%;
196
  position: relative;
197
  }
198
  #form-extra-controls {
213
  border-bottom:none;
214
  }
215
  #inbound-shortcodes-form-table tbody tr.form-row td.label{
216
+ min-width:120px;
217
+ max-width:120px;
218
  text-align:left;
219
  vertical-align:top;
220
  line-height:35px;
238
  #inbound-shortcodes-form-table textarea{=
239
  padding:5px 6px;
240
  margin:0 0 0px 0;
241
+ width: 100%;
242
  }
243
  #inbound-shortcodes-form-table textarea{
244
  max-width: 100%;
shared/shortcodes/inbound-shortcodes.php CHANGED
@@ -77,7 +77,7 @@ class Inbound_Shortcodes {
77
 
78
  if (isset($post)&&post_type_supports($post->post_type,'editor')||isset($post)&&'wp-call-to-action' === $post->post_type) {
79
  wp_enqueue_script('inbound-shortcodes', INBOUNDNOW_SHARED_URLPATH . 'shortcodes/js/shortcodes.js', array( 'jquery', 'jquery-cookie' ));
80
- $form_id = (isset($_GET['post']) && is_int( $_GET['post'] )) ? $_GET['post'] : '';
81
  wp_localize_script( 'inbound-shortcodes', 'inbound_shortcodes', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) , 'adminurl' => admin_url(), 'inbound_shortcode_nonce' => wp_create_nonce('inbound-shortcode-nonce') , 'form_id' => $form_id ) );
82
  wp_enqueue_script('selectjs', INBOUNDNOW_SHARED_URLPATH . 'shortcodes/js/select2.min.js');
83
  wp_enqueue_style('selectjs', INBOUNDNOW_SHARED_URLPATH . 'shortcodes/css/select2.css');
@@ -664,7 +664,7 @@ class Inbound_Shortcodes {
664
  <h3><?php _e( 'Inbound Pro Users' , INBOUNDNOW_TEXT_DOMAIN ); ?></h3>
665
  <div class='' style='padding-left:20px;'>
666
 
667
- <?php echo sprintf( __( ' Membership holders should ignore the setup area below and referrer to %s this document %s for instructions on setting up a followup email. We are leaveing this section in up for non members and for members that are leveraging it. We may remove it remove it entirely from the Inbound Pro plugin. ' , INBOUNDNOW_TEXT_DOMAIN ) , '<a href="http://docs.inboundnow.com/guide/creating-a-follow-up-email-using-inbound-now-as-an-autoresponder-marketing-automation/">', '</a>') ; ?>
668
  </div>
669
  <br>
670
  <?php
@@ -676,8 +676,6 @@ class Inbound_Shortcodes {
676
  $values = get_post_custom( $post->ID );
677
  $selected = isset( $values['inbound_email_send_notification'] ) ? esc_attr( $values['inbound_email_send_notification'][0] ) : "";
678
  $email_subject = get_post_meta( $post->ID, 'inbound_confirmation_subject', TRUE );
679
- $email_templates = self::get_email_templates();
680
- $email_template = get_post_meta( $post->ID, 'inbound_email_send_notification_template' , TRUE );
681
 
682
  ?>
683
  <div style='display:block; overflow: auto;'>
@@ -692,54 +690,36 @@ class Inbound_Shortcodes {
692
  </div>
693
 
694
  <?php
 
 
695
 
696
- if ($email_templates) {
697
-
698
- ?>
699
- <div style='display:block; overflow: auto;'>
700
- <div id=''>
701
- <label for="inbound_email_send_notification_template"><?php _e( 'Select Response Email Template' , INBOUNDNOW_TEXT_DOMAIN ); ?></label>
702
- <select name="inbound_email_send_notification_template" id="inbound_email_send_notification_template">
703
- <option value='custom' <?php selected( 'custom' , $email_template); ?>><?php _e( 'Do not use a premade email template' , INBOUNDNOW_TEXT_DOMAIN ); ?></option>
704
- <?php
705
-
706
- foreach ($email_templates as $id => $label) {
707
- echo '<option value="'.$id.'" '. selected($id , $email_template , false ) .'>'.$label.'</option>';
708
- }
709
- ?>
710
- </select>
711
- </div>
712
- </div>
713
- <table class='widefat tokens'>
714
- <tr><td>
715
- <h2>Available Dynamic Email Tokens</h2>
716
- <ul id="email-token-list">
717
- <li class='core_token' title='Email address of sender' style='cursor:pointer;'>{{admin-email-address}}</li>
718
- <li class='core_token' title='Name of this website' style='cursor:pointer;'>{{site-name}}</li>
719
- <li class='core_token' title='URL of this website' style='cursor:pointer;'>{{site-url}}</li>
720
- <li class='core_token' title='Datetime of Sent Email.' style='cursor:pointer;'>{{date-time}}</li>
721
- <li class='lead_token' title='First & Last name of recipient' style='cursor:pointer;'>{{lead-full-name}}</li>
722
- <li class='lead_token' title='First name of recipient' style='cursor:pointer;'>{{lead-first-name}}</li>
723
- <li class='lead_token' title='Last name of recipient' style='cursor:pointer;'>{{lead-last-name}}</li>
724
-
725
- <li class='lead_token' title='Email address of recipient' style='cursor:pointer;'>{{lead-email-address}}</li>
726
- <li class='lead_token' title='Company Name of recipient' style='cursor:pointer;'>{{lead-company-name}}</li>
727
- <li class='lead_token' title='Address Line 1 of recipient' style='cursor:pointer;'>{{lead-address-line-1}}</li>
728
- <li class='lead_token' title='Address Line 2 of recipient' style='cursor:pointer;'>{{lead-address-line-2}}</li>
729
- <li class='lead_token' title='City of recipient' style='cursor:pointer;'>{{lead-city}}</li>
730
- <li class='lead_token' title='Name of Inbound Now form user converted on' style='cursor:pointer;'>{{form-name}}</li>
731
- <li class='lead_token' title='Page the visitor singed-up on.' style='cursor:pointer;'>{{source}}</li>
732
- </ul>
733
- </td>
734
- </tr>
735
- </table>
736
- <?php
737
- }
738
-
739
- ?>
740
 
741
  <input type="text" name="inbound_confirmation_subject" placeholder="Email Subject Line" size="30" value="<?php echo $email_subject;?>" id="inbound_confirmation_subject" autocomplete="off">
742
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
743
  </div>
744
  <div id="inbound-shortcodes-popup">
745
  <div id="short_shortcode_form">
@@ -792,58 +772,11 @@ class Inbound_Shortcodes {
792
  </div>
793
  </div>
794
 
795
- <script type="text/javascript">
796
-
797
- function inbound_forms_select_email_template() {
798
- var selected = jQuery('#inbound_email_send_notification_template').val();
799
-
800
- if ( selected != 'custom') {
801
- jQuery('#postdivrich').hide();
802
- jQuery('#inbound_confirmation_subject').hide();
803
- jQuery('.tokens').hide();
804
- } else {
805
- jQuery('#postdivrich').show();
806
- jQuery('#inbound_confirmation_subject').show();
807
- jQuery('.tokens').show();
808
- }
809
- }
810
-
811
- jQuery(document).ready(function($) {
812
-
813
- jQuery('.child-clone-row').first().attr('id', 'row-1');
814
- setTimeout(function() {
815
- jQuery('#inbound-shortcodes-form input:visible').first().focus();
816
- }, 500);
817
-
818
- /* Hide Options Based on Selected Template */
819
- jQuery('body').on('change' , '#inbound_email_send_notification_template' , function() {
820
- inbound_forms_select_email_template();
821
- });
822
-
823
- });
824
- </script>
825
 
826
  <?php
827
  }
828
 
829
- public static function get_email_templates() {
830
-
831
 
832
- $templates = get_posts(array(
833
- 'post_type' => 'email-template',
834
- 'posts_per_page' => -1
835
- ));
836
-
837
-
838
- foreach ( $templates as $template ) {
839
- $email_templates[$template->ID] = $template->post_title;
840
- }
841
-
842
- $email_templates = ( isset($email_templates) ) ? $email_templates : array();
843
-
844
- return $email_templates;
845
-
846
- }
847
  }
848
  }
849
  /* Initialize InboundNow Shortcodes
77
 
78
  if (isset($post)&&post_type_supports($post->post_type,'editor')||isset($post)&&'wp-call-to-action' === $post->post_type) {
79
  wp_enqueue_script('inbound-shortcodes', INBOUNDNOW_SHARED_URLPATH . 'shortcodes/js/shortcodes.js', array( 'jquery', 'jquery-cookie' ));
80
+ $form_id = (isset($_GET['post']) && is_int( $_GET['post'] )) ? $_GET['post'] : '';
81
  wp_localize_script( 'inbound-shortcodes', 'inbound_shortcodes', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) , 'adminurl' => admin_url(), 'inbound_shortcode_nonce' => wp_create_nonce('inbound-shortcode-nonce') , 'form_id' => $form_id ) );
82
  wp_enqueue_script('selectjs', INBOUNDNOW_SHARED_URLPATH . 'shortcodes/js/select2.min.js');
83
  wp_enqueue_style('selectjs', INBOUNDNOW_SHARED_URLPATH . 'shortcodes/css/select2.css');
664
  <h3><?php _e( 'Inbound Pro Users' , INBOUNDNOW_TEXT_DOMAIN ); ?></h3>
665
  <div class='' style='padding-left:20px;'>
666
 
667
+ <?php echo sprintf( __( 'To learn how to creat a follow email series please referrer to %s this document %s. ' , INBOUNDNOW_TEXT_DOMAIN ) , '<a href="http://docs.inboundnow.com/guide/creating-a-follow-up-email-using-inbound-now-as-an-autoresponder-marketing-automation/">', '</a>') ; ?>
668
  </div>
669
  <br>
670
  <?php
676
  $values = get_post_custom( $post->ID );
677
  $selected = isset( $values['inbound_email_send_notification'] ) ? esc_attr( $values['inbound_email_send_notification'][0] ) : "";
678
  $email_subject = get_post_meta( $post->ID, 'inbound_confirmation_subject', TRUE );
 
 
679
 
680
  ?>
681
  <div style='display:block; overflow: auto;'>
690
  </div>
691
 
692
  <?php
693
+ do_action('inbound-forms/before-email-reponse-setup');
694
+ ?>
695
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
696
 
697
  <input type="text" name="inbound_confirmation_subject" placeholder="Email Subject Line" size="30" value="<?php echo $email_subject;?>" id="inbound_confirmation_subject" autocomplete="off">
698
 
699
+ <table class='widefat tokens'>
700
+ <tr><td>
701
+ <h2>Available Dynamic Email Tokens</h2>
702
+ <ul id="email-token-list">
703
+ <li class='core_token' title='Email address of sender' >{{admin-email-address}}</li>
704
+ <li class='core_token' title='Name of this website' >{{site-name}}</li>
705
+ <li class='core_token' title='URL of this website' >{{site-url}}</li>
706
+ <li class='core_token' title='Datetime of Sent Email.' >{{date-time}}</li>
707
+ <li class='lead_token' title='First & Last name of recipient' >{{lead-full-name}}</li>
708
+ <li class='lead_token' title='First name of recipient' >{{lead-first-name}}</li>
709
+ <li class='lead_token' title='Last name of recipient' >{{lead-last-name}}</li>
710
+
711
+ <li class='lead_token' title='Email address of recipient' >{{lead-email-address}}</li>
712
+ <li class='lead_token' title='Company Name of recipient' >{{lead-company-name}}</li>
713
+ <li class='lead_token' title='Address Line 1 of recipient' >{{lead-address-line-1}}</li>
714
+ <li class='lead_token' title='Address Line 2 of recipient' >{{lead-address-line-2}}</li>
715
+ <li class='lead_token' title='City of recipient' >{{lead-city}}</li>
716
+ <li class='lead_token' title='Name of Inbound Now form user converted on' >{{form-name}}</li>
717
+ <li class='lead_token' title='Page the visitor singed-up on.' >{{source}}</li>
718
+ </ul>
719
+ </td>
720
+ </tr>
721
+ </table>
722
+
723
  </div>
724
  <div id="inbound-shortcodes-popup">
725
  <div id="short_shortcode_form">
772
  </div>
773
  </div>
774
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
775
 
776
  <?php
777
  }
778
 
 
 
779
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
780
  }
781
  }
782
  /* Initialize InboundNow Shortcodes
shared/shortcodes/js/form-cpt.js CHANGED
@@ -1,16 +1,17 @@
1
- jQuery(document).ready(function($) {
2
- //var button = '<a href="#" id="inbound_save_this_form" style="" class="button-primary">Save This Form</a>';
3
- //jQuery("#inbound_save_form").before(button);
4
  var form_move = jQuery("#entire-form-area");
5
  jQuery("#titlediv").after(form_move);
6
  jQuery("#entire-form-area").fadeIn(1000);
7
- jQuery("#inbound_save_form").removeClass('button').addClass('button-primary').text('Save Form');
8
  jQuery("#inbound-shortcodes-preview").hide().fadeIn(5000);
9
- jQuery("body").on('change keyup', '#title', function () {
10
  jQuery("#title-prompt-text").hide();
11
- var this_val = jQuery(this).val();
12
- jQuery("#inbound_shortcode_form_name").val(this_val);
13
  });
 
14
  var build_form = ' <span id="view-form-builder" class="button view-form-builder">Build Form</span>';
15
  var view_leads_list = '<span id="view-leads-list" class="button view-leads-list">View Conversions</span>';
16
  var view_email_response = '<span id="view-email-response" class="button">Set Email Response</span>';
@@ -21,13 +22,17 @@ jQuery(document).ready(function($) {
21
  jQuery("body").on('click', '#view-form-builder', function () {
22
  jQuery("#form-leads-list").hide();
23
  jQuery("#inbound-shortcodes-popup").show();
24
- jQuery('#form-leads-list, #title, #inbound-email-response,#postdivrich').hide();
25
  });
26
-
27
  jQuery("body").on('click', '#view-email-response', function () {
28
  jQuery('#inbound-shortcodes-popup, #form-leads-list, #title, #inbound-email-response').hide();
29
  jQuery('#inbound-email-response').show();
30
- jQuery('#postdivrich').show();
 
 
 
 
31
  });
32
 
33
  jQuery("body").on('click', '#view-leads-list', function () {
@@ -36,12 +41,12 @@ jQuery(document).ready(function($) {
36
  });
37
 
38
  jQuery("body").on('change keyup', '#inbound_shortcode_form_name', function () {
39
- jQuery("#title-prompt-text").hide();
40
- var this_val = jQuery(this).val();
41
- jQuery("#title").val(this_val);
42
  });
43
  jQuery("body").on('click', '#inbound_save_this_form', function () {
44
- var post_id = jQuery("#post_ID").val();
45
  });
46
  var post_status = jQuery("#hidden_post_status").val();
47
  if (post_status === 'draft') {
@@ -51,76 +56,102 @@ jQuery(document).ready(function($) {
51
  var post_title = jQuery("#title").val();
52
  //jQuery("#inbound_shortcode_form_name").val(post_title);
53
  var form_toggle = 'form_' + post_id;
54
- setTimeout(function() {
55
- jQuery("#inbound_shortcode_insert_default").val(form_toggle);
56
- InboundShortcodes.update_fields();
57
- fill_form_fields();
58
- }, 1000);
59
-
60
- setTimeout(function() {
61
-
62
- fill_form_fields();
63
- }, 2000);
64
-
65
- function fill_form_fields(){
66
- var SelectionData = jQuery("#cpt-form-serialize").text();
67
- if (SelectionData != "") {
68
-
69
- jQuery.each(SelectionData.split('&'), function (index, elem) {
70
- var vals = elem.split('=');
71
-
72
- var $select_val = jQuery('select[name="'+vals[0]+'"]').attr('name');
73
- var $select = jQuery('select[name="'+vals[0]+'"]');
74
- var $input = jQuery('input[name="'+vals[0]+'"]'); // input vals
75
- var input_type = jQuery('input[name="'+vals[0]+'"]').attr('type');
76
- var $checkbox = jQuery('input[name="'+vals[0]+'"]'); // input vals
77
- var $textarea = jQuery('textarea[name="'+vals[0]+'"]'); // input vals
78
- var separator = '';
79
- /*if ($div.html().length > 0) {
80
- separator = ', ';
81
- }*/
82
- //console.log(input_type);
83
- $input.val(decodeURIComponent(vals[1].replace(/\+/g, ' ')));
84
- if (input_type === 'checkbox' && vals[1] === 'on'){
85
- $input.prop( "checked", true );
86
- }
87
- if ($select_val != 'inbound_shortcode_insert_default'){
88
  $select.val(decodeURIComponent(vals[1].replace(/\+/g, ' ')));
89
- }
90
- $textarea.val(decodeURIComponent(vals[1].replace(/\+/g, ' ')));
91
- });
92
 
93
- }
94
  }
 
95
  if (post_status === 'draft') {
96
- setTimeout(function() {
97
  jQuery("#inbound_shortcode_insert_default").val('none');
98
- }, 1000);
99
  }
100
- if (post_status === 'draft' && post_title != "" || post_status ==='pending' && post_title != "" ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
 
103
- // run auto publish ajax
104
- jQuery.ajax({
105
- type: 'POST',
106
- url: ajaxurl,
107
- context: this,
108
- data: {
109
- action: 'inbound_form_auto_publish',
110
- post_id: post_id,
111
- post_title: post_title
112
- },
113
 
114
- success: function (data) {
115
- console.log("This Form has been auto published");
116
- },
 
 
 
 
 
 
 
 
 
 
117
 
118
- error: function (MLHttpRequest, textStatus, errorThrown) {
119
- alert("Ajax not enabled");
120
- }
121
- });
122
 
 
 
 
 
123
 
 
 
 
 
124
 
125
- }
126
- });
1
+ jQuery(document).ready(function ($) {
2
+
3
+
4
  var form_move = jQuery("#entire-form-area");
5
  jQuery("#titlediv").after(form_move);
6
  jQuery("#entire-form-area").fadeIn(1000);
7
+ jQuery("#inbound_save_form").removeClass('button').addClass('button-primary').text('Save Form');
8
  jQuery("#inbound-shortcodes-preview").hide().fadeIn(5000);
9
+ jQuery("body").on('change keyup', '#title', function () {
10
  jQuery("#title-prompt-text").hide();
11
+ var this_val = jQuery(this).val();
12
+ jQuery("#inbound_shortcode_form_name").val(this_val);
13
  });
14
+
15
  var build_form = ' <span id="view-form-builder" class="button view-form-builder">Build Form</span>';
16
  var view_leads_list = '<span id="view-leads-list" class="button view-leads-list">View Conversions</span>';
17
  var view_email_response = '<span id="view-email-response" class="button">Set Email Response</span>';
22
  jQuery("body").on('click', '#view-form-builder', function () {
23
  jQuery("#form-leads-list").hide();
24
  jQuery("#inbound-shortcodes-popup").show();
25
+ jQuery('#form-leads-list, #title, #inbound-email-response,#postdivrich').hide();
26
  });
27
+
28
  jQuery("body").on('click', '#view-email-response', function () {
29
  jQuery('#inbound-shortcodes-popup, #form-leads-list, #title, #inbound-email-response').hide();
30
  jQuery('#inbound-email-response').show();
31
+ jQuery('#postdivrich').show();
32
+ /* if custom email selected then hide email response */
33
+ if (jQuery('#inbound_email_send_notification_template').length > -1) {
34
+ jQuery('#inbound_email_send_notification_template').trigger('change');
35
+ }
36
  });
37
 
38
  jQuery("body").on('click', '#view-leads-list', function () {
41
  });
42
 
43
  jQuery("body").on('change keyup', '#inbound_shortcode_form_name', function () {
44
+ jQuery("#title-prompt-text").hide();
45
+ var this_val = jQuery(this).val();
46
+ jQuery("#title").val(this_val);
47
  });
48
  jQuery("body").on('click', '#inbound_save_this_form', function () {
49
+ var post_id = jQuery("#post_ID").val();
50
  });
51
  var post_status = jQuery("#hidden_post_status").val();
52
  if (post_status === 'draft') {
56
  var post_title = jQuery("#title").val();
57
  //jQuery("#inbound_shortcode_form_name").val(post_title);
58
  var form_toggle = 'form_' + post_id;
59
+ setTimeout(function () {
60
+ jQuery("#inbound_shortcode_insert_default").val(form_toggle);
61
+ InboundShortcodes.update_fields();
62
+ fill_form_fields();
63
+ }, 1000);
64
+
65
+ setTimeout(function () {
66
+
67
+ fill_form_fields();
68
+ }, 2000);
69
+
70
+ function fill_form_fields() {
71
+ var SelectionData = jQuery("#cpt-form-serialize").text();
72
+ if (SelectionData != "") {
73
+
74
+ jQuery.each(SelectionData.split('&'), function (index, elem) {
75
+ var vals = elem.split('=');
76
+
77
+ var $select_val = jQuery('select[name="' + vals[0] + '"]').attr('name');
78
+ var $select = jQuery('select[name="' + vals[0] + '"]');
79
+ var $input = jQuery('input[name="' + vals[0] + '"]'); // input vals
80
+ var input_type = jQuery('input[name="' + vals[0] + '"]').attr('type');
81
+ var $checkbox = jQuery('input[name="' + vals[0] + '"]'); // input vals
82
+ var $textarea = jQuery('textarea[name="' + vals[0] + '"]'); // input vals
83
+ var separator = '';
84
+ /*if ($div.html().length > 0) {
85
+ separator = ', ';
86
+ }*/
87
+ //console.log(input_type);
88
+ $input.val(decodeURIComponent(vals[1].replace(/\+/g, ' ')));
89
+ if (input_type === 'checkbox' && vals[1] === 'on') {
90
+ $input.prop("checked", true);
91
+ }
92
+ if ($select_val != 'inbound_shortcode_insert_default') {
93
  $select.val(decodeURIComponent(vals[1].replace(/\+/g, ' ')));
94
+ }
95
+ $textarea.val(decodeURIComponent(vals[1].replace(/\+/g, ' ')));
96
+ });
97
 
98
+ }
99
  }
100
+
101
  if (post_status === 'draft') {
102
+ setTimeout(function () {
103
  jQuery("#inbound_shortcode_insert_default").val('none');
104
+ }, 1000);
105
  }
106
+ if (post_status === 'draft' && post_title != "" || post_status === 'pending' && post_title != "") {
107
+
108
+
109
+ // run auto publish ajax
110
+ jQuery.ajax({
111
+ type: 'POST',
112
+ url: ajaxurl,
113
+ context: this,
114
+ data: {
115
+ action: 'inbound_form_auto_publish',
116
+ post_id: post_id,
117
+ post_title: post_title
118
+ },
119
+
120
+ success: function (data) {
121
+ console.log("This Form has been auto published");
122
+ },
123
+
124
+ error: function (MLHttpRequest, textStatus, errorThrown) {
125
+ alert("Ajax not enabled");
126
+ }
127
+ });
128
 
129
 
130
+ }
 
 
 
 
 
 
 
 
 
131
 
132
+ function inbound_forms_select_email_template() {
133
+ var selected = jQuery('#inbound_email_send_notification_template').val();
134
+
135
+ if ( selected != 'custom') {
136
+ jQuery('#postdivrich').hide();
137
+ jQuery('#inbound_confirmation_subject').hide();
138
+ jQuery('.tokens').hide();
139
+ } else {
140
+ jQuery('#postdivrich').show();
141
+ jQuery('#inbound_confirmation_subject').show();
142
+ jQuery('.tokens').show();
143
+ }
144
+ }
145
 
 
 
 
 
146
 
147
+ jQuery('.child-clone-row').first().attr('id', 'row-1');
148
+ setTimeout(function() {
149
+ jQuery('#inbound-shortcodes-form input:visible').first().focus();
150
+ }, 500);
151
 
152
+ /* Hide Options Based on Selected Template */
153
+ jQuery('body').on('change' , '#inbound_email_send_notification_template' , function() {
154
+ inbound_forms_select_email_template();
155
+ });
156
 
157
+ });
 
shared/shortcodes/shortcodes/forms.php CHANGED
@@ -17,24 +17,24 @@ $shortcodes_config['forms'] = array(
17
  'no_preview' => false,
18
  'options' => array(
19
  'insert_default' => array(
20
- 'name' => __('Choose Starting Template', 'leads'),
21
- 'desc' => __('Start Building Your Form from premade templates', 'leads'),
22
  'type' => 'select',
23
  'options' => $form_names,
24
  'std' => 'none',
25
  'class' => 'main-form-settings',
26
  ),
27
  'form_name' => array(
28
- 'name' => __('Form Name<span class="small-required-text">*</span>', 'leads'),
29
- 'desc' => __('This is not shown to visitors', 'leads'),
30
  'type' => 'text',
31
  'placeholder' => "Example: XYZ Whitepaper Download",
32
  'std' => '',
33
  'class' => 'main-form-settings',
34
  ),
35
  /*'confirmation' => array(
36
- 'name' => __('Form Layout', 'leads'),
37
- 'desc' => __('Choose Your Form Layout', 'leads'),
38
  'type' => 'select',
39
  'options' => array(
40
  "redirect" => "Redirect After Form Completion",
@@ -43,8 +43,8 @@ $shortcodes_config['forms'] = array(
43
  'std' => 'redirect'
44
  ),*/
45
  'redirect' => array(
46
- 'name' => __('Redirect URL<span class="small-required-text">*</span>', 'leads'),
47
- 'desc' => __('Where do you want to send people after they fill out the form?', 'leads'),
48
  'type' => 'text',
49
  'placeholder' => "http://www.yoursite.com/thank-you",
50
  'std' => '',
@@ -52,61 +52,61 @@ $shortcodes_config['forms'] = array(
52
  'class' => 'main-form-settings',
53
  ),
54
  /*'thank_you_text' => array(
55
- 'name' => __('Field Description <span class="small-optional-text">(optional)</span>', 'leads'),
56
- 'desc' => __('Put field description here.', 'leads'),
57
  'type' => 'textarea',
58
  'std' => '',
59
  'class' => 'advanced',
60
  'reveal_on' => 'text'
61
  ), */
62
  'notify' => array(
63
- 'name' => __('Notify on Form Completions<span class="small-required-text">*</span>', 'leads'),
64
- 'desc' => __('Who should get admin notifications on this form?<br>For multiple notifications separate email addresses with commas', 'leads'),
65
  'type' => 'text',
66
  'placeholder' => "youremail@email.com",
67
  'std' => '',
68
  'class' => 'main-form-settings',
69
  ),
70
  'notify_subject' => array(
71
- 'name' => __('Admin Email Subject Line<span class="small-required-text">*</span>', 'leads'),
72
- 'desc' => __('Customize the subject line of email notifications arriving from this form. default: {{site-name}} {{form-name}} - New Lead Conversion', 'leads'),
73
  'type' => 'text',
74
  'std' => "{{site-name}} {{form-name}} - New Lead Conversion",
75
  'palceholder' => '{{site-name}} {{form-name}} - New Lead Conversion',
76
  'class' => 'main-form-settings',
77
  ),
78
  'lists' => array(
79
- 'name' => __('Add to List(s)', 'leads'),
80
- 'desc' => __('Add the converting lead to 1 or more lead lists', 'leads'),
81
  'type' => 'leadlists',
82
  'options' => $lead_list_names,
83
  'class' => 'main-form-settings',
84
  ),
85
 
86
  'lists_hidden' => array(
87
- 'name' => __('Hidden List Values', 'leads'),
88
- 'desc' => __('Hidden list values', 'leads'),
89
  'type' => 'hidden',
90
  'class' => 'main-form-settings',
91
  ),
92
 
93
  'helper-block-one' => array(
94
- 'name' => __('Name Name Name', 'leads'),
95
- 'desc' => __('<span class="switch-to-form-insert button">Cancel Form Creation & Insert Existing Form</span>', 'leads'),
96
  'type' => 'helper-block',
97
  'std' => '',
98
  'class' => 'main-form-settings',
99
  ),
100
  'heading_design' => array(
101
- 'name' => __('Name Name Name', 'leads'),
102
- 'desc' => __('Layout Options', 'leads'),
103
  'type' => 'helper-block',
104
  'std' => '',
105
  'class' => 'main-design-settings',
106
  ),
107
  'layout' => array(
108
- 'name' => __('Form Layout', 'leads'),
109
- 'desc' => __('Choose Your Form Layout', 'leads'),
110
  'type' => 'select',
111
  'options' => array(
112
  "vertical" => "Vertical",
@@ -116,8 +116,8 @@ $shortcodes_config['forms'] = array(
116
  'class' => 'main-design-settings',
117
  ),
118
  'labels' => array(
119
- 'name' => __('Label Alignment', 'leads'),
120
- 'desc' => __('Choose Label Layout', 'leads'),
121
  'type' => 'select',
122
  'options' => array(
123
  "top" => "Labels on Top",
@@ -129,30 +129,30 @@ $shortcodes_config['forms'] = array(
129
  'class' => 'main-design-settings',
130
  ),
131
  'font-size' => array(
132
- 'name' => __('Form Font Size', 'leads'),
133
- 'desc' => __('Size of Label Font. This also determines default submit button size', 'leads'),
134
  'type' => 'text',
135
  'std' => '16',
136
  'class' => 'main-design-settings',
137
  ),
138
  'icon' => array(
139
- 'name' => __('Submit Button Icon', 'leads'),
140
- 'desc' => __('Select an icon.', 'leads'),
141
  'type' => 'select',
142
  'options' => $fontawesome,
143
  'std' => 'none',
144
  'class' => 'main-design-settings'
145
  ),
146
  'submit' => array(
147
- 'name' => __('Submit Button Text', 'leads'),
148
- 'desc' => __('Enter the text you want to show on the submit button. (or a link to a custom submit button image)', 'leads'),
149
  'type' => 'text',
150
  'std' => 'Submit',
151
  'class' => 'main-design-settings',
152
  ),
153
  'submit-colors' => array(
154
- 'name' => __('Submit Color Options', 'leads'),
155
- 'desc' => __('Choose Your Form Layout', 'leads'),
156
  'type' => 'select',
157
  'options' => array(
158
  "on" => "Color Options On",
@@ -162,22 +162,22 @@ $shortcodes_config['forms'] = array(
162
  'class' => 'main-design-settings',
163
  ),
164
  'submit-text-color' => array(
165
- 'name' => __('Button Text Color', 'leads'),
166
- 'desc' => __('Color of text. Must toggle on "Submit Color Options" on', 'leads'),
167
  'type' => 'colorpicker',
168
  'std' => '#434242',
169
  'class' => 'main-design-settings',
170
  ),
171
  'submit-bg-color' => array(
172
- 'name' => __('Button BG Color', 'leads'),
173
- 'desc' => __('Background color of button. Must toggle on "Submit Color Options" on', 'leads'),
174
  'type' => 'colorpicker',
175
  'std' => '#E9E9E9',
176
  'class' => 'main-design-settings',
177
  ),
178
  'width' => array(
179
- 'name' => __('Custom Width', 'leads'),
180
- 'desc' => __('Enter in pixel width or % width. Example: 400 <u>or</u> 100%', 'leads'),
181
  'type' => 'text',
182
  'std' => '',
183
  'class' => 'main-design-settings',
@@ -186,15 +186,15 @@ $shortcodes_config['forms'] = array(
186
  'child' => array(
187
  'options' => array(
188
  'label' => array(
189
- 'name' => __('Field Label', 'leads'),
190
  'desc' => '',
191
  'type' => 'text',
192
  'std' => '',
193
  'placeholder' => __("Enter the Form Field Label. Example: First Name" , "leads" )
194
  ),
195
  'field_type' => array(
196
- 'name' => __('Field Type', 'leads'),
197
- 'desc' => __('Select an form field type', 'leads'),
198
  'type' => 'select',
199
  'options' => array(
200
  "text" => __('Single Line Text' , INBOUNDNOW_TEXT_DOMAIN ),
@@ -217,122 +217,130 @@ $shortcodes_config['forms'] = array(
217
  'tel' => __( 'Telephone' , INBOUNDNOW_TEXT_DOMAIN ),
218
  'datetime-local' => __('Date Time Pick Selector Field' , INBOUNDNOW_TEXT_DOMAIN ),
219
  'file_upload' => __('File Upload' , INBOUNDNOW_TEXT_DOMAIN ),
220
- 'editor' => __('HTML Editor' ,'leads'),
221
- 'multi-select' => __('multi-select' , 'leads')
222
  */
223
  ),
224
  'std' => ''
225
  ),
226
 
227
  'dropdown_options' => array(
228
- 'name' => __('Dropdown choices', 'leads'),
229
- 'desc' => __('Enter Your Dropdown Options. Separate by commas. You may also use label|value to have a different value than the label stored.', 'leads'),
230
  'type' => 'text',
231
  'std' => '',
232
  'placeholder' => __('Choice 1|a, Choice 2, Choice 3' , 'cta' ),
233
  'reveal_on' => 'dropdown' // on select choice show this
234
  ),
235
  'radio_options' => array(
236
- 'name' => __('Radio Choices', 'leads'),
237
- 'desc' => __('Enter Your Radio Options. Separate by commas. You may also use label|value to have a different value than the label stored.', 'leads'),
238
  'type' => 'text',
239
  'std' => '',
240
  'placeholder' => 'Choice 1|a, Choice 2',
241
  'reveal_on' => 'radio' // on select choice show this
242
  ),
243
  'checkbox_options' => array(
244
- 'name' => __('Checkbox choices', 'leads'),
245
- 'desc' => __('Enter Your Checkbox Options. Separate by commas. You may also use label|value to have a different value than the label stored.', 'leads'),
246
  'type' => 'text',
247
  'std' => '',
248
  'placeholder' => __( 'Choice 1|a, Choice 2, Choice 3', 'cta' ),
249
  'reveal_on' => 'checkbox' // on select choice show this
250
  ),
251
  'html_block_options' => array(
252
- 'name' => __('HTML Block', 'leads'),
253
- 'desc' => __('This is a raw HTML block in the form. Insert text/HTML', 'leads'),
254
  'type' => 'textarea',
255
  'std' => '',
256
  'reveal_on' => 'html-block' // on select choice show this
257
  ),
 
 
 
 
 
 
 
 
258
  'default_value' => array(
259
- 'name' => __('Default Value', 'leads'),
260
- 'desc' => __('Enter the Default Value', 'leads'),
261
  'type' => 'text',
262
  'std' => '',
263
  'placeholder' => 'Enter Default Value',
264
  'reveal_on' => 'hidden' // on select choice show this
265
  ),
266
  'divider_options' => array(
267
- 'name' => __('Divider Text (optional)', 'leads'),
268
- 'desc' => __('This is the text in the divider', 'leads'),
269
  'type' => 'text',
270
  'std' => '',
271
  'reveal_on' => 'divider' // on select choice show this
272
  ),
273
  'required' => array(
274
- 'name' => __('Required Field? <span class="small-optional-text">(optional)</span>', 'leads'),
275
- 'checkbox_text' => __('Check to make field required', 'leads'),
276
  'desc' => '',
277
  'type' => 'checkbox',
278
  'std' => '0',
279
  'class' => '',
280
  ),
281
  'exclude_tracking' => array(
282
- 'name' => __('Exclude Tracking? <span class="small-optional-text">(optional)</span>', 'leads'),
283
- 'checkbox_text' => __('Check to exclude this form field from being tracked. Note this will not store in your Database', 'leads'),
284
  'desc' => '',
285
  'type' => 'checkbox',
286
  'std' => '0',
287
  'class' => 'advanced',
288
  ),
289
  'helper' => array(
290
- 'name' => __('Field Description <span class="small-optional-text">(optional)</span>', 'leads'),
291
- 'desc' => __('<span class="show-advanced-fields">Show advanced fields</span>', 'leads'),
292
  'type' => 'helper-block',
293
  'std' => '',
294
  'class' => '',
295
  ),
296
  'map_to' => array(
297
- 'name' => __('Map Field To <span class="small-optional-text">(optional)</span>', 'leads'),
298
- 'desc' => __('Map this field to Leads Value', 'leads'),
299
  'type' => 'select',
300
  'options' => $lead_mapping_fields,
301
  'std' => 'none',
302
  'class' => 'advanced exclude',
303
  ),
304
  'placeholder' => array(
305
- 'name' => __('Field Placeholder <span class="small-optional-text">(optional)</span>', 'leads'),
306
- 'desc' => __('Put field placeholder text here. Only works for normal text inputs', 'leads'),
307
  'type' => 'text',
308
  'std' => '',
309
  'class' => 'advanced',
310
  ),
311
  'description' => array(
312
- 'name' => __('Field Description <span class="small-optional-text">(optional)</span>', 'leads'),
313
- 'desc' => __('Put field description here.', 'leads'),
314
  'type' => 'textarea',
315
  'std' => '',
316
  'class' => 'advanced',
317
  ),
318
  'field_container_class' => array(
319
- 'name' => __('Field Container Classes <span class="small-optional-text">(optional)</span>', 'leads'),
320
- 'desc' => __('Add additional class ids to the div that contains this field. Separate classes with spaces.', 'leads'),
321
  'type' => 'text',
322
  'std' => '',
323
  'class' => 'advanced',
324
  ),
325
  'field_input_class' => array(
326
- 'name' => __('Field Input Classes <span class="small-optional-text">(optional)</span>', 'leads'),
327
- 'desc' => __('Add additional class ids to this input field. Separate classes with spaces.', 'leads'),
328
  'type' => 'text',
329
  'std' => '',
330
  'class' => 'advanced',
331
  ),
332
 
333
  'hidden_input_options' => array(
334
- 'name' => __('Dynamic Field Filling <span class="small-optional-text">(optional)</span>', 'leads'),
335
- 'desc' => __('Enter Your Dynamic URL parameter', 'leads'),
336
  'type' => 'text',
337
  'std' => '',
338
  'placeholder' => 'enter dynamic url parameter example: utm_campaign ',
@@ -340,8 +348,8 @@ $shortcodes_config['forms'] = array(
340
  //'reveal_on' => 'hidden' // on select choice show this
341
  )
342
  ),
343
- 'shortcode' => '[inbound_field label="{{label}}" type="{{field_type}}" description="{{description}}" required="{{required}}" exclude_tracking={{exclude_tracking}} dropdown="{{dropdown_options}}" radio="{{radio_options}}" checkbox="{{checkbox_options}}" placeholder="{{placeholder}}" field_container_class="{{field_container_class}}" field_input_class="{{field_input_class}}" html="{{html_block_options}}" dynamic="{{hidden_input_options}}" default="{{default_value}}" map_to="{{map_to}}" divider_options="{{divider_options}}"]',
344
- 'clone' => __('Add Another Field', 'cta' )
345
  ),
346
  'shortcode' => '[inbound_form name="{{form_name}}" lists="{{lists_hidden}}" redirect="{{redirect}}" notify="{{notify}}" notify_subject="{{notify_subject}}" layout="{{layout}}" font_size="{{font-size}}" labels="{{labels}}" icon="{{icon}}" submit="{{submit}}" submit="{{submit}}" submit_colors="{{submit-colors}}" submit_text_color="{{submit-text-color}}" submit_bg_color="{{submit-bg-color}}" width="{{width}}"]{{child}}[/inbound_form]',
347
  'popup_title' => 'Insert Inbound Form Shortcode'
17
  'no_preview' => false,
18
  'options' => array(
19
  'insert_default' => array(
20
+ 'name' => __('Choose Starting Template', INBOUNDNOW_TEXT_DOMAIN ),
21
+ 'desc' => __('Start Building Your Form from premade templates', INBOUNDNOW_TEXT_DOMAIN ),
22
  'type' => 'select',
23
  'options' => $form_names,
24
  'std' => 'none',
25
  'class' => 'main-form-settings',
26
  ),
27
  'form_name' => array(
28
+ 'name' => __('Form Name<span class="small-required-text">*</span>', INBOUNDNOW_TEXT_DOMAIN ),
29
+ 'desc' => __('This is for internal use and is not shown to visitors', INBOUNDNOW_TEXT_DOMAIN ),
30
  'type' => 'text',
31
  'placeholder' => "Example: XYZ Whitepaper Download",
32
  'std' => '',
33
  'class' => 'main-form-settings',
34
  ),
35
  /*'confirmation' => array(
36
+ 'name' => __('Form Layout', INBOUNDNOW_TEXT_DOMAIN ),
37
+ 'desc' => __('Choose Your Form Layout', INBOUNDNOW_TEXT_DOMAIN ),
38
  'type' => 'select',
39
  'options' => array(
40
  "redirect" => "Redirect After Form Completion",
43
  'std' => 'redirect'
44
  ),*/
45
  'redirect' => array(
46
+ 'name' => __('Redirect URL<span class="small-required-text">*</span>', INBOUNDNOW_TEXT_DOMAIN ),
47
+ 'desc' => __('Where do you want to send people after they fill out the form?', INBOUNDNOW_TEXT_DOMAIN ),
48
  'type' => 'text',
49
  'placeholder' => "http://www.yoursite.com/thank-you",
50
  'std' => '',
52
  'class' => 'main-form-settings',
53
  ),
54
  /*'thank_you_text' => array(
55
+ 'name' => __('Field Description <span class="small-optional-text">(optional)</span>', INBOUNDNOW_TEXT_DOMAIN ),
56
+ 'desc' => __('Put field description here.', INBOUNDNOW_TEXT_DOMAIN ),
57
  'type' => 'textarea',
58
  'std' => '',
59
  'class' => 'advanced',
60
  'reveal_on' => 'text'
61
  ), */
62
  'notify' => array(
63
+ 'name' => __('Notify on Form Completions<span class="small-required-text">*</span>', INBOUNDNOW_TEXT_DOMAIN ),
64
+ 'desc' => __('Who should get admin notifications on this form?<br>For multiple notifications separate email addresses with commas', INBOUNDNOW_TEXT_DOMAIN ),
65
  'type' => 'text',
66
  'placeholder' => "youremail@email.com",
67
  'std' => '',
68
  'class' => 'main-form-settings',
69
  ),
70
  'notify_subject' => array(
71
+ 'name' => __('Admin Email Subject Line<span class="small-required-text">*</span>', INBOUNDNOW_TEXT_DOMAIN ),
72
+ 'desc' => __('Customize the subject line of email notifications arriving from this form. default: {{site-name}} {{form-name}} - New Lead Conversion', INBOUNDNOW_TEXT_DOMAIN ),
73
  'type' => 'text',
74
  'std' => "{{site-name}} {{form-name}} - New Lead Conversion",
75
  'palceholder' => '{{site-name}} {{form-name}} - New Lead Conversion',
76
  'class' => 'main-form-settings',
77
  ),
78
  'lists' => array(
79
+ 'name' => __('Add to List(s)', INBOUNDNOW_TEXT_DOMAIN ),
80
+ 'desc' => __('Add the converting lead to 1 or more lead lists', INBOUNDNOW_TEXT_DOMAIN ),
81
  'type' => 'leadlists',
82
  'options' => $lead_list_names,
83
  'class' => 'main-form-settings',
84
  ),
85
 
86
  'lists_hidden' => array(
87
+ 'name' => __('Hidden List Values', INBOUNDNOW_TEXT_DOMAIN ),
88
+ 'desc' => __('Hidden list values', INBOUNDNOW_TEXT_DOMAIN ),
89
  'type' => 'hidden',
90
  'class' => 'main-form-settings',
91
  ),
92
 
93
  'helper-block-one' => array(
94
+ 'name' => __('Name Name Name', INBOUNDNOW_TEXT_DOMAIN ),
95
+ 'desc' => __('<span class="switch-to-form-insert button">Cancel Form Creation & Insert Existing Form</span>', INBOUNDNOW_TEXT_DOMAIN ),
96
  'type' => 'helper-block',
97
  'std' => '',
98
  'class' => 'main-form-settings',
99
  ),
100
  'heading_design' => array(
101
+ 'name' => __('Name Name Name', INBOUNDNOW_TEXT_DOMAIN ),
102
+ 'desc' => __('Layout Options', INBOUNDNOW_TEXT_DOMAIN ),
103
  'type' => 'helper-block',
104
  'std' => '',
105
  'class' => 'main-design-settings',
106
  ),
107
  'layout' => array(
108
+ 'name' => __('Form Layout', INBOUNDNOW_TEXT_DOMAIN ),
109
+ 'desc' => __('Choose Your Form Layout', INBOUNDNOW_TEXT_DOMAIN ),
110
  'type' => 'select',
111
  'options' => array(
112
  "vertical" => "Vertical",
116
  'class' => 'main-design-settings',
117
  ),
118
  'labels' => array(
119
+ 'name' => __('Label Alignment', INBOUNDNOW_TEXT_DOMAIN ),
120
+ 'desc' => __('Choose Label Layout', INBOUNDNOW_TEXT_DOMAIN ),
121
  'type' => 'select',
122
  'options' => array(
123
  "top" => "Labels on Top",
129
  'class' => 'main-design-settings',
130
  ),
131
  'font-size' => array(
132
+ 'name' => __('Form Font Size', INBOUNDNOW_TEXT_DOMAIN ),
133
+ 'desc' => __('Size of Label Font. This also determines default submit button size', INBOUNDNOW_TEXT_DOMAIN ),
134
  'type' => 'text',
135
  'std' => '16',
136
  'class' => 'main-design-settings',
137
  ),
138
  'icon' => array(
139
+ 'name' => __('Submit Button Icon', INBOUNDNOW_TEXT_DOMAIN ),
140
+ 'desc' => __('Select an icon.', INBOUNDNOW_TEXT_DOMAIN ),
141
  'type' => 'select',
142
  'options' => $fontawesome,
143
  'std' => 'none',
144
  'class' => 'main-design-settings'
145
  ),
146
  'submit' => array(
147
+ 'name' => __('Submit Button Text', INBOUNDNOW_TEXT_DOMAIN ),
148
+ 'desc' => __('Enter the text you want to show on the submit button. (or a link to a custom submit button image)', INBOUNDNOW_TEXT_DOMAIN ),
149
  'type' => 'text',
150
  'std' => 'Submit',
151
  'class' => 'main-design-settings',
152
  ),
153
  'submit-colors' => array(
154
+ 'name' => __('Submit Color Options', INBOUNDNOW_TEXT_DOMAIN ),
155
+ 'desc' => __('Choose Your Form Layout', INBOUNDNOW_TEXT_DOMAIN ),
156
  'type' => 'select',
157
  'options' => array(
158
  "on" => "Color Options On",
162
  'class' => 'main-design-settings',
163
  ),
164
  'submit-text-color' => array(
165
+ 'name' => __('Button Text Color', INBOUNDNOW_TEXT_DOMAIN ),
166
+ 'desc' => __('Color of text. Must toggle on "Submit Color Options" on', INBOUNDNOW_TEXT_DOMAIN ),
167
  'type' => 'colorpicker',
168
  'std' => '#434242',
169
  'class' => 'main-design-settings',
170
  ),
171
  'submit-bg-color' => array(
172
+ 'name' => __('Button BG Color', INBOUNDNOW_TEXT_DOMAIN ),
173
+ 'desc' => __('Background color of button. Must toggle on "Submit Color Options" on', INBOUNDNOW_TEXT_DOMAIN ),
174
  'type' => 'colorpicker',
175
  'std' => '#E9E9E9',
176
  'class' => 'main-design-settings',
177
  ),
178
  'width' => array(
179
+ 'name' => __('Custom Width', INBOUNDNOW_TEXT_DOMAIN ),
180
+ 'desc' => __('Enter in pixel width or % width. Example: 400 <u>or</u> 100%', INBOUNDNOW_TEXT_DOMAIN ),
181
  'type' => 'text',
182
  'std' => '',
183
  'class' => 'main-design-settings',
186
  'child' => array(
187
  'options' => array(
188
  'label' => array(
189
+ 'name' => __('Field Label', INBOUNDNOW_TEXT_DOMAIN ),
190
  'desc' => '',
191
  'type' => 'text',
192
  'std' => '',
193
  'placeholder' => __("Enter the Form Field Label. Example: First Name" , "leads" )
194
  ),
195
  'field_type' => array(
196
+ 'name' => __('Field Type', INBOUNDNOW_TEXT_DOMAIN ),
197
+ 'desc' => __('Select an form field type', INBOUNDNOW_TEXT_DOMAIN ),
198
  'type' => 'select',
199
  'options' => array(
200
  "text" => __('Single Line Text' , INBOUNDNOW_TEXT_DOMAIN ),
217
  'tel' => __( 'Telephone' , INBOUNDNOW_TEXT_DOMAIN ),
218
  'datetime-local' => __('Date Time Pick Selector Field' , INBOUNDNOW_TEXT_DOMAIN ),
219
  'file_upload' => __('File Upload' , INBOUNDNOW_TEXT_DOMAIN ),
220
+ 'editor' => __('HTML Editor' ,INBOUNDNOW_TEXT_DOMAIN ),
221
+ 'multi-select' => __('multi-select' , INBOUNDNOW_TEXT_DOMAIN )
222
  */
223
  ),
224
  'std' => ''
225
  ),
226
 
227
  'dropdown_options' => array(
228
+ 'name' => __('Dropdown choices', INBOUNDNOW_TEXT_DOMAIN ),
229
+ 'desc' => __('Enter Your Dropdown Options. Separate by commas. You may also use label|value to have a different value than the label stored.', INBOUNDNOW_TEXT_DOMAIN ),
230
  'type' => 'text',
231
  'std' => '',
232
  'placeholder' => __('Choice 1|a, Choice 2, Choice 3' , 'cta' ),
233
  'reveal_on' => 'dropdown' // on select choice show this
234
  ),
235
  'radio_options' => array(
236
+ 'name' => __('Radio Choices', INBOUNDNOW_TEXT_DOMAIN ),
237
+ 'desc' => __('Enter Your Radio Options. Separate by commas. You may also use label|value to have a different value than the label stored.', INBOUNDNOW_TEXT_DOMAIN ),
238
  'type' => 'text',
239
  'std' => '',
240
  'placeholder' => 'Choice 1|a, Choice 2',
241
  'reveal_on' => 'radio' // on select choice show this
242
  ),
243
  'checkbox_options' => array(
244
+ 'name' => __('Checkbox choices', INBOUNDNOW_TEXT_DOMAIN ),
245
+ 'desc' => __('Enter Your Checkbox Options. Separate by commas. You may also use label|value to have a different value than the label stored.', INBOUNDNOW_TEXT_DOMAIN ),
246
  'type' => 'text',
247
  'std' => '',
248
  'placeholder' => __( 'Choice 1|a, Choice 2, Choice 3', 'cta' ),
249
  'reveal_on' => 'checkbox' // on select choice show this
250
  ),
251
  'html_block_options' => array(
252
+ 'name' => __('HTML Block', INBOUNDNOW_TEXT_DOMAIN ),
253
+ 'desc' => __('This is a raw HTML block in the form. Insert text/HTML', INBOUNDNOW_TEXT_DOMAIN ),
254
  'type' => 'textarea',
255
  'std' => '',
256
  'reveal_on' => 'html-block' // on select choice show this
257
  ),
258
+ 'range_options' => array(
259
+ 'name' => __('Range Setup', INBOUNDNOW_TEXT_DOMAIN ),
260
+ 'desc' => __('Enter the min, max, and steps inside the range input. Separate each with a pipe, eg: min|max|steps', INBOUNDNOW_TEXT_DOMAIN ),
261
+ 'type' => 'text',
262
+ 'std' => '',
263
+ 'placeholder' => __('0|100|10' , INBOUNDNOW_TEXT_DOMAIN ),
264
+ 'reveal_on' => 'range' // on select choice show this
265
+ ),
266
  'default_value' => array(
267
+ 'name' => __('Default Value', INBOUNDNOW_TEXT_DOMAIN ),
268
+ 'desc' => __('Enter the Default Value', INBOUNDNOW_TEXT_DOMAIN ),
269
  'type' => 'text',
270
  'std' => '',
271
  'placeholder' => 'Enter Default Value',
272
  'reveal_on' => 'hidden' // on select choice show this
273
  ),
274
  'divider_options' => array(
275
+ 'name' => __('Divider Text (optional)', INBOUNDNOW_TEXT_DOMAIN ),
276
+ 'desc' => __('This is the text in the divider', INBOUNDNOW_TEXT_DOMAIN ),
277
  'type' => 'text',
278
  'std' => '',
279
  'reveal_on' => 'divider' // on select choice show this
280
  ),
281
  'required' => array(
282
+ 'name' => __('Required Field? <span class="small-optional-text">(optional)</span>', INBOUNDNOW_TEXT_DOMAIN ),
283
+ 'checkbox_text' => __('Check to make field required', INBOUNDNOW_TEXT_DOMAIN ),
284
  'desc' => '',
285
  'type' => 'checkbox',
286
  'std' => '0',
287
  'class' => '',
288
  ),
289
  'exclude_tracking' => array(
290
+ 'name' => __('Exclude Tracking? <span class="small-optional-text">(optional)</span>', INBOUNDNOW_TEXT_DOMAIN ),
291
+ 'checkbox_text' => __('Check to exclude this form field from being tracked. Note this will not store in your Database', INBOUNDNOW_TEXT_DOMAIN ),
292
  'desc' => '',
293
  'type' => 'checkbox',
294
  'std' => '0',
295
  'class' => 'advanced',
296
  ),
297
  'helper' => array(
298
+ 'name' => __('Field Description <span class="small-optional-text">(optional)</span>', INBOUNDNOW_TEXT_DOMAIN ),
299
+ 'desc' => __('<span class="show-advanced-fields">Show advanced fields</span>', INBOUNDNOW_TEXT_DOMAIN ),
300
  'type' => 'helper-block',
301
  'std' => '',
302
  'class' => '',
303
  ),
304
  'map_to' => array(
305
+ 'name' => __('Map Field To <span class="small-optional-text">(optional)</span>', INBOUNDNOW_TEXT_DOMAIN ),
306
+ 'desc' => __('Map this field to Leads Value', INBOUNDNOW_TEXT_DOMAIN ),
307
  'type' => 'select',
308
  'options' => $lead_mapping_fields,
309
  'std' => 'none',
310
  'class' => 'advanced exclude',
311
  ),
312
  'placeholder' => array(
313
+ 'name' => __('Field Placeholder <span class="small-optional-text">(optional)</span>', INBOUNDNOW_TEXT_DOMAIN ),
314
+ 'desc' => __('Put field placeholder text here. Only works for normal text inputs', INBOUNDNOW_TEXT_DOMAIN ),
315
  'type' => 'text',
316
  'std' => '',
317
  'class' => 'advanced',
318
  ),
319
  'description' => array(
320
+ 'name' => __('Field Description <span class="small-optional-text">(optional)</span>', INBOUNDNOW_TEXT_DOMAIN ),
321
+ 'desc' => __('Put field description here.', INBOUNDNOW_TEXT_DOMAIN ),
322
  'type' => 'textarea',
323
  'std' => '',
324
  'class' => 'advanced',
325
  ),
326
  'field_container_class' => array(
327
+ 'name' => __('Field Container Classes <span class="small-optional-text">(optional)</span>', INBOUNDNOW_TEXT_DOMAIN ),
328
+ 'desc' => __('Add additional class ids to the div that contains this field. Separate classes with spaces.', INBOUNDNOW_TEXT_DOMAIN ),
329
  'type' => 'text',
330
  'std' => '',
331
  'class' => 'advanced',
332
  ),
333
  'field_input_class' => array(
334
+ 'name' => __('Field Input Classes <span class="small-optional-text">(optional)</span>', INBOUNDNOW_TEXT_DOMAIN ),
335
+ 'desc' => __('Add additional class ids to this input field. Separate classes with spaces.', INBOUNDNOW_TEXT_DOMAIN ),
336
  'type' => 'text',
337
  'std' => '',
338
  'class' => 'advanced',
339
  ),
340
 
341
  'hidden_input_options' => array(
342
+ 'name' => __('Dynamic Field Filling <span class="small-optional-text">(optional)</span>', INBOUNDNOW_TEXT_DOMAIN ),
343
+ 'desc' => __('Enter Your Dynamic URL parameter', INBOUNDNOW_TEXT_DOMAIN ),
344
  'type' => 'text',
345
  'std' => '',
346
  'placeholder' => 'enter dynamic url parameter example: utm_campaign ',
348
  //'reveal_on' => 'hidden' // on select choice show this
349
  )
350
  ),
351
+ 'shortcode' => '[inbound_field label="{{label}}" type="{{field_type}}" description="{{description}}" required="{{required}}" exclude_tracking={{exclude_tracking}} dropdown="{{dropdown_options}}" radio="{{radio_options}}" checkbox="{{checkbox_options}}" range="{{range_options}}" placeholder="{{placeholder}}" field_container_class="{{field_container_class}}" field_input_class="{{field_input_class}}" html="{{html_block_options}}" dynamic="{{hidden_input_options}}" default="{{default_value}}" map_to="{{map_to}}" divider_options="{{divider_options}}"]',
352
+ 'clone' => __('Add Another Field', INBOUNDNOW_TEXT_DOMAIN )
353
  ),
354
  'shortcode' => '[inbound_form name="{{form_name}}" lists="{{lists_hidden}}" redirect="{{redirect}}" notify="{{notify}}" notify_subject="{{notify_subject}}" layout="{{layout}}" font_size="{{font-size}}" labels="{{labels}}" icon="{{icon}}" submit="{{submit}}" submit="{{submit}}" submit_colors="{{submit-colors}}" submit_text_color="{{submit-text-color}}" submit_bg_color="{{submit-bg-color}}" width="{{width}}"]{{child}}[/inbound_form]',
355
  'popup_title' => 'Insert Inbound Form Shortcode'
templates/countdown-lander/config.php CHANGED
@@ -66,7 +66,7 @@ array(
66
  'description' => "What date are we counting down to?", // what field does
67
  'id' => 'date-picker', // metakey. $key Prefix is appended from parent in array loop
68
  'type' => 'datepicker', // metafield type
69
- 'default' => '2013-1-31 13:00', // default content
70
  'context' => 'normal' // Context in screen (advanced layouts in future)
71
  ),
72
  array(
66
  'description' => "What date are we counting down to?", // what field does
67
  'id' => 'date-picker', // metakey. $key Prefix is appended from parent in array loop
68
  'type' => 'datepicker', // metafield type
69
+ 'default' => '2015-12-31 13:00', // default content
70
  'context' => 'normal' // Context in screen (advanced layouts in future)
71
  ),
72
  array(
templates/countdown-lander/index.php CHANGED
@@ -60,9 +60,9 @@ function lp_Hex_2_RGB($hex) {
60
 
61
  }
62
  $RBG_array = lp_Hex_2_RGB($submit_button_color);
63
- $red = $RBG_array['r'];
64
- $green = $RBG_array["g"];
65
- $blue = $RBG_array["b"];
66
 
67
 
68
 
@@ -86,7 +86,7 @@ $blue = $RBG_array["b"];
86
  #content-background{ width: 550px; padding-top: 20px; padding-bottom:20px;border-radius: 6px; margin: auto; }
87
 
88
  <?php if ($bg_image != "") { ?>
89
-
90
  html { background: none;}
91
 
92
  body { background: url(<?php echo $bg_image; ?>) no-repeat center center fixed;
@@ -100,8 +100,8 @@ $blue = $RBG_array["b"];
100
  div, p, #note, label, #lp_container { color: #<?php echo $text_color; ?>}
101
  .countDiv::before, .countDiv::after {
102
  background-color: #<?php echo $text_color; ?>;
103
- }
104
-
105
  <?php if ($headline_color != "") { echo "h1 {color: #$headline_color;}"; } ?>
106
  <?php if ($background_on === "on") { echo "#content-background{background: url('".$path."image.php?hex=$content_color');}"; }?>
107
  <?php if ($submit_button_color != "") {
60
 
61
  }
62
  $RBG_array = lp_Hex_2_RGB($submit_button_color);
63
+ $red = (isset($RBG_array['r'])) ? $RBG_array['r'] : '0';
64
+ $green = (isset($RBG_array['g'])) ? $RBG_array['g'] : '0';
65
+ $blue = (isset($RBG_array['b'])) ? $RBG_array['b'] : '0';
66
 
67
 
68
 
86
  #content-background{ width: 550px; padding-top: 20px; padding-bottom:20px;border-radius: 6px; margin: auto; }
87
 
88
  <?php if ($bg_image != "") { ?>
89
+
90
  html { background: none;}
91
 
92
  body { background: url(<?php echo $bg_image; ?>) no-repeat center center fixed;
100
  div, p, #note, label, #lp_container { color: #<?php echo $text_color; ?>}
101
  .countDiv::before, .countDiv::after {
102
  background-color: #<?php echo $text_color; ?>;
103
+ }
104
+
105
  <?php if ($headline_color != "") { echo "h1 {color: #$headline_color;}"; } ?>
106
  <?php if ($background_on === "on") { echo "#content-background{background: url('".$path."image.php?hex=$content_color');}"; }?>
107
  <?php if ($submit_button_color != "") {
tests/codeception/_bootstrap.php CHANGED
@@ -6,4 +6,8 @@ require '../../../wp-admin/includes/plugin.php';
6
 
7
  /* load required landing pages files */
8
  include_once LANDINGPAGES_PATH . 'modules/module.install.php';
9
- include_once LANDINGPAGES_PATH . 'classes/class.statistics.php';
 
 
 
 
6
 
7
  /* load required landing pages files */
8
  include_once LANDINGPAGES_PATH . 'modules/module.install.php';
9
+ include_once LANDINGPAGES_PATH . 'classes/class.statistics.php';
10
+
11
+ /* Set current users */
12
+ wp_set_current_user( 1 );
13
+ global $wpdb;
tests/codeception/acceptance.suite.yml CHANGED
@@ -7,12 +7,12 @@
7
  class_name: AcceptanceTester
8
  modules:
9
  enabled:
10
- - WebDriver
11
  - AcceptanceHelper
12
  - Asserts
13
  config:
14
  WebDriver:
15
- url: 'http://local.wordpress.dev/'
16
  browser: firefox
17
  clear_cookies: false
18
  window_size: 1024x768
7
  class_name: AcceptanceTester
8
  modules:
9
  enabled:
10
+ - PhpBrowser
11
  - AcceptanceHelper
12
  - Asserts
13
  config:
14
  WebDriver:
15
+ url: 'http://inboundtesting.dev/'
16
  browser: firefox
17
  clear_cookies: false
18
  window_size: 1024x768
tests/codeception/acceptance/LoginCept.php CHANGED
@@ -1,8 +1,36 @@
1
  <?php
 
 
 
 
 
 
 
 
 
 
2
  $I = new AcceptanceTester($scenario);
 
 
3
  $I->wantTo('login to wp-admin');
4
  $I->amOnPage( site_url().'/wp-login.php' );
5
  $I->fillField('Username', 'admin');
6
  $I->fillField('Password','admin');
7
  $I->click('Log In');
8
  $I->see('Dashboard');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <?php
2
+ /**
3
+ * - login
4
+ * - navigate to plugins
5
+ * - deactivate landing pages
6
+ * - activate landing pages
7
+ * - confirm welcome page shows
8
+ *
9
+ */
10
+
11
+
12
  $I = new AcceptanceTester($scenario);
13
+
14
+
15
  $I->wantTo('login to wp-admin');
16
  $I->amOnPage( site_url().'/wp-login.php' );
17
  $I->fillField('Username', 'admin');
18
  $I->fillField('Password','admin');
19
  $I->click('Log In');
20
  $I->see('Dashboard');
21
+
22
+
23
+ $I->wantTo('Navigate to plugins');
24
+ $I->click( [ 'link' => 'Installed Plugins']);
25
+ $I->see('Active');
26
+
27
+ $I->wantTo('Deactivate Landing Pages');
28
+ $I->click( '#landing-pages .deactivate a');
29
+ $I->see('deactivated');
30
+
31
+ $I->wantTo('Reactivate Landing Pages');
32
+ $I->click( '#landing-pages .activate a');
33
+
34
+ $I->wantTo('Confirm welcome page');
35
+ $I->see('Welcome to WordPress Landing Pages ');
36
+
tests/codeception/acceptance/StatisticsCept.php CHANGED
@@ -1,55 +1,47 @@
1
- <?php
2
-
3
  /**
4
  * This test is desnged to test the impressions/conversions systems of landing pages.
5
  * Systems tested:
6
- * - Dmummy landing page creation
7
- * - Impression/Conversion UI display on landing page edit screen
8
- * - Cear individual landing page stats buttons.
9
- * - Manually setting impressions/conversions
10
- * - Makes sure landing page does not 404
11
- * - Frontend impression ajax systems
12
- * - Variation rotation systems
13
- * - Conversion tracking system for inbount form
14
- * - Conversion tracking system for tracked link
 
15
  */
16
 
17
- /* create test landing page */
18
- $lp_id = inbound_install_example_lander();
19
- shell_exec('here');
20
- shell_exec($lp_id);
21
- $permalink = get_post_permalink( $lp_id , false );
22
- shell_exec($permalink);
23
  $I = new AcceptanceTester($scenario);
24
-
25
-
26
- $I->wantTo('check example landing page is editable');
27
- $I->amOnPage( admin_url( 'post.php?post='. $lp_id .'&action=edit&frontend=false') );
28
- $I->seeInField( '#title','A/B Testing Landing Page Example');
29
-
 
 
 
 
 
 
30
  $I->wantTo('check if impressions are correct for variation a');
31
  $imp = $I->grabTextFrom('#lp-variation-A .bab-stat-span-impressions');
32
  $I->assertContains( '30' , $imp );
33
-
34
  $I->wantTo('check check impressions for variation b');
35
  $imp = $I->grabTextFrom('#lp-variation-B .bab-stat-span-impressions');
36
  $I->assertContains( '35' , $imp , '' );
37
-
38
  $I->wantTo('check conversions for variation a');
39
  $con = $I->grabTextFrom('#lp-variation-A .bab-stat-span-conversions');
40
  $I->assertContains( '10' , $con , '' );
41
-
42
  $I->wantTo('check conversions for variation b');
43
  $con = $I->grabTextFrom('#lp-variation-B .bab-stat-span-conversions');
44
  $I->assertContains( '15' , $con );
45
-
46
  $I->wantTo('check the conversion rate of variation a');
47
  $per = $I->grabTextFrom('#lp-variation-A .bab-stat-span-conversion_rate');
48
  $I->assertContains( '33' , $per );
49
-
50
  $I->wantTo('check the conversion rate of variation b');
51
  $per = $I->grabTextFrom('#lp-variation-B .bab-stat-span-conversion_rate');
52
  $I->assertContains( '43' , $per );
53
-
54
-
55
-
1
+ <?php
 
2
  /**
3
  * This test is desnged to test the impressions/conversions systems of landing pages.
4
  * Systems tested:
5
+ * [x] Login to WordPress
6
+ * [x] Navigate to Landing Pages
7
+ * [x] Open example landing page
8
+ * [x] Check if impression/conversion UI display on landing page edit screen
9
+ * [ ] Reset impressions/conversions and refresh page
10
+ * [ ] Make sure stats read 0
11
+ * [ ] Open landing page and make sure it does not 404
12
+ * [ ] Refresh landing page and make sure variation 2 loads
13
+ * [ ] Submit test conversion on variation 2
14
+ * [ ] Navigate back to edit page and make sure stats read correctly
15
  */
16
 
 
 
 
 
 
 
17
  $I = new AcceptanceTester($scenario);
18
+ $I->wantTo('login to wp-admin');
19
+ $I->amOnPage( site_url().'/wp-login.php' );
20
+ $I->fillField('Username', 'admin');
21
+ $I->fillField('Password','admin');
22
+ $I->click('Log In');
23
+ $I->see('Dashboard');
24
+ $I->wantTo('Navigate to landing pages list');
25
+ $I->click('Landing Pages');
26
+ $I->amOnPage( admin_url( 'edit.php?post_type=landing-page') );
27
+ $I->see( 'Landing Pages');
28
+ $I->wantTo('Open example landing page');
29
+ $I->click( [ 'link' => 'A/B Testing Landing Page Example']);
30
  $I->wantTo('check if impressions are correct for variation a');
31
  $imp = $I->grabTextFrom('#lp-variation-A .bab-stat-span-impressions');
32
  $I->assertContains( '30' , $imp );
 
33
  $I->wantTo('check check impressions for variation b');
34
  $imp = $I->grabTextFrom('#lp-variation-B .bab-stat-span-impressions');
35
  $I->assertContains( '35' , $imp , '' );
 
36
  $I->wantTo('check conversions for variation a');
37
  $con = $I->grabTextFrom('#lp-variation-A .bab-stat-span-conversions');
38
  $I->assertContains( '10' , $con , '' );
 
39
  $I->wantTo('check conversions for variation b');
40
  $con = $I->grabTextFrom('#lp-variation-B .bab-stat-span-conversions');
41
  $I->assertContains( '15' , $con );
 
42
  $I->wantTo('check the conversion rate of variation a');
43
  $per = $I->grabTextFrom('#lp-variation-A .bab-stat-span-conversion_rate');
44
  $I->assertContains( '33' , $per );
 
45
  $I->wantTo('check the conversion rate of variation b');
46
  $per = $I->grabTextFrom('#lp-variation-B .bab-stat-span-conversion_rate');
47
  $I->assertContains( '43' , $per );
 
 
 
tests/phpunit/bootstrap.php CHANGED
@@ -11,20 +11,5 @@ require '../../../wp-load.php';
11
  /* load plugins */
12
  require '../../../wp-admin/includes/plugin.php';
13
 
14
- /**
15
- * Replacement for wp_remote_get
16
- * processes javascript through PhantomJs
17
- */
18
- function inbound_remote_get( $url ) {
19
- $response = wp_remote_get(
20
- add_query_arg(
21
- array( 'url' => urlencode( $url ) ) ,
22
- LANDINGPAGES_URLPATH . 'tests/phantomjs/server.php'
23
- )
24
- );
25
-
26
- return $response;
27
- }
28
-
29
 
30
 
11
  /* load plugins */
12
  require '../../../wp-admin/includes/plugin.php';
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15