| 1 |
jQuery(document).ready(function($) { |
| 2 |
UpdraftCentral = UpdraftCentral(); |
| 3 |
}); |
| 4 |
|
| 5 |
// Only needed for IE9 support - http://caniuse.com/#feat=console-basic |
| 6 |
// Console-polyfill. MIT license. |
| 7 |
// https://github.com/paulmillr/console-polyfill |
| 8 |
// Make it safe to do console.log() always. |
| 9 |
(function(global) { |
| 10 |
'use strict'; |
| 11 |
global.console = global.console || {}; |
| 12 |
var con = global.console; |
| 13 |
var prop, method; |
| 14 |
var empty = {}; |
| 15 |
var dummy = function() {}; |
| 16 |
var properties = 'memory'.split(','); |
| 17 |
var methods = ('assert,clear,count,debug,dir,dirxml,error,exception,group,' + |
| 18 |
'groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd,' + |
| 19 |
'show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn').split(','); |
| 20 |
while (prop = properties.pop()) if (!con[prop]) con[prop] = empty; |
| 21 |
while (method = methods.pop()) if (typeof con[method] !== 'function') con[method] = dummy; |
| 22 |
// Using `this` for web workers & supports Browserify / Webpack. |
| 23 |
})(typeof window === 'undefined' ? this : window); |
| 24 |
|
| 25 |
var UpdraftCentral = function() { |
| 26 |
|
| 27 |
// This is just used internally to log more things. Set to 0 to turn it off (which won't necessarily prevent all console logging). |
| 28 |
// It's not completely systematic/consistent. Logging has been added in an ad hoc manner during development/testing to help with debugging. |
| 29 |
// This gets passed through from the PHP constant UPDRAFTCENTRAL_DEBUG_LEVEL |
| 30 |
var updraftcentral_debug_level = ('undefined' === typeof udclion || !udclion.hasOwnProperty('debug_level')) ? 0 : udclion.debug_level; |
| 31 |
var listener_poll_interval = (updraftcentral_debug_level > 0) ? 5000 : 10000; |
| 32 |
|
| 33 |
var mobile_width = 583 |
| 34 |
|
| 35 |
var $ = jQuery; |
| 36 |
|
| 37 |
var modal_action_callback; |
| 38 |
|
| 39 |
// Use to hold the current site being operated on (e.g. state for modals) (N.B. - you may need to explicitly set this, depending on whether you're using a convenience function that already sets it, or not) |
| 40 |
var $row_parent; |
| 41 |
|
| 42 |
/** |
| 43 |
* @param {Number} debug_level - debugging level, controlling how much console output there will be. The higher the value, the more output. Generally only 0 (minimal), 1 (some), 2 (very much) and 3 (even more) are useful levels |
| 44 |
*/ |
| 45 |
this.set_debug_level = function(debug_level) { |
| 46 |
updraftcentral_debug_level = debug_level; |
| 47 |
} |
| 48 |
|
| 49 |
// Dialog methods - this is just an abstraction layer (currently onto Bootbox, http://bootboxjs.com), allowing us to easily swap to a different provider if we ever need to |
| 50 |
this.dialog = {}; |
| 51 |
|
| 52 |
/** |
| 53 |
* Function to be called whenever a bootbox dialog is opened |
| 54 |
* |
| 55 |
* We use it simply to move the bootbox within the DOM if in fullscreen mode (because otherwise it won't be seen). |
| 56 |
*/ |
| 57 |
var bootbox_opened = function() { |
| 58 |
// It only needs moving if in full-screen mode; so, we're conservative and otherwise leave it alone |
| 59 |
if ($.fullscreen.isFullScreen()) { |
| 60 |
$('.bootbox.modal').prependTo('#updraftcentral_dashboard'); |
| 61 |
} |
| 62 |
// Use a new browser portal for any clicks to updraftplus.com |
| 63 |
$('.bootbox.modal').on('click', 'a', function(e) { |
| 64 |
var href = $(this).attr('href'); |
| 65 |
if (href.substring(0, 23) == 'https://updraftplus.com' || href.substring(0, 23) == 'http://updraftplus.com') { |
| 66 |
e.preventDefault(); |
| 67 |
var win = window.open(href, '_blank'); |
| 68 |
win.focus(); |
| 69 |
} |
| 70 |
}); |
| 71 |
$('.bootbox.modal .updraftcentral_site_editdescription').click(function(e) { |
| 72 |
e.preventDefault(); |
| 73 |
$(this).closest('.modal').modal('hide'); |
| 74 |
open_site_configuration(UpdraftCentral.$row_parent); |
| 75 |
}); |
| 76 |
$('.bootbox.modal .updraftcentral_test_other_connection_methods').click(function(e) { |
| 77 |
e.preventDefault(); |
| 78 |
$(this).closest('.modal').modal('hide'); |
| 79 |
open_connection_test(UpdraftCentral.$row_parent); |
| 80 |
}); |
| 81 |
} |
| 82 |
|
| 83 |
$('#updraftcentral_modal').on('click', '.updraftcentral_test_other_connection_methods', function(e) { |
| 84 |
e.preventDefault(); |
| 85 |
open_connection_test(UpdraftCentral.$row_parent); |
| 86 |
}); |
| 87 |
$('#updraftcentral_modal').on('click', 'a.connection-test-switch', function(e) { |
| 88 |
e.preventDefault(); |
| 89 |
var connection_method = $(this).data('connection_method'); |
| 90 |
|
| 91 |
UpdraftCentral.close_modal(); |
| 92 |
|
| 93 |
var site_id = $(this).data('site_id'); |
| 94 |
|
| 95 |
send_ajax('edit_site_connection_method', { site_id: site_id, connection_method: connection_method }, null, 'via_mothership_encrypting', '#updraftcentral_dashboard_existingsites', function(resp, code, error_code) { |
| 96 |
|
| 97 |
if ('ok' == code) { |
| 98 |
|
| 99 |
if (resp.hasOwnProperty('message')) { add_dashboard_notice(resp.message); } |
| 100 |
|
| 101 |
if (resp.hasOwnProperty('sites_html')) { |
| 102 |
set_existing_sites_to(resp.sites_html); |
| 103 |
setup_menunav(); |
| 104 |
} else { |
| 105 |
console.log(resp); |
| 106 |
add_dashboard_notice(udclion.unknown_response, 'error'); |
| 107 |
} |
| 108 |
if (resp.hasOwnProperty('status_info')) { process_sites_status_info(resp.status_info); } |
| 109 |
} |
| 110 |
}); |
| 111 |
}); |
| 112 |
|
| 113 |
/** |
| 114 |
* @callable dialogresultCallback |
| 115 |
* @param {null|boolean|String} [result=null] - the button clicked (for a confirm call) or the value entered (for a prompt); or, undefined for an alert |
| 116 |
*/ |
| 117 |
|
| 118 |
/** |
| 119 |
* Open an alert box (as a more aesthetic alternative to the traditional browser-provided alert()). |
| 120 |
* @param {string} message - the message to display in the alert box |
| 121 |
* @param {dialogresultCallback} - callback function that is invoked when the alert box is closed |
| 122 |
* @param {Boolean} [sanitize_message=true] - whether or not to put the message through sanitize_html() |
| 123 |
* |
| 124 |
* @uses sanitize_html |
| 125 |
*/ |
| 126 |
this.dialog.alert = function(message, result_callback, sanitize_message) { |
| 127 |
sanitize_message = ('undefined' == sanitize_message) ? true : sanitize_message; |
| 128 |
if (sanitize_message) { |
| 129 |
message = this.sanitize_html(message); |
| 130 |
} |
| 131 |
bootbox.alert(message, result_callback); |
| 132 |
bootbox_opened(); |
| 133 |
} |
| 134 |
|
| 135 |
/** |
| 136 |
* Open a confirmation box (as a more aesthetic alternative to the traditional browser-provided confirm()). |
| 137 |
* @param {string} message - the message to display in the alert box |
| 138 |
* @param {dialogresultCallback} - callback function that is invoked when the alert box is closed |
| 139 |
*/ |
| 140 |
this.dialog.confirm = function(question, result_callback) { |
| 141 |
bootbox.confirm(question, result_callback); |
| 142 |
bootbox_opened(); |
| 143 |
} |
| 144 |
|
| 145 |
/** |
| 146 |
* Open a prompt box (as a more aesthetic alternative to the traditional browser-provided prompt()). |
| 147 |
* @param {string} title - the message to display in the alert box |
| 148 |
* @param {string} default_value - the default value for the user response field |
| 149 |
* @param {dialogresultCallback} - callback function that is invoked when the alert box is closed |
| 150 |
*/ |
| 151 |
this.dialog.prompt = function(title, default_value, result_callback) { |
| 152 |
bootbox.prompt({ title: title, value: default_value, callback: result_callback}); |
| 153 |
bootbox_opened(); |
| 154 |
} |
| 155 |
|
| 156 |
/** |
| 157 |
* Encode to base64 encoding |
| 158 |
* This (or its callers) can be swapped for atob() once IE 10 support is not desired - http://caniuse.com/#feat=atob-btoa |
| 159 |
* |
| 160 |
* @param {String} data - the data to base64-encode |
| 161 |
* |
| 162 |
* @returns {String} - the encoded data |
| 163 |
*/ |
| 164 |
this.base64_encode = function(data) { |
| 165 |
return forge.util.encode64(data); |
| 166 |
} |
| 167 |
|
| 168 |
/** |
| 169 |
* Open a modal window with the specified contents. Modals are separate to dialogues - that is, you can have both open at once without them interfering. |
| 170 |
* @param {String} title - the title to use for the modal window |
| 171 |
* @param {String} body - the HTML contents to place in the modal window |
| 172 |
* @param {callback|true} action_button_callback - a callback to call when the main action button is pressed; or just true to close the modal |
| 173 |
* @param {String|false} [action_button_text="Go"] - text for the action button; or, if false, an indication that there should be no action button |
| 174 |
* @param {callback|null} [pre_open_callback=null] - an optional callback to call immediately before opening the modal |
| 175 |
* @param {boolean} [sanitize_body=true] - whether or not to call the sanitize_html() method the passed body, or not, before placing it in the modal |
| 176 |
* @param {String} [extra_classes=''] - extra CSS classes for the modal dialog (e.g. modal-lg) |
| 177 |
*/ |
| 178 |
this.open_modal = function(title, body, action_button_callback, action_button_text, pre_open_callback, sanitize_body, extra_classes) { |
| 179 |
action_button_text = typeof action_button_text !== 'undefined' ? action_button_text : udclion.go; |
| 180 |
// By default, we assume that the input is potentially evil, and sanitize it |
| 181 |
sanitize_body = typeof sanitize_body !== 'undefined' ? sanitize_body : true; |
| 182 |
extra_classes = typeof extra_classes !== 'undefined' ? extra_classes : ''; |
| 183 |
|
| 184 |
// Reset the modal's CSS classes |
| 185 |
$('#updraftcentral_modal .modal-dialog').removeClass().addClass('modal-dialog '+extra_classes); |
| 186 |
|
| 187 |
$('#updraftcentral_modal .modal-title').html(title); |
| 188 |
if (sanitize_body) body = this.sanitize_html(body); |
| 189 |
$('#updraftcentral_modal .modal-body').html(body); |
| 190 |
if (false === action_button_text) { |
| 191 |
$('#updraftcentral_modal button.updraft_modal_button_goahead').hide(); |
| 192 |
} else { |
| 193 |
$('#updraftcentral_modal button.updraft_modal_button_goahead').html(action_button_text).show(); |
| 194 |
} |
| 195 |
modal_action_callback = action_button_callback; |
| 196 |
if (typeof pre_open_callback !== 'undefined' && null !== pre_open_callback) pre_open_callback.call(this); |
| 197 |
|
| 198 |
$('#updraftcentral_modal').modal(); |
| 199 |
} |
| 200 |
|
| 201 |
/** |
| 202 |
* Allow the user to download/save a file, with contents supplied from the inner HTML of a specified element |
| 203 |
* |
| 204 |
* @param {string} filename - the filename that will be suggested to the user to save as |
| 205 |
* @param {string} element_id - the DOM id of the element whose inner HTML is to be used as content |
| 206 |
* @param {string} [mime_type='text/plain'] - the MIME type to indicate in the header sent to the browser |
| 207 |
*/ |
| 208 |
this.download_inner_html = function(filename, element_id, mime_type) { |
| 209 |
mime_type = mime_type || 'text/plain'; |
| 210 |
var element_html = document.getElementById(element_id).innerHTML; |
| 211 |
var link = document.body.appendChild(document.createElement('a')); |
| 212 |
link.setAttribute('download', filename); |
| 213 |
link.setAttribute('style', "display:none;"); |
| 214 |
link.setAttribute('href', 'data:' + mime_type + ';charset=utf-8,' + encodeURIComponent(element_html)); |
| 215 |
link.click(); |
| 216 |
} |
| 217 |
|
| 218 |
/** |
| 219 |
* Close the modal dialog |
| 220 |
*/ |
| 221 |
this.close_modal = function() { |
| 222 |
$('#updraftcentral_modal').modal('hide'); |
| 223 |
} |
| 224 |
|
| 225 |
/** |
| 226 |
* Sanitizes passed HTML, so that it is safe for display. Uses Google's Caja parser. |
| 227 |
* |
| 228 |
* @param {string} html - the potentially suspicious HTML |
| 229 |
* @returns {string} The sanitized HTML |
| 230 |
*/ |
| 231 |
this.sanitize_html = function(html) { |
| 232 |
var web_only = function(url) { if(/^https?:\/\//.test(url)) { return url; }} |
| 233 |
var same_id = function(id) { return id; } |
| 234 |
// The html_sanitize object comes from Google's Caja |
| 235 |
// This version retains data- attributes. It removes style attributes (but not CSS classes) |
| 236 |
return html_sanitize.sanitize(html, web_only, same_id); |
| 237 |
} |
| 238 |
|
| 239 |
/** |
| 240 |
* jQuery callback for row click events |
| 241 |
* |
| 242 |
* @callable rowclickerCallback |
| 243 |
* @param {string} $row_parent - the jQuery row object for the site that the click was for |
| 244 |
* @param {Number} site_id - the site ID for the site that the click was for |
| 245 |
* @param {Object} event - the event received from jQuery |
| 246 |
* |
| 247 |
* @return {*} prevent_default - if anything other than (boolean)true, then event.preventDefault() is called |
| 248 |
*/ |
| 249 |
|
| 250 |
/** |
| 251 |
* Register click events for specified items in the UpdraftCentral site list (prevents repeating lots of jQuery boilerplate) |
| 252 |
* |
| 253 |
* @param {string} selector - the selector to use |
| 254 |
* @param {rowclickerCallback} - callback function that will be called upon the click event |
| 255 |
* @param {Boolean} [hide_other_sites=false] - if set, then the click will cause other sites in the tab to be hidden |
| 256 |
* @param {string} [onevent='click'] - the event type to listen for. In the special case of 'keypress', the default event will not be prevented |
| 257 |
*/ |
| 258 |
this.register_row_clicker = function(selector, callback, hide_other_sites, on_event) { |
| 259 |
on_event = typeof on_event !== 'undefined' ? on_event : 'click'; |
| 260 |
hide_other_sites = typeof hide_other_sites !== 'undefined' ? hide_other_sites : false; |
| 261 |
params = {}; |
| 262 |
if ('tripleclick' == on_event) { params.threshold = 500; }; |
| 263 |
$('#updraftcentral_dashboard_existingsites_container').on(on_event, '.updraftcentral_site_row '+selector, params, function(event) { |
| 264 |
if (on_event != 'keypress') { event.preventDefault(); } |
| 265 |
UpdraftCentral.$row_parent = $(this).closest('.updraftcentral_site_row'); |
| 266 |
var site_id = UpdraftCentral.$row_parent.data('site_id'); |
| 267 |
if (hide_other_sites) { |
| 268 |
$('#updraftcentral_dashboard_existingsites > .updraftcentral_site_row:not([data-site_id="'+site_id+'"]), #updraftcentral_dashboard_existingsites > .updraftcentral_row_divider').slideUp(); |
| 269 |
$('.updraftcentral_mode_actions .updraftcentral_action_choose_another_site').show(); |
| 270 |
} |
| 271 |
callback.call(this, UpdraftCentral.$row_parent, site_id, event); |
| 272 |
}); |
| 273 |
} |
| 274 |
|
| 275 |
$('#updraftcentral_modal button.updraft_modal_button_goahead').click(function() { |
| 276 |
if (true === modal_action_callback) { |
| 277 |
this.close_modal(); |
| 278 |
} else { |
| 279 |
modal_action_callback.call(this); |
| 280 |
} |
| 281 |
}); |
| 282 |
|
| 283 |
|
| 284 |
/** |
| 285 |
* jQuery callback for row click events |
| 286 |
* |
| 287 |
* @callable listenerCallback |
| 288 |
* |
| 289 |
* @param {Object} $listener_row - the jQuery object of the listener itself |
| 290 |
* @param {Object} $site_row - the jQuery row object for the site that the click was for |
| 291 |
* @param {Number} site_id - the site ID for the site that this is a listener for |
| 292 |
* @param {*} [data] - the returned data from the polling operation (if it is that sort of listener) |
| 293 |
* @returns {*} - if 0 is returned, then the listener will be closed. If 1 is returned, then no more polling will be done, but the listener will not be closed. If an object with a property 'call' is returned, then this will be called. Otherwise, nothing will be done. |
| 294 |
*/ |
| 295 |
|
| 296 |
var listener_processors = {}; |
| 297 |
/** |
| 298 |
* Register a listener callback - a callback function to be used in association with dashboard notices which poll and update |
| 299 |
* |
| 300 |
* @see create_dashboard_listener |
| 301 |
* |
| 302 |
* @param {string} listener-type - an identifying string, indicating the listener type |
| 303 |
* @param {listenerCallback} - a listener callback function |
| 304 |
*/ |
| 305 |
this.register_listener_processor = function(listener_type, callback) { |
| 306 |
listener_processors[listener_type] = callback; |
| 307 |
} |
| 308 |
|
| 309 |
/** |
| 310 |
* Poll all listener rows on the dashboard for activity |
| 311 |
*/ |
| 312 |
function poll_listeners() { |
| 313 |
|
| 314 |
// var listener_calls = {}; |
| 315 |
|
| 316 |
$('#updraftcentral_notice_container .updraftcentral_listener').each(function(ind) { |
| 317 |
var site_id = $(this).data('site_id'); |
| 318 |
var listener_type = $(this).data('type'); |
| 319 |
var $listener_row = this; |
| 320 |
var $site_row = $('#updraftcentral_dashboard_existingsites .updraftcentral_site_row[data-site_id="'+site_id+'"'); |
| 321 |
var finished = $(this).data('finished'); |
| 322 |
|
| 323 |
if (finished) { return; } |
| 324 |
|
| 325 |
console.log("site_id="+site_id+", listener_type="+listener_type); |
| 326 |
|
| 327 |
if ($site_row.length > 0 && listener_processors.hasOwnProperty(listener_type)) { |
| 328 |
// if (typeof listener_calls[site_id] === 'undefined') listener_calls[site_id] = []; |
| 329 |
var call_this = listener_processors[listener_type].call(this, $listener_row, $site_row, site_id); |
| 330 |
// We could multiplex all the calls to the same site. That would involve plenty of work, but would be worth if for the efficiency - if it weren't the case that HTTP/2 takes care of this. |
| 331 |
if (0 === call_this) { |
| 332 |
$(this).data('finished', true); |
| 333 |
$($listener_row).clearQueue().delay(10000).slideUp('slow', function() { $(this).remove(); }); |
| 334 |
} else if (1 === call_this) { |
| 335 |
$(this).data('finished', true); |
| 336 |
} else if (null != call_this && call_this.hasOwnProperty('call')) { |
| 337 |
var call_type = call_this.call; |
| 338 |
UpdraftCentral.send_site_rpc(call_this.call, call_this.data, $site_row, function(response, code, error_code) { |
| 339 |
if ('ok' == code && false !== response && response.hasOwnProperty('data')) { |
| 340 |
if (listener_processors.hasOwnProperty(call_type)) { |
| 341 |
listener_processors[call_type].call(this, $listener_row, $site_row, site_id, response.data); |
| 342 |
} else { |
| 343 |
console.log("UpdraftCentral: listener type "+call_type+" has no registered processor (dump of all registered processors follows)"); |
| 344 |
console.log(listener_processors); |
| 345 |
} |
| 346 |
} |
| 347 |
}); |
| 348 |
} |
| 349 |
} else if ($site_row.length > 0) { |
| 350 |
console.log("UpdraftCentral: listener type "+listener_type+" has no registered processor (dump of all registered processors follows)"); |
| 351 |
console.log(listener_processors); |
| 352 |
} else { |
| 353 |
console.log("UpdraftCentral: listener for site_id="+site_id+" with type "+listener_type+": site row not found"); |
| 354 |
} |
| 355 |
}); |
| 356 |
|
| 357 |
} |
| 358 |
|
| 359 |
setInterval(function() { poll_listeners(); }, listener_poll_interval); |
| 360 |
|
| 361 |
// A separate ud_rpc object for each site |
| 362 |
var ud_rpcs = []; |
| 363 |
|
| 364 |
/** |
| 365 |
* Given a site (identified by its row), get a UpdraftPlus_Remote_Communications (remote communications) object for remote communications. This function does the heavy lifting of getting all the connection configuration for the site, and then passing it along to get_udrpc() |
| 366 |
* |
| 367 |
* @param {Object} $site_row - the jQuery object for the site row |
| 368 |
* @param {String} [connection_method_config="direct"] - either 'direct_default_auth'|'direct_jquery_auth'|'direct_manual_auth' (which means to send directly to the destination) or 'via_mothership' which also sends via the PHP-back-end, but does the RSA encryption in the browser. This is not necessarily the same value as inferred from $site_row - we provide it as an extra parameter to make it possible to over-ride - e.g. for diagnostics, or where the browser mixed-content model restricts the choices. |
| 369 |
* |
| 370 |
* @returns {Object} - the UpdraftPlus_Remote_Communications object |
| 371 |
* |
| 372 |
* @uses get_udrpc |
| 373 |
*/ |
| 374 |
function get_site_udrpc($site_row, connection_method_config) { |
| 375 |
|
| 376 |
var site_remote_public_key = $site_row.data('site_remote_public_key'); |
| 377 |
var site_local_private_key = $site_row.data('site_local_private_key'); |
| 378 |
var site_url = $site_row.data('site_url'); |
| 379 |
var site_id = $site_row.data('site_id'); |
| 380 |
var key_name_indicator = $site_row.data('key_name_indicator'); |
| 381 |
var remote_user_id = $site_row.data('remote_user_id'); |
| 382 |
|
| 383 |
if ('undefined' === typeof connection_method_config || ('direct_manual_auth' != connection_method_config && 'via_mothership' != connection_method_config && 'via_mothership_encrypting' != connection_method_config && 'direct_jquery_auth' != connection_method_config)) { connection_method_config = 'direct_default_auth'; } |
| 384 |
|
| 385 |
// The connection method ought not to be via_mothership - such sites shouldn't be being routed into here (but via_mothership_encrypting is allowed) |
| 386 |
if ('via_mothership_encrypting' == connection_method_config) { |
| 387 |
console.warn("UpdraftCentral: A site ("+site_id+", "+site_url+") routed via_mothership_encrypting was passed into get_site_udrpc"); |
| 388 |
console.log($site_row); |
| 389 |
} |
| 390 |
|
| 391 |
var message_wrapper = false; |
| 392 |
|
| 393 |
if ('direct_default_auth' == connection_method_config) { |
| 394 |
// Normally, of course, feature detection should be used. But, it really is the case that we need to do browser-detection here, as they're working differently. |
| 395 |
var is_firefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; |
| 396 |
// N.B. If they're set to use Digest authentication, this should not use manual - should switch back |
| 397 |
// connection_method = (is_firefox) ? 'direct_manual_auth' : 'direct_jquery_auth'; |
| 398 |
// Actually, 'jQuery method' also works in Firefox |
| 399 |
connection_method = 'direct_jquery_auth'; |
| 400 |
} else { |
| 401 |
connection_method = connection_method_config; |
| 402 |
|
| 403 |
// We're relying here on the fact that UDRPC object store includes the connection method in its unique ID - i.e. that this can be set on the UDRPC object, without any bad consequences. |
| 404 |
|
| 405 |
if ('via_mothership' == connection_method) { |
| 406 |
|
| 407 |
message_wrapper = { |
| 408 |
action: 'updraftcentral_dashboard_ajax', |
| 409 |
subaction: 'site_rpc', |
| 410 |
component: 'dashboard', |
| 411 |
nonce: udclion.updraftcentral_dashboard_nonce, |
| 412 |
site_id: site_id, |
| 413 |
site_rpc_preencrypted: 1 |
| 414 |
}; |
| 415 |
|
| 416 |
} |
| 417 |
|
| 418 |
} |
| 419 |
|
| 420 |
var send_cors_headers = $site_row.data('send_cors_headers'); |
| 421 |
if ('undefined' === typeof send_cors_headers) { send_cors_headers = 1; } |
| 422 |
|
| 423 |
var auth_method = ('direct_manual_auth' == connection_method) ? 'manual' : 'jquery'; |
| 424 |
|
| 425 |
var http_credentials = {}; |
| 426 |
|
| 427 |
var comms_url = site_url; |
| 428 |
|
| 429 |
// When routing via the mothership, don't put in credentials, as the mothership will do that |
| 430 |
if ('via_mothership_encrypting' != connection_method && 'via_mothership' != connection_method) { |
| 431 |
var http_username = $site_row.data('http_username'); |
| 432 |
if ('undefined' !== typeof http_username && http_username) { |
| 433 |
http_credentials.username = http_username; |
| 434 |
var http_password = $site_row.data('http_password'); |
| 435 |
if ('undefined' !== typeof http_password) { |
| 436 |
http_credentials.password = http_password; |
| 437 |
} |
| 438 |
} |
| 439 |
} else { |
| 440 |
comms_url = udclion.ajaxurl; |
| 441 |
} |
| 442 |
|
| 443 |
if (updraftcentral_debug_level > 0) { |
| 444 |
console.log("UDRPC communications method: site_id="+site_id+", name_indicator="+key_name_indicator+", site_url="+site_url+", comms_url="+comms_url+", remote_user_id="+remote_user_id+", connection_method="+connection_method_config+"/"+connection_method+", send_cors_headers="+send_cors_headers); |
| 445 |
if (updraftcentral_debug_level > 1) { |
| 446 |
console.log("Remote public key follows"); |
| 447 |
console.log(site_remote_public_key); |
| 448 |
} |
| 449 |
} |
| 450 |
|
| 451 |
var reuse_id = site_id+' '+connection_method; |
| 452 |
|
| 453 |
return get_udrpc(reuse_id, remote_user_id, key_name_indicator, site_remote_public_key, site_local_private_key, comms_url, send_cors_headers, http_credentials, auth_method, message_wrapper); |
| 454 |
|
| 455 |
} |
| 456 |
|
| 457 |
/** |
| 458 |
* Given the relevant site information, get a UpdraftPlus_Remote_Communications (remote communications) object for remote communications, and also sets the current debugging level upon it. |
| 459 |
* |
| 460 |
* @param {Number} reuse_id - A unique ID, that can be used for re-using of the result |
| 461 |
* @param {Number} remote_user_id - The ID of the user on the remote WP site that the keys are for |
| 462 |
* @param {String} key_name_indicator - The key name indicator (which indicates to the remote site which key to use to decrypt the message) |
| 463 |
* @param {String} site_remote_public_key - The RSA public key for contacting the remote site, in PEM format |
| 464 |
* @param {String} site_local_private_key - The RSA private key for the local site, in PEM format |
| 465 |
* @param {String} site_url - The URL for the remote site |
| 466 |
* @param {Boolean} [cors_headers_wanted=true] - Whether to request that the remote application sets CORS headers with its reply |
| 467 |
* @param {Object} [http_credentials={}] - an object with any HTTP credentials to be set (useful properties: username, password) |
| 468 |
* @param {String} [auth_method] - the authentication method to use ('jquery' or 'manual') |
| 469 |
* @param {Object|Boolean} [message_wrapper=false] - a wrapper to enclose the message in; or false for none. This is passed on to the UDRPC library, which is where the actual wrapping is done. |
| 470 |
* |
| 471 |
* |
| 472 |
* @returns {Object} - the UpdraftPlus_Remote_Communications object |
| 473 |
* |
| 474 |
*/ |
| 475 |
function get_udrpc(reuse_id, remote_user_id, key_name_indicator, site_remote_public_key, site_local_private_key, site_url, cors_headers_wanted, http_credentials, auth_method, message_wrapper) { |
| 476 |
if (typeof ud_rpcs[reuse_id] != 'undefined') { |
| 477 |
ud_rpc = ud_rpcs[reuse_id]; |
| 478 |
} else { |
| 479 |
cors_headers_wanted = (typeof cors_headers_wanted === 'undefined') ? true : cors_headers_wanted; |
| 480 |
var ud_rpc = new UpdraftPlus_Remote_Communications(key_name_indicator); |
| 481 |
ud_rpc.set_key_local(site_local_private_key); |
| 482 |
ud_rpc.set_key_remote(site_remote_public_key); |
| 483 |
ud_rpc.activate_replay_protection(); |
| 484 |
ud_rpc.set_destination_url(site_url); |
| 485 |
if ('undefined' != typeof http_credentials) { ud_rpc.set_http_credentials(http_credentials); } |
| 486 |
if ('undefined' != typeof auth_method) { ud_rpc.set_auth_method(auth_method); } |
| 487 |
if ('undefined' != typeof message_wrapper && false !== message_wrapper) { |
| 488 |
ud_rpc.set_message_wrapper(message_wrapper); |
| 489 |
ud_rpc.set_message_unwrapper(function(response) { |
| 490 |
var processed = process_direct_ajax_response(response, 2, false); |
| 491 |
if (true === processed) { |
| 492 |
if (response.hasOwnProperty('wrapped_response')) { |
| 493 |
return response.wrapped_response; |
| 494 |
} else { |
| 495 |
processed = 'wrapped_response_not_found'; |
| 496 |
} |
| 497 |
} |
| 498 |
console.error("UDRPC: Attempt to unwrap the message failed (code: "+processed+")"); |
| 499 |
// This is usually redundant - something further down the line will log it |
| 500 |
if (updraftcentral_debug_level > 1) { |
| 501 |
console.log(response); |
| 502 |
} |
| 503 |
return false; |
| 504 |
}); |
| 505 |
} |
| 506 |
ud_rpc.set_cors_headers_wanted(cors_headers_wanted); |
| 507 |
ud_rpcs[reuse_id] = ud_rpc; |
| 508 |
} |
| 509 |
if (updraftcentral_debug_level > 0) { |
| 510 |
// UDRPC, at debug level 2, console.log()s lots of cryptographic internals which are only really needed when debugging that |
| 511 |
var ud_rpc_debug_level = (updraftcentral_debug_level > 2) ? 2 : 1; |
| 512 |
ud_rpc.set_debug_level(ud_rpc_debug_level); |
| 513 |
} |
| 514 |
return ud_rpc; |
| 515 |
} |
| 516 |
|
| 517 |
/** |
| 518 |
* @callable ajaxCallback |
| 519 |
* @param {*} response - the response data for the result of the call |
| 520 |
* @param {String} [code] - the response code; can be 'error' in the case of an error |
| 521 |
* @param {String} [error_code] - in the case of code being 'error', this contains the error code |
| 522 |
*/ |
| 523 |
|
| 524 |
/** |
| 525 |
* This function is for processing responses received via send_ajax. Since that function has two separate methods for routing the request, the common response code is abstracted out. |
| 526 |
* |
| 527 |
* @param {ajaxCallback} response_callback - callback that will be called with the results of the AJAX call |
| 528 |
* @param {String} [code] - the response code; can be 'error' in the case of an error |
| 529 |
* @param {String} [error_code] - in the case of code being 'error', this contains the error code |
| 530 |
* @param {Boolean|Number} is_site_rpc - whether it was command to a remote site or not. If set to '2', then this indicates that it is site_rpc, and that the encryption was definitely done in the browser (so, we can ignore/drop certain unencrypted responses) |
| 531 |
* @param {ajaxCallback} response_callback - callback that will be called with the results of the AJAX call |
| 532 |
* @param {Boolean} [allow_visual_responses=true] - whether or not it is permissible to display UI elements in response to the results (set this to false if the caller wants to handle it internally only) |
| 533 |
*/ |
| 534 |
function process_ajax_response(response, code, error_code, is_site_rpc, response_callback, allow_visual_responses) { |
| 535 |
|
| 536 |
allow_visual_responses = ('undefined' === typeof allow_visual_responses) ? true : allow_visual_responses; |
| 537 |
|
| 538 |
// Bring errors up from the RPC layer. "ok" as the main code just means that a result came back successfully; but that result might itself be an error. That is someting to handle here, not in the lower-level communications library. |
| 539 |
|
| 540 |
if ('error' == code) { |
| 541 |
console.error("process_ajax_response: return code: "+code+", error_code: "+error_code+" - parsed response follows"); |
| 542 |
console.log(response); |
| 543 |
} else if (updraftcentral_debug_level > 0) { |
| 544 |
console.log("process_ajax_response: return code: "+code+" - parsed response follows"); |
| 545 |
console.log(response); |
| 546 |
} |
| 547 |
|
| 548 |
if (is_site_rpc && 'ok' == code && response.hasOwnProperty('response') && 'rpcerror' == response.response) { |
| 549 |
code = 'error'; |
| 550 |
error_code = 'rpc_unknown_error'; |
| 551 |
if (response.hasOwnProperty('data') && response.data.hasOwnProperty('code')) { |
| 552 |
error_code = response.data.code; |
| 553 |
console.error("UpdraftCentral: RPC: Error occurred ("+error_code+"); data follows"); |
| 554 |
console.log(response.data); |
| 555 |
response = response.data.data; |
| 556 |
// A default message for if we don't recognise the code |
| 557 |
var dash_message = udclion.js_exception_occurred+' ('+error_code+')'; |
| 558 |
// Get the error's own message, if we know about it |
| 559 |
if (udclion.rpcerrors.hasOwnProperty(error_code)) { dash_message = udclion.rpcerrors[error_code]; } |
| 560 |
|
| 561 |
if (allow_visual_responses) { UpdraftCentral.dialog.alert('<h2>'+udclion.communications_error+'</h2>'+dash_message); } |
| 562 |
|
| 563 |
response_callback.call(this, response, code, error_code); |
| 564 |
|
| 565 |
} |
| 566 |
} |
| 567 |
|
| 568 |
if (code == 'error') { |
| 569 |
|
| 570 |
var msg = udclion.general_js_comms_failure; |
| 571 |
// This variable doesn't have to be 100% correct - it's use is that a link to a relevant article is shown if it is true |
| 572 |
var is_comms_failure = true; |
| 573 |
var title = udclion.error; |
| 574 |
|
| 575 |
// If the response didn't unwrap, it may be an error response. |
| 576 |
if (2 == is_site_rpc && 'unwrapper_failure' == error_code && response.hasOwnProperty('code')) { error_code = response.code; } |
| 577 |
|
| 578 |
if ('json_parse_fail' == error_code) { |
| 579 |
if (response.indexOf('<html') > -1) { |
| 580 |
console.error("UpdraftCentral: JSON parse fail: looks like html was returned - remote plugin is probably not installed/inactive/blocked"); |
| 581 |
msg = udclion.general_js_comms_failure; |
| 582 |
title = udclion.communications_error; |
| 583 |
} |
| 584 |
} else if ('response_empty' == error_code || 'http_post_fail' == error_code) { |
| 585 |
msg = udclion.general_js_comms_failure; |
| 586 |
title = udclion.communications_error; |
| 587 |
} else if ('timeout' == error_code) { |
| 588 |
msg = udclion.comms_failure_timeout; |
| 589 |
title = udclion.communications_error; |
| 590 |
} else if ('unauthorized' == error_code) { |
| 591 |
msg = udclion.comms_failure_unauthorised; |
| 592 |
title = udclion.communications_error; |
| 593 |
} else if ('unknown_response' == error_code) { |
| 594 |
msg = udclion.unknown_response; |
| 595 |
title = udclion.communications_error; |
| 596 |
} else if ('cannot_contact_localdev' == error_code) { |
| 597 |
title = udclion.communications_error; |
| 598 |
msg = response.message; |
| 599 |
if (response.hasOwnProperty('request_info') && response.request_info.hasOwnProperty('method') && response.request_info.hasOwnProperty('use_method') && response.request_info.method != response.request_info.use_method) { |
| 600 |
msg += '<br>'+udclion.localdev_can_work_better_with_https; |
| 601 |
} |
| 602 |
} else if ('unexpected_http_code' == error_code && is_site_rpc && response.hasOwnProperty('data') && response.data !== null && response.data.hasOwnProperty('headers') && response.data.headers.hasOwnProperty('www-authenticate') && response.data.headers['www-authenticate'].search(/Digest/i) == 0) { |
| 603 |
msg = response.message; |
| 604 |
msg += "<br>"+udclion.digest_auth_not_supported; |
| 605 |
} else if ('unexpected_http_code' == error_code && is_site_rpc && response.hasOwnProperty('data') && null !== response.data && response.data.hasOwnProperty('response') && response.data.response.hasOwnProperty('code') && 401 == response.data.response.code) { |
| 606 |
msg = udclion.comms_failure_unauthorised+' <a href="#" class="updraftcentral_site_editdescription">'+udclion.open_site_configuration+'...</a>'; |
| 607 |
} else if (response.hasOwnProperty('message')) { |
| 608 |
msg = response.message; |
| 609 |
if (!is_site_rpc) { is_comms_failure = false; } |
| 610 |
} else { |
| 611 |
is_comms_failure = false; |
| 612 |
msg += '<br>'+udclion.error_code+': '+error_code; |
| 613 |
} |
| 614 |
|
| 615 |
// ns_error_dom_bad_uri: access to restricted uri denied - Firefox |
| 616 |
if (response.hasOwnProperty('status') && 401 == response.status) { |
| 617 |
msg = udclion.comms_failure_unauthorised+' <a href="#" class="updraftcentral_site_editdescription">'+udclion.open_site_configuration+'...</a>'; |
| 618 |
} else if ('ns_error_dom_bad_uri: access to restricted uri denied' == error_code) { |
| 619 |
msg = udclion.comms_failure_unauthorised_by_browser+' <a href="#" class="updraftcentral_site_editdescription">'+udclion.open_site_configuration+'...</a>'; |
| 620 |
} |
| 621 |
|
| 622 |
msg = '<p>'+msg+'</p>'; |
| 623 |
|
| 624 |
if (is_comms_failure) { |
| 625 |
msg += '<p><a href="'+udclion.common_urls.connection_checklist+'">'+udclion.go_here_for_connection_help+'</a></p>'; |
| 626 |
msg += '<p><a href="#" class="updraftcentral_test_other_connection_methods">'+udclion.test_other_connection_methods+'</a></p>'; |
| 627 |
} |
| 628 |
|
| 629 |
if (response.hasOwnProperty('status') && 200 != response.status && 0 != response.status) { |
| 630 |
msg += '<p>'+udclion.http_response_status+': '+response.status+'</p>'; |
| 631 |
} |
| 632 |
|
| 633 |
if (allow_visual_responses) { UpdraftCentral.dialog.alert('<h2>'+title+'</h2>'+msg); } |
| 634 |
} |
| 635 |
|
| 636 |
if (is_site_rpc && response.hasOwnProperty('data') && null != response.data) { |
| 637 |
if (response.data.hasOwnProperty('php_events')) { |
| 638 |
$.each(response.data.php_events, function(index, logline) { |
| 639 |
console.log("UpdraftCentral: PHP event on remote side: "+logline); |
| 640 |
}); |
| 641 |
} |
| 642 |
if (response.data.hasOwnProperty('caught_output')) { |
| 643 |
console.log("UpdraftCentral: direct output on remote side: "+response.data.caught_output); |
| 644 |
} |
| 645 |
if (response.data.hasOwnProperty('php_events') || response.data.hasOwnProperty('caught_output')) { |
| 646 |
response.data = response.data.previous_data; |
| 647 |
} |
| 648 |
} |
| 649 |
|
| 650 |
response_callback.call(this, response, code, error_code); |
| 651 |
} |
| 652 |
|
| 653 |
/** |
| 654 |
* Process responses received back from the mothership over AJAX. This will do some processing, and then call process_ajax_response() |
| 655 |
* |
| 656 |
* @param {String} response - the response received |
| 657 |
* @param {Boolean} is_site_rpc - whether it was command to a remote site or not. |
| 658 |
* @param {ajaxCallback|Boolean} response_callback - callback that will be called with the results of the AJAX call - or, to not call, false |
| 659 |
* @param {Boolean} [allow_visual_responses=true] - whether or not it is permissible to display UI elements in response to the results (set this to false if the caller wants to handle it internally only). This is just passed on to process_ajax_response |
| 660 |
* |
| 661 |
* @returns {Boolean|String} - If the parsing did not turn up any errors, true is return; otherwise, an error code. |
| 662 |
*/ |
| 663 |
function process_direct_ajax_response(response, is_site_rpc, response_callback, allow_visual_responses) { |
| 664 |
|
| 665 |
allow_visual_responses = ('undefined' === typeof allow_visual_responses) ? true : allow_visual_responses; |
| 666 |
|
| 667 |
// AJAX via the mothership comes with its results wrapped |
| 668 |
|
| 669 |
if (response.hasOwnProperty('responsetype') && 'error' == response.responsetype) { |
| 670 |
if (response.hasOwnProperty('message')) { console.error("UpdraftCentral error via AJAX: "+response.message); } |
| 671 |
if ('cannot_contact_localdev' == response.code) { response.request_info = { method: method, use_method: use_method} } |
| 672 |
if (false !== response_callback) { |
| 673 |
process_ajax_response(response, 'error', response.code, is_site_rpc, response_callback, allow_visual_responses); |
| 674 |
} |
| 675 |
return response.code; |
| 676 |
} |
| 677 |
|
| 678 |
if (!response.hasOwnProperty('message') && !response.hasOwnProperty('code')) { |
| 679 |
console.log(response); |
| 680 |
if (false !== response_callback) { |
| 681 |
process_ajax_response(response, 'error', 'unknown_response', is_site_rpc, response_callback, allow_visual_responses); |
| 682 |
} |
| 683 |
return 'unknown_response'; |
| 684 |
} |
| 685 |
|
| 686 |
if (updraftcentral_debug_level > 1) { |
| 687 |
console.log(response.responsetype+': '+response.message); |
| 688 |
} |
| 689 |
|
| 690 |
// When doing site RPC, the remote site's reply is in the 'data' attribute |
| 691 |
if (is_site_rpc) { |
| 692 |
|
| 693 |
if (response.hasOwnProperty('php_events')) { |
| 694 |
$.each(response.php_events, function(index, logline) { |
| 695 |
console.info("UpdraftCentral: PHP event on remote side: "+logline); |
| 696 |
}); |
| 697 |
} |
| 698 |
|
| 699 |
if (response.hasOwnProperty('mothership_caught_output')) { |
| 700 |
console.info("UpdraftCentral: direct output on remote side: "+response.caught_output); |
| 701 |
} |
| 702 |
|
| 703 |
// This is set for a successful communication |
| 704 |
if (response.hasOwnProperty('rpc_response')) { |
| 705 |
response = response.rpc_response; |
| 706 |
} |
| 707 |
} |
| 708 |
|
| 709 |
if (false !== response_callback) { |
| 710 |
process_ajax_response(response, 'ok', null, is_site_rpc, response_callback, allow_visual_responses); |
| 711 |
} |
| 712 |
|
| 713 |
return true; |
| 714 |
} |
| 715 |
|
| 716 |
/** |
| 717 |
* Sends a remote command via AJAX - either directly, or via the site that this plugin is installed upon. |
| 718 |
* |
| 719 |
* @param {String} command - the command to send |
| 720 |
* @param {*} data - data to send with the remote request |
| 721 |
* @param {Object|null} $site_row - the jQuery object for the site that the command is for; or, for commands not associated with a site, null |
| 722 |
* @param {String} [connection_method="direct_default_auth"] - either 'direct_default_auth' (which means to send directly to the destination) or 'via_mothership_encrypting' (which means to send via our PHP-back-end, which then sends the request), or 'via_mothership' which also sends via the PHP-back-end, but does the RSA encryption in the browser. This is not necessarily the same value as inferred from $site_row - we provide it as an extra parameter to make it possible to over-ride - e.g. for diagnostics, or where the browser mixed-content model restricts the choices. |
| 723 |
* @param {Object|String|null} [spinner_where=null] - jQuery object or CSS identifier indicating where, if anywhere, to add a spinner whilst the call is ongoing |
| 724 |
* @param {ajaxCallback} response_callback - callback that will be called with the results of the AJAX call |
| 725 |
* @param {Number} [timeout=30] - the number of seconds to allow before the call times out |
| 726 |
* @param {Boolean} [allow_visual_responses=true] - whether or not it is permissible to display UI elements in response to the results (set this to false if the caller wants to handle it internally only). This is just passed on to process_ajax_response |
| 727 |
* |
| 728 |
* @uses process_ajax_response |
| 729 |
*/ |
| 730 |
|
| 731 |
function send_ajax(command, data, $site_row, connection_method, spinner_where, response_callback, timeout, allow_visual_responses) { |
| 732 |
|
| 733 |
connection_method = typeof connection_method !== 'undefined' ? connection_method : 'direct_default_auth'; |
| 734 |
timeout = typeof timeout !== 'undefined' ? timeout : 30; |
| 735 |
spinner_where = typeof spinner_where !== 'undefined' ? spinner_where : null; |
| 736 |
allow_visual_responses = ('undefined' === typeof allow_visual_responses) ? true : allow_visual_responses; |
| 737 |
|
| 738 |
// Boil it down to one of 'direct', 'server', 'server_proxies' (i.e. factor out the sub-methods) |
| 739 |
var ajax_method = ('via_mothership' == connection_method || 'via_mothership_encrypting' == connection_method) ? ('via_mothership' == connection_method ? 'server_proxies' : 'server') : 'direct'; |
| 740 |
|
| 741 |
var is_site_rpc = (null === $site_row) ? false : true; |
| 742 |
|
| 743 |
if (is_site_rpc) { |
| 744 |
var unlicensed = $site_row.data('site_unlicensed'); |
| 745 |
if ('undefined' !== typeof unlicensed && unlicensed) { |
| 746 |
UpdraftCentral.dialog.alert('<h2>'+udclion.error+'</h2>'+udclion.site_unlicensed_message); |
| 747 |
return; |
| 748 |
} |
| 749 |
} |
| 750 |
|
| 751 |
if (spinner_where) { |
| 752 |
// $(spinner_where).addClass('updraftcentral_spinner'); |
| 753 |
$(spinner_where).prepend('<div class="updraftcentral_spinner"></div>'); |
| 754 |
} |
| 755 |
|
| 756 |
if ('direct' == ajax_method && 'https:'== document.location.protocol) { |
| 757 |
var site_url = $site_row.data('site_url'); |
| 758 |
if (site_url.substring(0, 5).toLowerCase() == 'http:') { |
| 759 |
// Mixed content policy in all mainstream desktop browsers forbids requests to HTTP from HTTPS domains |
| 760 |
ajax_method = 'server'; |
| 761 |
} |
| 762 |
} |
| 763 |
|
| 764 |
if (updraftcentral_debug_level > 0) { |
| 765 |
console.log("send_message(ajax_method="+ajax_method+", requested_method="+connection_method+", command="+command+", data(follows))"); |
| 766 |
console.log(data); |
| 767 |
} |
| 768 |
|
| 769 |
if ('direct' == ajax_method || 'server_proxies' == ajax_method) { |
| 770 |
|
| 771 |
if (!is_site_rpc) { throw 'send_ajax() called with direct method ('+connection_method+'), but no site row object passed in'; } |
| 772 |
|
| 773 |
var ud_rpc = get_site_udrpc($site_row, connection_method); |
| 774 |
|
| 775 |
ud_rpc.send_message(command, data, timeout, function(response, code, error_code) { |
| 776 |
|
| 777 |
if (spinner_where) { |
| 778 |
$(spinner_where).removeClass('updraftcentral_spinner'); |
| 779 |
$(spinner_where).children('.updraftcentral_spinner').remove(); |
| 780 |
} |
| 781 |
|
| 782 |
if (updraftcentral_debug_level > 2) { |
| 783 |
console.log("Raw response, pre-processing, follows"); |
| 784 |
console.log(response); |
| 785 |
} |
| 786 |
|
| 787 |
var is_site_rpc_flag = ('server_proxies' == ajax_method) ? 2 : 1; |
| 788 |
|
| 789 |
try { |
| 790 |
process_ajax_response(response, code, error_code, is_site_rpc_flag, response_callback, allow_visual_responses); |
| 791 |
} catch (e) { |
| 792 |
UpdraftCentral.dialog.alert('<h2>'+udclion.error+'</h2>'+udclion.js_exception_occurred+'<br>'+e.toString()); |
| 793 |
console.log(e); |
| 794 |
} |
| 795 |
}); |
| 796 |
|
| 797 |
|
| 798 |
} else { |
| 799 |
// 'server' == ajax_method |
| 800 |
|
| 801 |
var site_id = 0; |
| 802 |
if (null !== $site_row) { |
| 803 |
site_id = $site_row.data('site_id'); |
| 804 |
} |
| 805 |
|
| 806 |
var ajax_subaction = (is_site_rpc) ? 'site_rpc' : command; |
| 807 |
|
| 808 |
var ajax_data = (is_site_rpc) ? { command: command, data: data } : data; |
| 809 |
|
| 810 |
var ajax_options = { |
| 811 |
type: 'POST', |
| 812 |
url: udclion.ajaxurl, |
| 813 |
timeout: timeout * 1000, // In ms |
| 814 |
headers: { |
| 815 |
'X-Secondary-User-Agent': 'UpdraftCentral-dashboard.js/'+udclion.udc_version, |
| 816 |
}, |
| 817 |
data: { |
| 818 |
action: 'updraftcentral_dashboard_ajax', |
| 819 |
subaction: ajax_subaction, |
| 820 |
component: 'dashboard', |
| 821 |
nonce: udclion.updraftcentral_dashboard_nonce, |
| 822 |
site_id: site_id, |
| 823 |
data: ajax_data |
| 824 |
}, |
| 825 |
dataType: 'text', |
| 826 |
success: function(response) { |
| 827 |
|
| 828 |
if (spinner_where) { |
| 829 |
$(spinner_where).children('.updraftcentral_spinner').remove(); |
| 830 |
// $(spinner_where).removeClass('updraftcentral_spinner'); |
| 831 |
} |
| 832 |
|
| 833 |
if ('undefined' === typeof response || '' === response) { |
| 834 |
console.log("UDRPC: the response from the remote site was empty"); |
| 835 |
process_ajax_response(response, 'error', 'response_empty', is_site_rpc, response_callback, allow_visual_responses); |
| 836 |
return; |
| 837 |
} |
| 838 |
|
| 839 |
try { |
| 840 |
var parsed_response = JSON.parse(response); |
| 841 |
} catch(e) { |
| 842 |
console.log(e); |
| 843 |
console.log(response); |
| 844 |
process_ajax_response(response, 'error', 'json_parse_fail', is_site_rpc, response_callback, allow_visual_responses); |
| 845 |
return; |
| 846 |
} |
| 847 |
|
| 848 |
response = parsed_response; |
| 849 |
|
| 850 |
process_direct_ajax_response(response, is_site_rpc, response_callback, allow_visual_responses); |
| 851 |
|
| 852 |
}, |
| 853 |
error: function(request, status, error_thrown) { |
| 854 |
|
| 855 |
if (spinner_where) { |
| 856 |
$(spinner_where).children('.updraftcentral_spinner').remove(); |
| 857 |
// $(spinner_where).removeClass('updraftcentral_spinner'); |
| 858 |
} |
| 859 |
|
| 860 |
console.error("UpdraftCentral: Error in AJAX operation"); |
| 861 |
console.log(request); |
| 862 |
console.log(status); |
| 863 |
// https://api.jquery.com/jquery.ajax/ says: 'When an HTTP error occurs, (this parameter) receives the textual portion of the HTTP status, such as "Not Found" or "Internal Server Error."' |
| 864 |
// "Unauthorized" is what you get when HTTP authentication is required. "Timeout" when there's a timeout. |
| 865 |
console.error(error_thrown); |
| 866 |
|
| 867 |
if ('' == error_thrown) { error_thrown = 'http_post_fail'; } |
| 868 |
|
| 869 |
if (error_thrown.hasOwnProperty('statusText')) { |
| 870 |
error_thrown = error_thrown.statusText.toString(); |
| 871 |
} |
| 872 |
|
| 873 |
if ('function' === typeof error_thrown.toLowerCase) { |
| 874 |
error_thrown = error_thrown.toLowerCase(); |
| 875 |
} else { |
| 876 |
try { |
| 877 |
var tmp = error_thrown.toString().toLowerCase(); |
| 878 |
if (tmp) { error_thrown = tmp; } |
| 879 |
} catch (e) { |
| 880 |
} |
| 881 |
} |
| 882 |
|
| 883 |
process_ajax_response(request, 'error', error_thrown, is_site_rpc, response_callback, allow_visual_responses); |
| 884 |
} |
| 885 |
} |
| 886 |
|
| 887 |
if (updraftcentral_debug_level > 1) { |
| 888 |
console.log("UpdraftCentral: jQuery POST: options follow:"); |
| 889 |
console.log(ajax_options); |
| 890 |
} |
| 891 |
|
| 892 |
jQuery.ajax(ajax_options); |
| 893 |
|
| 894 |
} |
| 895 |
|
| 896 |
} |
| 897 |
|
| 898 |
/** |
| 899 |
* Set up menu navigation for each site row item. This should be called after any actions that replace the HTML of row items |
| 900 |
*/ |
| 901 |
function setup_menunav() { |
| 902 |
// This is no longer needed. |
| 903 |
//$('#updraftcentral_dashboard .updraft-dropdown-menu').dropit(); |
| 904 |
var how_many_sites = $('#updraftcentral_dashboard_existingsites > .updraftcentral_site_row:not(.updraft_site_unlicensed)').length; |
| 905 |
$('#updraftcentral_licences_in_use').html(how_many_sites); |
| 906 |
} |
| 907 |
|
| 908 |
// Toggle the mobile menu on/off, if at a relevant width |
| 909 |
$('#updraftcentral_dashboard .updraft-mobile-menu').on('click', function() { |
| 910 |
var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); |
| 911 |
// Currently only using the width. |
| 912 |
// var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); |
| 913 |
if(w <= mobile_width) { |
| 914 |
$('#updraftcentral_dashboard .updraft-menu-item-links').slideToggle(); |
| 915 |
} |
| 916 |
}); |
| 917 |
|
| 918 |
/** |
| 919 |
* Set the section of the dashboard that displays the existing sites to the specified value. All code that wants to update this section should route through here, so that any other associated operations can be carried out. |
| 920 |
* |
| 921 |
* @param {String} html - the HTML to place within the site list container in the dashboard |
| 922 |
*/ |
| 923 |
function set_existing_sites_to(html) { |
| 924 |
// Reset the connection objects, as the IDs and credentials/options may have changed |
| 925 |
ud_rpcs = []; |
| 926 |
$('#updraftcentral_dashboard_existingsites').html(html); |
| 927 |
// Show/hide the relevant buttons/sections for the current tab |
| 928 |
UpdraftCentral.set_dashboard_mode(true, true); |
| 929 |
setup_menunav(); |
| 930 |
} |
| 931 |
|
| 932 |
/* |
| 933 |
* Used to show or hide the mobile menu on click. Will also toggle when a menu item has been clicked. |
| 934 |
* Only want this to occur at when the mobile menu is visible to stop the toggle at higher viewports |
| 935 |
*/ |
| 936 |
// Toggle the mobile menu on/off, if at a relevant width |
| 937 |
$('#updraftcentral_dashboard .updraft-mobile-menu, .updraft-menu-item-links').on('click', function() { |
| 938 |
var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; |
| 939 |
|
| 940 |
if(width < mobile_width) { |
| 941 |
$('#updraftcentral_dashboard .updraft-menu-item-links').slideToggle(); |
| 942 |
} |
| 943 |
}); |
| 944 |
|
| 945 |
/** |
| 946 |
* Quote the input, so that it is suitable for placing in HTML attributes values |
| 947 |
* @see https://stackoverflow.com/questions/7753448/how-do-i-escape-quotes-in-html-attribute-values |
| 948 |
* |
| 949 |
* @param {String} s - The string to be quoted |
| 950 |
* @param {boolean} preserveCR - if true, then \r and \n are replaced with an HTML entity; otherwise with \n |
| 951 |
* |
| 952 |
* @returns {String} the quoted string |
| 953 |
*/ |
| 954 |
function quote_attribute(s, preserveCR) { |
| 955 |
preserveCR = preserveCR ? ' ' : '\n'; |
| 956 |
return ('' + s) /* Forces the conversion to string. */ |
| 957 |
.replace(/&/g, '&') /* This MUST be the 1st replacement. */ |
| 958 |
.replace(/'/g, ''') /* The 4 other predefined entities, required. */ |
| 959 |
.replace(/"/g, '"') |
| 960 |
.replace(/</g, '<') |
| 961 |
.replace(/>/g, '>') |
| 962 |
/* |
| 963 |
* You may add other replacements here for HTML only |
| 964 |
* (but it's not necessary). |
| 965 |
* Or for XML, only if the named entities are defined in its DTD. |
| 966 |
*/ |
| 967 |
.replace(/\r\n/g, preserveCR) /* Must be before the next replacement. */ |
| 968 |
.replace(/[\r\n]/g, preserveCR); |
| 969 |
; |
| 970 |
} |
| 971 |
|
| 972 |
/** |
| 973 |
* Adds a dashboard notice |
| 974 |
* |
| 975 |
* @param {string} message - The message text to display |
| 976 |
* @param {string} [level="notice"] - The level for the notice. Can also start with 'listener_', which is styled as if it were 'info' (but can be over-ridden, as it gets its own classes too) |
| 977 |
* @param {Number|bool} [remove_after=30000] - The number of milliseconds to remove the notice after; or, 0|false to not remove |
| 978 |
* @param {Object} [extra_data={}] - Extra data to store with the dashboard notice (via data attributes) |
| 979 |
* @returns {Object} the jQuery object for the newly created notice |
| 980 |
*/ |
| 981 |
this.add_dashboard_notice = function(message, level, remove_after, extra_data) { |
| 982 |
remove_after = typeof remove_after !== 'undefined' ? remove_after : 30000; |
| 983 |
extra_data = typeof extra_data !== 'undefined' ? extra_data : { }; |
| 984 |
level = typeof level !== 'undefined' ? level : 'notice'; |
| 985 |
var type = 'notice'; |
| 986 |
var extra_classes = ''; |
| 987 |
|
| 988 |
if ('listener_' == level.substr(0, 9)) { |
| 989 |
type = 'listener'; |
| 990 |
extra_classes = 'updraftcentral_listener updraftcentral_listener_'+level.substr(9); |
| 991 |
extra_data.type = level.substr(9); |
| 992 |
level = 'info'; |
| 993 |
} |
| 994 |
|
| 995 |
$container = $('#updraftcentral_notice_container'); |
| 996 |
|
| 997 |
var newnotice_container_opener = '<div class="updraftcentral_notice updraftcentral_notice_new updraftcentral_notice_level_'+level+' '+extra_classes+'"'; |
| 998 |
$.each(extra_data, function(key, val) { |
| 999 |
newnotice_container_opener += 'data-'+key+'="'+quote_attribute(val)+'"'; |
| 1000 |
}); |
| 1001 |
|
| 1002 |
var $newnotice = $(newnotice_container_opener+'><button type="button" class="updraftcentral_notice_dismiss"></button><div class="updraftcentral_notice_contents">'+message+'</div></div>'); |
| 1003 |
$container.append($newnotice); |
| 1004 |
if (remove_after) { |
| 1005 |
$newnotice.slideDown('medium').delay(30000).slideUp('slow', function() { |
| 1006 |
$(this).remove(); |
| 1007 |
}); |
| 1008 |
} else { |
| 1009 |
$newnotice.slideDown('medium'); |
| 1010 |
} |
| 1011 |
|
| 1012 |
return $newnotice; |
| 1013 |
} |
| 1014 |
|
| 1015 |
/** |
| 1016 |
* Creates a special type of dashboard notice which polls for status updates |
| 1017 |
* @see register_listener_processor |
| 1018 |
* |
| 1019 |
* @param {string} type - Listener type (an identifying string) (not shown; stored and used for CSS classes) |
| 1020 |
* @param {Object} $site_row - a jQuery object identifying the site row that the listener is associated with |
| 1021 |
* @param {string} message - HTML to be placed in the dashboard notice |
| 1022 |
* @param {*} [data={}] - Data associated with the listener (which will be stored in an HTML data attribute) |
| 1023 |
* @returns {Object} the jQuery object for the newly created notice |
| 1024 |
* |
| 1025 |
*/ |
| 1026 |
this.create_dashboard_listener = function(type, $site_row, message, data) { |
| 1027 |
data = ('undefined' === typeof data) ? {} : data; |
| 1028 |
data.site_url = $site_row.data('site_url'); |
| 1029 |
data.site_id = $site_row.data('site_id'); |
| 1030 |
return this.add_dashboard_notice('<h2>'+$site_row.data('site_description')+'</h2>'+message, 'listener_'+type, false, data); |
| 1031 |
} |
| 1032 |
|
| 1033 |
// Only trigger a removal if the close button is directly in the notice. This allows other sub-elements to re-use the style class. |
| 1034 |
$('#updraftcentral_notice_container').on('click', '.updraftcentral_notice > .updraftcentral_notice_dismiss', function() { |
| 1035 |
$(this).parents('.updraftcentral_notice').clearQueue().slideUp('slow', function() { $(this).remove(); }); |
| 1036 |
}); |
| 1037 |
|
| 1038 |
/** |
| 1039 |
* Set up the dashboard, by hiding things that don't belong in the currently active tab |
| 1040 |
* |
| 1041 |
* @param {String|Boolean} new_mode=true - the mode to switch to. These correspond to keys for items placed in the main menu via the updraftcentral_main_navigation_items filter. If set to true, then it will choose the current mode (only useful if setting force to true). |
| 1042 |
* @param {Boolean} [force=false] - run the commands to set up the mode, even if it appears to be the current mode (useful for resetting the state within the mode) |
| 1043 |
*/ |
| 1044 |
this.set_dashboard_mode = function (new_mode, force) { |
| 1045 |
|
| 1046 |
force = ('undefined' === typeof force) ? false : true; |
| 1047 |
|
| 1048 |
var current_mode = $('#updraftcentral_dashboard').data('updraftcentral_mode'); |
| 1049 |
|
| 1050 |
if (true === new_mode) { new_mode = current_mode; } |
| 1051 |
|
| 1052 |
if (!force && new_mode == current_mode) { return; } |
| 1053 |
|
| 1054 |
if (current_mode) { $('#updraftcentral_dashboard').removeClass('updraftcentral_mode_'+current_mode); } |
| 1055 |
|
| 1056 |
$('#updraftcentral_dashboard_existingsites_container .updraftcentral_row_extracontents').empty(); |
| 1057 |
|
| 1058 |
// Show all sites again |
| 1059 |
$('#updraftcentral_dashboard_existingsites > .updraftcentral_site_row, #updraftcentral_dashboard_existingsites > .updraftcentral_row_divider').show(); |
| 1060 |
|
| 1061 |
$('#updraftcentral_dashboard').data('updraftcentral_mode', new_mode); |
| 1062 |
$('#updraftcentral_dashboard').addClass('updraftcentral_mode_'+new_mode); |
| 1063 |
$('#updraft-menu-item-'+current_mode).removeClass('updraft-menu-item-links-active'); |
| 1064 |
$('#updraft-menu-item-'+new_mode).addClass('updraft-menu-item-links-active'); |
| 1065 |
|
| 1066 |
// Since there exist classes for both "show everywhere except <here>" and "hide everywhere except here", you could, of course, add CSS classes that result in contradictory instructions. The outcome of doing so is not defined. |
| 1067 |
$('#updraftcentral_dashboard .updraftcentral-hide-in-other-tabs:not(.updraftcentral-show-in-tab-'+new_mode+'), #updraftcentral_dashboard .updraftcentral-hide-in-tab-'+new_mode).hide(); |
| 1068 |
$('#updraftcentral_dashboard .updraftcentral-show-in-tab-'+new_mode+' .updraftcentral-hide-in-tab-initially').hide(); |
| 1069 |
$('#updraftcentral_dashboard .updraftcentral-show-in-tab-'+new_mode+', #updraftcentral_dashboard .updraftcentral-show-in-other-tabs:not(.updraftcentral-hide-in-tab-'+new_mode+')').slideDown(1); |
| 1070 |
} |
| 1071 |
|
| 1072 |
$('.updraftcentral_mode_actions .updraftcentral_action_choose_another_site').click(function() { |
| 1073 |
UpdraftCentral.set_dashboard_mode(true, true); |
| 1074 |
}); |
| 1075 |
|
| 1076 |
$('#updraft-central-navigation .updraft-menu-item').click(function() { |
| 1077 |
var item_dom_id = $(this).attr('id'); |
| 1078 |
if ('undefined' === typeof item_dom_id) { return; } |
| 1079 |
if ('updraft-menu-item-' != item_dom_id.substring(0, 18)) { |
| 1080 |
console.log("UDCentral: menu item without the ID in the expected format"); |
| 1081 |
console.log(this); |
| 1082 |
return; |
| 1083 |
} |
| 1084 |
|
| 1085 |
var new_mode = item_dom_id.substring(18); |
| 1086 |
UpdraftCentral.set_dashboard_mode(new_mode); |
| 1087 |
|
| 1088 |
}); |
| 1089 |
|
| 1090 |
$('#updraftcentral_dashboard .updraftcentral_action_box .updraftcentral_action_manage_sites').click(function() { |
| 1091 |
UpdraftCentral.set_dashboard_mode('sites'); |
| 1092 |
}); |
| 1093 |
|
| 1094 |
$('#updraftcentral_modal').on('click', '#updraftcentral_addsite_expertoptions_show', function(e) { |
| 1095 |
$(this).slideUp(); |
| 1096 |
$('#updraftcentral_modal #updraftcentral_editsite_expertoptions .initially-hidden').show(); |
| 1097 |
e.preventDefault(); |
| 1098 |
}); |
| 1099 |
|
| 1100 |
/** |
| 1101 |
* Do any processing necessary with the passed information about current status |
| 1102 |
* |
| 1103 |
* @params {Object} status_info - any recognised properties will be processed |
| 1104 |
*/ |
| 1105 |
function process_sites_status_info(status_info) { |
| 1106 |
if (status_info.hasOwnProperty('how_many_licences_in_use')) { |
| 1107 |
$('.updraftcentral_licences_in_use').html(status_info.how_many_licences_in_use); |
| 1108 |
} |
| 1109 |
if (status_info.hasOwnProperty('how_many_licences_available')) { |
| 1110 |
var display = (status_info.how_many_licences_available < 0) ? '∞' : status_info.how_many_licences_available; |
| 1111 |
$('.updraftcentral_licences_total').html(display); |
| 1112 |
} |
| 1113 |
} |
| 1114 |
|
| 1115 |
$('#updraftcentral_modal').on('change', '#updraftcentral_site_connection_method', function() { |
| 1116 |
var site_connection_method = $('#updraftcentral_site_connection_method').val(); |
| 1117 |
|
| 1118 |
if (null == site_connection_method) { return; } |
| 1119 |
|
| 1120 |
if (site_connection_method.substring(0, 7) == 'direct_' && 'https:' == document.location.protocol) { |
| 1121 |
$('#updraftcentral_site_connection_method_message').show().html(udclion.http_must_go_via_mothership); |
| 1122 |
} else { |
| 1123 |
$('#updraftcentral_site_connection_method_message').hide(); |
| 1124 |
} |
| 1125 |
}); |
| 1126 |
|
| 1127 |
$('#updraftcentral_dashboard_newsite').click(function() { |
| 1128 |
|
| 1129 |
var advanced_site_options_html = UpdraftCentral.get_advanced_site_options_html({ http_username: '', http_password: ''}); |
| 1130 |
|
| 1131 |
UpdraftCentral.open_modal(udclion.add_site, UpdraftCentral.template_replace('sites-add-new-modal', { advanced_options: advanced_site_options_html } ), function() { |
| 1132 |
|
| 1133 |
var key = $('#updraftcentral_addsite_key').val(); |
| 1134 |
UpdraftCentral.close_modal(); |
| 1135 |
|
| 1136 |
if ('undefined' === typeof key || key === null || key === '') { return; } |
| 1137 |
|
| 1138 |
var extra_site_info = UpdraftCentral.get_serialized_options('#updraftcentral_modal #updraftcentral_editsite_expertoptions .expert_option'); |
| 1139 |
var send_cors_headers = $('#updraftcentral_modal #updraftcentral_site_send_cors_headers').is(':checked') ? 1 : 0; |
| 1140 |
var connection_method = $('#updraftcentral_modal #updraftcentral_site_connection_method').val(); |
| 1141 |
|
| 1142 |
send_ajax('newsite', { key: key, extra_site_info: extra_site_info, send_cors_headers: send_cors_headers, connection_method: connection_method }, null, 'via_mothership_encrypting', '#updraftcentral_dashboard_existingsites', function(resp, code, error_code) { |
| 1143 |
|
| 1144 |
if ('ok' == code) { |
| 1145 |
|
| 1146 |
if (resp.hasOwnProperty('message')) { |
| 1147 |
add_dashboard_notice(resp.message, 'info'); |
| 1148 |
if (resp.hasOwnProperty('sites_html')) { |
| 1149 |
set_existing_sites_to(resp.sites_html); |
| 1150 |
} else { |
| 1151 |
console.log("Expected sites_html data not found:"); |
| 1152 |
console.log(resp); |
| 1153 |
} |
| 1154 |
if (resp.hasOwnProperty('status_info')) { process_sites_status_info(resp.status_info); } |
| 1155 |
} |
| 1156 |
|
| 1157 |
if (resp.hasOwnProperty('key_needs_sending')) { |
| 1158 |
|
| 1159 |
var site_id = resp.key_needs_sending.key_site_id; |
| 1160 |
var site_ajax_url = resp.key_needs_sending.url; |
| 1161 |
var $site_row = $('#updraftcentral_dashboard_existingsites .updraftcentral_site_row[data-site_id="'+site_id+'"'); |
| 1162 |
var site_remote_public_key = resp.key_needs_sending.remote_public_key; |
| 1163 |
|
| 1164 |
$($site_row).prepend('<div class="updraftcentral_spinner"></div>'); |
| 1165 |
|
| 1166 |
var send_key_url = site_ajax_url+'&action=updraftcentral_receivepublickey&updraft_key_index='+encodeURIComponent(resp.key_needs_sending.updraft_key_index)+'&public_key='+encodeURIComponent(UpdraftCentral.base64_encode(site_remote_public_key)); |
| 1167 |
var win = window.open(send_key_url, '_blank', 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=600,height=320'); |
| 1168 |
win.focus(); |
| 1169 |
return; |
| 1170 |
|
| 1171 |
// This requires CORS code on the receiving side |
| 1172 |
/* |
| 1173 |
$.post(site_ajax_url, { |
| 1174 |
action: 'updraftcentral_receivepublickey', |
| 1175 |
public_key: site_remote_public_key, |
| 1176 |
updraft_key_index: resp.key_needs_sending.updraft_key_index |
| 1177 |
}, function(response) { |
| 1178 |
if (spinner_where) { |
| 1179 |
$($site_row).children('.updraftcentral_spinner').remove(); |
| 1180 |
} |
| 1181 |
// Obsolete - parse_response() no longer exists |
| 1182 |
var resp = parse_response(response); |
| 1183 |
if (false !== resp) { |
| 1184 |
if (resp.code == 'ok') { |
| 1185 |
console.log("UDCentral: successfully sent public key to remote site"); |
| 1186 |
} else { |
| 1187 |
console.log("UDCentral: failed to send public key to remote site"); |
| 1188 |
console.log(resp); |
| 1189 |
if (resp.code == 'already_have' || resp.code == 'already_have') { |
| 1190 |
add_dashboard_notice(udclion[resp.code], 'error'); |
| 1191 |
} else { |
| 1192 |
add_dashboard_notice(udclion.unknown_response, 'error'); |
| 1193 |
} |
| 1194 |
} |
| 1195 |
} |
| 1196 |
|
| 1197 |
}); |
| 1198 |
*/ |
| 1199 |
|
| 1200 |
} |
| 1201 |
} |
| 1202 |
}); |
| 1203 |
}, udclion.add_site, function() { |
| 1204 |
$('#updraftcentral_modal #updraftcentral_site_send_cors_headers').prop('checked', true); |
| 1205 |
// $('#updraftcentral_modal #updraftcentral_addsite_key').focus(); |
| 1206 |
} , false); |
| 1207 |
}); |
| 1208 |
|
| 1209 |
this.register_row_clicker('.updraftcentral_site_adddescription', function($row_parent) { |
| 1210 |
open_site_configuration($row_parent); |
| 1211 |
}); |
| 1212 |
$('#updraftcentral_modal').on('click', '.updraftcentral_site_editdescription', function(e) { |
| 1213 |
e.preventDefault(); |
| 1214 |
open_site_configuration(UpdraftCentral.$row_parent); |
| 1215 |
}); |
| 1216 |
|
| 1217 |
/** |
| 1218 |
* Gets the HTML fragment for advanced site editing options |
| 1219 |
* |
| 1220 |
* @param {Object} values - the values to pass to the template |
| 1221 |
* |
| 1222 |
* @returns {String} - the HTML |
| 1223 |
*/ |
| 1224 |
this.get_advanced_site_options_html = function(values) { |
| 1225 |
return UpdraftCentral.template_replace('sites-advanced-site-options', values); |
| 1226 |
} |
| 1227 |
|
| 1228 |
/** |
| 1229 |
* Get serialized options within a specified selector. Includes making sure that checkboxes are included when not checked. |
| 1230 |
* |
| 1231 |
* @param {String} selector - the jQuery selector to use to locate the options |
| 1232 |
* |
| 1233 |
* @returns {String} - the serialized options |
| 1234 |
*/ |
| 1235 |
this.get_serialized_options = function(selector) { |
| 1236 |
var form_data = $(selector).serialize(); |
| 1237 |
$.each($(selector+' input[type=checkbox]') |
| 1238 |
.filter(function(idx){ |
| 1239 |
return $(this).prop('checked') == false |
| 1240 |
}), |
| 1241 |
function(idx, el){ |
| 1242 |
//attach matched element names to the form_data with chosen value. |
| 1243 |
var empty_val = '0'; |
| 1244 |
form_data += '&' + $(el).attr('name') + '=' + empty_val; |
| 1245 |
}); |
| 1246 |
return form_data; |
| 1247 |
} |
| 1248 |
|
| 1249 |
/** |
| 1250 |
* Opens the site connection test dialog for the specified site |
| 1251 |
* |
| 1252 |
* @param {Object} - the jQuery row object for the site whose configuration is to be edited |
| 1253 |
*/ |
| 1254 |
this.open_connection_test = function($row_parent) { |
| 1255 |
|
| 1256 |
var site_url = $row_parent.data('site_url'); |
| 1257 |
var site_id = $row_parent.data('site_id'); |
| 1258 |
|
| 1259 |
var current_connection_method = $row_parent.data('connection_method'); |
| 1260 |
|
| 1261 |
if ('via_mothership_encrypting' == current_connection_method) { |
| 1262 |
UpdraftCentral.dialog.alert('<h2>'+udclion.test_connection_methods+'</h2><p>'+udclion.test_not_possible_in_current_mode+'</p>'); |
| 1263 |
// <p><a href="#" class="updraftcentral_site_editdescription">'+udclion.open_site_configuration+'...</a></p> |
| 1264 |
return; |
| 1265 |
} |
| 1266 |
|
| 1267 |
var current_method_simplified = ('direct_jquery_auth' == current_connection_method || 'direct_default_auth' == current_connection_method || 'direct_default_auth' == current_connection_method) ? 'direct' : current_connection_method; |
| 1268 |
|
| 1269 |
UpdraftCentral.open_modal(udclion.test_connection_methods, UpdraftCentral.template_replace('sites-connection-test', { site_url: site_url }), true, false, function() { |
| 1270 |
|
| 1271 |
var direct_method_can_be_attempted = true; |
| 1272 |
if ('https:'== document.location.protocol) { |
| 1273 |
if (site_url.substring(0, 5).toLowerCase() == 'http:') { |
| 1274 |
direct_method_can_be_attempted = false; |
| 1275 |
} |
| 1276 |
} |
| 1277 |
|
| 1278 |
if (direct_method_can_be_attempted) { |
| 1279 |
|
| 1280 |
$('#updraftcentral_modal .connection-test-direct .connection-test-result').html(''); |
| 1281 |
|
| 1282 |
send_ajax('ping', null, $row_parent, 'direct_default_auth', '#updraftcentral_modal .connection-test-direct .connection-test-result', function(response, code, error_code) { |
| 1283 |
if (updraftcentral_debug_level > 0) { |
| 1284 |
console.log("Result follows for 'direct_default_auth' method:"); |
| 1285 |
console.log(response); |
| 1286 |
} |
| 1287 |
if ('ok' == code) { |
| 1288 |
var new_html = '<span class="connection-test-succeeded">'+udclion.succeeded+'</span> '; |
| 1289 |
if ('direct' == current_method_simplified) { |
| 1290 |
new_html += udclion.current_method+' '+udclion.best_method+' '+udclion.recommend_keep; |
| 1291 |
} else { |
| 1292 |
new_html += udclion.best_method+' '+udclion.recommend_use+' <a href="#" class="connection-test-switch" data-site_id="'+site_id+'" data-connection_method="direct_default_auth">'+udclion.switch_to+'...</a>'; |
| 1293 |
} |
| 1294 |
$('#updraftcentral_modal .connection-test-direct .connection-test-result').html(new_html); |
| 1295 |
} else { |
| 1296 |
$('#updraftcentral_modal .connection-test-direct .connection-test-result').html('<span class="connection-test-failed">'+udclion.failed+' ('+error_code+')</span>'); |
| 1297 |
} |
| 1298 |
}, 30, false); |
| 1299 |
|
| 1300 |
} else { |
| 1301 |
$('#updraftcentral_modal .connection-test-direct .connection-test-result').html(udclion.not_possible_browser_restrictions); |
| 1302 |
} |
| 1303 |
|
| 1304 |
$('#updraftcentral_modal .connection-test-via_mothership .connection-test-result').html(''); |
| 1305 |
send_ajax('ping', null, $row_parent, 'via_mothership', '#updraftcentral_modal .connection-test-via_mothership .connection-test-result', function(response, code, error_code) { |
| 1306 |
$('#updraftcentral_modal .connection-test-via_mothership .connection-test-result').html(code); |
| 1307 |
if (updraftcentral_debug_level > 0) { |
| 1308 |
console.log("Result follows for 'via_mothership' method:"); |
| 1309 |
console.log(response); |
| 1310 |
} |
| 1311 |
if ('ok' == code) { |
| 1312 |
var new_html = '<span class="connection-test-succeeded">'+udclion.succeeded+'</span> '; |
| 1313 |
if ('via_mothership' != current_connection_method) { |
| 1314 |
new_html += '<a href="#" class="connection-test-switch" data-site_id="'+site_id+'" data-connection_method="via_mothership">'+udclion.switch_to+'...</a>'; |
| 1315 |
} else { |
| 1316 |
new_html += udclion.current_method; |
| 1317 |
} |
| 1318 |
$('#updraftcentral_modal .connection-test-via_mothership .connection-test-result').html(new_html); |
| 1319 |
} else { |
| 1320 |
$('#updraftcentral_modal .connection-test-via_mothership .connection-test-result').html('<span class="connection-test-failed">'+udclion.failed+' ('+error_code+')</span>'); |
| 1321 |
} |
| 1322 |
}, 30, false); |
| 1323 |
|
| 1324 |
$('#updraftcentral_modal .connection-test-via_mothership_encrypting .connection-test-result').html(''); |
| 1325 |
send_ajax('ping', null, $row_parent, 'via_mothership_encrypting', '#updraftcentral_modal .connection-test-via_mothership_encrypting .connection-test-result', function(response, code, error_code) { |
| 1326 |
if (updraftcentral_debug_level > 0) { |
| 1327 |
console.log("Result follows for 'via_mothership_encrypting' method:"); |
| 1328 |
console.log(response); |
| 1329 |
} |
| 1330 |
if ('ok' == code) { |
| 1331 |
var new_html = '<span class="connection-test-succeeded">'+udclion.succeeded+'</span> '; |
| 1332 |
if ('via_mothership_encrypting' != current_connection_method) { |
| 1333 |
new_html += '<a href="#" class="connection-test-switch" data-site_id="'+site_id+'" data-connection_method="via_mothership_encrypting">'+udclion.switch_to+'...</a>'; |
| 1334 |
} else { |
| 1335 |
new_html += udclion.current_method; |
| 1336 |
} |
| 1337 |
$('#updraftcentral_modal .connection-test-via_mothership_encrypting .connection-test-result').html(new_html); |
| 1338 |
} else { |
| 1339 |
$('#updraftcentral_modal .connection-test-via_mothership_encrypting .connection-test-result').html('<span class="connection-test-failed">'+udclion.failed+' ('+error_code+')</span>'); |
| 1340 |
} |
| 1341 |
}, 30, false); |
| 1342 |
|
| 1343 |
}, true, 'modal-lg'); |
| 1344 |
} |
| 1345 |
|
| 1346 |
/** |
| 1347 |
* Opens the site configuration dialog for the specified site |
| 1348 |
* |
| 1349 |
* @param {Object} - the jQuery row object for the site whose configuration is to be edited |
| 1350 |
*/ |
| 1351 |
this.open_site_configuration = function($row_parent) { |
| 1352 |
var site_url = $row_parent.data('site_url'); |
| 1353 |
var http_username = $row_parent.data('http_username'); |
| 1354 |
if ('undefined' === typeof http_username) { http_username = ''; } |
| 1355 |
|
| 1356 |
var http_password = $row_parent.data('http_password'); |
| 1357 |
if ('undefined' === typeof http_password) { http_password = ''; } |
| 1358 |
|
| 1359 |
var connection_method = $row_parent.data('connection_method'); |
| 1360 |
if ('undefined' === typeof connection_method) { connection_method = 'direct_default_auth'; } |
| 1361 |
|
| 1362 |
var http_authentication_method = $row_parent.data('http_authentication_method'); |
| 1363 |
if ('undefined' === typeof http_authentication_method) { http_authentication_method = 'basic'; } |
| 1364 |
|
| 1365 |
var existing_description = $row_parent.data('site_description'); |
| 1366 |
if (existing_description == site_url) { existing_description = ''; } |
| 1367 |
|
| 1368 |
var send_cors_headers = $row_parent.data('send_cors_headers'); |
| 1369 |
if ('undefined' === typeof send_cors_headers || send_cors_headers) { send_cors_headers = 1; } |
| 1370 |
|
| 1371 |
var advanced_site_options_html = UpdraftCentral.get_advanced_site_options_html({http_username: http_username, http_password: http_password}); |
| 1372 |
|
| 1373 |
UpdraftCentral.open_modal(udclion.edit_site_configuration, UpdraftCentral.template_replace('sites-edit-configuration', { site_url: site_url, advanced_options: advanced_site_options_html }, { existing_description: existing_description } ), function() { |
| 1374 |
|
| 1375 |
var description = $('#updraftcentral-edit-site-description').val(); |
| 1376 |
|
| 1377 |
var send_cors_headers = $('#updraftcentral_modal #updraftcentral_site_send_cors_headers').is(':checked') ? 1 : 0; |
| 1378 |
|
| 1379 |
var connection_method = $('#updraftcentral_modal #updraftcentral_site_connection_method').val(); |
| 1380 |
|
| 1381 |
var site_id = $row_parent.data('site_id'); |
| 1382 |
if (!site_id) { return; } |
| 1383 |
|
| 1384 |
UpdraftCentral.close_modal(); |
| 1385 |
|
| 1386 |
var extra_site_info = UpdraftCentral.get_serialized_options('#updraftcentral_modal .expert_option'); |
| 1387 |
|
| 1388 |
send_ajax('edit_site_configuration', { site_id: site_id, description: description, extra_site_info: extra_site_info, send_cors_headers: send_cors_headers, connection_method: connection_method }, null, 'via_mothership_encrypting', '#updraftcentral_dashboard_existingsites', function(resp, code, error_code) { |
| 1389 |
|
| 1390 |
if ('ok' == code) { |
| 1391 |
|
| 1392 |
if (resp.hasOwnProperty('message')) { add_dashboard_notice(resp.message); } |
| 1393 |
|
| 1394 |
if (resp.hasOwnProperty('sites_html')) { |
| 1395 |
set_existing_sites_to(resp.sites_html); |
| 1396 |
setup_menunav(); |
| 1397 |
} else { |
| 1398 |
console.log(resp); |
| 1399 |
add_dashboard_notice(udclion.unknown_response, 'error'); |
| 1400 |
} |
| 1401 |
if (resp.hasOwnProperty('status_info')) { process_sites_status_info(resp.status_info); } |
| 1402 |
} |
| 1403 |
}); |
| 1404 |
|
| 1405 |
}, udclion.edit, function() { |
| 1406 |
$('#updraftcentral_modal #updraftcentral_site_connection_method').val(connection_method).change(); |
| 1407 |
if (send_cors_headers) { $('#updraftcentral_modal #updraftcentral_site_send_cors_headers').prop('checked', true); } |
| 1408 |
$('#updraftcentral_modal #updraftcentral_addsite_http_authentication_method').val(http_authentication_method); |
| 1409 |
}, false); |
| 1410 |
} |
| 1411 |
|
| 1412 |
this.register_row_clicker('.updraftcentral_site_delete', function($row_parent) { |
| 1413 |
UpdraftCentral.dialog.confirm('<h2>'+udclion.remove_site+'</h2><p>'+$row_parent.data('site_url')+'</p><p>'+udclion.really_delete_site+'</p>', function(result) { |
| 1414 |
if (!result) return; |
| 1415 |
var site_id = UpdraftCentral.$row_parent.data('site_id'); |
| 1416 |
if (!site_id) { return; } |
| 1417 |
$row_parent.slideUp('slow'); |
| 1418 |
|
| 1419 |
send_ajax('delete_site', { site_id: site_id }, null, 'via_mothership_encrypting', '#updraftcentral_dashboard_existingsites', function(resp, code, error_code) { |
| 1420 |
if ('ok' == code) { |
| 1421 |
if (resp.hasOwnProperty('message')) { |
| 1422 |
add_dashboard_notice(resp.message); |
| 1423 |
} |
| 1424 |
if (resp.hasOwnProperty('sites_html')) { |
| 1425 |
set_existing_sites_to(resp.sites_html); |
| 1426 |
} else { |
| 1427 |
console.log(resp); |
| 1428 |
add_dashboard_notice(udclion.unknown_response, 'error'); |
| 1429 |
} |
| 1430 |
if (resp.hasOwnProperty('status_info')) { process_sites_status_info(resp.status_info); } |
| 1431 |
} |
| 1432 |
}); |
| 1433 |
|
| 1434 |
}); |
| 1435 |
}); |
| 1436 |
|
| 1437 |
/** |
| 1438 |
* @callable RPCCallback |
| 1439 |
* @param {Object} response - the data returned by the RPC call |
| 1440 |
* @param {string} code - the code returned by the RPC call |
| 1441 |
* @param {string|null} error_code - the error code returned by the RPC call (if any) |
| 1442 |
*/ |
| 1443 |
|
| 1444 |
/** |
| 1445 |
* Send a command to the remote site. This is a very thin wrapper around send_ajax. |
| 1446 |
* |
| 1447 |
* @param {string} rpc_command - the command to send |
| 1448 |
* @param {*} data - the data to send with the command |
| 1449 |
* @param {Object} $site_row - the jQuery object for the row of the site that the request is being sent to |
| 1450 |
* @param {RPCCallback} - function to call with the results |
| 1451 |
* @param {Object|null} spinner_where - jQuery object indicating where any spinner should be shown |
| 1452 |
* @param {Number} [timeout=30] - the number of seconds for the timeout on the HTTP call |
| 1453 |
* |
| 1454 |
* @uses send_ajax |
| 1455 |
*/ |
| 1456 |
this.send_site_rpc = function(rpc_command, data, $site_row, callback, spinner_where, timeout) { |
| 1457 |
timeout = 'undefined' !== typeof timeout ? timeout : 30; |
| 1458 |
var site_id = $site_row.data('site_id'); |
| 1459 |
if (!site_id) { |
| 1460 |
console.log("UpdraftCentral: sent_site_rpc("+rpc_command+") command sent, but site ID could not be identified from the row (follows)"); |
| 1461 |
console.log($site_row); |
| 1462 |
} |
| 1463 |
|
| 1464 |
var connection_method = $site_row.data('connection_method'); |
| 1465 |
|
| 1466 |
if ('undefined' === typeof spinner_where || null === spinner_where || false === spinner_where) { spinner_where = $site_row; } |
| 1467 |
|
| 1468 |
try { |
| 1469 |
return send_ajax(rpc_command, data, $site_row, connection_method, spinner_where, callback); |
| 1470 |
} catch (e) { |
| 1471 |
if (spinner_where) { |
| 1472 |
$(spinner_where).children('.updraftcentral_spinner').remove(); |
| 1473 |
} |
| 1474 |
// add_dashboard_notice(udclion.js_exception_occurred+'<br>'+e.toString(), 'error'); |
| 1475 |
UpdraftCentral.dialog.alert('<h2>'+udclion.error+'</h2>'+udclion.js_exception_occurred+'<br>'+e.toString()); |
| 1476 |
console.log(e); |
| 1477 |
} |
| 1478 |
} |
| 1479 |
|
| 1480 |
this.register_row_clicker('.row_siteinfo', function($row_parent) { |
| 1481 |
UpdraftCentral.send_site_rpc('core.site_info', null, $row_parent, function(response, code, error_code) { |
| 1482 |
if (updraftcentral_debug_level > 1) { |
| 1483 |
console.log("send_site_rpc(site_info): parsed response follows"); |
| 1484 |
console.log(response); |
| 1485 |
} |
| 1486 |
if ('ok' == code) { |
| 1487 |
if (false !== response) { |
| 1488 |
var versions = response.data.versions; |
| 1489 |
var bloginfo = response.data.bloginfo; |
| 1490 |
var url = UpdraftCentral.sanitize_html(bloginfo.url); |
| 1491 |
var name = UpdraftCentral.sanitize_html(bloginfo.name); |
| 1492 |
// 'This site is running WordPress version %s (PHP %s, MySQL %s) and UpdraftPlus version %s (UDRPC version %s)' |
| 1493 |
// var message = sprintf(udclion.what_remote_running, UpdraftCentral.sanitize_html(versions.wp), UpdraftCentral.sanitize_html(versions.php), UpdraftCentral.sanitize_html(versions.mysql), UpdraftCentral.sanitize_html(versions.ud), UpdraftCentral.sanitize_html(versions.udrpc_php)); |
| 1494 |
// add_dashboard_notice(message, 'info'); |
| 1495 |
// N.B. By default, open_modal() sanitizes the body. |
| 1496 |
var ud_version = versions.ud; |
| 1497 |
if ('none' == ud_version) { ud_version = udclion.updraftplus.version_none; } |
| 1498 |
var message = sprintf(udclion.what_remote_running, versions.wp, versions.php, versions.mysql, ud_version, versions.udrpc_php); |
| 1499 |
UpdraftCentral.open_modal( |
| 1500 |
UpdraftCentral.sanitize_html(bloginfo.name), |
| 1501 |
UpdraftCentral.template_replace('dashboard-siteinfo', { url: url, message: message, phpinfo: udclion.phpinfo }), |
| 1502 |
null, |
| 1503 |
false |
| 1504 |
); |
| 1505 |
} |
| 1506 |
} |
| 1507 |
}); |
| 1508 |
}); |
| 1509 |
|
| 1510 |
$('#updraftcentral_modal').on('click', '.updraftcentral_siteinfo_results .phpinfo', function(e) { |
| 1511 |
e.preventDefault(); |
| 1512 |
UpdraftCentral.send_site_rpc('core.phpinfo', null, UpdraftCentral.$row_parent, function(response, code, error_code) { |
| 1513 |
|
| 1514 |
if ('ok' == code && response.data) { |
| 1515 |
var output = ''; |
| 1516 |
$.each(response.data, function(name, section) { |
| 1517 |
output += "<h3>"+name+"</h3>\n"+'<table>'+"\n"; |
| 1518 |
$.each(section, function(key, val) { |
| 1519 |
if (val.constructor === Array) { |
| 1520 |
output += "<tr><td>"+key+"</td><td>"+val[0]+"</td><td>"+val[1]+"</td></tr>\n"; |
| 1521 |
} else if (typeof val === 'string') { |
| 1522 |
if ($.isNumeric(key)) { |
| 1523 |
output += "<tr><td></td><td>"+val+"</td></tr>\n"; |
| 1524 |
} else { |
| 1525 |
output += "<tr><td>"+key+"</td><td>"+val+"</td></tr>\n"; |
| 1526 |
} |
| 1527 |
} else { |
| 1528 |
console.log("UpdraftCentral: phpinfo: Unrecognised output for key "+key+" (follows)"); |
| 1529 |
console.log(val); |
| 1530 |
} |
| 1531 |
}); |
| 1532 |
output += "</table>\n"; |
| 1533 |
}); |
| 1534 |
|
| 1535 |
// N.B. open_modal() by default sanitizes the body data |
| 1536 |
UpdraftCentral.open_modal(udclion.phpinfo, '<div id="updraftcentral_phpinfo_results">'+output+'</div>', null, false, null, true, 'modal-lg'); |
| 1537 |
} |
| 1538 |
}, $(this)); |
| 1539 |
}); |
| 1540 |
|
| 1541 |
/** |
| 1542 |
* Opens a new browser portal at the specified URL |
| 1543 |
* @param {Object} $row_parent - jQuery object for the site row |
| 1544 |
* @param {Object|string|null} [redirect_to=null] - where to redirect to (defaults to the network admin) |
| 1545 |
* @param {Object} [spinner_where=$row_parent] - jQuery object indicating where to put the site row. |
| 1546 |
*/ |
| 1547 |
this.open_browser_at = function($row_parent, redirect_to, spinner_where) { |
| 1548 |
redirect_to = typeof redirect_to !== 'undefined' ? redirect_to : null; |
| 1549 |
spinner_where = ('undefined' === typeof spinner_where) ? $row_parent : spinner_where; |
| 1550 |
UpdraftCentral.send_site_rpc('core.get_login_url', redirect_to, $row_parent, function(response, code, error_code) { |
| 1551 |
if ('ok' == code && false !== response && response.hasOwnProperty('data')) { |
| 1552 |
var login_url = response.data.login_url; |
| 1553 |
var win = window.open(login_url, '_blank'); |
| 1554 |
if ('undefined' != typeof win) { |
| 1555 |
win.focus(); |
| 1556 |
} else { |
| 1557 |
UpdraftCentral.dialog.alert('<h2>'+udclion.open_new_window+'</h2>'+udclion.window_may_be_blocked); |
| 1558 |
} |
| 1559 |
} |
| 1560 |
}, spinner_where); |
| 1561 |
} |
| 1562 |
|
| 1563 |
$('#updraftcentral_dashboard_existingsites_container').on('click', '.updraftcentral_site_dashboard', function() { |
| 1564 |
var $row_parent = $(this).parents('.updraftcentral_site_row'); |
| 1565 |
UpdraftCentral.open_browser_at($row_parent); |
| 1566 |
}); |
| 1567 |
|
| 1568 |
/** |
| 1569 |
* Toggle whether or not UpdraftCentral is in "full screen" mode |
| 1570 |
*/ |
| 1571 |
this.toggle_fullscreen = function() { |
| 1572 |
// https://github.com/private-face/jquery.fullscreen |
| 1573 |
if ($.fullscreen.isFullScreen()) { |
| 1574 |
$('footer').show(); |
| 1575 |
$.fullscreen.exit(); |
| 1576 |
} else { |
| 1577 |
$('footer').hide(); |
| 1578 |
$('#updraftcentral_dashboard').fullscreen({overflow: 'scroll', toggleClass: 'updraft-fullscreen' }); |
| 1579 |
} |
| 1580 |
} |
| 1581 |
|
| 1582 |
$('#updraftcentral_dashboard .updraft-central-logo img').dblclick(function() { UpdraftCentral.toggle_fullscreen(); }); |
| 1583 |
|
| 1584 |
$('#updraft-central-navigation .updraft-full-screen').on('click', function() { UpdraftCentral.toggle_fullscreen(); }); |
| 1585 |
|
| 1586 |
$('#updraft-central-navigation .updraftcentral-help').on('click', function() { |
| 1587 |
UpdraftCentral.dialog.alert(UpdraftCentral.template_replace('dashboard-help', { uc_version: udclion.updraftcentral_version+': '+udclion.udc_version, running_on: UpdraftCentral.version_info_as_text() })); |
| 1588 |
}); |
| 1589 |
|
| 1590 |
/** |
| 1591 |
* Return a string with information on the current installation |
| 1592 |
*/ |
| 1593 |
this.version_info_as_text = function() { |
| 1594 |
return 'WP/'+udclion.wp_version+' PHP/'+udclion.php_version+' MySQL/'+udclion.mysql_version+' Curl/'+udclion.curl_version; |
| 1595 |
} |
| 1596 |
|
| 1597 |
$('#updraft-central-navigation .updraftcentral-settings').on('click', function() { |
| 1598 |
|
| 1599 |
UpdraftCentral.open_modal(udclion.settings, UpdraftCentral.template_replace('dashboard-settings', { uc_version: udclion.updraftcentral_version+': '+udclion.udc_version, running_on: UpdraftCentral.version_info_as_text() }), function() { |
| 1600 |
var new_debugging_level = $('#updraftcentral_debug_level').val(); |
| 1601 |
if (new_debugging_level >= 0 && new_debugging_level <=3) { |
| 1602 |
UpdraftCentral.set_debug_level(new_debugging_level); |
| 1603 |
} |
| 1604 |
UpdraftCentral.close_modal(); |
| 1605 |
}, udclion.save_settings, function() { |
| 1606 |
$('#updraftcentral_debug_level').val(updraftcentral_debug_level); |
| 1607 |
}); |
| 1608 |
|
| 1609 |
}); |
| 1610 |
|
| 1611 |
// Refresh dashicon rotates after it has been clicked - stops when the settings are refreshed. |
| 1612 |
$('.updraftcentral_row_extracontents').on('click', '.dashicons-image-rotate', function() { |
| 1613 |
$('.dashicons-image-rotate').addClass('dashicon-image-rotating'); |
| 1614 |
}); |
| 1615 |
|
| 1616 |
/** |
| 1617 |
* Returns the result of filling in the specified Handlebars (http://handlebarsjs.com) template with the provided values |
| 1618 |
* |
| 1619 |
* @param {String} template_name - the name of the Handlebars template, based (though it is filterable) on the path within the 'templates' directory, with slashes replaced by dashes. e.g. templates/dashboard/something.handlebars.html is accessed via a name of 'dashboard-something' |
| 1620 |
* @param {Object} [vars] - an object with properties (and corresponding values) corresponding to the named variables in the template and the values to replace them. N.B. The udclion object is always passed through (as udclion). |
| 1621 |
* @param {Object} [attr_vars] - an optional object with properties (and corresponding values) corresponding to the named variables in the template and the values to replace them, but for which the values will first be sanitized for use in HTML attributes. This may not be necessary on input which isn't user-supplied (e.g. its format may already be known to be attribute-safe). |
| 1622 |
* |
| 1623 |
* @returns {String} The template with values filled in |
| 1624 |
*/ |
| 1625 |
this.template_replace = function(template_name, vars, attr_vars) { |
| 1626 |
vars = ('undefined' === typeof vars) ? {} : vars; |
| 1627 |
if (!UpdraftCentral_Handlebars.hasOwnProperty(template_name)) { |
| 1628 |
console.log("UDCentral: UpdraftCentral_Handlebars template not found: "+template_name); |
| 1629 |
console.log(UpdraftCentral_Handlebars); |
| 1630 |
} |
| 1631 |
if ('undefined' !== typeof attr_vars) { |
| 1632 |
$.each(attr_vars, function(k, v) { |
| 1633 |
vars[k] = quote_attribute(v); |
| 1634 |
}); |
| 1635 |
} |
| 1636 |
vars.udclion = udclion; |
| 1637 |
return UpdraftCentral_Handlebars[template_name](vars); |
| 1638 |
} |
| 1639 |
|
| 1640 |
// Use a new browser portal for any clicks to updraftplus.com |
| 1641 |
$('#updraftcentral_modal').on('click', 'a', function(e) { |
| 1642 |
var href = $(this).attr('href'); |
| 1643 |
if ('undefined' === typeof href) { return; } |
| 1644 |
if (href.substring(0, 23) == 'https://updraftplus.com' || href.substring(0, 23) == 'http://updraftplus.com') { |
| 1645 |
e.preventDefault(); |
| 1646 |
var win = window.open(href, '_blank'); |
| 1647 |
win.focus(); |
| 1648 |
} |
| 1649 |
}); |
| 1650 |
|
| 1651 |
/** |
| 1652 |
* Compiles any Handlebars templates that have been passed into the page. By default, they are pre-compiled; but compilation happens in-browser when in developer mode. |
| 1653 |
*/ |
| 1654 |
function compile_handlebars_templates() { |
| 1655 |
// Initialise Handlebars.templates - it may not already exist |
| 1656 |
UpdraftCentral_Handlebars = (typeof UpdraftCentral_Handlebars === 'undefined') ? {} : UpdraftCentral_Handlebars; |
| 1657 |
if (!udclion.hasOwnProperty('handlebars')) return; |
| 1658 |
if (udclion.handlebars.hasOwnProperty('compile')) { |
| 1659 |
$.each(udclion.handlebars.compile, function(template_name, source) { |
| 1660 |
console.log("UpdraftCentral: in developer mode: compile template: "+template_name); |
| 1661 |
UpdraftCentral_Handlebars[template_name] = Handlebars.compile(source); |
| 1662 |
}); |
| 1663 |
} |
| 1664 |
} |
| 1665 |
|
| 1666 |
compile_handlebars_templates(); |
| 1667 |
|
| 1668 |
setup_menunav(); |
| 1669 |
|
| 1670 |
set_dashboard_mode('sites'); |
| 1671 |
|
| 1672 |
if ('undefined' !== typeof Modernizr && !Modernizr.lastchild) { |
| 1673 |
console.log("UDCentral: Unsupported web browser"); |
| 1674 |
$('#updraftcentral_dashboard_loading').fadeOut(); |
| 1675 |
$('#updraftcentral_updraftplus_actions, #updraftcentral_sites_actions, #updraftcentral_dashboard_existingsites_container').remove(); |
| 1676 |
this.add_dashboard_notice(udclion.unsupported_browser, 'error', false); |
| 1677 |
} else { |
| 1678 |
|
| 1679 |
$('#updraftcentral_dashboard_loading').fadeOut(); |
| 1680 |
$('#updraftcentral_dashboard_existingsites_container').fadeIn(); |
| 1681 |
|
| 1682 |
if (udclion.hasOwnProperty('show_licence_counts') && udclion.show_licence_counts) { $('.updraftcentral_licence_info').show(); } |
| 1683 |
|
| 1684 |
// Refresh the sites list every 24 hours |
| 1685 |
setInterval(function(){ |
| 1686 |
send_ajax('sites_html', null, null, 'via_mothership_encrypting', '#updraftcentral_dashboard_existingsites', function(resp, code, error_code) { |
| 1687 |
if ('ok' == code) { |
| 1688 |
if (resp.hasOwnProperty('sites_html')) { |
| 1689 |
set_existing_sites_to(resp.sites_html); |
| 1690 |
} else { |
| 1691 |
console.log("Expected sites_html data not found:"); |
| 1692 |
console.log(resp); |
| 1693 |
} |
| 1694 |
if (resp.hasOwnProperty('status_info')) { process_sites_status_info(resp.status_info); } |
| 1695 |
} |
| 1696 |
}); |
| 1697 |
}, 86400000); |
| 1698 |
|
| 1699 |
} |
| 1700 |
|
| 1701 |
// Remove any indicated notices that came pre-printed on the page |
| 1702 |
$('#updraftcentral_notice_container .updraftcentral_notice.remove_after_load').delay(30000).slideUp('slow', function() { |
| 1703 |
$(this).remove(); |
| 1704 |
}); |
| 1705 |
|
| 1706 |
return this; |
| 1707 |
} |
| 1708 |
|
| 1709 |
// https://github.com/richadams/jquery-tripleclick/ |
| 1710 |
// @author Rich Adams <rich@richadams.me> |
| 1711 |
// Implements a triple-click event. Click (or touch) three times within 1s on the element to trigger. |
| 1712 |
// Licence: https://creativecommons.org/licenses/by/3.0/ (verified at 2-Feb-2016) |
| 1713 |
|
| 1714 |
;(function($) |
| 1715 |
{ |
| 1716 |
// Default options |
| 1717 |
var defaults = { |
| 1718 |
threshold: 1000, // ms |
| 1719 |
} |
| 1720 |
|
| 1721 |
function tripleHandler(event) |
| 1722 |
{ |
| 1723 |
var $elem = jQuery(this); |
| 1724 |
|
| 1725 |
// Merge the defaults and any user defined settings. |
| 1726 |
settings = jQuery.extend({}, defaults, event.data); |
| 1727 |
|
| 1728 |
// Get current values, or 0 if they don't yet exist. |
| 1729 |
var clicks = $elem.data("triclick_clicks") || 0; |
| 1730 |
var start = $elem.data("triclick_start") || 0; |
| 1731 |
|
| 1732 |
// If first click, register start time. |
| 1733 |
if (clicks === 0) { start = event.timeStamp; } |
| 1734 |
|
| 1735 |
// If we have a start time, check it's within limit |
| 1736 |
if (start != 0 |
| 1737 |
&& event.timeStamp > start + settings.threshold) |
| 1738 |
{ |
| 1739 |
// Tri-click failed, took too long. |
| 1740 |
clicks = 0; |
| 1741 |
start = event.timeStamp; |
| 1742 |
} |
| 1743 |
|
| 1744 |
// Increment counter, and do finish action. |
| 1745 |
clicks += 1; |
| 1746 |
if (clicks === 3) |
| 1747 |
{ |
| 1748 |
clicks = 0; |
| 1749 |
start = 0; |
| 1750 |
event.type = "tripleclick"; |
| 1751 |
|
| 1752 |
// Let jQuery handle the triggering of "tripleclick" event handlers |
| 1753 |
if (jQuery.event.handle === undefined) { |
| 1754 |
jQuery.event.dispatch.apply(this, arguments); |
| 1755 |
} |
| 1756 |
else { |
| 1757 |
// for jQuery before 1.9 |
| 1758 |
jQuery.event.handle.apply(this, arguments); |
| 1759 |
} |
| 1760 |
} |
| 1761 |
|
| 1762 |
// Update object data |
| 1763 |
$elem.data("triclick_clicks", clicks); |
| 1764 |
$elem.data("triclick_start", start); |
| 1765 |
} |
| 1766 |
|
| 1767 |
var tripleclick = $.event.special.tripleclick = |
| 1768 |
{ |
| 1769 |
setup: function(data, namespaces) |
| 1770 |
{ |
| 1771 |
$(this).bind("touchstart click.triple", data, tripleHandler); |
| 1772 |
}, |
| 1773 |
teardown: function(namespaces) |
| 1774 |
{ |
| 1775 |
$(this).unbind("touchstart click.triple", data, tripleHandler); |
| 1776 |
} |
| 1777 |
}; |
| 1778 |
})(jQuery); |
| 1779 |
|