Version Description
- New preview views in landing page edit screens
- Temporarily disabling geolocation services
Download this release
Release Info
Developer | DavidWells |
Plugin | WordPress Landing Pages |
Version | 1.9.0 |
Comparing to | |
See all releases |
Code changes from version 1.8.8 to 1.9.0
- classes/class.activation.php +53 -4
- css/admin-style.css +15 -0
- js/admin/admin.post-edit.js +5 -2
- js/libraries/easyXDM.debug.js +1 -1
- js/libraries/jquery.zoomer.js +436 -0
- landing-pages.php +46 -48
- libraries/class-tgm-plugin-activation.php +3497 -2048
- libraries/shareme/sharrre/jquery.sharrre-1.3.3.min.js +1 -7
- modules/module.ab-testing.php +72 -146
- modules/module.admin-menus.php +5 -2
- modules/module.ajax-setup.php +1 -1
- modules/module.customizer.php +1 -1
- modules/module.install.php +12 -12
- modules/module.javascript-admin.php +1 -1
- modules/module.javascript-frontend.php +15 -6
- modules/module.metaboxes.php +64 -36
- modules/module.post-type.php +0 -10
- modules/module.store.php +1 -1
- modules/module.welcome.php +28 -9
- package.json +45 -0
- phpunit.xml.dist +8 -0
- readme.txt +5 -1
- shared/assets/assets.loader.class.php +1 -0
- shared/assets/js/frontend/analytics-src/analytics.forms.js +2 -2
- shared/assets/js/frontend/analytics-src/analytics.page.js +1 -0
- shared/assets/js/frontend/analytics/inboundAnalytics.js +3 -2
- shared/assets/js/frontend/analytics/inboundAnalytics.min.js +2 -2
- shared/classes/class.feedback.php +1 -1
- shared/classes/class.form.php +188 -50
- shared/classes/class.inbound-forms.akismet.php +0 -2
- shared/classes/class.lead-storage.php +713 -714
- shared/classes/class.licensing.php +11 -10
- shared/classes/class.load-shared.php +1 -1
- shared/classes/class.magic.php +2 -2
- shared/classes/class.master-license.php +4 -4
- shared/classes/class.menu.php +5 -7
- shared/classes/class.menus.adminbar.php +2 -4
- shared/classes/class.welcome.php +3 -3
- shared/shortcodes/css/form-cpt.css +9 -4
- shared/shortcodes/css/select2.png +0 -0
- shared/shortcodes/css/shortcodes.css +4 -3
- shared/shortcodes/inbound-shortcodes.php +28 -95
- shared/shortcodes/js/form-cpt.js +105 -74
- shared/shortcodes/shortcodes/forms.php +85 -77
- templates/countdown-lander/config.php +1 -1
- templates/countdown-lander/index.php +6 -6
- tests/codeception/_bootstrap.php +5 -1
- tests/codeception/acceptance.suite.yml +2 -2
- tests/codeception/acceptance/LoginCept.php +28 -0
- tests/codeception/acceptance/StatisticsCept.php +23 -31
- 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 |
* local: "name.html",
|
905 |
* onReady: function(){
|
906 |
* // you need to wait for the onReady callback before using the socket
|
907 |
* socket.postMessage("foo-message");
|
908 |
* },
|
909 |
* onMessage: function(message, origin) {
|
910 |
* alert("received " + message + " from " + origin);
|
911 |
* }
|
912 |
* });
|
913 |
* </code></pre>
|
914 |
* Setting up the consumer:
|
915 |
* <pre><code>
|
916 |
* var socket = new easyXDM.Socket({
|
917 |
* remote: "http://remotedomain/page.html",
|
918 |
* remoteHelper: "http://remotedomain/name.html",
|
919 |
* onReady: function(){
|
920 |
* // you need to wait for the onReady callback before using the socket
|
921 |
* socket.postMessage("foo-message");
|
922 |
* },
|
923 |
* onMessage: function(message, origin) {
|
924 |
* alert("received " + message + " from " + origin);
|
925 |
* }
|
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 |
* // this configuration is equal to that used by the Socket.
|
990 |
* remote: "http://remotedomain/...",
|
991 |
* onReady: function(){
|
992 |
* // you need to wait for the onReady callback before using the proxy
|
993 |
* rpc.foo(...
|
994 |
* }
|
995 |
* },{
|
996 |
* local: {..},
|
997 |
* remote: {..}
|
998 |
* });
|
999 |
* </code></pre>
|
1000 |
*
|
1001 |
* <h2>Exposing functions (procedures)</h2>
|
1002 |
* <pre><code>
|
1003 |
* var rpc = new easyXDM.Rpc({
|
1004 |
* ...
|
1005 |
* },{
|
1006 |
* local: {
|
1007 |
* nameOfMethod: {
|
1008 |
* method: function(arg1, arg2, success, error){
|
1009 |
* ...
|
1010 |
* }
|
1011 |
* },
|
1012 |
* // with shorthand notation
|
1013 |
* nameOfAnotherMethod: function(arg1, arg2, success, error){
|
1014 |
* }
|
1015 |
* },
|
1016 |
* 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 |
* ...
|
1042 |
* },{
|
1043 |
* local: {...},
|
1044 |
* remote: {
|
1045 |
* nameOfMethod: {}
|
1046 |
* }
|
1047 |
* });
|
1048 |
* </code></pre>
|
1049 |
* To call a remote method use
|
1050 |
* <pre><code>
|
1051 |
* rpc.nameOfMethod("arg1", "arg2", function(value) {
|
1052 |
* alert("success: " + value);
|
1053 |
* }, function(message) {
|
1054 |
* 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 |
* ...
|
1067 |
* },{
|
1068 |
* local: {...},
|
1069 |
* remote: {...},
|
1070 |
* serializer : {
|
1071 |
* parse: function(string){ ... },
|
1072 |
* stringify: function(object) {...}
|
1073 |
* }
|
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 |
* local: "name.html",
|
2917 |
* onReady: function(){
|
2918 |
* // you need to wait for the onReady callback before using the socket
|
2919 |
* socket.postMessage("foo-message");
|
2920 |
* },
|
2921 |
* onMessage: function(message, origin) {
|
2922 |
* alert("received " + message + " from " + origin);
|
2923 |
* }
|
2924 |
* });
|
2925 |
* </code></pre>
|
2926 |
* Setting up the consumer:
|
2927 |
* <pre><code>
|
2928 |
* var socket = new easyXDM.Socket({
|
2929 |
* remote: "http://remotedomain/page.html",
|
2930 |
* remoteHelper: "http://remotedomain/name.html",
|
2931 |
* onReady: function(){
|
2932 |
* // you need to wait for the onReady callback before using the socket
|
2933 |
* socket.postMessage("foo-message");
|
2934 |
* },
|
2935 |
* onMessage: function(message, origin) {
|
2936 |
* alert("received " + message + " from " + origin);
|
2937 |
* }
|
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 |
* // this configuration is equal to that used by the Socket.
|
2996 |
* remote: "http://remotedomain/...",
|
2997 |
* onReady: function(){
|
2998 |
* // you need to wait for the onReady callback before using the proxy
|
2999 |
* rpc.foo(...
|
3000 |
* }
|
3001 |
* },{
|
3002 |
* local: {..},
|
3003 |
* remote: {..}
|
3004 |
* });
|
3005 |
* </code></pre>
|
3006 |
*
|
3007 |
* <h2>Exposing functions (procedures)</h2>
|
3008 |
* <pre><code>
|
3009 |
* var rpc = new easyXDM.Rpc({
|
3010 |
* ...
|
3011 |
* },{
|
3012 |
* local: {
|
3013 |
* nameOfMethod: {
|
3014 |
* method: function(arg1, arg2, success, error){
|
3015 |
* ...
|
3016 |
* }
|
3017 |
* },
|
3018 |
* // with shorthand notation
|
3019 |
* nameOfAnotherMethod: function(arg1, arg2, success, error){
|
3020 |
* }
|
3021 |
* },
|
3022 |
* 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 |
* ...
|
3048 |
* },{
|
3049 |
* local: {...},
|
3050 |
* remote: {
|
3051 |
* nameOfMethod: {}
|
3052 |
* }
|
3053 |
* });
|
3054 |
* </code></pre>
|
3055 |
* To call a remote method use
|
3056 |
* <pre><code>
|
3057 |
* rpc.nameOfMethod("arg1", "arg2", function(value) {
|
3058 |
* alert("success: " + value);
|
3059 |
* }, function(message) {
|
3060 |
* 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 |
* ...
|
3073 |
* },{
|
3074 |
* local: {...},
|
3075 |
* remote: {...},
|
3076 |
* serializer : {
|
3077 |
* parse: function(string){ ... },
|
3078 |
* stringify: function(object) {...}
|
3079 |
* }
|
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 |
* local: "name.html",
|
904 |
* onReady: function(){
|
905 |
* // you need to wait for the onReady callback before using the socket
|
906 |
* socket.postMessage("foo-message");
|
907 |
* },
|
908 |
* onMessage: function(message, origin) {
|
909 |
* alert("received " + message + " from " + origin);
|
910 |
* }
|
911 |
* });
|
912 |
* </code></pre>
|
913 |
* Setting up the consumer:
|
914 |
* <pre><code>
|
915 |
* var socket = new easyXDM.Socket({
|
916 |
* remote: "http://remotedomain/page.html",
|
917 |
* remoteHelper: "http://remotedomain/name.html",
|
918 |
* onReady: function(){
|
919 |
* // you need to wait for the onReady callback before using the socket
|
920 |
* socket.postMessage("foo-message");
|
921 |
* },
|
922 |
* onMessage: function(message, origin) {
|
923 |
* alert("received " + message + " from " + origin);
|
924 |
* }
|
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 |
* // this configuration is equal to that used by the Socket.
|
989 |
* remote: "http://remotedomain/...",
|
990 |
* onReady: function(){
|
991 |
* // you need to wait for the onReady callback before using the proxy
|
992 |
* rpc.foo(...
|
993 |
* }
|
994 |
* },{
|
995 |
* local: {..},
|
996 |
* remote: {..}
|
997 |
* });
|
998 |
* </code></pre>
|
999 |
*
|
1000 |
* <h2>Exposing functions (procedures)</h2>
|
1001 |
* <pre><code>
|
1002 |
* var rpc = new easyXDM.Rpc({
|
1003 |
* ...
|
1004 |
* },{
|
1005 |
* local: {
|
1006 |
* nameOfMethod: {
|
1007 |
* method: function(arg1, arg2, success, error){
|
1008 |
* ...
|
1009 |
* }
|
1010 |
* },
|
1011 |
* // with shorthand notation
|
1012 |
* nameOfAnotherMethod: function(arg1, arg2, success, error){
|
1013 |
* }
|
1014 |
* },
|
1015 |
* 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 |
* ...
|
1041 |
* },{
|
1042 |
* local: {...},
|
1043 |
* remote: {
|
1044 |
* nameOfMethod: {}
|
1045 |
* }
|
1046 |
* });
|
1047 |
* </code></pre>
|
1048 |
* To call a remote method use
|
1049 |
* <pre><code>
|
1050 |
* rpc.nameOfMethod("arg1", "arg2", function(value) {
|
1051 |
* alert("success: " + value);
|
1052 |
* }, function(message) {
|
1053 |
* 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 |
* ...
|
1066 |
* },{
|
1067 |
* local: {...},
|
1068 |
* remote: {...},
|
1069 |
* serializer : {
|
1070 |
* parse: function(string){ ... },
|
1071 |
* stringify: function(object) {...}
|
1072 |
* }
|
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 |
* local: "name.html",
|
2917 |
* onReady: function(){
|
2918 |
* // you need to wait for the onReady callback before using the socket
|
2919 |
* socket.postMessage("foo-message");
|
2920 |
* },
|
2921 |
* onMessage: function(message, origin) {
|
2922 |
* alert("received " + message + " from " + origin);
|
2923 |
* }
|
2924 |
* });
|
2925 |
* </code></pre>
|
2926 |
* Setting up the consumer:
|
2927 |
* <pre><code>
|
2928 |
* var socket = new easyXDM.Socket({
|
2929 |
* remote: "http://remotedomain/page.html",
|
2930 |
* remoteHelper: "http://remotedomain/name.html",
|
2931 |
* onReady: function(){
|
2932 |
* // you need to wait for the onReady callback before using the socket
|
2933 |
* socket.postMessage("foo-message");
|
2934 |
* },
|
2935 |
* onMessage: function(message, origin) {
|
2936 |
* alert("received " + message + " from " + origin);
|
2937 |
* }
|
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 |
* // this configuration is equal to that used by the Socket.
|
2996 |
* remote: "http://remotedomain/...",
|
2997 |
* onReady: function(){
|
2998 |
* // you need to wait for the onReady callback before using the proxy
|
2999 |
* rpc.foo(...
|
3000 |
* }
|
3001 |
* },{
|
3002 |
* local: {..},
|
3003 |
* remote: {..}
|
3004 |
* });
|
3005 |
* </code></pre>
|
3006 |
*
|
3007 |
* <h2>Exposing functions (procedures)</h2>
|
3008 |
* <pre><code>
|
3009 |
* var rpc = new easyXDM.Rpc({
|
3010 |
* ...
|
3011 |
* },{
|
3012 |
* local: {
|
3013 |
* nameOfMethod: {
|
3014 |
* method: function(arg1, arg2, success, error){
|
3015 |
* ...
|
3016 |
* }
|
3017 |
* },
|
3018 |
* // with shorthand notation
|
3019 |
* nameOfAnotherMethod: function(arg1, arg2, success, error){
|
3020 |
* }
|
3021 |
* },
|
3022 |
* 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 |
* ...
|
3048 |
* },{
|
3049 |
* local: {...},
|
3050 |
* remote: {
|
3051 |
* nameOfMethod: {}
|
3052 |
* }
|
3053 |
* });
|
3054 |
* </code></pre>
|
3055 |
* To call a remote method use
|
3056 |
* <pre><code>
|
3057 |
* rpc.nameOfMethod("arg1", "arg2", function(value) {
|
3058 |
* alert("success: " + value);
|
3059 |
* }, function(message) {
|
3060 |
* 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 |
* ...
|
3073 |
* },{
|
3074 |
* local: {...},
|
3075 |
* remote: {...},
|
3076 |
* serializer : {
|
3077 |
* parse: function(string){ ... },
|
3078 |
* stringify: function(object) {...}
|
3079 |
* }
|
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.
|
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.
|
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.
|
7 |
-
* @
|
8 |
-
* @author Gary Jones
|
9 |
-
* @copyright Copyright (c)
|
10 |
-
* @license
|
11 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
*/
|
13 |
|
14 |
/*
|
15 |
-
Copyright
|
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
|
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( '
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
482 |
-
|
483 |
-
|
484 |
-
|
485 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
-
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
|
530 |
-
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
558 |
-
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
|
563 |
-
|
564 |
-
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
|
576 |
-
|
577 |
-
|
578 |
-
|
579 |
-
|
580 |
-
|
581 |
-
|
582 |
-
|
583 |
-
|
584 |
-
|
585 |
-
|
586 |
-
|
587 |
-
|
588 |
-
|
589 |
-
|
590 |
-
|
591 |
-
|
592 |
-
|
593 |
-
|
594 |
-
|
595 |
-
|
596 |
-
|
597 |
-
|
598 |
-
|
599 |
-
|
600 |
-
|
601 |
-
|
602 |
-
|
603 |
-
|
604 |
-
|
605 |
-
|
606 |
-
|
607 |
-
|
608 |
-
|
609 |
-
|
610 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
|
617 |
-
|
618 |
-
|
619 |
-
|
620 |
-
|
621 |
-
|
622 |
-
|
623 |
-
|
624 |
-
|
625 |
-
|
626 |
-
|
627 |
-
|
628 |
-
|
629 |
-
|
630 |
-
|
631 |
-
|
632 |
-
|
633 |
-
|
634 |
-
|
635 |
-
|
636 |
-
|
637 |
-
|
638 |
-
|
639 |
-
|
640 |
-
|
641 |
-
|
642 |
-
|
643 |
-
|
644 |
-
|
645 |
-
|
646 |
-
|
647 |
-
|
648 |
-
|
649 |
-
|
650 |
-
|
651 |
-
|
652 |
-
|
653 |
-
|
654 |
-
|
655 |
-
|
656 |
-
|
657 |
-
|
658 |
-
|
659 |
-
|
660 |
-
|
661 |
-
|
662 |
-
|
663 |
-
|
664 |
-
|
665 |
-
|
666 |
-
|
667 |
-
|
668 |
-
|
669 |
-
|
670 |
-
|
671 |
-
|
672 |
-
|
673 |
-
|
674 |
-
|
675 |
-
|
676 |
-
|
677 |
-
|
678 |
-
|
679 |
-
|
680 |
-
|
681 |
-
|
682 |
-
|
683 |
-
|
684 |
-
|
685 |
-
|
686 |
-
|
687 |
-
|
688 |
-
|
689 |
-
|
690 |
-
|
691 |
-
|
692 |
-
|
693 |
-
|
694 |
-
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
-
|
699 |
-
|
700 |
-
|
701 |
-
|
702 |
-
|
703 |
-
|
704 |
-
|
705 |
-
|
706 |
-
|
707 |
-
|
708 |
-
|
709 |
-
|
710 |
-
|
711 |
-
|
712 |
-
|
713 |
-
|
714 |
-
|
715 |
-
|
716 |
-
|
717 |
-
|
718 |
-
|
719 |
-
|
720 |
-
|
721 |
-
|
722 |
-
|
723 |
-
|
724 |
-
|
725 |
-
|
726 |
-
|
727 |
-
|
728 |
-
|
729 |
-
|
730 |
-
|
731 |
-
|
732 |
-
|
733 |
-
|
734 |
-
|
735 |
-
|
736 |
-
|
737 |
-
|
738 |
-
|
739 |
-
|
740 |
-
|
741 |
-
|
742 |
-
|
743 |
-
|
744 |
-
|
745 |
-
|
746 |
-
|
747 |
-
|
748 |
-
|
749 |
-
|
750 |
-
|
751 |
-
|
752 |
-
|
753 |
-
|
754 |
-
|
755 |
-
|
756 |
-
|
757 |
-
|
758 |
-
|
759 |
-
|
760 |
-
|
761 |
-
|
762 |
-
|
763 |
-
|
764 |
-
|
765 |
-
|
766 |
-
|
767 |
-
|
768 |
-
|
769 |
-
|
770 |
-
|
771 |
-
|
772 |
-
|
773 |
-
|
774 |
-
|
775 |
-
|
776 |
-
|
777 |
-
|
778 |
-
|
779 |
-
|
780 |
-
|
781 |
-
|
782 |
-
|
783 |
-
|
784 |
-
|
785 |
-
|
786 |
-
|
787 |
-
|
788 |
-
|
789 |
-
|
790 |
-
|
791 |
-
|
792 |
-
|
793 |
-
|
794 |
-
|
795 |
-
|
796 |
-
|
797 |
-
|
798 |
-
|
799 |
-
|
800 |
-
|
801 |
-
|
802 |
-
|
803 |
-
|
804 |
-
|
805 |
-
|
806 |
-
|
807 |
-
|
808 |
-
|
809 |
-
|
810 |
-
|
811 |
-
|
812 |
-
|
813 |
-
|
814 |
-
|
815 |
-
|
816 |
-
|
817 |
-
|
818 |
-
|
819 |
-
|
820 |
-
|
821 |
-
|
822 |
-
|
823 |
-
|
824 |
-
|
825 |
-
|
826 |
-
|
827 |
-
|
828 |
-
|
829 |
-
|
830 |
-
|
831 |
-
|
832 |
-
|
833 |
-
|
834 |
-
|
835 |
-
|
836 |
-
|
837 |
-
|
838 |
-
|
839 |
-
|
840 |
-
|
841 |
-
|
842 |
-
|
843 |
-
|
844 |
-
|
845 |
-
|
846 |
-
|
847 |
-
|
848 |
-
|
849 |
-
|
850 |
-
|
851 |
-
|
852 |
-
|
853 |
-
|
854 |
-
|
855 |
-
|
856 |
-
|
857 |
-
|
858 |
-
|
859 |
-
|
860 |
-
|
861 |
-
|
862 |
-
|
863 |
-
|
864 |
-
|
865 |
-
|
866 |
-
|
867 |
-
|
868 |
-
|
869 |
-
|
870 |
-
|
871 |
-
|
872 |
-
|
873 |
-
|
874 |
-
|
875 |
-
|
876 |
-
|
877 |
-
|
878 |
-
|
879 |
-
|
880 |
-
|
881 |
-
|
882 |
-
|
883 |
-
|
884 |
-
|
885 |
-
|
886 |
-
|
887 |
-
|
888 |
-
|
889 |
-
|
890 |
-
|
891 |
-
|
892 |
-
|
893 |
-
|
894 |
-
|
895 |
-
|
896 |
-
|
897 |
-
|
898 |
-
|
899 |
-
|
900 |
-
|
901 |
-
|
902 |
-
|
903 |
-
|
904 |
-
|
905 |
-
|
906 |
-
|
907 |
-
|
908 |
-
|
909 |
-
|
910 |
-
|
911 |
-
|
912 |
-
|
913 |
-
|
914 |
-
|
915 |
-
|
916 |
-
|
917 |
-
|
918 |
-
|
919 |
-
|
920 |
-
|
921 |
-
|
922 |
-
|
923 |
-
|
924 |
-
|
925 |
-
|
926 |
-
|
927 |
-
|
928 |
-
|
929 |
-
|
930 |
-
|
931 |
-
|
932 |
-
|
933 |
-
|
934 |
-
|
935 |
-
|
936 |
-
|
937 |
-
|
938 |
-
|
939 |
-
|
940 |
-
|
941 |
-
|
942 |
-
|
943 |
-
|
944 |
-
|
945 |
-
|
946 |
-
|
947 |
-
|
948 |
-
|
949 |
-
|
950 |
-
|
951 |
-
|
952 |
-
|
953 |
-
|
954 |
-
|
955 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
956 |
}
|
957 |
|
958 |
-
|
959 |
-
|
960 |
-
|
961 |
-
|
962 |
-
|
963 |
-
|
964 |
-
|
965 |
-
|
966 |
-
|
967 |
-
|
968 |
-
|
969 |
-
|
970 |
-
|
971 |
-
|
972 |
-
|
973 |
-
|
974 |
-
|
975 |
-
|
976 |
-
|
977 |
-
|
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 |
-
|
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 |
-
*
|
1589 |
* of the class.
|
1590 |
*
|
1591 |
* @since 2.2.0
|
1592 |
*/
|
1593 |
-
|
1594 |
-
|
1595 |
-
|
1596 |
-
|
1597 |
-
|
1598 |
-
|
1599 |
-
|
1600 |
-
|
1601 |
-
|
1602 |
-
|
1603 |
-
|
1604 |
-
|
1605 |
-
|
1606 |
-
|
1607 |
-
|
1608 |
-
|
1609 |
-
|
1610 |
-
|
1611 |
-
|
1612 |
-
|
1613 |
-
|
1614 |
-
|
1615 |
-
|
1616 |
-
|
1617 |
-
|
1618 |
-
|
1619 |
-
|
1620 |
-
|
1621 |
-
|
1622 |
-
|
1623 |
-
|
1624 |
-
|
1625 |
-
|
1626 |
-
|
1627 |
-
|
1628 |
-
|
1629 |
-
|
1630 |
-
|
1631 |
-
|
1632 |
-
|
1633 |
-
|
1634 |
-
|
1635 |
-
|
1636 |
-
|
1637 |
-
|
1638 |
-
|
1639 |
-
|
1640 |
-
|
1641 |
-
|
1642 |
-
|
1643 |
-
|
1644 |
-
|
1645 |
-
|
1646 |
-
|
1647 |
-
|
1648 |
-
|
1649 |
-
|
1650 |
-
|
1651 |
-
|
1652 |
-
|
1653 |
-
|
1654 |
-
|
1655 |
-
|
1656 |
-
|
1657 |
-
|
1658 |
-
|
1659 |
-
|
1660 |
-
|
1661 |
-
|
1662 |
-
|
1663 |
-
|
1664 |
-
|
1665 |
-
|
1666 |
-
|
1667 |
-
|
1668 |
-
|
1669 |
-
|
1670 |
-
|
1671 |
-
|
1672 |
-
|
1673 |
-
|
1674 |
-
|
1675 |
-
|
1676 |
-
|
1677 |
-
|
1678 |
-
|
1679 |
-
|
1680 |
-
|
1681 |
-
|
1682 |
-
|
1683 |
-
|
1684 |
-
|
1685 |
-
|
1686 |
-
|
1687 |
-
|
1688 |
-
|
1689 |
-
|
1690 |
-
|
1691 |
-
|
1692 |
-
|
1693 |
-
|
1694 |
-
|
1695 |
-
|
1696 |
-
|
1697 |
-
|
1698 |
-
|
1699 |
-
|
1700 |
-
|
1701 |
-
|
1702 |
-
|
1703 |
-
|
1704 |
-
|
1705 |
-
|
1706 |
-
|
1707 |
-
|
1708 |
-
|
1709 |
-
|
1710 |
-
|
1711 |
-
|
1712 |
-
|
1713 |
-
|
1714 |
-
|
1715 |
-
|
1716 |
-
|
1717 |
-
|
1718 |
-
|
1719 |
-
|
1720 |
-
|
1721 |
-
|
1722 |
-
|
1723 |
-
|
1724 |
-
|
1725 |
-
|
1726 |
-
|
1727 |
-
|
1728 |
-
|
1729 |
-
|
1730 |
-
|
1731 |
-
|
1732 |
-
|
1733 |
-
|
1734 |
-
|
1735 |
-
|
1736 |
-
|
1737 |
-
|
1738 |
-
|
1739 |
-
|
1740 |
-
|
1741 |
-
|
1742 |
-
|
1743 |
-
|
1744 |
-
|
1745 |
-
|
1746 |
-
|
1747 |
-
|
1748 |
-
|
1749 |
-
|
1750 |
-
|
1751 |
-
|
1752 |
-
|
1753 |
-
|
1754 |
-
|
1755 |
-
|
1756 |
-
|
1757 |
-
|
1758 |
-
|
1759 |
-
|
1760 |
-
|
1761 |
-
|
1762 |
-
|
1763 |
-
|
1764 |
-
|
1765 |
-
|
1766 |
-
|
1767 |
-
|
1768 |
-
|
1769 |
-
|
1770 |
-
|
1771 |
-
|
1772 |
-
|
1773 |
-
|
1774 |
-
|
1775 |
-
|
1776 |
-
|
1777 |
-
|
1778 |
-
|
1779 |
-
|
1780 |
-
|
1781 |
-
|
1782 |
-
|
1783 |
-
|
1784 |
-
|
1785 |
-
|
1786 |
-
|
1787 |
-
|
1788 |
-
|
1789 |
-
|
1790 |
-
|
1791 |
-
|
1792 |
-
|
1793 |
-
|
1794 |
-
|
1795 |
-
|
1796 |
-
|
1797 |
-
|
1798 |
-
|
1799 |
-
|
1800 |
-
|
1801 |
-
|
1802 |
-
|
1803 |
-
|
1804 |
-
|
1805 |
-
|
1806 |
-
|
1807 |
-
|
1808 |
-
|
1809 |
-
|
1810 |
-
|
1811 |
-
|
1812 |
-
|
1813 |
-
|
1814 |
-
|
1815 |
-
|
1816 |
-
|
1817 |
-
|
1818 |
-
|
1819 |
-
|
1820 |
-
|
1821 |
-
|
1822 |
-
|
1823 |
-
|
1824 |
-
|
1825 |
-
|
1826 |
-
|
1827 |
-
|
1828 |
-
|
1829 |
-
|
1830 |
-
|
1831 |
-
|
1832 |
-
|
1833 |
-
|
1834 |
-
|
1835 |
-
|
1836 |
-
|
1837 |
-
|
1838 |
-
|
1839 |
-
|
1840 |
-
|
1841 |
-
|
1842 |
-
|
1843 |
-
|
1844 |
-
|
1845 |
-
|
1846 |
-
|
1847 |
-
|
1848 |
-
|
1849 |
-
|
1850 |
-
|
1851 |
-
|
1852 |
-
|
1853 |
-
|
1854 |
-
|
1855 |
-
|
1856 |
-
|
1857 |
-
|
1858 |
-
|
1859 |
-
|
1860 |
-
|
1861 |
-
|
1862 |
-
|
1863 |
-
|
1864 |
-
|
1865 |
-
|
1866 |
-
|
1867 |
-
|
1868 |
-
|
1869 |
-
|
1870 |
-
|
1871 |
-
|
1872 |
-
|
1873 |
-
|
1874 |
-
|
1875 |
-
|
1876 |
-
|
1877 |
-
|
1878 |
-
|
1879 |
-
|
1880 |
-
|
1881 |
-
|
1882 |
-
|
1883 |
-
|
1884 |
-
|
1885 |
-
|
1886 |
-
|
1887 |
-
|
1888 |
-
|
1889 |
-
|
1890 |
-
|
1891 |
-
|
1892 |
-
|
1893 |
-
|
1894 |
-
|
1895 |
-
|
1896 |
-
|
1897 |
-
|
1898 |
-
|
1899 |
-
|
1900 |
-
|
1901 |
-
|
1902 |
-
|
1903 |
-
|
1904 |
-
|
1905 |
-
|
1906 |
-
|
1907 |
-
|
1908 |
-
|
1909 |
-
|
1910 |
-
|
1911 |
-
|
1912 |
-
|
1913 |
-
|
1914 |
-
|
1915 |
-
|
1916 |
-
|
1917 |
-
|
1918 |
-
|
1919 |
-
|
1920 |
-
|
1921 |
-
|
1922 |
-
|
1923 |
-
|
1924 |
-
|
1925 |
-
|
1926 |
-
|
1927 |
-
|
1928 |
-
|
1929 |
-
|
1930 |
-
|
1931 |
-
|
1932 |
-
|
1933 |
-
|
1934 |
-
|
1935 |
-
|
1936 |
-
|
1937 |
-
|
1938 |
-
|
1939 |
-
|
1940 |
-
|
1941 |
-
|
1942 |
-
|
1943 |
-
|
1944 |
-
|
1945 |
-
|
1946 |
-
|
1947 |
-
|
1948 |
-
|
1949 |
-
|
1950 |
-
|
1951 |
-
|
1952 |
-
|
1953 |
-
|
1954 |
-
|
1955 |
-
|
1956 |
-
|
1957 |
-
|
1958 |
-
|
1959 |
-
|
1960 |
-
|
1961 |
-
|
1962 |
-
|
1963 |
-
|
1964 |
-
|
1965 |
-
|
1966 |
-
|
1967 |
-
|
1968 |
-
|
1969 |
-
|
1970 |
-
|
1971 |
-
|
1972 |
-
|
1973 |
-
|
1974 |
-
|
1975 |
-
|
1976 |
-
|
1977 |
-
|
1978 |
-
|
1979 |
-
|
1980 |
-
|
1981 |
-
|
1982 |
-
|
1983 |
-
|
1984 |
-
|
1985 |
-
|
1986 |
-
|
1987 |
-
|
1988 |
-
|
1989 |
-
|
1990 |
-
|
1991 |
-
|
1992 |
-
|
1993 |
-
|
1994 |
-
|
1995 |
-
|
1996 |
-
|
1997 |
-
|
1998 |
-
|
1999 |
-
|
2000 |
-
|
2001 |
-
|
2002 |
-
|
2003 |
-
|
2004 |
-
|
2005 |
-
|
2006 |
-
|
2007 |
-
|
2008 |
-
|
2009 |
-
|
2010 |
-
|
2011 |
-
|
2012 |
-
|
2013 |
-
|
2014 |
-
|
2015 |
-
|
2016 |
-
|
2017 |
-
|
2018 |
-
|
2019 |
-
|
2020 |
-
|
2021 |
-
|
2022 |
-
|
2023 |
-
|
2024 |
-
|
2025 |
-
|
2026 |
-
|
2027 |
-
|
2028 |
-
|
2029 |
-
|
2030 |
-
|
2031 |
-
|
2032 |
-
|
2033 |
-
|
2034 |
-
|
2035 |
-
|
2036 |
-
|
2037 |
-
|
2038 |
-
|
2039 |
-
|
2040 |
-
|
2041 |
-
|
2042 |
-
|
2043 |
-
|
2044 |
-
|
2045 |
-
|
2046 |
-
|
2047 |
-
|
2048 |
-
|
2049 |
-
|
2050 |
-
|
2051 |
-
|
2052 |
-
|
2053 |
-
|
2054 |
-
|
2055 |
-
|
2056 |
-
|
2057 |
-
|
2058 |
-
|
2059 |
-
|
2060 |
-
|
2061 |
-
|
2062 |
-
|
2063 |
-
|
2064 |
-
|
2065 |
-
|
2066 |
-
|
2067 |
-
|
2068 |
-
|
2069 |
-
|
2070 |
-
|
2071 |
-
|
2072 |
-
|
2073 |
-
|
2074 |
-
|
2075 |
-
|
2076 |
-
|
2077 |
-
|
2078 |
-
|
2079 |
-
|
2080 |
-
|
2081 |
-
|
2082 |
-
|
2083 |
-
|
2084 |
-
|
2085 |
-
|
2086 |
-
|
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 ' '; // 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 (
|
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 |
-
|
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 |
-
|
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 |
-
|
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', __('
|
|
|
|
|
|
|
|
|
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 |
-
|
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.'
|
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') .
|
143 |
'slug' => 'cta',
|
144 |
'required' => false,
|
145 |
),
|
@@ -187,6 +187,6 @@ function lp_install_register_required_plugins() {
|
|
187 |
)
|
188 |
);
|
189 |
|
190 |
-
|
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 |
-
|
72 |
-
// Automation pass params to GA. Look for documentation
|
73 |
if (typeof window.history.pushState == 'function') {
|
74 |
-
var
|
75 |
-
|
|
|
|
|
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 |
-
|
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 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
$datetime = the_modified_date('YmjH',null,null,false);
|
32 |
-
$permalink = $
|
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 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
|
61 |
</div>
|
62 |
-
|
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){
|
|
|
|
|
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')) ?
|
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 |
-
|
|
|
|
|
342 |
} else {
|
343 |
-
|
|
|
|
|
344 |
}
|
345 |
$thumb = true;
|
346 |
}
|
347 |
-
|
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
|
593 |
-
* time
|
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.
|
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
|
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
|
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 |
-
|
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
|
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 |
-
|
889 |
-
|
890 |
-
|
891 |
-
|
892 |
-
|
893 |
-
$confirm_email_message = $template_array['body'];
|
894 |
|
895 |
-
|
896 |
-
|
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(']]>', ']]>', $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 |
-
$
|
937 |
-
|
938 |
-
|
939 |
-
|
940 |
-
|
941 |
-
|
942 |
-
|
943 |
-
|
944 |
-
|
945 |
-
|
946 |
-
|
947 |
-
|
948 |
-
|
949 |
-
|
950 |
-
|
951 |
-
|
952 |
-
|
953 |
-
|
954 |
-
|
955 |
-
|
956 |
-
|
957 |
-
|
958 |
-
|
959 |
-
|
960 |
-
|
961 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(']]>', ']]>', $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"> </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"> </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"> </td>
|
1059 |
+
<td valign="top" height="18" style="height:18px;color:#272727"></td>
|
1060 |
+
<td style="color:#272727"> </td>
|
1061 |
+
<td style="color:#545454;text-align:right" align="right"> </td>
|
1062 |
+
<td valign="middle" width="30" style="color:#272727"> </td>
|
1063 |
+
</tr>
|
1064 |
+
<tr>
|
1065 |
+
<td valign="middle" width="30" style="color:#272727"> </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"> </td>
|
1075 |
+
</tr>
|
1076 |
+
<tr>
|
1077 |
+
<td valign="top" height="6" style="color:#272727;line-height:1px"> </td>
|
1078 |
+
<td style="color:#272727;line-height:1px"> </td>
|
1079 |
+
<td style="color:#272727;line-height:1px"> </td>
|
1080 |
+
<td style="color:#545454;text-align:right;line-height:1px" align="right"> </td>
|
1081 |
+
<td valign="middle" width="30" style="color:#272727;line-height:1px"> </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"> </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', $
|
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 |
-
|
421 |
-
|
422 |
-
$response
|
423 |
-
|
424 |
-
$
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
$ip_addresses =
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
$
|
443 |
-
$
|
444 |
-
$
|
445 |
-
$
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
$websites
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
$
|
471 |
-
$
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
482 |
-
|
483 |
-
|
484 |
-
|
485 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
-
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
|
530 |
-
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
$
|
550 |
-
|
551 |
-
$key =
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
$newMap['
|
557 |
-
|
558 |
-
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
|
563 |
-
|
564 |
-
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
);
|
576 |
-
$wpdb->
|
577 |
-
|
578 |
-
$lead_id
|
579 |
-
|
580 |
-
|
581 |
-
|
582 |
-
|
583 |
-
|
584 |
-
|
585 |
-
|
586 |
-
|
587 |
-
$val
|
588 |
-
|
589 |
-
|
590 |
-
|
591 |
-
$keys
|
592 |
-
|
593 |
-
|
594 |
-
&& is_array( $
|
595 |
-
|
596 |
-
|
597 |
-
|
598 |
-
|
599 |
-
|
600 |
-
|
601 |
-
|
602 |
-
|
603 |
-
|
604 |
-
|
605 |
-
|
606 |
-
|
607 |
-
|
608 |
-
|
609 |
-
|
610 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
|
617 |
-
|
618 |
-
|
619 |
-
|
620 |
-
|
621 |
-
|
622 |
-
|
623 |
-
|
624 |
-
|
625 |
-
|
626 |
-
|
627 |
-
|
628 |
-
|
629 |
-
|
630 |
-
|
631 |
-
|
632 |
-
*
|
633 |
-
* @param
|
634 |
-
|
635 |
-
|
636 |
-
|
637 |
-
|
638 |
-
|
639 |
-
|
640 |
-
|
641 |
-
|
642 |
-
|
643 |
-
|
644 |
-
|
645 |
-
|
646 |
-
|
647 |
-
|
648 |
-
|
649 |
-
|
650 |
-
|
651 |
-
|
652 |
-
|
653 |
-
|
654 |
-
|
655 |
-
|
656 |
-
|
657 |
-
|
658 |
-
|
659 |
-
|
660 |
-
|
661 |
-
|
662 |
-
|
663 |
-
|
664 |
-
|
665 |
-
|
666 |
-
|
667 |
-
|
668 |
-
|
669 |
-
|
670 |
-
|
671 |
-
|
672 |
-
|
673 |
-
|
674 |
-
|
675 |
-
|
676 |
-
|
677 |
-
|
678 |
-
|
679 |
-
|
680 |
-
*
|
681 |
-
* @param
|
682 |
-
|
683 |
-
|
684 |
-
|
685 |
-
|
686 |
-
|
687 |
-
|
688 |
-
|
689 |
-
$
|
690 |
-
$
|
691 |
-
$conversion_data =
|
692 |
-
$
|
693 |
-
|
694 |
-
|
695 |
-
|
696 |
-
$c_count =
|
697 |
-
$conversion_data[$c_count]['
|
698 |
-
$conversion_data[$c_count]['
|
699 |
-
|
700 |
-
|
701 |
-
$c_count =
|
702 |
-
$conversion_data[$c_count]['
|
703 |
-
$conversion_data[$c_count]['
|
704 |
-
$conversion_data[$c_count]['
|
705 |
-
|
706 |
-
|
707 |
-
|
708 |
-
$
|
709 |
-
update_post_meta($lead_id,'
|
710 |
-
|
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 ) )
|
|
|
|
|
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 |
-
|
105 |
-
|
106 |
-
|
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:
|
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:
|
65 |
-
|
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:
|
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 |
-
|
217 |
-
|
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( __( '
|
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 |
-
|
3 |
-
|
4 |
var form_move = jQuery("#entire-form-area");
|
5 |
jQuery("#titlediv").after(form_move);
|
6 |
jQuery("#entire-form-area").fadeIn(1000);
|
7 |
-
|
8 |
jQuery("#inbound-shortcodes-preview").hide().fadeIn(5000);
|
9 |
-
|
10 |
jQuery("#title-prompt-text").hide();
|
11 |
-
|
12 |
-
|
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 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
40 |
-
|
41 |
-
|
42 |
});
|
43 |
jQuery("body").on('click', '#inbound_save_this_form', function () {
|
44 |
-
|
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 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
setTimeout(function() {
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
function fill_form_fields(){
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
$select.val(decodeURIComponent(vals[1].replace(/\+/g, ' ')));
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
|
93 |
-
|
94 |
}
|
|
|
95 |
if (post_status === 'draft') {
|
96 |
-
setTimeout(function() {
|
97 |
jQuery("#inbound_shortcode_insert_default").val('none');
|
98 |
-
|
99 |
}
|
100 |
-
if (post_status === 'draft' && post_title != "" || post_status ==='pending' && post_title != ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
|
102 |
|
103 |
-
|
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 |
-
|
115 |
-
|
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',
|
21 |
-
'desc' => __('Start Building Your Form from premade templates',
|
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>',
|
29 |
-
'desc' => __('This is not shown to visitors',
|
30 |
'type' => 'text',
|
31 |
'placeholder' => "Example: XYZ Whitepaper Download",
|
32 |
'std' => '',
|
33 |
'class' => 'main-form-settings',
|
34 |
),
|
35 |
/*'confirmation' => array(
|
36 |
-
'name' => __('Form Layout',
|
37 |
-
'desc' => __('Choose Your Form Layout',
|
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>',
|
47 |
-
'desc' => __('Where do you want to send people after they fill out the form?',
|
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>',
|
56 |
-
'desc' => __('Put field description here.',
|
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>',
|
64 |
-
'desc' => __('Who should get admin notifications on this form?<br>For multiple notifications separate email addresses with commas',
|
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>',
|
72 |
-
'desc' => __('Customize the subject line of email notifications arriving from this form. default: {{site-name}} {{form-name}} - New Lead Conversion',
|
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)',
|
80 |
-
'desc' => __('Add the converting lead to 1 or more lead lists',
|
81 |
'type' => 'leadlists',
|
82 |
'options' => $lead_list_names,
|
83 |
'class' => 'main-form-settings',
|
84 |
),
|
85 |
|
86 |
'lists_hidden' => array(
|
87 |
-
'name' => __('Hidden List Values',
|
88 |
-
'desc' => __('Hidden list values',
|
89 |
'type' => 'hidden',
|
90 |
'class' => 'main-form-settings',
|
91 |
),
|
92 |
|
93 |
'helper-block-one' => array(
|
94 |
-
'name' => __('Name Name Name',
|
95 |
-
'desc' => __('<span class="switch-to-form-insert button">Cancel Form Creation & Insert Existing Form</span>',
|
96 |
'type' => 'helper-block',
|
97 |
'std' => '',
|
98 |
'class' => 'main-form-settings',
|
99 |
),
|
100 |
'heading_design' => array(
|
101 |
-
'name' => __('Name Name Name',
|
102 |
-
'desc' => __('Layout Options',
|
103 |
'type' => 'helper-block',
|
104 |
'std' => '',
|
105 |
'class' => 'main-design-settings',
|
106 |
),
|
107 |
'layout' => array(
|
108 |
-
'name' => __('Form Layout',
|
109 |
-
'desc' => __('Choose Your Form Layout',
|
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',
|
120 |
-
'desc' => __('Choose Label Layout',
|
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',
|
133 |
-
'desc' => __('Size of Label Font. This also determines default submit button size',
|
134 |
'type' => 'text',
|
135 |
'std' => '16',
|
136 |
'class' => 'main-design-settings',
|
137 |
),
|
138 |
'icon' => array(
|
139 |
-
'name' => __('Submit Button Icon',
|
140 |
-
'desc' => __('Select an icon.',
|
141 |
'type' => 'select',
|
142 |
'options' => $fontawesome,
|
143 |
'std' => 'none',
|
144 |
'class' => 'main-design-settings'
|
145 |
),
|
146 |
'submit' => array(
|
147 |
-
'name' => __('Submit Button Text',
|
148 |
-
'desc' => __('Enter the text you want to show on the submit button. (or a link to a custom submit button image)',
|
149 |
'type' => 'text',
|
150 |
'std' => 'Submit',
|
151 |
'class' => 'main-design-settings',
|
152 |
),
|
153 |
'submit-colors' => array(
|
154 |
-
'name' => __('Submit Color Options',
|
155 |
-
'desc' => __('Choose Your Form Layout',
|
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',
|
166 |
-
'desc' => __('Color of text. Must toggle on "Submit Color Options" on',
|
167 |
'type' => 'colorpicker',
|
168 |
'std' => '#434242',
|
169 |
'class' => 'main-design-settings',
|
170 |
),
|
171 |
'submit-bg-color' => array(
|
172 |
-
'name' => __('Button BG Color',
|
173 |
-
'desc' => __('Background color of button. Must toggle on "Submit Color Options" on',
|
174 |
'type' => 'colorpicker',
|
175 |
'std' => '#E9E9E9',
|
176 |
'class' => 'main-design-settings',
|
177 |
),
|
178 |
'width' => array(
|
179 |
-
'name' => __('Custom Width',
|
180 |
-
'desc' => __('Enter in pixel width or % width. Example: 400 <u>or</u> 100%',
|
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',
|
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',
|
197 |
-
'desc' => __('Select an form field type',
|
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' ,
|
221 |
-
'multi-select' => __('multi-select' ,
|
222 |
*/
|
223 |
),
|
224 |
'std' => ''
|
225 |
),
|
226 |
|
227 |
'dropdown_options' => array(
|
228 |
-
'name' => __('Dropdown choices',
|
229 |
-
'desc' => __('Enter Your Dropdown Options. Separate by commas. You may also use label|value to have a different value than the label stored.',
|
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',
|
237 |
-
'desc' => __('Enter Your Radio Options. Separate by commas. You may also use label|value to have a different value than the label stored.',
|
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',
|
245 |
-
'desc' => __('Enter Your Checkbox Options. Separate by commas. You may also use label|value to have a different value than the label stored.',
|
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',
|
253 |
-
'desc' => __('This is a raw HTML block in the form. Insert text/HTML',
|
254 |
'type' => 'textarea',
|
255 |
'std' => '',
|
256 |
'reveal_on' => 'html-block' // on select choice show this
|
257 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
258 |
'default_value' => array(
|
259 |
-
'name' => __('Default Value',
|
260 |
-
'desc' => __('Enter the Default Value',
|
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)',
|
268 |
-
'desc' => __('This is the text in the divider',
|
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>',
|
275 |
-
'checkbox_text' => __('Check to make field required',
|
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>',
|
283 |
-
'checkbox_text' => __('Check to exclude this form field from being tracked. Note this will not store in your Database',
|
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>',
|
291 |
-
'desc' => __('<span class="show-advanced-fields">Show advanced fields</span>',
|
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>',
|
298 |
-
'desc' => __('Map this field to Leads Value',
|
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>',
|
306 |
-
'desc' => __('Put field placeholder text here. Only works for normal text inputs',
|
307 |
'type' => 'text',
|
308 |
'std' => '',
|
309 |
'class' => 'advanced',
|
310 |
),
|
311 |
'description' => array(
|
312 |
-
'name' => __('Field Description <span class="small-optional-text">(optional)</span>',
|
313 |
-
'desc' => __('Put field description here.',
|
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>',
|
320 |
-
'desc' => __('Add additional class ids to the div that contains this field. Separate classes with spaces.',
|
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>',
|
327 |
-
'desc' => __('Add additional class ids to this input field. Separate classes with spaces.',
|
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>',
|
335 |
-
'desc' => __('Enter Your Dynamic URL parameter',
|
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',
|
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' => '
|
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[
|
65 |
-
$blue = $RBG_array[
|
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 |
-
-
|
11 |
- AcceptanceHelper
|
12 |
- Asserts
|
13 |
config:
|
14 |
WebDriver:
|
15 |
-
url: 'http://
|
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 |
-
*
|
7 |
-
*
|
8 |
-
*
|
9 |
-
*
|
10 |
-
*
|
11 |
-
*
|
12 |
-
*
|
13 |
-
*
|
14 |
-
*
|
|
|
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->
|
27 |
-
$I->
|
28 |
-
$I->
|
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 |
|