Add upstream

This commit is contained in:
root
2019-10-24 00:12:05 +02:00
parent 85d41e4216
commit ac980f592c
3504 changed files with 1049983 additions and 29971 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,611 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No access.');
if (defined('UPDRAFTCENTRAL_CLIENT_DIR')) return;
define('UPDRAFTCENTRAL_CLIENT_DIR', dirname(__FILE__));
// This file is included during plugins_loaded
// Load the listener class that we rely on to pick up messages
if (!class_exists('UpdraftPlus_UpdraftCentral_Listener')) require_once('listener.php');
class UpdraftPlus_UpdraftCentral_Main {
public function __construct() {
// Add the section to the 'advanced tools' page
add_action('updraftplus_debugtools_dashboard', array($this, 'debugtools_dashboard'), 20);
add_action('udrpc_log', array($this, 'udrpc_log'), 10, 3);
add_action('wp_ajax_updraftcentral_receivepublickey', array($this, 'wp_ajax_updraftcentral_receivepublickey'));
add_action('wp_ajax_nopriv_updraftcentral_receivepublickey', array($this, 'wp_ajax_updraftcentral_receivepublickey'));
// The 'updraftplus' commands are registered in UpdraftPlus::plugins_loaded()
$command_classes = apply_filters('updraftplus_remotecontrol_command_classes', array(
'core' => 'UpdraftCentral_Core_Commands',
'updates' => 'UpdraftCentral_Updates_Commands',
'users' => 'UpdraftCentral_Users_Commands',
'comments' => 'UpdraftCentral_Comments_Commands',
'analytics' => 'UpdraftCentral_Analytics_Commands',
'plugin' => 'UpdraftCentral_Plugin_Commands',
'theme' => 'UpdraftCentral_Theme_Commands',
'posts' => 'UpdraftCentral_Posts_Commands'
));
// If nothing was sent, then there is no incoming message, so no need to set up a listener (or CORS request, etc.). This avoids a DB SELECT query on the option below in the case where it didn't get autoloaded, which is the case when there are no keys.
if (!empty($_SERVER['REQUEST_METHOD']) && ('GET' == $_SERVER['REQUEST_METHOD'] || 'POST' == $_SERVER['REQUEST_METHOD']) && (empty($_REQUEST['action']) || 'updraft_central' !== $_REQUEST['action']) && empty($_REQUEST['udcentral_action']) && empty($_REQUEST['udrpc_message'])) return;
// Remote control keys
// These are different from the remote send keys, which are set up in the Migrator add-on
$our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys');
if (is_array($our_keys) && !empty($our_keys)) {
$remote_control = new UpdraftPlus_UpdraftCentral_Listener($our_keys, $command_classes);
}
}
public function wp_ajax_updraftcentral_receivepublickey() {
// The actual nonce check is done in the method below
if (empty($_GET['_wpnonce']) || empty($_GET['public_key']) || !isset($_GET['updraft_key_index'])) die;
$result = $this->receive_public_key();
if (!is_array($result) || empty($result['responsetype'])) die;
echo '<html><head><title>UpdraftCentral</title></head><body><h1>'.__('UpdraftCentral Connection', 'updraftplus').'</h1><h2>'.htmlspecialchars(network_site_url()).'</h2><p>';
if ('ok' == $result['responsetype']) {
echo __('An UpdraftCentral connection has been made successfully.', 'updraftplus');
} else {
echo '<strong>'.__('A new UpdraftCentral connection has not been made.', 'updraftplus').'</strong><br>';
switch ($result['code']) {
case 'unknown_key':
echo __('The key referred to was unknown.', 'updraftplus');
break;
case 'not_logged_in':
echo __('You are not logged into this WordPress site in your web browser.', 'updraftplus').' '.__('You must visit this URL in the same browser and login session as you created the key in.', 'updraftplus');
break;
case 'nonce_failure':
echo 'Security check. ';
_e('You must visit this link in the same browser and login session as you created the key in.', 'updraftplus');
break;
case 'already_have':
echo __('This connection appears to already have been made.', 'updraftplus');
break;
default:
echo htmlspecialchars(print_r($result, true));
break;
}
}
echo '</p><p><a href="'.UpdraftPlus::get_current_clean_url().'" onclick="window.close();">'.__('Close...', 'updraftplus').'</a></p>';
die;
}
/**
* Checks _wpnonce, and if successful, saves the public key found in $_GET
*
* @return Array - with keys responsetype (can be 'error' or 'ok') and code, indicating whether the parse was successful
*/
private function receive_public_key() {
if (!is_user_logged_in()) {
return array('responsetype' => 'error', 'code' => 'not_logged_in');
}
if (!wp_verify_nonce($_GET['_wpnonce'], 'updraftcentral_receivepublickey')) return array('responsetype' => 'error', 'code' => 'nonce_failure');
$updraft_key_index = $_GET['updraft_key_index'];
$our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys');
if (!is_array($our_keys)) $our_keys = array();
if (!isset($our_keys[$updraft_key_index])) {
return array('responsetype' => 'error', 'code' => 'unknown_key');
}
if (!empty($our_keys[$updraft_key_index]['publickey_remote'])) {
return array('responsetype' => 'error', 'code' => 'already_have');
}
$our_keys[$updraft_key_index]['publickey_remote'] = base64_decode($_GET['public_key']);
UpdraftPlus_Options::update_updraft_option('updraft_central_localkeys', $our_keys, true, 'no');
return array('responsetype' => 'ok', 'code' => 'ok');
}
/**
* Action parameters, from udrpc: $message, $level, $this->key_name_indicator, $this->debug, $this
*
* @param string $message The log message
* @param string $level Log level
* @param string $key_name_indicator This indicates the key name
*/
public function udrpc_log($message, $level, $key_name_indicator) {
$udrpc_log = get_site_option('updraftcentral_client_log');
if (!is_array($udrpc_log)) $udrpc_log = array();
$new_item = array(
'time' => time(),
'level' => $level,
'message' => $message,
'key_name_indicator' => $key_name_indicator
);
if (!empty($_SERVER['REMOTE_ADDR'])) {
$new_item['remote_ip'] = $_SERVER['REMOTE_ADDR'];
}
if (!empty($_SERVER['HTTP_USER_AGENT'])) {
$new_item['http_user_agent'] = $_SERVER['HTTP_USER_AGENT'];
}
if (!empty($_SERVER['HTTP_X_SECONDARY_USER_AGENT'])) {
$new_item['http_secondary_user_agent'] = $_SERVER['HTTP_X_SECONDARY_USER_AGENT'];
}
$udrpc_log[] = $new_item;
if (count($udrpc_log) > 50) array_shift($udrpc_log);
update_site_option('updraftcentral_client_log', $udrpc_log);
}
/**
* Delete UpdraftCentral Key
*
* @param array $key_id key_id of UpdraftCentral
* @return array which contains deleted flag and key table. If error, Returns array which contains fatal_error flag and fatal_error_message
*/
public function delete_key($key_id) {
$our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys');
if (!is_array($our_keys)) $our_keys = array();
if (isset($our_keys[$key_id])) {
unset($our_keys[$key_id]);
UpdraftPlus_Options::update_updraft_option('updraft_central_localkeys', $our_keys);
}
return array('deleted' => 1, 'keys_table' => $this->get_keys_table());
}
/**
* Get UpdraftCentral Log
*
* @param array $params which have action, subaction and nonce
* @return array which contains log_contents. If error, Returns array which contains fatal_error flag and fatal_error_message
*/
public function get_log($params) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
$udrpc_log = get_site_option('updraftcentral_client_log');
if (!is_array($udrpc_log)) $udrpc_log = array();
$log_contents = '';
// Events are appended to the array in the order they happen. So, reversing the order gets them into most-recent-first order.
rsort($udrpc_log);
if (empty($udrpc_log)) {
$log_contents = '<em>'.__('(Nothing yet logged)', 'updraftplus').'</em>';
}
foreach ($udrpc_log as $m) {
// Skip invalid data
if (!isset($m['time'])) continue;
$time = gmdate('Y-m-d H:i:s O', $m['time']);
// $level is not used yet. We could put the message in different colours for different levels, if/when it becomes used.
$key_name_indicator = empty($m['key_name_indicator']) ? '' : $m['key_name_indicator'];
$log_contents .= '<span title="'.esc_attr(print_r($m, true)).'">'."$time ";
if (!empty($m['remote_ip'])) $log_contents .= '['.htmlspecialchars($m['remote_ip']).'] ';
$log_contents .= "[".htmlspecialchars($key_name_indicator)."] ".htmlspecialchars($m['message'])."</span>\n";
}
return array('log_contents' => $log_contents);
}
public function create_key($params) {
// Use the site URL - this means that if the site URL changes, communication ends; which is the case anyway
$user = wp_get_current_user();
$where_send = empty($params['where_send']) ? '' : (string) $params['where_send'];
if ('__updraftpluscom' != $where_send) {
$purl = parse_url($where_send);
if (empty($purl) || !array($purl) || empty($purl['scheme']) || empty($purl['host'])) return array('error' => __('An invalid URL was entered', 'updraftplus'));
}
// ENT_HTML5 exists only on PHP 5.4+
// @codingStandardsIgnoreLine
$flags = defined('ENT_HTML5') ? ENT_QUOTES | ENT_HTML5 : ENT_QUOTES;
$extra_info = array(
'user_id' => $user->ID,
'user_login' => $user->user_login,
'ms_id' => get_current_blog_id(),
'site_title' => html_entity_decode(get_bloginfo('name'), $flags),
);
if ($where_send) {
$extra_info['mothership'] = $where_send;
if (!empty($params['mothership_firewalled'])) {
$extra_info['mothership_firewalled'] = true;
}
}
if (!empty($params['key_description'])) {
$extra_info['name'] = (string) $params['key_description'];
}
$key_size = (empty($params['key_size']) || !is_numeric($params['key_size']) || $params['key_size'] < 512) ? 2048 : (int) $params['key_size'];
$extra_info['key_size'] = $key_size;
$created = $this->create_remote_control_key(false, $extra_info, $where_send);
if (is_array($created)) {
$created['keys_table'] = $this->get_keys_table();
$created['keys_guide'] = '<h2 class="updraftcentral_wizard_success">'. __('UpdraftCentral key created successfully') .'</h2>';
if ('__updraftpluscom' != $where_send) {
$created['keys_guide'] .= '<div class="updraftcentral_wizard_success"><p>'.sprintf(__('You now need to copy the key below and enter it at your %s.', 'updraftplus'), '<a href="'.$where_send.'" target="_blank">UpdraftCentral dashboard</a>').'</p><p>'.__('At your UpdraftCentral dashboard you should press the "Add Site" button then paste the key in the input box.', 'updraftplus').'</p><p>'.sprintf(__('Detailed instructions for this can be found at %s', 'updraftplus'), '<a target="_blank" href="https://updraftplus.com/updraftcentral-how-to-add-a-site/">UpdraftPlus.com</a>').'</p></div>';
} else {
$created['keys_guide'] .= '<div class="updraftcentral_wizard_success"><p>'. sprintf(__('You can now control this site via your UpdraftCentral dashboard at %s.', 'updraftplus'), '<a target="_blank" href="https://updraftplus.com/my-account/updraftcentral-remote-control/">UpdraftPlus.com</a>').'</p></div>';
}
}
return $created;
}
private function indicator_name_from_index($index) {
return $index.'.central.updraftplus.com';
}
private function create_remote_control_key($index = false, $extra_info = array(), $post_it = false) {
global $updraftplus;
$our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys');
if (!is_array($our_keys)) $our_keys = array();
if (false === $index) {
if (empty($our_keys)) {
$index = 0;
} else {
$index = max(array_keys($our_keys))+1;
}
}
$name_hash = $index;
if (isset($our_keys[$name_hash])) {
unset($our_keys[$name_hash]);
}
$indicator_name = $this->indicator_name_from_index($name_hash);
$ud_rpc = $updraftplus->get_udrpc($indicator_name);
$send_to_updraftpluscom = false;
if ('__updraftpluscom' == $post_it) {
$send_to_updraftpluscom = true;
$post_it = defined('UPDRAFTPLUS_OVERRIDE_UDCOM_DESTINATION') ? UPDRAFTPLUS_OVERRIDE_UDCOM_DESTINATION : 'https://updraftplus.com/?updraftcentral_action=receive_key';
$post_it_description = 'UpdraftPlus.Com';
} else {
$post_it_description = $post_it;
}
// Normally, key generation takes seconds, even on a slow machine. However, some Windows machines appear to have a setup in which it takes a minute or more. And then, if you're on a double-localhost setup on slow hardware - even worse. It doesn't hurt to just raise the maximum execution time.
@set_time_limit(UPDRAFTPLUS_SET_TIME_LIMIT);
$key_size = (empty($extra_info['key_size']) || !is_numeric($extra_info['key_size']) || $extra_info['key_size'] < 512) ? 2048 : (int) $extra_info['key_size'];
if (is_object($ud_rpc) && $ud_rpc->generate_new_keypair($key_size)) {
if ($post_it && empty($extra_info['mothership_firewalled'])) {
$p_url = parse_url($post_it);
if (is_array($p_url) && !empty($p_url['user'])) {
$http_username = $p_url['user'];
$http_password = empty($p_url['pass']) ? '' : $p_url['pass'];
$post_it = $p_url['scheme'].'://'.$p_url['host'];
if (!empty($p_url['port'])) $post_it .= ':'.$p_url['port'];
$post_it .= $p_url['path'];
if (!empty($p_url['query'])) $post_it .= '?'.$p_url['query'];
}
$post_options = array(
'timeout' => 90,
'body' => array(
'updraftcentral_action' => 'receive_key',
'key' => $ud_rpc->get_key_remote()
)
);
if (!empty($http_username)) {
$post_options['headers'] = array(
'Authorization' => 'Basic '.base64_encode($http_username.':'.$http_password)
);
}
// This option allows the key to be sent to the other side via a known-secure channel (e.g. http over SSL), rather than potentially allowing it to travel over an unencrypted channel (e.g. http back to the user's browser). As such, if specified, it is compulsory for it to work.
$updraftplus->register_wp_http_option_hooks();
$sent_key = wp_remote_post(
$post_it,
$post_options
);
$updraftplus->register_wp_http_option_hooks(false);
if (is_wp_error($sent_key) || empty($sent_key)) {
$err_msg = sprintf(__('A key was created, but the attempt to register it with %s was unsuccessful - please try again later.', 'updraftplus'), (string) $post_it_description);
if (is_wp_error($sent_key)) $err_msg .= ' '.$sent_key->get_error_message().' ('.$sent_key->get_error_code().')';
return array(
'r' => $err_msg
);
}
$response = json_decode(wp_remote_retrieve_body($sent_key), true);
if (!is_array($response) || !isset($response['key_id']) || !isset($response['key_public'])) {
return array(
'r' => sprintf(__('A key was created, but the attempt to register it with %s was unsuccessful - please try again later.', 'updraftplus'), (string) $post_it_description),
'raw' => wp_remote_retrieve_body($sent_key)
);
}
$key_hash = hash('sha256', $ud_rpc->get_key_remote());
$local_bundle = $ud_rpc->get_portable_bundle('base64_with_count', $extra_info, array('key' => array('key_hash' => $key_hash, 'key_id' => $response['key_id'])));
} elseif ($post_it) {
// Don't send; instead, include in the bundle info that the mothership is firewalled; this will then tell the mothership to try the reverse connection instead
if (is_array($extra_info)) {
$extra_info['mothership_firewalled_callback_url'] = wp_nonce_url(admin_url('admin-ajax.php'), 'updraftcentral_receivepublickey');
$extra_info['updraft_key_index'] = $index;
}
$local_bundle = $ud_rpc->get_portable_bundle('base64_with_count', $extra_info, array('key' => $ud_rpc->get_key_remote()));
}
if (isset($extra_info['name'])) {
$name = (string) $extra_info['name'];
unset($extra_info['name']);
} else {
$name = 'UpdraftCentral Remote Control';
}
$our_keys[$name_hash] = array(
'name' => $name,
'key' => $ud_rpc->get_key_local(),
'extra_info' => $extra_info,
'created' => time(),
);
// Store the other side's public key
if (!empty($response) && is_array($response) && !empty($response['key_public'])) {
$our_keys[$name_hash]['publickey_remote'] = $response['key_public'];
}
UpdraftPlus_Options::update_updraft_option('updraft_central_localkeys', $our_keys, true, 'no');
return array(
'bundle' => $local_bundle,
'r' => __('Key created successfully.', 'updraftplus').' '.__('You must copy and paste this key now - it cannot be shown again.', 'updraftplus'),
// 'selector' => $this->get_remotesites_selector(array()),
// 'ourkeys' => $this->list_our_keys($our_keys),
);
}
return false;
}
public function get_keys_table() {
$ret = '';
$our_keys = UpdraftPlus_Options::get_updraft_option('updraft_central_localkeys');
if (!is_array($our_keys)) $our_keys = array();
if (empty($our_keys)) {
$ret .= '<tr><td colspan="2"><em>'.__('There are no UpdraftCentral dashboards that can currently control this site.', 'updraftplus').'</em></td></tr>';
}
foreach ($our_keys as $i => $key) {
if (empty($key['extra_info'])) continue;
$user_id = $key['extra_info']['user_id'];
if (!empty($key['extra_info']['mothership'])) {
$mothership_url = $key['extra_info']['mothership'];
if ('__updraftpluscom' == $mothership_url) {
$reconstructed_url = 'https://updraftplus.com';
} else {
$purl = parse_url($mothership_url);
$path = empty($purl['path']) ? '' : $purl['path'];
$reconstructed_url = $purl['scheme'].'://'.$purl['host'].(!empty($purl['port']) ? ':'.$purl['port'] : '').$path;
}
} else {
$reconstructed_url = __('Unknown', 'updraftplus');
}
$name = $key['name'];
$user = get_user_by('id', $user_id);
$user_display = is_a($user, 'WP_User') ? $user->user_login.' ('.$user->user_email.')' : __('Unknown', 'updraftplus');
$ret .= '<tr class="updraft_debugrow"><td style="vertical-align:top;">'.htmlspecialchars($name).' ('.htmlspecialchars($i).')</td><td>'.__("Access this site as user:", 'updraftplus')." ".htmlspecialchars($user_display)."<br>".__('Public key was sent to:', 'updraftplus').' '.htmlspecialchars($reconstructed_url).'<br>';
if (!empty($key['created'])) {
$ret .= __('Created:', 'updraftplus').' '.date_i18n(get_option('date_format').' '.get_option('time_format'), $key['created']).'.';
if (!empty($key['extra_info']['key_size'])) {
$ret .= ' '.sprintf(__('Key size: %d bits', 'updraftplus'), $key['extra_info']['key_size']).'.';
}
$ret .= '<br>';
}
$ret .= '<a href="'.UpdraftPlus::get_current_clean_url().'" data-key_id="'.esc_attr($i).'" class="updraftcentral_key_delete">'.__('Delete...', 'updraftplus').'</a></td></tr>';
}
ob_start();
?>
<div id="updraftcentral_keys_content" style="margin: 10px 0;">
<?php if (!empty($our_keys)) { ?>
<a href="<?php echo UpdraftPlus::get_current_clean_url(); ?>" class="updraftcentral_keys_show hidden-in-updraftcentral"><?php printf(__('Manage existing keys (%d)...', 'updraftplus'), count($our_keys)); ?></a>
<?php } ?>
<table id="updraftcentral_keys_table">
<thead>
<tr>
<th style="text-align:left;"><?php _e('Key description', 'updraftplus'); ?></th>
<th style="text-align:left;"><?php _e('Details', 'updraftplus'); ?></th>
</tr>
</thead>
<tbody>
<?php
echo $ret;
?>
</tbody>
</table>
</div>
<?php
return ob_get_clean();
}
/**
* Return HTML markup for the 'create key' section
*
* @return String - the HTML
*/
private function create_key_markup() {
ob_start();
?>
<div class="create_key_container">
<h4 class="updraftcentral_wizard_stage1"> <?php _e('Connect this site to an UpdraftCentral dashboard found at...', 'updraftplus'); ?></h4>
<table style="width: 100%; table-layout:fixed;">
<thead></thead>
<tbody>
<tr class="updraftcentral_wizard_stage1">
<td>
<div class="updraftcentral_wizard_mothership updraftcentral_wizard_option">
<label class="button-primary">
<input checked="checked" type="radio" name="updraftcentral_mothership" id="updraftcentral_mothership_updraftpluscom" style="display: none;">
<?php _e('UpdraftPlus.Com', 'updraftplus');?>
</label><br>
<div><?php printf(__('i.e. if you have %s there', 'updraftplus'), '<a target="_blank" href="https://updraftplus.com/my-account/">'.__('an account', 'updraftplus').'</a>'); ?></div>
</div>
<div class="updraftcentral_wizard_self_hosted_stage1 updraftcentral_wizard_option">
<label class="button-primary">
<input type="radio" name="updraftcentral_mothership" id="updraftcentral_mothership_other" style="display: none;">
<?php _e('Self-hosted dashboard', 'updraftplus');?>
</label><br>
<div><?php printf(__('A website where you have installed %s', 'updraftplus'), '<a target="_blank" href="https://wordpress.org/plugins/updraftcentral/">UpdraftCentral</a>'); ?></div>
</div>
<div class="updraftcentral_wizard_self_hosted_stage2" style="float:left; clear:left;display:none;">
<p style="font-size: 13px;"><?php echo __('Enter the URL where your self-hosted install of UpdraftCentral is located:', 'updraftplus');?></p>
<p style="font-size: 13px;" id="updraftcentral_wizard_stage1_error"></p>
<input disabled="disabled" id="updraftcentral_keycreate_mothership" type="text" size="40" placeholder="<?php _e('URL for the site of your UpdraftCentral dashboard', 'updraftplus'); ?>" value="">
<button type="button" class="button button-primary" id="updraftcentral_stage2_go"><?php _e('Next', 'updraftplus'); ?></button>
</div>
</td>
</tr>
<tr class="updraft_debugrow updraftcentral_wizard_stage2" style="display: none;">
<h4 class="updraftcentral_wizard_stage2" style="display: none;"><?php _e('UpdraftCentral dashboard connection details', 'updraftplus'); ?></h4>
<td class="updraftcentral_keycreate_description">
<?php _e('Description', 'updraftplus'); ?>:
<input id="updraftcentral_keycreate_description" type="text" size="20" placeholder="<?php _e('Enter any description', 'updraftplus'); ?>" value="" >
</td>
</tr>
<tr class="updraft_debugrow updraftcentral_wizard_stage2" style="display: none;">
<td>
<?php _e('Encryption key size:', 'updraftplus'); ?>
<select style="" id="updraftcentral_keycreate_keysize">
<option value="512"><?php echo sprintf(__('%s bits', 'updraftplus').' - '.__('easy to break, fastest', 'updraftplus'), '512'); ?></option>
<option value="1024"><?php echo sprintf(__('%s bits', 'updraftplus').' - '.__('faster (possibility for slow PHP installs)', 'updraftplus'), '1024'); ?></option>
<option value="2048" selected="selected"><?php echo sprintf(__('%s bytes', 'updraftplus').' - '.__('recommended', 'updraftplus'), '2048'); ?></option>
<option value="4096"><?php echo sprintf(__('%s bits', 'updraftplus').' - '.__('slower, strongest', 'updraftplus'), '4096'); ?></option>
</select>
<br>
<div id="updraftcentral_keycreate_mothership_firewalled_container">
<label>
<input id="updraftcentral_keycreate_mothership_firewalled" type="checkbox">
<?php _e('Use the alternative method for making a connection with the dashboard.', 'updraftplus'); ?>
<a href="<?php echo UpdraftPlus::get_current_clean_url(); ?>" id="updraftcentral_keycreate_altmethod_moreinfo_get"><?php _e('More information...', 'updraftplus'); ?></a>
<p id="updraftcentral_keycreate_altmethod_moreinfo" style="display:none; border: 1px dotted; padding: 3px; margin: 2px 10px 2px 24px;">
<em><?php _e('This is useful if the dashboard webserver cannot be contacted with incoming traffic by this website (for example, this is the case if this website is hosted on the public Internet, but the UpdraftCentral dashboard is on localhost, or on an Intranet, or if this website has an outgoing firewall), or if the dashboard website does not have a SSL certificate.');?></em>
</p>
</label>
</div>
</td>
</tr>
<tr class="updraft_debugrow updraftcentral_wizard_stage2" style="display: none;">
<td>
<button style="margin-top: 5px;" type="button" class="button button-primary" id="updraftcentral_keycreate_go"><?php _e('Create', 'updraftplus'); ?></button>
</td>
</tr>
<tr class="updraft_debugrow updraftcentral_wizard_stage2" style="display: none;">
<td>
<a id="updraftcentral_stage1_go"><?php _e('Back...', 'updraftplus'); ?></a>
</td>
</tr>
</tbody>
</table>
</div>
<?php
return ob_get_clean();
}
private function create_log_markup() {
ob_start();
?>
<div id="updraftcentral_view_log_container" style="margin: 10px 0;">
<a href="<?php echo UpdraftPlus::get_current_clean_url(); ?>" id="updraftcentral_view_log"><?php _e('View recent UpdraftCentral log events', 'updraftplus'); ?>...</a><br>
<pre id="updraftcentral_view_log_contents" style="min-height: 110px; padding: 0 4px;">
</pre>
</div>
<?php
return ob_get_clean();
}
public function debugtools_dashboard() {
?>
<div class="advanced_tools updraft_central">
<h3><?php _e('UpdraftCentral (Remote Control)', 'updraftplus'); ?></h3>
<p>
<?php echo __('UpdraftCentral enables control of your WordPress sites (including management of backups and updates) from a central dashboard.', 'updraftplus').' <a target="_blank" href="https://updraftcentral.com">'.__('Read more about it here.', 'updraftplus').'</a>'; ?>
</p>
<div style="min-height: 310px;" id="updraftcentral_keys">
<?php echo $this->create_key_markup(); ?>
<?php echo $this->get_keys_table(); ?>
<button style="display: none;" type="button" class="button button-primary" id="updraftcentral_wizard_go"><?php _e('Create another key', 'updraftplus'); ?></button>
<?php echo $this->create_log_markup(); ?>
</div>
</div>
<?php
}
}
global $updraftplus_updraftcentral_main;
$updraftplus_updraftcentral_main = new UpdraftPlus_UpdraftCentral_Main();

View File

@@ -0,0 +1,112 @@
<?php
// @codingStandardsIgnoreFile
if (!defined('ABSPATH')) die('No direct access.');
// Extracted from 4.5.2/wordpress/wp-admin/includes/class-wp-upgrader-skins.php; with the bulk_*() methods added since they are not in the base class on all WP versions.
// Needed only on WP < 3.7
/**
* Upgrader Skin for Automatic WordPress Upgrades
*
* Extracted from 4.5.2/wordpress/wp-admin/includes/class-wp-upgrader-skins.php; with the bulk_*() methods added since they are not in the base class on all WP versions.
* Needed only on WP < 3.7
*
* This skin is designed to be used when no output is intended, all output
* is captured and stored for the caller to process and log/email/discard.
*
* @package WordPress
* @subpackage Upgrader
* @since 3.7.0
*/
class Automatic_Upgrader_Skin extends WP_Upgrader_Skin {
protected $messages = array();
/**
* Request filesystem credentials
*
* @param bool $error Check if there is an error: default is false
* @param string $context Context for credentails
* @param bool $allow_relaxed_file_ownership Check if relaxed file ownership is allowed
* @return bool
*/
public function request_filesystem_credentials($error = false, $context = '', $allow_relaxed_file_ownership = false) {
if ($context) {
$this->options['context'] = $context;
}
// TODO: fix up request_filesystem_credentials(), or split it, to allow us to request a no-output version
// This will output a credentials form in event of failure, We don't want that, so just hide with a buffer
ob_start();
$result = parent::request_filesystem_credentials($error, $context, $allow_relaxed_file_ownership);
ob_end_clean();
return $result;
}
/**
* Get update message
*
* @return array reti=urns an array of messages
*/
public function get_upgrade_messages() {
return $this->messages;
}
/**
* Feedback
*
* @param string|array|WP_Error $data THis is the data to be used for the feedback
*/
public function feedback($data) {
if (is_wp_error($data)) {
$string = $data->get_error_message();
} elseif (is_array($data)) {
return;
} else {
$string = $data;
}
if (!empty($this->upgrader->strings[$string]))
$string = $this->upgrader->strings[$string];
if (false !== strpos($string, '%')) {
$args = func_get_args();
$args = array_splice($args, 1);
if (!empty($args))
$string = vsprintf($string, $args);
}
$string = trim($string);
// Only allow basic HTML in the messages, as it'll be used in emails/logs rather than direct browser output.
$string = wp_kses($string, array(
'a' => array(
'href' => true
),
'br' => true,
'em' => true,
'strong' => true,
));
if (empty($string))
return;
$this->messages[] = $string;
}
public function header() {
ob_start();
}
public function footer() {
$output = ob_get_clean();
if (!empty($output))
$this->feedback($output);
}
/**
* @access public
*/
public function bulk_header() {}
public function bulk_footer() {
}
}

View File

@@ -0,0 +1,76 @@
<?php
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
/**
* - A container for all the RPC commands implemented. Commands map exactly onto method names (and hence this class should not implement anything else, beyond the constructor, and private methods)
* - Return format is array('response' => (string - a code), 'data' => (mixed));
*
* RPC commands are not allowed to begin with an underscore. So, any private methods can be prefixed with an underscore.
*/
abstract class UpdraftCentral_Commands {
protected $rc;
protected $ud;
public function __construct($rc) {
$this->rc = $rc;
global $updraftplus;
$this->ud = $updraftplus;
}
final protected function _admin_include() {
$files = func_get_args();
foreach ($files as $file) {
include_once(ABSPATH.'/wp-admin/includes/'.$file);
}
}
final protected function _frontend_include() {
$files = func_get_args();
foreach ($files as $file) {
include_once(ABSPATH.WPINC.'/'.$file);
}
}
final protected function _response($data = null, $code = 'rpcok') {
return array(
'response' => $code,
'data' => $data
);
}
final protected function _generic_error_response($code = 'central_unspecified', $data = null) {
return $this->_response(
array(
'code' => $code,
'data' => $data
),
'rpcerror'
);
}
/**
* Checks whether a backup and a security credentials is required for the given request
*
* @param array $dir The directory location to check
* @return array
*/
final protected function _get_backup_credentials_settings($dir) {
// Do we need to ask the user for filesystem credentials? when installing and/or deleting items in the given directory
$filesystem_method = get_filesystem_method(array(), $dir);
ob_start();
$filesystem_credentials_are_stored = request_filesystem_credentials(site_url());
ob_end_clean();
$request_filesystem_credentials = ('direct' != $filesystem_method && !$filesystem_credentials_are_stored);
// Do we need to execute a backup process before installing/managing items
$automatic_backups = (class_exists('UpdraftPlus_Options') && class_exists('UpdraftPlus_Addon_Autobackup') && UpdraftPlus_Options::get_updraft_option('updraft_autobackup_default', true)) ? true : false;
return array(
'request_filesystem_credentials' => $request_filesystem_credentials,
'automatic_backups' => $automatic_backups
);
}
}

View File

@@ -0,0 +1,268 @@
<?php
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
/**
* This class is the basic glue between the lower-level UpdraftPlus_Remote_Communications (UDRPC) class, and UpdraftPlus. It does not contain actual commands themselves; the class names to use for actual commands are passed in as a parameter to the constructor.
*/
class UpdraftPlus_UpdraftCentral_Listener {
public $udrpc_version;
private $ud = null;
private $receivers = array();
private $extra_info = array();
private $php_events = array();
private $commands = array();
private $current_udrpc = null;
private $command_classes;
public function __construct($keys = array(), $command_classes = array()) {
global $updraftplus;
$this->ud = $updraftplus;
// It seems impossible for this condition to result in a return; but it seems Plesk can do something odd within the control panel that causes a problem - see HS#6276
if (!is_a($this->ud, 'UpdraftPlus')) return;
$this->command_classes = $command_classes;
foreach ($keys as $name_hash => $key) {
// publickey_remote isn't necessarily set yet, depending on the key exchange method
if (!is_array($key) || empty($key['extra_info']) || empty($key['publickey_remote'])) continue;
$indicator = $name_hash.'.central.updraftplus.com';
$ud_rpc = $this->ud->get_udrpc($indicator);
$this->udrpc_version = $ud_rpc->version;
// Only turn this on if you are comfortable with potentially anything appearing in your PHP error log
if (defined('UPDRAFTPLUS_UDRPC_FORCE_DEBUG') && UPDRAFTPLUS_UDRPC_FORCE_DEBUG) $ud_rpc->set_debug(true);
$this->receivers[$indicator] = $ud_rpc;
$this->extra_info[$indicator] = isset($key['extra_info']) ? $key['extra_info'] : null;
$ud_rpc->set_key_local($key['key']);
$ud_rpc->set_key_remote($key['publickey_remote']);
// Create listener (which causes WP actions to be fired when messages are received)
$ud_rpc->activate_replay_protection();
if (!empty($key['extra_info']) && isset($key['extra_info']['mothership'])) {
$mothership = $key['extra_info']['mothership'];
unset($url);
if ('__updraftpluscom' == $mothership) {
$url = 'https://updraftplus.com';
} elseif (false != ($parsed = parse_url($key['extra_info']['mothership'])) && is_array($parsed)) {
$url = $parsed['scheme'].'://'.$parsed['host'];
}
if (!empty($url)) $ud_rpc->set_allow_cors_from(array($url));
}
$ud_rpc->create_listener();
}
// If we ever need to expand beyond a single GET action, this can/should be generalised and put into the commands class
if (!empty($_GET['udcentral_action']) && 'login' == $_GET['udcentral_action']) {
// auth_redirect() does not return, according to the documentation; but the code shows that it can
// auth_redirect();
if (!empty($_GET['login_id']) && is_numeric($_GET['login_id']) && !empty($_GET['login_key'])) {
$login_user = get_user_by('id', $_GET['login_id']);
include_once(ABSPATH.WPINC.'/version.php');
if (is_a($login_user, 'WP_User') || (version_compare($wp_version, '3.5', '<') && !empty($login_user->ID))) {
// Allow site implementers to disable this functionality
$allow_autologin = apply_filters('updraftcentral_allow_autologin', true, $login_user);
if ($allow_autologin) {
$login_key = get_user_meta($login_user->ID, 'updraftcentral_login_key', true);
if (is_array($login_key) && !empty($login_key['created']) && $login_key['created'] > time() - 60 && !empty($login_key['key']) && $login_key['key'] == $_GET['login_key']) {
$autologin = empty($login_key['redirect_url']) ? network_admin_url() : $login_key['redirect_url'];
}
}
}
}
if (!empty($autologin)) {
// Allow use once only
delete_user_meta($login_user->ID, 'updraftcentral_login_key');
$this->autologin_user($login_user, $autologin);
}
}
add_filter('udrpc_action', array($this, 'udrpc_action'), 10, 5);
add_filter('updraftcentral_get_command_info', array($this, 'updraftcentral_get_command_info'), 10, 2);
}
/**
* Retrieves command class information and includes class file if class
* is currently not available.
*
* @param mixed $response The default response to return if the submitted command does not exists
* @param string $command The command to parse and check
* @return array Contains the following command information "command_php_class", "class_prefix" and "command"
*/
public function updraftcentral_get_command_info($response, $command) {
if (!preg_match('/^([a-z0-9]+)\.(.*)$/', $command, $matches)) return $response;
$class_prefix = $matches[1];
$command = $matches[2];
// We only handle some commands - the others, we let something else deal with
if (!isset($this->command_classes[$class_prefix])) return $response;
$command_php_class = $this->command_classes[$class_prefix];
$command_base_class_at = apply_filters('updraftcentral_command_base_class_at', UPDRAFTCENTRAL_CLIENT_DIR.'/commands.php');
if (!class_exists('UpdraftCentral_Commands')) include_once($command_base_class_at);
// Second parameter has been passed since
do_action('updraftcentral_command_class_wanted', $command_php_class);
if (!class_exists($command_php_class)) {
if (file_exists(UPDRAFTCENTRAL_CLIENT_DIR.'/modules/'.$class_prefix.'.php')) {
include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/modules/'.$class_prefix.'.php');
}
}
return array(
'command_php_class' => $command_php_class,
'class_prefix' => $class_prefix,
'command' => $command
);
}
/**
* Do verification before calling this method
*
* @param WP_User|Object $user user object for autologin
* @param boolean $redirect_url Redirect URL
*/
private function autologin_user($user, $redirect_url = false) {
if (!is_user_logged_in()) {
// $user = get_user_by('id', $user_id);
// Don't check that it's a WP_User - that's WP 3.5+ only
if (!is_object($user) || empty($user->ID)) return;
wp_set_current_user($user->ID, $user->user_login);
wp_set_auth_cookie($user->ID);
do_action('wp_login', $user->user_login, $user);
}
if ($redirect_url) {
wp_safe_redirect($redirect_url);
exit;
}
}
/**
* WP filter udrpc_action
*
* @param Array $response - the unfiltered response that will be returned
* @param String $command - the command being called
* @param Array $data - the parameters to the command
* @param String $key_name_indicator - the UC key that is in use
* @param Object $ud_rpc - the UDRP object
*
* @return Array - filtered response
*/
public function udrpc_action($response, $command, $data, $key_name_indicator, $ud_rpc) {
if (empty($this->receivers[$key_name_indicator])) return $response;
// This can be used to detect an UpdraftCentral context
if (!defined('UPDRAFTCENTRAL_COMMAND')) define('UPDRAFTCENTRAL_COMMAND', $command);
$this->initialise_listener_error_handling();
$command_info = apply_filters('updraftcentral_get_command_info', false, $command);
if (!$command_info) return $response;
$class_prefix = $command_info['class_prefix'];
$command = $command_info['command'];
$command_php_class = $command_info['command_php_class'];
if (empty($this->commands[$class_prefix])) {
if (class_exists($command_php_class)) {
$this->commands[$class_prefix] = new $command_php_class($this);
}
}
$command_class = isset($this->commands[$class_prefix]) ? $this->commands[$class_prefix] : new stdClass;
if ('_' == substr($command, 0, 1) || !is_a($command_class, $command_php_class) || (!method_exists($command_class, $command) && !method_exists($command_class, '__call'))) {
if (defined('UPDRAFTPLUS_UDRPC_FORCE_DEBUG') && UPDRAFTPLUS_UDRPC_FORCE_DEBUG) error_log("Unknown RPC command received: ".$command);
return $this->return_rpc_message(array('response' => 'rpcerror', 'data' => array('code' => 'unknown_rpc_command', 'data' => array('prefix' => $class_prefix, 'command' => $command, 'class' => $command_php_class))));
}
$extra_info = isset($this->extra_info[$key_name_indicator]) ? $this->extra_info[$key_name_indicator] : null;
// Make it so that current_user_can() checks can apply + work
if (!empty($extra_info['user_id'])) wp_set_current_user($extra_info['user_id']);
$this->current_udrpc = $ud_rpc;
do_action('updraftcentral_listener_pre_udrpc_action', $command, $command_class, $data, $extra_info);
// Allow the command class to perform any boiler-plate actions.
if (is_callable(array($command_class, '_pre_action'))) call_user_func(array($command_class, '_pre_action'), $command, $data, $extra_info);
// Despatch
$msg = apply_filters('updraftcentral_listener_udrpc_action', call_user_func(array($command_class, $command), $data, $extra_info), $command_class, $class_prefix, $command, $data, $extra_info);
if (is_callable(array($command_class, '_post_action'))) call_user_func(array($command_class, '_post_action'), $command, $data, $extra_info);
do_action('updraftcentral_listener_post_udrpc_action', $command, $command_class, $data, $extra_info);
return $this->return_rpc_message($msg);
}
public function get_current_udrpc() {
return $this->current_udrpc;
}
private function initialise_listener_error_handling() {
$this->ud->error_reporting_stop_when_logged = true;
set_error_handler(array($this->ud, 'php_error'), E_ALL & ~E_STRICT);
$this->php_events = array();
@ob_start();
add_filter('updraftplus_logline', array($this, 'updraftplus_logline'), 10, 4);
if (!UpdraftPlus_Options::get_updraft_option('updraft_debug_mode')) return;
}
public function updraftplus_logline($line, $nonce, $level, $uniq_id) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
if ('notice' === $level && 'php_event' === $uniq_id) {
$this->php_events[] = $line;
}
return $line;
}
public function return_rpc_message($msg) {
if (is_array($msg) && isset($msg['response']) && 'error' == $msg['response']) {
$this->ud->log('Unexpected response code in remote communications: '.serialize($msg));
}
$caught_output = @ob_get_contents();
@ob_end_clean();
// If turning output-catching off, turn this on instead:
// $caught_output = ''; @ob_end_flush();
// If there's higher-level output buffering going on, then get rid of that
if (ob_get_level()) ob_end_clean();
if ($caught_output) {
if (!isset($msg['data'])) $msg['data'] = null;
$msg['data'] = array('caught_output' => $caught_output, 'previous_data' => $msg['data']);
$already_rearranged_data = true;
}
if (!empty($this->php_events)) {
if (!isset($msg['data'])) $msg['data'] = null;
if (!empty($already_rearranged_data)) {
$msg['data']['php_events'] = array();
} else {
$msg['data'] = array('php_events' => array(), 'previous_data' => $msg['data']);
}
foreach ($this->php_events as $logline) {
$msg['data']['php_events'][] = $logline;
}
}
restore_error_handler();
return $msg;
}
}

View File

@@ -0,0 +1,440 @@
<?php
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
/**
* Handles Analytics Commands
*
* @method array ga_checker()
* @method array get_access_token()
* @method array set_authorization_code()
*/
class UpdraftCentral_Analytics_Commands extends UpdraftCentral_Commands {
private $scope = 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/analytics.readonly';
private $endpoint = 'https://accounts.google.com/o/oauth2/auth';
private $token_info_endpoint = 'https://www.googleapis.com/oauth2/v1/tokeninfo';
private $access_key = 'updraftcentral_auth_server_access';
private $auth_endpoint;
private $client_id;
private $view_key = 'updraftcentral_analytics_views';
private $tracking_id_key = 'updraftcentral_analytics_tracking_id';
private $expiration;
/**
* Constructor
*/
public function __construct() {
$this->auth_endpoint = defined('UPDRAFTPLUS_GOOGLECLOUD_CALLBACK_URL') ? UPDRAFTPLUS_GOOGLECLOUD_CALLBACK_URL : 'https://auth.updraftplus.com/auth/googleanalytics';
$this->client_id = defined('UPDRAFTPLUS_GOOGLECLOUD_CLIENT_ID') ? UPDRAFTPLUS_GOOGLECLOUD_CLIENT_ID : '306245874349-6s896c3tjpra26ns3dpplhqcl6rv6qlb.apps.googleusercontent.com';
// Set transient expiration - default for 24 hours
$this->expiration = 86400;
}
/**
* Checks whether Google Analytics (GA) is installed or setup
*
* N.B. This check assumes GA is installed either using "wp_head" or "wp_footer" (e.g. attached
* to the <head/> or somewhere before </body>). It does not recursively check all the pages
* of the website to find if GA is installed on each or one of those pages, but only on the main/root page.
*
* @return array $result An array containing "ga_installed" property which returns "true" if GA (Google Analytics) is installed, "false" otherwise.
*/
public function ga_checker() {
try {
// Retrieves the tracking code/id if available
$tracking_id = $this->get_tracking_id();
$installed = true;
// If tracking code/id is currently not available then we
// parse the needed information from the buffered content through
// the "wp_head" and "wp_footer" hooks.
if (false === $tracking_id) {
$info = $this->extract_tracking_id();
$installed = $info['installed'];
$tracking_id = $info['tracking_id'];
}
// Get access token to be use to generate the report.
$access_token = $this->_get_token();
if (empty($access_token)) {
// If we don't get a valid access token then that would mean
// the access has been revoked by the user or UpdraftCentral was not authorized yet
// to access the user's analytics data, thus, we're clearing
// any previously stored user access so we're doing some housekeeping here.
$this->clear_user_access();
}
// Wrap and combined information for the requesting
// client's consumption
$result = array(
'ga_installed' => $installed,
'tracking_id' => $tracking_id,
'client_id' => $this->client_id,
'redirect_uri' => $this->auth_endpoint,
'scope' => $this->scope,
'access_token' => $access_token,
'endpoint' => $this->endpoint
);
} catch (Exception $e) {
$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
}
return $this->_response($result);
}
/**
* Extracts Google Tracking ID from contents rendered through the "wp_head" and "wp_footer" action hooks
*
* @internal
* @return array $result An array containing the result of the extraction.
*/
private function extract_tracking_id() {
// Define result array
$result = array();
// Retrieve header content
ob_start();
do_action('wp_head');
$header_content = ob_get_clean();
// Extract analytics information if available.
$output = $this->parse_content($header_content);
$result['installed'] = $output['installed'];
$result['tracking_id'] = $output['tracking_id'];
// If it was not found, then now try the footer
if (empty($tracking_id)) {
// Retrieve footer content
ob_start();
do_action('wp_footer');
$footer_content = ob_get_clean();
$output = $this->parse_content($footer_content);
$result['installed'] = $output['installed'];
$result['tracking_id'] = $output['tracking_id'];
}
if (!empty($tracking_id)) {
set_transient($this->tracking_id_key, $tracking_id, $this->expiration);
}
return $result;
}
/**
* Gets access token
*
* Validates whether the system currently have a valid token to use when connecting to Google Analytics API.
* If not, then it will send a token request based on the authorization code we stored during the
* authorization phase. Otherwise, it will return an empty token.
*
* @return array $result An array containing the Google Analytics API access token.
*/
public function get_access_token() {
try {
// Loads or request a valid token to use
$access_token = $this->_get_token();
if (!empty($access_token)) {
$result = array('access_token' => $access_token);
} else {
$result = array('error' => true, 'message' => 'ga_token_retrieval_failed', 'values' => array());
}
} catch (Exception $e) {
$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
}
return $this->_response($result);
}
/**
* Clears any previously stored user access
*
* @return bool
*/
public function clear_user_access() {
return delete_option($this->access_key);
}
/**
* Saves user is and access token received from the auth server
*
* @param array $query Parameter array containing the user id and access token from the auth server.
* @return array $result An array containing a "success" or "failure" message as a result of the current process.
*/
public function save_user_access($query) {
try {
$token = get_option($this->access_key, false);
$result = array();
if (false === $token) {
$token = array(
'user_id' => base64_decode(urldecode($query['user_id'])),
'access_token' => base64_decode(urldecode($query['access_token']))
);
if (false !== update_option($this->access_key, $token)) {
$result = array('error' => false, 'message' => 'ga_access_saved', 'values' => array());
} else {
$result = array('error' => true, 'message' => 'ga_access_saving_failed', 'values' => array($query['access_token']));
}
}
} catch (Exception $e) {
$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
}
return $this->_response($result);
}
/**
* Saves the tracking code/id manually (user input)
*
* @param array $query Parameter array containing the tracking code/id to save.
* @return array $result An array containing the result of the process.
*/
public function save_tracking_id($query) {
try {
$tracking_id = $query['tracking_id'];
$saved = false;
if (!empty($tracking_id)) {
$saved = set_transient($this->tracking_id_key, $tracking_id, $this->expiration);
}
$result = array('saved' => $saved);
} catch (Exception $e) {
$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
}
return $this->_response($result);
}
/**
* Retrieves any available access token either previously saved info or
* from a new request from the Google Server.
*
* @internal
* @return string $authorization_code
*/
private function _get_token() {
// Retrieves the tracking code/id if available
$tracking_id = $this->get_tracking_id();
$access_token = '';
$token = get_option($this->access_key, false);
if (false !== $token) {
$access_token = isset($token['access_token']) ? $token['access_token'] : '';
$user_id = isset($token['user_id']) ? $token['user_id'] : '';
if ((!empty($access_token) && !$this->_token_valid($access_token)) || (!empty($user_id) && empty($access_token) && !empty($tracking_id))) {
if (!empty($user_id)) {
$args = array(
'headers' => apply_filters('updraftplus_auth_headers', array())
);
$response = wp_remote_get($this->auth_endpoint.'?user_id='.$user_id.'&code=ud_googleanalytics_code', $args);
if (is_wp_error($response)) {
throw new Exception($response->get_error_message());
} else {
if (is_array($response)) {
$body = json_decode($response['body'], true);
$token_response = array();
if (is_array($body) && !isset($body['error'])) {
$token_response = json_decode(base64_decode($body[0]), true);
}
if (is_array($token_response) && isset($token_response['access_token'])) {
$access_token = $token_response['access_token'];
} else {
// If we don't get any valid response then that would mean that the
// permission was already revoked. Thus, we need to re-authorize the
// user before using the analytics feature once again.
$access_token = '';
}
$token['access_token'] = $access_token;
update_option($this->access_key, $token);
}
}
}
}
}
return $access_token;
}
/**
* Verifies whether the access token is still valid for use
*
* @internal
* @param string $token The access token to be check and validated
* @return bool
* @throws Exception If an error has occurred while connecting to the Google Server.
*/
private function _token_valid($token) {
$response = wp_remote_get($this->token_info_endpoint.'?access_token='.$token);
if (is_wp_error($response)) {
throw new Exception($response->get_error_message());
} else {
if (is_array($response)) {
$response = json_decode($response['body'], true);
if (!empty($response)) {
if (!isset($response['error']) && !isset($response['error_description'])) {
return true;
}
}
}
}
return false;
}
/**
* Parses and extracts the google analytics information (NEEDED)
*
* @internal
* @param string $content The content to parse
* @return array An array containing the status of the process along with the tracking code/id
*/
private function parse_content($content) {
$installed = false;
$gtm_installed = false;
$tracking_id = '';
$script_file_found = false;
$tracking_id_found = false;
// Pull google analytics script file(s)
preg_match_all('/<script\b[^>]*>([\s\S]*?)<\/script>/i', $content, $scripts);
for ($i=0; $i < count($scripts[0]); $i++) {
// Check for Google Analytics file
if (stristr($scripts[0][$i], 'ga.js') || stristr($scripts[0][$i], 'analytics.js')) {
$script_file_found = true;
}
// Check for Google Tag Manager file
// N.B. We are not checking for GTM but this check will be useful when
// showing the notice to the user if we haven't found Google Analytics
// directly being installed on the page.
if (stristr($scripts[0][$i], 'gtm.js')) {
$gtm_installed = true;
}
}
// Pull tracking code
preg_match_all('/UA-[0-9]{5,}-[0-9]{1,}/i', $content, $codes);
if (count($codes) > 0) {
if (!empty($codes[0])) {
$tracking_id_found = true;
$tracking_id = $codes[0][0];
}
}
// If we found both the script and the tracking code then it is safe
// to say that Google Analytics (GA) is installed. Thus, we're returning
// "true" as a response.
if ($script_file_found && $tracking_id_found) {
$installed = true;
}
// Return result of process.
return array(
'installed' => $installed,
'gtm_installed' => $gtm_installed,
'tracking_id' => $tracking_id
);
}
/**
* Retrieves the "analytics_tracking_id" transient
*
* @internal
* @return mixed Returns the value of the saved transient. Returns "false" if the transient does not exist.
*/
private function get_tracking_id() {
return get_transient($this->tracking_id_key);
}
/**
* Returns the current tracking id
*
* @return array $result An array containing the Google Tracking ID.
*/
public function get_current_tracking_id() {
try {
// Get current site transient stored for this key
$tracking_id = get_transient($this->tracking_id_key);
// Checks whether we have a valid token
$access_token = $this->_get_token();
if (empty($access_token)) {
$tracking_id = '';
}
if (false === $tracking_id) {
$result = $this->extract_tracking_id();
} else {
$result = array(
'installed' => true,
'tracking_id' => $tracking_id
);
}
} catch (Exception $e) {
$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
}
return $this->_response($result);
}
/**
* Clears user access from database
*
* @return array $result An array containing the "Remove" confirmation whether the action succeeded or not.
*/
public function remove_user_access() {
try {
// Clear user access
$is_cleared = $this->clear_user_access();
if (false !== $is_cleared) {
$result = array('removed' => true);
} else {
$result = array('error' => true, 'message' => 'user_access_remove_failed', 'values' => array());
}
} catch (Exception $e) {
$result = array('error' => true, 'message' => 'generic_response_error', 'values' => array($e->getMessage()));
}
return $this->_response($result);
}
}

View File

@@ -0,0 +1,834 @@
<?php
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
class UpdraftCentral_Comments_Commands extends UpdraftCentral_Commands {
/**
* The _search_comments function searches all available comments based
* on the following query parameters (type, status, search)
*
* Search Parameters/Filters:
* type - comment types can be 'comment', 'trackback' and 'pingback', defaults to 'comment'
* status - comment status can be 'hold' or unapprove, 'approve', 'spam', 'trash'
* search - user generated content or keyword
*
* @param array $query The query to search comments
* @return array
*/
private function _search_comments($query) {
// Basic parameters to the query and should display
// the results in descending order (latest comments) first
// based on their generated IDs
$args = array(
'orderby' => 'ID',
'order' => 'DESC',
'type' => $query['type'],
'status' => $query['status'],
'search' => esc_attr($query['search']),
);
$query = new WP_Comment_Query;
$found_comments = $query->query($args);
$comments = array();
foreach ($found_comments as $comment) {
// We're returning a collection of comment in an array,
// in sync with the originator of the request on the ui side
// so, we're pulling it one by one into the array before
// returning it.
if (!in_array($comment, $comments)) {
array_push($comments, $comment);
}
}
return $comments;
}
/**
* The _calculate_pages function generates and builds the pagination links
* based on the current search parameters/filters. Please see _search_comments
* for the breakdown of these parameters.
*
* @param array $query Query to generate pagination links
* @return array
*/
private function _calculate_pages($query) {
$per_page_options = array(10, 20, 30, 40, 50);
if (!empty($query)) {
if (!empty($query['search'])) {
return array(
'page_count' => 1,
'page_no' => 1
);
}
$pages = array();
$page_query = new WP_Comment_Query;
// Here, we're pulling the comments based on the
// two parameters namely type and status.
//
// The number of results/comments found will then
// be use to compute for the number of pages to be
// displayed as navigation links when browsing all
// comments from the frontend.
$comments = $page_query->query(array(
'type' => $query['type'],
'status' => $query['status']
));
$total_comments = count($comments);
$page_count = ceil($total_comments / $query['per_page']);
if ($page_count > 1) {
for ($i = 0; $i < $page_count; $i++) {
if ($i + 1 == $query['page_no']) {
$paginator_item = array(
'value' => $i+1,
'setting' => 'disabled'
);
} else {
$paginator_item = array(
'value' => $i+1
);
}
array_push($pages, $paginator_item);
}
if ($query['page_no'] >= $page_count) {
$page_next = array(
'value' => $page_count,
'setting' => 'disabled'
);
} else {
$page_next = array(
'value' => $query['page_no'] + 1
);
}
if (1 === $query['page_no']) {
$page_prev = array(
'value' => 1,
'setting' => 'disabled'
);
} else {
$page_prev = array(
'value' => $query['page_no'] - 1
);
}
return array(
'page_no' => $query['page_no'],
'per_page' => $query['per_page'],
'page_count' => $page_count,
'pages' => $pages,
'page_next' => $page_next,
'page_prev' => $page_prev,
'total_results' => $total_comments,
'per_page_options' => $per_page_options
);
} else {
return array(
'page_no' => $query['page_no'],
'per_page' => $query['per_page'],
'page_count' => $page_count,
'total_results' => $total_comments,
'per_page_options' => $per_page_options
);
}
} else {
return array(
'per_page_options' => $per_page_options
);
}
}
/**
* The get_blog_sites function pulls blog sites available for the current WP instance.
* If Multisite is enabled on the server, then sites under the network will be pulled, otherwise, it will return an empty array.
*
* @return array
*/
private function get_blog_sites() {
if (!is_multisite()) return array();
// Initialize array container
$sites = $network_sites = array();
// Check to see if latest get_sites (available on WP version >= 4.6) function is
// available to pull any available sites from the current WP instance. If not, then
// we're going to use the fallback function wp_get_sites (for older version).
if (function_exists('get_sites') && class_exists('WP_Site_Query')) {
$network_sites = get_sites();
} else {
if (function_exists('wp_get_sites')) {
$network_sites = wp_get_sites();
}
}
// We only process if sites array is not empty, otherwise, bypass
// the next block.
if (!empty($network_sites)) {
foreach ($network_sites as $site) {
// Here we're checking if the site type is an array, because
// we're pulling the blog_id property based on the type of
// site returned.
// get_sites returns an array of object, whereas the wp_get_sites
// function returns an array of array.
$blog_id = (is_array($site)) ? $site['blog_id'] : $site->blog_id;
// We're saving the blog_id and blog name as an associative item
// into the sites array, that will be used as "Sites" option in
// the frontend.
$sites[$blog_id] = get_blog_details($blog_id)->blogname;
}
}
return $sites;
}
/**
* The get_wp_option function pulls current blog options
* from the database using either following functions:
* - get_blog_option (for multisite)
* - get_option (for ordinary blog)
*
* @param array $blog_id This is the specific blog ID
* @param array $setting specifies settings
* @return array
*/
private function _get_wp_option($blog_id, $setting) {
return is_multisite() ? get_blog_option($blog_id, $setting) : get_option($setting);
}
/**
* The get_comments function pull all the comments from the database
* based on the current search parameters/filters. Please see _search_comments
* for the breakdown of these parameters.
*
* @param array $query Specific query to pull comments
* @return array
*/
public function get_comments($query) {
// Here, we're getting the current blog id. If blog id
// is passed along with the parameters then we override
// that current (default) value with the parameter blog id value.
$blog_id = get_current_blog_id();
if (isset($query['blog_id'])) $blog_id = $query['blog_id'];
// Here, we're switching to the actual blog that we need
// to pull comments from.
$switched = false;
if (function_exists('switch_to_blog')) {
$switched = switch_to_blog($blog_id);
}
if (!empty($query['search'])) {
// If a search keyword is present, then we'll call the _search_comments
// function to process the query.
$comments = $this->_search_comments($query);
} else {
// Set default parameter values if the designated
// parameters are empty.
if (empty($query['per_page'])) {
$query['per_page'] = 10;
}
if (empty($query['page_no'])) {
$query['page_no'] = 1;
}
if (empty($query['type'])) {
$query['type'] = '';
}
if (empty($query['status'])) {
$query['status'] = '';
}
// Since WP_Comment_Query parameters doesn't have a "page" attribute, we
// need to compute for the offset to get the exact content based on the
// current page and the number of items per page.
$offset = ((int) $query['page_no'] - 1) * (int) $query['per_page'];
$args = array(
'orderby' => 'ID',
'order' => 'DESC',
'number' => $query['per_page'],
'offset' => $offset,
'type' => $query['type'],
'status' => $query['status']
);
$comments_query = new WP_Comment_Query;
$comments = $comments_query->query($args);
}
// If no comments are found based on the current query then
// we return with error.
if (empty($comments)) {
$result = array('message' => 'comments_not_found');
return $this->_response($result);
}
// Otherwise, we're going to process each comment
// before we return it to the one issuing the request.
//
// Process in the sense that we add additional related info
// such as the post tile where the comment belongs to, the
// comment status, a formatted date field, and to which parent comment
// does the comment was intended to be as a reply.
foreach ($comments as &$comment) {
$comment = get_comment($comment->comment_ID, ARRAY_A);
if ($comment) {
$post = get_post($comment['comment_post_ID']);
if ($post) $comment['in_response_to'] = $post->post_title;
if (!empty($comment['comment_parent'])) {
$parent_comment = get_comment($comment['comment_parent'], ARRAY_A);
if ($parent_comment) $comment['in_reply_to'] = $parent_comment['comment_author'];
}
// We're formatting the comment_date to be exactly the same
// with that of WP Comments table (e.g. 2016/12/21 at 10:30 PM)
$comment['comment_date'] = date('Y/m/d \a\t g:i a', strtotime($comment['comment_date']));
$status = wp_get_comment_status($comment['comment_ID']);
if ($status) {
$comment['comment_status'] = $status;
}
}
}
// We return the following to the one issuing
// the request.
$result = array(
'comments' => $comments,
'paging' => $this->_calculate_pages($query)
);
// Here, we're restoring to the current (default) blog before we
// do the switched.
if (function_exists('restore_current_blog') && $switched) {
restore_current_blog();
}
return $this->_response($result);
}
/**
* The get_comment_filters function builds a array of options
* to be use as filters for the search function on the frontend.
*/
public function get_comment_filters() {
// Options for comment_types field
$comment_types = apply_filters('admin_comment_types_dropdown', array(
'comment' => __('Comments'),
'pings' => __('Pings'),
));
// Options for comment_status field
$comment_statuses = array(
'approve' => __('Approve'),
'hold' => __('Hold or Unapprove'),
'trash' => __('Trash'),
'spam' => __('Spam'),
);
// Pull sites options if available.
$sites = $this->get_blog_sites();
$result = array(
'sites' => $sites,
'types' => $comment_types,
'statuses' => $comment_statuses,
'paging' => $this->_calculate_pages(null),
);
return $this->_response($result);
}
/**
* The get_settings function pulls the current discussion settings
* option values.
*
* @param array $params Passing specific params for getting current discussion settings
* @return array
*/
public function get_settings($params) {
// Here, we're getting the current blog id. If blog id
// is passed along with the parameters then we override
// that current (default) value with the parameter blog id value.
$blog_id = get_current_blog_id();
if (isset($params['blog_id'])) $blog_id = $params['blog_id'];
// If user does not have sufficient privileges to manage and edit
// WP options then we return with error.
if (!current_user_can_for_blog($blog_id, 'manage_options')) {
$result = array('error' => true, 'message' => 'insufficient_permission');
return $this->_response($result);
}
// Pull sites options if available.
$sites = $this->get_blog_sites();
// Wrap current discussion settings values into an array item
// named settings.
$result = array(
'settings' => array(
'default_pingback_flag' => $this->_get_wp_option($blog_id, 'default_pingback_flag'),
'default_ping_status' => $this->_get_wp_option($blog_id, 'default_ping_status'),
'default_comment_status' => $this->_get_wp_option($blog_id, 'default_comment_status'),
'require_name_email' => $this->_get_wp_option($blog_id, 'require_name_email'),
'comment_registration' => $this->_get_wp_option($blog_id, 'comment_registration'),
'close_comments_for_old_posts' => $this->_get_wp_option($blog_id, 'close_comments_for_old_posts'),
'close_comments_days_old' => $this->_get_wp_option($blog_id, 'close_comments_days_old'),
'thread_comments' => $this->_get_wp_option($blog_id, 'thread_comments'),
'thread_comments_depth' => $this->_get_wp_option($blog_id, 'thread_comments_depth'),
'page_comments' => $this->_get_wp_option($blog_id, 'page_comments'),
'comments_per_page' => $this->_get_wp_option($blog_id, 'comments_per_page'),
'default_comments_page' => $this->_get_wp_option($blog_id, 'default_comments_page'),
'comment_order' => $this->_get_wp_option($blog_id, 'comment_order'),
'comments_notify' => $this->_get_wp_option($blog_id, 'comments_notify'),
'moderation_notify' => $this->_get_wp_option($blog_id, 'moderation_notify'),
'comment_moderation' => $this->_get_wp_option($blog_id, 'comment_moderation'),
'comment_whitelist' => $this->_get_wp_option($blog_id, 'comment_whitelist'),
'comment_max_links' => $this->_get_wp_option($blog_id, 'comment_max_links'),
'moderation_keys' => $this->_get_wp_option($blog_id, 'moderation_keys'),
'blacklist_keys' => $this->_get_wp_option($blog_id, 'blacklist_keys'),
),
'sites' => $sites,
);
return $this->_response($result);
}
/**
* The update_settings function updates the discussion settings
* basing on the user generated content/option from the frontend
* form.
*
* @param array $params Specific params to update settings based on discussion
* @return array
*/
public function update_settings($params) {
// Extract settings values from passed parameters.
$settings = $params['settings'];
// Here, we're getting the current blog id. If blog id
// is passed along with the parameters then we override
// that current (default) value with the parameter blog id value.
$blog_id = get_current_blog_id();
if (isset($params['blog_id'])) $blog_id = $params['blog_id'];
// If user does not have sufficient privileges to manage and edit
// WP options then we return with error.
if (!current_user_can_for_blog($blog_id, 'manage_options')) {
$result = array('error' => true, 'message' => 'insufficient_permission');
return $this->_response($result);
}
// Here, we're sanitizing the input fields before we save them to the database
// for safety and security reason. The "explode" and "implode" functions are meant
// to maintain the line breaks associated with a textarea input/value.
foreach ($settings as $key => $value) {
// We're using update_blog_option and update_option altogether to update the current
// discussion settings.
if (is_multisite()) {
update_blog_option($blog_id, $key, implode("\n", array_map('sanitize_text_field', explode("\n", $value))));
} else {
update_option($key, implode("\n", array_map('sanitize_text_field', explode("\n", $value))));
}
}
// We're not checking for errors here, but instead we're directly returning a success (error = false)
// status always, because WP's update_option will return fail if values were not changed, meaning
// previous values were not changed by the user's current request, not an actual exception thrown.
// Thus, giving a false positive message or report to the frontend.
$result = array('error' => false, 'message' => 'settings_updated', 'values' => array());
return $this->_response($result);
}
/**
* The get_comment function pulls a single comment based
* on a comment ID.
*
* @param array $params Specific params for getting a single comment
* @return array
*/
public function get_comment($params) {
// Here, we're getting the current blog id. If blog id
// is passed along with the parameters then we override
// that current (default) value with the parameter blog id value.
$blog_id = get_current_blog_id();
if (isset($params['blog_id'])) $blog_id = $params['blog_id'];
// If user does not have sufficient privileges to moderate or edit
// a comment then we return with error.
if (!current_user_can_for_blog($blog_id, 'moderate_comments')) {
$result = array('error' => true, 'message' => 'insufficient_permission');
return $this->_response($result);
}
// Here, we're switching to the actual blog that we need
// to pull comments from.
$switched = false;
if (function_exists('switch_to_blog')) {
$switched = switch_to_blog($blog_id);
}
// Get comment by comment_ID parameter and return result as an array.
$result = array(
'comment' => get_comment($params['comment_id'], ARRAY_A)
);
// Here, we're restoring to the current (default) blog before we
// do the switched.
if (function_exists('restore_current_blog') && $switched) {
restore_current_blog();
}
return $this->_response($result);
}
/**
* The reply_comment function creates a new comment as a reply
* to a certain/selected comment.
*
* @param array $params Specific params to create a new comment reply
* @return array
*/
public function reply_comment($params) {
// Extract reply info from the passed parameters
$reply = $params['comment'];
// Here, we're getting the current blog id. If blog id
// is passed along with the parameters then we override
// that current (default) value with the parameter blog id value.
$blog_id = get_current_blog_id();
if (isset($params['blog_id'])) $blog_id = $params['blog_id'];
// If user does not have sufficient privileges to moderate or edit
// a comment then we return with error.
if (!current_user_can_for_blog($blog_id, 'moderate_comments')) {
$result = array('error' => true, 'message' => 'comment_reply_no_permission');
return $this->_response($result);
}
// Here, we're switching to the actual blog that we need
// to apply our changes.
$switched = false;
if (function_exists('switch_to_blog')) {
$switched = switch_to_blog($blog_id);
}
// Get comment by comment_ID parameter.
$comment = get_comment($reply['comment_id']);
if ($comment) {
// Get the currently logged in user
$user = wp_get_current_user();
// If the current comment was not approved yet then
// we need to approve it before we create a reply to
// to the comment, mimicking exactly the WP behaviour
// in terms of creating a reply to a comment.
if (empty($comment->comment_approved)) {
$update_data = array(
'comment_ID' => $reply['comment_id'],
'comment_approved' => 1
);
wp_update_comment($update_data);
}
// Build new comment parameters based on current user info and
// the target comment for the reply.
$data = array(
'comment_post_ID' => $comment->comment_post_ID,
'comment_author' => $user->display_name,
'comment_author_email' => $user->user_email,
'comment_author_url' => $user->user_url,
'comment_content' => $reply['message'],
'comment_parent' => $reply['comment_id'],
'user_id' => $user->ID,
'comment_date' => current_time('mysql'),
'comment_approved' => 1
);
// Create new comment based on the parameters above, and return
// the status accordingly.
if (wp_insert_comment($data)) {
$result = array('error' => false, 'message' => 'comment_replied_with_comment_author', 'values' => array($comment->comment_author));
} else {
$result = array('error' => true, 'message' => 'comment_reply_failed_with_error', 'values' => array($comment->comment_ID));
}
} else {
$result = array('error' => true, 'message' => 'comment_does_not_exists_error', 'values' => array($reply['comment_id']));
}
// Here, we're restoring to the current (default) blog before we
// do the switched.
if (function_exists('restore_current_blog') && $switched) {
restore_current_blog();
}
return $this->_response($result);
}
/**
* The edit_comment function saves new information for the
* currently selected comment.
*
* @param array $params Specific params for editing a coment
* @return array
*/
public function edit_comment($params) {
// Extract new comment info from the passed parameters
$comment = $params['comment'];
// Here, we're getting the current blog id. If blog id
// is passed along with the parameters then we override
// that current (default) value with the parameter blog id value.
$blog_id = get_current_blog_id();
if (isset($params['blog_id'])) $blog_id = $params['blog_id'];
// If user does not have sufficient privileges to moderate or edit
// a comment then we return with error.
if (!current_user_can_for_blog($blog_id, 'moderate_comments')) {
$result = array('error' => true, 'message' => 'comment_edit_no_permission');
return $this->_response($result);
}
// Here, we're switching to the actual blog that we need
// to apply our changes.
$switched = false;
if (function_exists('switch_to_blog')) {
$switched = switch_to_blog($blog_id);
}
// Get current comment details
$original_comment = get_comment($comment['comment_id']);
if ($original_comment) {
$data = array();
// Replace "comment_id" with "comment_ID" since WP does not recognize
// the small case "id".
$comment['comment_ID'] = $original_comment->comment_ID;
unset($comment['comment_id']);
// Here, we're sanitizing the input fields before we save them to the database
// for safety and security reason. The "explode" and "implode" functions are meant
// to maintain the line breaks associated with a textarea input/value.
foreach ($comment as $key => $value) {
$data[$key] = implode("\n", array_map('sanitize_text_field', explode("\n", $value)));
}
// Update existing comment based on the passed parameter fields and
// return the status accordingly.
if (wp_update_comment($data)) {
$result = array('error' => false, 'message' => 'comment_edited_with_comment_author', 'values' => array($original_comment->comment_author));
} else {
$result = array('error' => true, 'message' => 'comment_edit_failed_with_error', 'values' => array($original_comment->comment_ID));
}
} else {
$result = array('error' => true, 'message' => 'comment_does_not_exists_error', 'values' => array($comment['comment_id']));
}
// Here, we're restoring to the current (default) blog before we
// do the switched.
if (function_exists('restore_current_blog') && $switched) {
restore_current_blog();
}
return $this->_response($result);
}
/**
* The update_comment_status function is a generic handler for the following
* comment actions:
*
* - approve comment
* - unapprove comment
* - set comment as spam
* - move commment to trash
* - delete comment permanently
* - unset comment as spam
* - restore comment
*
* @param array $params Specific params to update comment status
* @return array
*/
public function update_comment_status($params) {
// Here, we're getting the current blog id. If blog id
// is passed along with the parameters then we override
// that current (default) value with the parameter blog id value.
$blog_id = get_current_blog_id();
if (isset($params['blog_id'])) $blog_id = $params['blog_id'];
// If user does not have sufficient privileges to moderate or edit
// a comment then we return with error.
if (!current_user_can_for_blog($blog_id, 'moderate_comments')) {
$result = array('error' => true, 'message' => 'comment_change_status_no_permission');
return $this->_response($result);
}
// Here, we're switching to the actual blog that we need
// to apply our changes.
$switched = false;
if (function_exists('switch_to_blog')) {
$switched = switch_to_blog($blog_id);
}
// We make sure that we still have a valid comment from the server
// before we apply the currently selected action.
$comment = get_comment($params['comment_id']);
if ($comment) {
$post = get_post($comment->comment_post_ID);
if ($post) $comment->in_response_to = $post->post_title;
if (!empty($comment->comment_parent)) {
$parent_comment = get_comment($comment->comment_parent);
if ($parent_comment) $comment->in_reply_to = $parent_comment->comment_author;
}
// We're formatting the comment_date to be exactly the same
// with that of WP Comments table (e.g. 2016/12/21 at 10:30 PM)
$comment->comment_date = date('Y/m/d \a\t g:i a', strtotime($comment->comment_date));
$status = wp_get_comment_status($comment->comment_ID);
if ($status) {
$comment->comment_status = $status;
}
$succeeded = false;
$message = '';
// Here, we're using WP's wp_set_comment_status function to change the state
// of the selected comment based on the current action, except for the "delete" action
// where we use the wp_delete_comment to delete the comment permanently by passing
// "true" to the second argument.
switch ($params['action']) {
case 'approve':
$succeeded = wp_set_comment_status($params['comment_id'], 'approve');
$message = 'comment_approve_with_comment_author';
break;
case 'unapprove':
$succeeded = wp_set_comment_status($params['comment_id'], 'hold');
$message = 'comment_unapprove_with_comment_author';
break;
case 'spam':
$succeeded = wp_set_comment_status($params['comment_id'], 'spam');
$message = 'comment_spam_with_comment_author';
break;
case 'trash':
$succeeded = wp_set_comment_status($params['comment_id'], 'trash');
$message = 'comment_trash_with_comment_author';
break;
case 'delete':
$succeeded = wp_delete_comment($params['comment_id'], true);
$message = 'comment_delete_with_comment_author';
break;
case 'notspam':
$succeeded = wp_set_comment_status($params['comment_id'], 'hold');
$message = 'comment_not_spam_with_comment_author';
break;
case 'restore':
$succeeded = wp_set_comment_status($params['comment_id'], 'hold');
$message = 'comment_restore_with_comment_author';
break;
}
// If the current action succeeded, then we return a success message, otherwise,
// we return an error message to the user issuing the request.
if ($succeeded) {
$result = array('error' => false, 'message' => $message, 'values' => array($comment->comment_author), 'status' => $comment->comment_status, 'approved' => $comment->comment_approved);
} else {
$result = array('error' => true, 'message' => 'comment_change_status_failed_with_error', 'values' => array($comment->comment_ID));
}
} else {
$result = array('error' => true, 'message' => 'comment_does_not_exists_error', 'values' => array($params['comment_id']));
}
// Here, we're restoring to the current (default) blog before we
// do the switched.
if (function_exists('restore_current_blog') && $switched) {
restore_current_blog();
}
return $this->_response($result);
}
}

View File

@@ -0,0 +1,422 @@
<?php
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
/**
* - A container for RPC commands (core UpdraftCentral commands). Commands map exactly onto method names (and hence this class should not implement anything else, beyond the constructor, and private methods)
* - Return format is array('response' => (string - a code), 'data' => (mixed));
*
* RPC commands are not allowed to begin with an underscore. So, any private methods can be prefixed with an underscore.
*/
class UpdraftCentral_Core_Commands extends UpdraftCentral_Commands {
/**
* Executes a list of submitted commands (multiplexer)
*
* @param Array $query An array containing the commands to execute and a flag to indicate how to handle command execution failure.
* @return Array An array containing the results of the process.
*/
public function execute_commands($query) {
try {
$commands = $query['commands'];
$command_results = array();
$error_count = 0;
/**
* Should be one of the following options:
* 1 = Abort on first failure
* 2 = Abort if any command fails
* 3 = Abort if all command fails (default)
*/
$error_flag = isset($query['error_flag']) ? (int) $query['error_flag'] : 3;
foreach ($commands as $command => $params) {
$command_info = apply_filters('updraftcentral_get_command_info', false, $command);
if (!$command_info) {
list($_prefix, $_command) = explode('.', $command);
$command_results[$_prefix][$_command] = array('response' => 'rpcerror', 'data' => array('code' => 'unknown_rpc_command', 'data' => $command));
$error_count++;
if (1 === $error_flag) break;
} else {
$class_prefix = $command_info['class_prefix'];
$action = $command_info['command'];
$command_php_class = $command_info['command_php_class'];
// Instantiate the command class and execute the needed action
if (class_exists($command_php_class)) {
$instance = new $command_php_class($this->rc);
if (method_exists($instance, $action)) {
$params = empty($params) ? array() : $params;
$call_result = call_user_func_array(array($instance, $action), $params);
$command_results[$command] = $call_result;
if ('rpcerror' === $call_result['response'] || (isset($call_result['data']['error']) && $call_result['data']['error'])) {
$error_count++;
if (1 === $error_flag) break;
}
}
}
}
}
if (0 !== $error_count) {
// N.B. These error messages should be defined in UpdraftCentral's translation file (dashboard-translations.php)
// before actually using this multiplexer function.
$message = 'general_command_execution_error';
switch ($error_flag) {
case 1:
$message = 'command_execution_aborted';
break;
case 2:
$message = 'failed_to_execute_some_commands';
break;
case 3:
if (count($commands) === $error_count) {
$message = 'failed_to_execute_all_commands';
}
break;
default:
break;
}
$result = array('error' => true, 'message' => $message, 'values' => $command_results);
} else {
$result = $command_results;
}
} catch (Exception $e) {
$result = array('error' => true, 'message' => $e->getMessage());
}
return $this->_response($result);
}
/**
* Validates the credentials entered by the user
*
* @param array $creds an array of filesystem credentials
* @return array An array containing the result of the validation process.
*/
public function validate_credentials($creds) {
try {
$entity = $creds['entity'];
if (isset($creds['filesystem_credentials'])) {
parse_str($creds['filesystem_credentials'], $filesystem_credentials);
if (is_array($filesystem_credentials)) {
foreach ($filesystem_credentials as $key => $value) {
// Put them into $_POST, which is where request_filesystem_credentials() checks for them.
$_POST[$key] = $value;
}
}
}
// Include the needed WP Core file(s)
// template.php needed for submit_button() which is called by request_filesystem_credentials()
$this->_admin_include('file.php', 'template.php');
// Directory entities that we currently need permissions
// to update.
$entity_directories = array(
'plugins' => WP_PLUGIN_DIR,
'themes' => WP_CONTENT_DIR.'/themes',
'core' => untrailingslashit(ABSPATH)
);
$url = wp_nonce_url(site_url());
$directory = $entity_directories[$entity];
// Check if credentials are valid and have sufficient
// privileges to create and delete (e.g. write)
$credentials = request_filesystem_credentials($url, '', false, $directory);
if (WP_Filesystem($credentials, $directory)) {
global $wp_filesystem;
$path = $entity_directories[$entity].'/.updraftcentral';
if (!$wp_filesystem->put_contents($path, '', 0644)) {
$result = array('error' => true, 'message' => 'failed_credentials', 'values' => array());
} else {
$wp_filesystem->delete($path);
$result = array('error' => false, 'message' => 'credentials_ok', 'values' => array());
}
} else {
$result = array('error' => true, 'message' => 'failed_credentials', 'values' => array());
}
} catch (Exception $e) {
$result = array('error' => true, 'message' => $e->getMessage(), 'values' => array());
}
return $this->_response($result);
}
/**
* Gets the FileSystem Credentials
*
* Extract the needed filesystem credentials (permissions) to be used
* to update/upgrade the plugins, themes and the WP core.
*
* @return array $result - An array containing the creds form and some flags
* to determine whether we need to extract the creds
* manually from the user.
*/
public function get_credentials() {
try {
// Check whether user has enough permission to update entities
if (!current_user_can('update_plugins') && !current_user_can('update_themes') && !current_user_can('update_core')) return $this->_generic_error_response('updates_permission_denied');
// Include the needed WP Core file(s)
$this->_admin_include('file.php', 'template.php');
// A container that will hold the state (in this case, either true or false) of
// each directory entities (plugins, themes, core) that will be used to determine
// whether or not there's a need to show a form that will ask the user for their credentials
// manually.
$request_filesystem_credentials = array();
// A container for the filesystem credentials form if applicable.
$filesystem_form = '';
// Directory entities that we currently need permissions
// to update.
$check_fs = array(
'plugins' => WP_PLUGIN_DIR,
'themes' => WP_CONTENT_DIR.'/themes',
'core' => untrailingslashit(ABSPATH)
);
// Here, we're looping through each entities and find output whether
// we have sufficient permissions to update objects belonging to them.
foreach ($check_fs as $entity => $dir) {
// We're determining which method to use when updating
// the files in the filesystem.
$filesystem_method = get_filesystem_method(array(), $dir);
// Buffering the output to pull the actual credentials form
// currently being used by this WP instance if no sufficient permissions
// is found.
$url = wp_nonce_url(site_url());
ob_start();
$filesystem_credentials_are_stored = request_filesystem_credentials($url, $filesystem_method);
$form = strip_tags(ob_get_contents(), '<div><h2><p><input><label><fieldset><legend><span><em>');
if (!empty($form)) {
$filesystem_form = $form;
}
ob_end_clean();
// Save the state whether or not there's a need to show the
// credentials form to the user.
$request_filesystem_credentials[$entity] = ('direct' !== $filesystem_method && !$filesystem_credentials_are_stored);
}
// Wrapping the credentials info before passing it back
// to the client issuing the request.
$result = array(
'request_filesystem_credentials' => $request_filesystem_credentials,
'filesystem_form' => $filesystem_form
);
} catch (Exception $e) {
$result = array('error' => true, 'message' => $e->getMessage(), 'values' => array());
}
return $this->_response($result);
}
/**
* Fetches a browser-usable URL which will automatically log the user in to the site
*
* @param String $redirect_to - the URL to got to after logging in
* @param Array $extra_info - valid keys are user_id, which should be a numeric user ID to log in as.
*/
public function get_login_url($redirect_to, $extra_info) {
if (is_array($extra_info) && !empty($extra_info['user_id']) && is_numeric($extra_info['user_id'])) {
$user_id = $extra_info['user_id'];
if (false == ($login_key = $this->_get_autologin_key($user_id))) return $this->_generic_error_response('user_key_failure');
// Default value
$redirect_url = network_admin_url();
if (is_array($redirect_to) && !empty($redirect_to['module'])) {
switch ($redirect_to['module']) {
case 'updraftplus':
if ('initiate_restore' == $redirect_to['action'] && class_exists('UpdraftPlus_Options')) {
$redirect_url = UpdraftPlus_Options::admin_page_url().'?page=updraftplus&udaction=initiate_restore&entities='.urlencode($redirect_to['data']['entities']).'&showdata='.urlencode($redirect_to['data']['showdata']).'&backup_timestamp='.(int) $redirect_to['data']['backup_timestamp'];
} elseif ('download_file' == $redirect_to['action']) {
$findex = empty($redirect_to['data']['findex']) ? 0 : (int) $redirect_to['data']['findex'];
// e.g. ?udcentral_action=dl&action=updraftplus_spool_file&backup_timestamp=1455101696&findex=0&what=plugins
$redirect_url = site_url().'?udcentral_action=spool_file&action=updraftplus_spool_file&findex='.$findex.'&what='.urlencode($redirect_to['data']['what']).'&backup_timestamp='.(int) $redirect_to['data']['backup_timestamp'];
}
break;
case 'direct_url':
$redirect_url = $redirect_to['url'];
break;
}
}
$login_key = apply_filters('updraftplus_remotecontrol_login_key', array(
'key' => $login_key,
'created' => time(),
'redirect_url' => $redirect_url
), $redirect_to, $extra_info);
// Over-write any previous value - only one can be valid at a time)
update_user_meta($user_id, 'updraftcentral_login_key', $login_key);
return $this->_response(array(
'login_url' => network_site_url('?udcentral_action=login&login_id='.$user_id.'&login_key='.$login_key['key'])
));
} else {
return $this->_generic_error_response('user_unknown');
}
}
/**
* Get information derived from phpinfo()
*
* @return Array
*/
public function phpinfo() {
$phpinfo = $this->_get_phpinfo_array();
if (!empty($phpinfo)) {
return $this->_response($phpinfo);
}
return $this->_generic_error_response('phpinfo_fail');
}
/**
* The key obtained is only intended to be short-lived. Hence, there's no intention other than that it is random and only used once - only the most recent one is valid.
*
* @param Integer $user_id Specific user ID to get the autologin key
* @return Array
*/
public function _get_autologin_key($user_id) {
$secure_auth_key = defined('SECURE_AUTH_KEY') ? SECURE_AUTH_KEY : hash('sha256', DB_PASSWORD).'_'.rand(0, 999999999);
if (!defined('SECURE_AUTH_KEY')) return false;
$hash_it = $user_id.'_'.microtime(true).'_'.rand(0, 999999999).'_'.$secure_auth_key;
$hash = hash('sha256', $hash_it);
return $hash;
}
public function site_info() {
global $wpdb;
@include(ABSPATH.WPINC.'/version.php');
$ud_version = is_a($this->ud, 'UpdraftPlus') ? $this->ud->version : 'none';
return $this->_response(array(
'versions' => array(
'ud' => $ud_version,
'php' => PHP_VERSION,
'wp' => $wp_version,
'mysql' => $wpdb->db_version(),
'udrpc_php' => $this->rc->udrpc_version,
),
'bloginfo' => array(
'url' => network_site_url(),
'name' => get_bloginfo('name'),
)
));
}
/**
* This calls the WP_Action within WP
*
* @param array $data Array of Data to be used within call_wp_action
* @return array
*/
public function call_wordpress_action($data) {
if (false === ($updraftplus_admin = $this->_load_ud_admin())) return $this->_generic_error_response('no_updraftplus');
$response = $updraftplus_admin->call_wp_action($data);
if (empty($data["wpaction"])) {
return $this->_generic_error_response("error", "no command sent");
}
return $this->_response(array(
"response" => $response['response'],
"status" => $response['status'],
"log" => $response['log']
));
}
/**
* Get disk space used
*
* @uses UpdraftPlus_Filesystem_Functions::get_disk_space_used()
*
* @param String $entity - the entity to count (e.g. 'plugins', 'themes')
*
* @return Array - response
*/
public function count($entity) {
if (!class_exists('UpdraftPlus_Filesystem_Functions')) return $this->_generic_error_response('no_updraftplus');
$response = UpdraftPlus_Filesystem_Functions::get_disk_space_used($entity);
return $this->_response($response);
}
/**
* https://secure.php.net/phpinfo
*
* @return null|array
*/
private function _get_phpinfo_array() {
ob_start();
phpinfo(INFO_GENERAL|INFO_CREDITS|INFO_MODULES);
$phpinfo = array('phpinfo' => array());
if (preg_match_all('#(?:<h2>(?:<a name=".*?">)?(.*?)(?:</a>)?</h2>)|(?:<tr(?: class=".*?")?><t[hd](?: class=".*?")?>(.*?)\s*</t[hd]>(?:<t[hd](?: class=".*?")?>(.*?)\s*</t[hd]>(?:<t[hd](?: class=".*?")?>(.*?)\s*</t[hd]>)?)?</tr>)#s', ob_get_clean(), $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
if (strlen($match[1])) {
$phpinfo[$match[1]] = array();
} elseif (isset($match[3])) {
$keys1 = array_keys($phpinfo);
$phpinfo[end($keys1)][$match[2]] = isset($match[4]) ? array($match[3], $match[4]) : $match[3];
} else {
$keys1 = array_keys($phpinfo);
$phpinfo[end($keys1)][] = $match[2];
}
}
return $phpinfo;
}
return false;
}
/**
* Return an UpdraftPlus_Admin object
*
* @return UpdraftPlus_Admin|Boolean - false in case of failure
*/
private function _load_ud_admin() {
if (!defined('UPDRAFTPLUS_DIR') || !is_file(UPDRAFTPLUS_DIR.'/admin.php')) return false;
include_once(UPDRAFTPLUS_DIR.'/admin.php');
global $updraftplus_admin;
return $updraftplus_admin;
}
}

View File

@@ -0,0 +1,509 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No access.');
/**
* Handles UpdraftCentral Plugin Commands which basically handles
* the installation and activation of a plugin
*/
class UpdraftCentral_Plugin_Commands extends UpdraftCentral_Commands {
private $switched = false;
/**
* Function that gets called before every action
*
* @param string $command a string that corresponds to UDC command to call a certain method for this class.
* @param array $data an array of data post or get fields
* @param array $extra_info extrainfo use in the udrpc_action, e.g. user_id
*
* link to udrpc_action main function in class UpdraftPlus_UpdraftCentral_Listener
*/
public function _pre_action($command, $data, $extra_info) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
// Here we assign the current blog_id to a variable $blog_id
$blog_id = get_current_blog_id();
if (!empty($data['site_id'])) $blog_id = $data['site_id'];
if (function_exists('switch_to_blog') && is_multisite() && $blog_id) {
$this->switched = switch_to_blog($blog_id);
}
}
/**
* Function that gets called after every action
*
* @param string $command a string that corresponds to UDC command to call a certain method for this class.
* @param array $data an array of data post or get fields
* @param array $extra_info extrainfo use in the udrpc_action, e.g. user_id
*
* link to udrpc_action main function in class UpdraftPlus_UpdraftCentral_Listener
*/
public function _post_action($command, $data, $extra_info) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
// Here, we're restoring to the current (default) blog before we switched
if ($this->switched) restore_current_blog();
}
/**
* Constructor
*/
public function __construct() {
$this->_admin_include('plugin.php', 'file.php', 'template.php', 'class-wp-upgrader.php', 'plugin-install.php', 'update.php');
}
/**
* Checks whether the plugin is currently installed and activated.
*
* @param array $query Parameter array containing the name of the plugin to check
* @return array Contains the result of the current process
*/
public function is_plugin_installed($query) {
if (!isset($query['plugin']))
return $this->_generic_error_response('plugin_name_required');
$result = $this->_get_plugin_info($query['plugin']);
return $this->_response($result);
}
/**
* Applies currently requested action for plugin processing
*
* @param string $action The action to apply (e.g. activate or install)
* @param array $query Parameter array containing information for the currently requested action
*
* @return array
*/
private function _apply_plugin_action($action, $query) {
$result = array();
switch ($action) {
case 'activate':
case 'network_activate':
$info = $this->_get_plugin_info($query['plugin']);
if ($info['installed']) {
if (is_multisite() && 'network_activate' === $action) {
$activate = activate_plugin($info['plugin_path'], '', true);
} else {
$activate = activate_plugin($info['plugin_path']);
}
if (is_wp_error($activate)) {
$result = $this->_generic_error_response('generic_response_error', array($activate->get_error_message()));
} else {
$result = array('activated' => true);
}
} else {
$result = $this->_generic_error_response('plugin_not_installed', array($query['plugin']));
}
break;
case 'deactivate':
case 'network_deactivate':
$info = $this->_get_plugin_info($query['plugin']);
if ($info['active']) {
if (is_multisite() && 'network_deactivate' === $action) {
deactivate_plugins($info['plugin_path'], false, true);
} else {
deactivate_plugins($info['plugin_path']);
}
if (!is_plugin_active($info['plugin_path'])) {
$result = array('deactivated' => true);
} else {
$result = $this->_generic_error_response('deactivate_plugin_failed', array($query['plugin']));
}
} else {
$result = $this->_generic_error_response('not_active', array($query['plugin']));
}
break;
case 'install':
$api = plugins_api('plugin_information', array(
'slug' => $query['slug'],
'fields' => array(
'short_description' => false,
'sections' => false,
'requires' => false,
'rating' => false,
'ratings' => false,
'downloaded' => false,
'last_updated' => false,
'added' => false,
'tags' => false,
'compatibility' => false,
'homepage' => false,
'donate_link' => false,
)
));
if (is_wp_error($api)) {
$result = $this->_generic_error_response('generic_response_error', array($api->get_error_message()));
} else {
$info = $this->_get_plugin_info($query['plugin']);
$installed = $info['installed'];
if (!$installed) {
// WP < 3.7
if (!class_exists('Automatic_Upgrader_Skin')) include_once(UPDRAFTPLUS_DIR.'/central/classes/class-automatic-upgrader-skin.php');
$skin = new Automatic_Upgrader_Skin();
$upgrader = new Plugin_Upgrader($skin);
$download_link = $api->download_link;
$installed = $upgrader->install($download_link);
}
if (!$installed) {
$result = $this->_generic_error_response('plugin_install_failed', array($query['plugin']));
} else {
$result = array('installed' => true);
}
}
break;
}
return $result;
}
/**
* Preloads the submitted credentials to the global $_POST variable
*
* @param array $query Parameter array containing information for the currently requested action
*/
private function _preload_credentials($query) {
if (!empty($query) && isset($query['filesystem_credentials'])) {
parse_str($query['filesystem_credentials'], $filesystem_credentials);
if (is_array($filesystem_credentials)) {
foreach ($filesystem_credentials as $key => $value) {
// Put them into $_POST, which is where request_filesystem_credentials() checks for them.
$_POST[$key] = $value;
}
}
}
}
/**
* Checks whether we have the required fields submitted and the user has
* the capabilities to execute the requested action
*
* @param array $query The submitted information
* @param array $fields The required fields to check
* @param array $capabilities The capabilities to check and validate
*
* @return array|string
*/
private function _validate_fields_and_capabilities($query, $fields, $capabilities) {
$error = '';
if (!empty($fields)) {
for ($i=0; $i<count($fields); $i++) {
$field = $fields[$i];
if (!isset($query[$field])) {
if ('keyword' === $field) {
$error = $this->_generic_error_response('keyword_required');
} else {
$error = $this->_generic_error_response('plugin_'.$query[$field].'_required');
}
break;
}
}
}
if (empty($error) && !empty($capabilities)) {
for ($i=0; $i<count($capabilities); $i++) {
if (!current_user_can($capabilities[$i])) {
$error = $this->_generic_error_response('plugin_insufficient_permission');
break;
}
}
}
return $error;
}
/**
* Activates the plugin
*
* @param array $query Parameter array containing the name of the plugin to activate
* @return array Contains the result of the current process
*/
public function activate_plugin($query) {
$error = $this->_validate_fields_and_capabilities($query, array('plugin'), array('activate_plugins'));
if (!empty($error)) {
return $error;
}
$action = 'activate';
if (!empty($query['multisite']) && (bool) $query['multisite']) $action = 'network_'.$action;
$result = $this->_apply_plugin_action($action, $query);
if (empty($result['activated'])) {
return $result;
}
return $this->_response($result);
}
/**
* Deactivates the plugin
*
* @param array $query Parameter array containing the name of the plugin to deactivate
* @return array Contains the result of the current process
*/
public function deactivate_plugin($query) {
$error = $this->_validate_fields_and_capabilities($query, array('plugin'), array('activate_plugins'));
if (!empty($error)) {
return $error;
}
$action = 'deactivate';
if (!empty($query['multisite']) && (bool) $query['multisite']) $action = 'network_'.$action;
$result = $this->_apply_plugin_action($action, $query);
if (empty($result['deactivated'])) {
return $result;
}
return $this->_response($result);
}
/**
* Download, install and activates the plugin
*
* @param array $query Parameter array containing the filesystem credentials entered by the user along with the plugin name and slug
* @return array Contains the result of the current process
*/
public function install_activate_plugin($query) {
$error = $this->_validate_fields_and_capabilities($query, array('plugin', 'slug'), array('install_plugins', 'activate_plugins'));
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$result = $this->_apply_plugin_action('install', $query);
if (!empty($result['installed']) && $result['installed']) {
$action = 'activate';
if (!empty($query['multisite']) && (bool) $query['multisite']) $action = 'network_'.$action;
$result = $this->_apply_plugin_action($action, $query);
if (empty($result['activated'])) {
return $result;
}
} else {
return $result;
}
return $this->_response($result);
}
/**
* Download, install the plugin
*
* @param array $query Parameter array containing the filesystem credentials entered by the user along with the plugin name and slug
* @return array Contains the result of the current process
*/
public function install_plugin($query) {
$error = $this->_validate_fields_and_capabilities($query, array('plugin', 'slug'), array('install_plugins'));
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$result = $this->_apply_plugin_action('install', $query);
if (empty($result['installed'])) {
return $result;
}
return $this->_response($result);
}
/**
* Uninstall/delete the plugin
*
* @param array $query Parameter array containing the filesystem credentials entered by the user along with the plugin name and slug
* @return array Contains the result of the current process
*/
public function delete_plugin($query) {
$error = $this->_validate_fields_and_capabilities($query, array('plugin'), array('delete_plugins'));
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$info = $this->_get_plugin_info($query['plugin']);
if ($info['installed']) {
$deleted = delete_plugins(array($info['plugin_path']));
if ($deleted) {
$result = array('deleted' => true);
} else {
$result = $this->_generic_error_response('delete_plugin_failed', array($query['plugin']));
}
} else {
$result = $this->_generic_error_response('plugin_not_installed', array($query['plugin']));
}
return $this->_response($result);
}
/**
* Updates/upgrade the plugin
*
* @param array $query Parameter array containing the filesystem credentials entered by the user along with the plugin name and slug
* @return array Contains the result of the current process
*/
public function update_plugin($query) {
$error = $this->_validate_fields_and_capabilities($query, array('plugin', 'slug'), array('update_plugins'));
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$info = $this->_get_plugin_info($query['plugin']);
// Make sure that we still have the plugin installed before running
// the update process
if ($info['installed']) {
// Load the updates command class if not existed
if (!class_exists('UpdraftCentral_Updates_Commands')) include_once('updates.php');
$update_command = new UpdraftCentral_Updates_Commands($this->rc);
$result = $update_command->update_plugin($info['plugin_path'], $query['slug']);
if (!empty($result['error'])) {
$result['values'] = array($query['plugin']);
}
} else {
$result = $this->_generic_error_response('plugin_not_installed', array($query['plugin']));
}
return $this->_response($result);
}
/**
* Gets the plugin information along with its active and install status
*
* @internal
* @param array $plugin The name of the plugin to pull the information from
* @return array Contains the plugin information
*/
private function _get_plugin_info($plugin) {
$info = array(
'active' => false,
'installed' => false
);
// Clear plugin cache so that newly installed/downloaded plugins
// gets reflected when calling "get_plugins"
wp_clean_plugins_cache();
// Gets all plugins available.
$get_plugins = get_plugins();
// Loops around each plugin available.
foreach ($get_plugins as $key => $value) {
// If the plugin name matches that of the specified name, it will gather details.
if ($value['Name'] === $plugin) {
$info['installed'] = true;
$info['active'] = is_plugin_active($key);
$info['plugin_path'] = $key;
$info['data'] = $value;
break;
}
}
return $info;
}
/**
* Loads all available plugins with additional attributes and settings needed by UpdraftCentral
*
* @param array $query Parameter array Any available parameters needed for this action
* @return array Contains the result of the current process
*/
public function load_plugins($query) {
$error = $this->_validate_fields_and_capabilities($query, array(), array('install_plugins', 'activate_plugins'));
if (!empty($error)) {
return $error;
}
$website = get_bloginfo('name');
$results = array();
// Load the updates command class if not existed
if (!class_exists('UpdraftCentral_Updates_Commands')) include_once('updates.php');
$updates = new UpdraftCentral_Updates_Commands($this->rc);
// Get plugins for update
$plugin_updates = $updates->get_item_updates('plugins');
// Get all plugins
$plugins = get_plugins();
foreach ($plugins as $key => $value) {
$slug = basename($key, '.php');
$plugin = new stdClass();
$plugin->name = $value['Name'];
$plugin->description = $value['Description'];
$plugin->slug = $slug;
$plugin->version = $value['Version'];
$plugin->author = $value['Author'];
$plugin->status = is_plugin_active($key) ? 'active' : 'inactive';
$plugin->website = $website;
$plugin->multisite = is_multisite();
if (!empty($plugin_updates[$key])) {
$update_info = $plugin_updates[$key];
if (version_compare($update_info->Version, $update_info->update->new_version, '<')) {
if (!empty($update_info->update->new_version)) $plugin->latest_version = $update_info->update->new_version;
if (!empty($update_info->update->package)) $plugin->download_link = $update_info->update->package;
if (!empty($update_info->update->sections)) $plugin->sections = $update_info->update->sections;
}
}
if (empty($plugin->short_description) && !empty($plugin->description)) {
// Only pull the first sentence as short description, it should be enough rather than displaying
// an empty description or a full blown one which the user can access anytime if they press on
// the view details link in UpdraftCentral.
$temp = explode('.', $plugin->description);
$short_description = $temp[0];
// Adding the second sentence wouldn't hurt, in case the first sentence is too short.
if (isset($temp[1])) $short_description .= '.'.$temp[1];
$plugin->short_description = $short_description.'.';
}
$results[] = $plugin;
}
$result = array(
'plugins' => $results
);
$result = array_merge($result, $this->_get_backup_credentials_settings(WP_PLUGIN_DIR));
return $this->_response($result);
}
/**
* Gets the backup and security credentials settings for this website
*
* @param array $query Parameter array Any available parameters needed for this action
* @return array Contains the result of the current process
*/
public function get_plugin_requirements() {
return $this->_response($this->_get_backup_credentials_settings(WP_PLUGIN_DIR));
}
}

View File

@@ -0,0 +1,768 @@
<?php
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
/**
* Handles Posts Commands
*/
class UpdraftCentral_Posts_Commands extends UpdraftCentral_Commands {
private $switched = false;
/**
* Function that gets called before every action
*
* @param string $command a string that corresponds to UDC command to call a certain method for this class.
* @param array $data an array of data post or get fields
* @param array $extra_info extrainfo use in the udrpc_action, e.g. user_id
*
* link to udrpc_action main function in class UpdraftPlus_UpdraftCentral_Listener
*/
public function _pre_action($command, $data, $extra_info) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
// Here we assign the current blog_id to a variable $blog_id
$blog_id = get_current_blog_id();
if (!empty($data['site_id'])) $blog_id = $data['site_id'];
if (function_exists('switch_to_blog') && is_multisite() && $blog_id) {
$this->switched = switch_to_blog($blog_id);
}
}
/**
* Function that gets called after every action
*
* @param string $command a string that corresponds to UDC command to call a certain method for this class.
* @param array $data an array of data post or get fields
* @param array $extra_info extrainfo use in the udrpc_action, e.g. user_id
*
* link to udrpc_action main function in class UpdraftPlus_UpdraftCentral_Listener
*/
public function _post_action($command, $data, $extra_info) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
// Here, we're restoring to the current (default) blog before we switched
if ($this->switched) restore_current_blog();
}
/**
* Method to fetch all posts which depends on parameters passed in site_post.js, on success return posts object
*
* @param array $params An array containing the "site_id" and "paged" parameters needed to pull all posts
* @return array
*
* @link {https://developer.wordpress.org/reference/functions/get_posts/}
* @link {https://developer.wordpress.org/reference/functions/wp_count_posts}
* @link {https://developer.wordpress.org/reference/functions/get_the_author_meta/}
* @link {https://developer.wordpress.org/reference/functions/get_the_category}
*/
public function get_requested_posts($params) {
if (empty($params['numberposts'])) return $this->_generic_error_response('numberposts_parameter_missing', $params);
// check paged parameter; if empty set to 1
$paged = !empty($params['paged']) ? (int) $params['paged'] : 1;
$args = array(
'posts_per_page' => $params['numberposts'],
'paged' => $paged,
'post_type' => 'post',
'post_status' => !empty($params['post_status']) ? $params['post_status'] : 'any'
);
if (!empty($params['category'][0])) {
$term_id = (int) $params['category'][0];
$args['category__in'] = array($term_id);
}
// Using default function get_posts to fetch all posts object from passed parameters
// Count all fetch posts objects
// get total fetch posts and divide to number of posts for pagination
$query = new WP_Query($args);
$result = $query->posts;
$count_posts = $query->found_posts;
$page_count = 0;
$postdata = array();
if ((int) $count_posts > 0) {
$page_count = absint((int) $count_posts / (int) $params['numberposts']);
$remainder = absint((int) $count_posts % (int) $params['numberposts']);
$page_count = ($remainder > 0) ? ++$page_count : $page_count;
}
$info = array(
'page' => $paged,
'pages' => $page_count,
'results' => $count_posts
);
if (empty($result)) {
$error_data = array(
'count' => $page_count,
'paged' => $paged,
'info' => $info
);
return $this->_generic_error_response('post_not_found_with_keyword', $error_data);
} else {
foreach ($result as $post) {
// initialize our stdclass variable data
$data = new stdClass();
// get the author name
$author = get_the_author_meta('display_name', $post->post_author);
// get categories associated with the post
$categories = get_the_category($post->ID);
$cat_array = array();
foreach ($categories as $category) {
$cat_array[] = $category->name;
}
// Adding author name and category assigned to the post object
$data->author_name = $author;
$data->category_name = $cat_array;
$data->post_title = $post->post_title;
$data->post_status = $post->post_status;
$data->ID = $post->ID;
$postdata[] = $data;
}
$response = array(
'posts' => $postdata,
'count' => $page_count,
'paged' => $paged,
'categories' => $this->get_requested_categories(array('parent' => 0, 'return_object' => true)),
'message' => "found_posts_count",
'params' => $params,
'info' => $info
);
}
return $this->_response($response);
}
/**
* Method to fetch post object based on parameter ID, on success return post object
*
* @param array $params An array containing the "site_id" and "ID" parameters needed to pull a single post
* @return array
*
* @link {https://developer.wordpress.org/reference/functions/get_post/}
* @link {https://developer.wordpress.org/reference/functions/switch_to_blog/}
*/
public function get_requested_post($params) {
// Check parameter ID if empty
if (empty($params['ID'])) {
return $this->_generic_error_response('post_id_not_set', array());
}
// initialize stdclass variable data
$data = new stdClass();
// assign parameter ID to a variable
$post_id = $params['ID'];
// using default get_post to get post object by its ID
$post = get_post($post_id);
// assign
$visibility = get_post_status($post_id);
// Get all associated category of the post
$categories = $this->get_requested_post_categories($post_id);
if (is_wp_error($post)) {
// Return the wp_error
$error_data = array(
'visibility' => $visibility,
'categories' => $categories,
);
return $this->_generic_error_response('posts_not_found', $error_data);
} else {
$data->ID = $post->ID;
$data->post_title = $post->post_title;
$data->post_content = $post->post_content;
$data->post_status = $post->post_status;
$data->guid = $post->guid;
$data->post_date = $post->post_date;
$response = array(
'posts' => $data,
'visibility' => $visibility,
'categories' => $categories,
'message' => "found_post"
);
return $this->_response($response);
}
}
/**
* Method to fetch array of categories loop through all children
*
* @param array $params An array containing the "site_id" and "parent" parameters needed to pull a collection of categories
* @return array
*
* @link {https://developer.wordpress.org/reference/functions/get_categories/}
* @link {https://developer.wordpress.org/reference/functions/switch_to_blog/}
*/
public function get_requested_categories($params) {
$parent = $params['parent'];
$categories = $this->get_taxonomy_hierarchy('category', $parent);
$category = new stdClass();
$arrobj = array();
// Add to existing category list | parent->children
$category->children = $categories;
$category->default = get_option('default_category');
$arrobj[] = $category;
if (!empty($params['return_object'])) {
return $arrobj;
}
return $this->_response($arrobj);
}
/**
* Method to get category on assigned to a post
*
* @param int $post_id The ID of the post where the categories are to be retrieve from
* @return array - returns an array of category
*
* @link {https://developer.wordpress.org/reference/functions/get_categories/}
* @link {https://developer.wordpress.org/reference/functions/switch_to_blog/}
*/
public function get_requested_post_categories($post_id) {
$categories = $this->get_taxonomy_hierarchy('category');
$category = new stdClass();
$arrobj = array();
// Add to existing category list | parent->children
$category->children = $categories;
$category->default = get_option('default_category');
$arrterms = array();
$post_terms = get_the_terms($post_id, 'category');
foreach ($category->children as $term) {
foreach ($post_terms as $post_term) {
$arrterms[] = $post_term->term_id;
if ($term->term_id == $post_term->term_id) {
$term->selected = 1;
}
}
}
$arrobj[] = $category;
return $arrobj;
}
/**
* Method used to insert post from UDC to remote site
* Using the default wp_insert_post function
*
* @param array $post_array Default post_type "post" and basic parameters post_title, post_content, category, post_status
* @return array - Containing information whether the process was successful or not.
* Post ID on success, custom error object on failure.
*
* @link {https://developer.wordpress.org/reference/functions/wp_insert_post/}
* @link {https://developer.wordpress.org/reference/functions/get_current_user_id/}
* @link {https://developer.wordpress.org/reference/functions/current_user_can/}
* @link {https://developer.wordpress.org/reference/functions/switch_to_blog/}
*/
public function insert_requested_post($post_array) {
// Check if post_title parameter is not set
if (empty($post_array['post_title'])) {
return $this->_generic_error_response('post_title_not_set', array());
}
// Check if user has capability
if (!current_user_can('edit_posts')) {
return $this->_generic_error_response('user_no_permission_to_edit_post', array());
}
$author = get_current_user_id();
$category = get_option('default_category');
$post_category = empty($post_array['post_category']) ? array($category) : $post_array['post_category'];
$post_title = $post_array['post_title'];
$post_content = $post_array['post_content'];
$post_status = $post_array['post_status'];
// Create post array
$post = array(
'post_title' => wp_strip_all_tags($post_title),
'post_content' => $post_content,
'post_author' => $author,
'post_category' => $post_category,
'post_status' => $post_status
);
// Insert the post array into the database, return post_id on success
$post_id = wp_insert_post($post);
// Check if result is false
if (is_wp_error($post_id)) {
$error_data = array(
'message' => __('Error inserting post')
);
return $this->_generic_error_response('post_insert_error', $error_data);
} else {
$result = array(
'ID' => $post_id,
'message' => "post_save_success",
'status' => $post_status
);
}
return $this->_response($result);
}
/**
* Method used to update post
* Using default wp_update_post
*
* @param array $params Post array to update specific post by ID
* @return array - Containing information whether the process was successful or not.
* Post ID on success, custom error object on failure.
*
* @link {https://developer.wordpress.org/reference/functions/wp_update_post/}
* @link {https://developer.wordpress.org/reference/functions/current_user_can/}
* @link {https://developer.wordpress.org/reference/functions/switch_to_blog/}
*/
public function update_requested_post($params) {
// Check post_id parameter if set
if (empty($params['ID'])) {
return $this->_generic_error_response('post_id_not_set', array());
}
// Check if user has capability
if (!current_user_can('edit_posts') && !current_user_can('edit_other_posts')) {
return $this->_generic_error_response('user_no_permission_to_edit_post', array());
}
$category = get_option('default_category');
$post_category = empty($post_array['post_category']) ? array($category) : $post_array['post_category'];
// Assign post array values
$post = array(
'post_title' => wp_strip_all_tags($params['post_title']),
'post_content' => $params['post_content'],
'ID' => (int) $params['ID'],
'post_status' => $params['post_status'],
'post_category' => $post_category
);
// Do post update
$response = wp_update_post($post);
$result = array();
// Check if response is false
if (is_wp_error($response)) {
$error_data = array(
'message' => __('Error updating post')
);
return $this->_generic_error_response('post_update_error', $error_data);
}
$result = array(
'ID' => $response,
'message' => "post_update_success",
'status' => $params['post_status']
);
return $this->_response($result);
}
/**
* Method used to move post to trash, default action trash
* If delete set to true will delete permanently following all wp process
* If force_delete is true bypass process and force delete post
*
* @param array $params An array containing the ID of the post to delete and a
* couple of flags ("delete" and "force_delete") that will determine whether
* the post will be moved to trash or permanently deleted.
* @return array - Containing information whether the process was successful or not. True on success, false on failure.
*
* @link {https://developer.wordpress.org/reference/functions/wp_trash_post/}
* @link {https://developer.wordpress.org/reference/functions/wp_delete_post/}
* @link {https://developer.wordpress.org/reference/functions/current_user_can/}
* @link {https://developer.wordpress.org/reference/functions/switch_to_blog/}
*/
public function trash_requested_post($params) {
// Check if post_id is set
if (empty($params['ID'])) {
return $this->_generic_error_response('post_id_not_set', array());
}
// Check user capability
if (!current_user_can('delete_posts')) {
return $this->_generic_error_response('user_no_permission_to_delete_post', array());
}
$post_id = (int) $params['ID'];
$forcedelete = !empty($params['force_delete']);
$trash = false;
// Here check if force_delete is set from UDC. then permanently delete bypass wp_trash_post.
if ($forcedelete) {
$response = wp_delete_post($post_id, $forcedelete);
} else {
$response = wp_trash_post($post_id);
$trash = true;
}
$result = array();
// Check if response if false
if (is_wp_error($response)) {
$error_data = array(
'message' => __('Error deleting post')
);
return $this->_generic_error_response('post_delete_error', $error_data);
}
$result = array(
'posts' => $response,
'error' => false,
);
if ($trash) {
$result["message"] = "post_has_been_moved_to_trash";
$status["status"] = "trash";
} else {
$result["message"] = "post_has_been_deleted_permanently";
$status["status"] = "delete";
}
return $this->_response($result);
}
/**
* Method used to insert/create a category
* Using default taxonomy "category"
* Will create slug based on cat_name parameter
*
* @param array $params cat_name parameter to insert category
* @return array - Containing the result of the process. Category ID, category object, etc.
*
* @link {https://developer.wordpress.org/reference/functions/wp_insert_category/}
* @link {https://developer.wordpress.org/reference/functions/current_user_can/}
* @link {https://developer.wordpress.org/reference/functions/switch_to_blog/}
*/
public function insert_requested_category($params) {
/**
* Include admin taxonomy.php file to enable us to use all necessary functions for post category
*/
$this->_admin_include('taxonomy.php');
$result = array();
// Check if parameter cat_name is set
if (empty($params['cat_name'])) {
return $this->_generic_error_response('category_name_not_set', array());
}
// Check user capability
if (!current_user_can('manage_categories')) {
return $this->_generic_error_response('user_no_permission_to_add_category', array());
}
// set category array
$args = array(
'cat_name' => $params["cat_name"],
'category_nicename' => sanitize_title($params["cat_name"])
);
// Do wp_insert_category
$term_id = wp_insert_category($args, true);
if (is_wp_error($response)) {
$error_data = array(
'message' => __('Error inserting category')
);
return $this->_generic_error_response('category_insert_error', $error_data);
}
$category = array(
'cat_name' => $params["cat_name"],
'term_id' => $term_id,
'parent' => 0
);
$result = array(
'ID' => $term_id,
'category' => $category,
'error' => false,
'message' => "category_added"
);
return $this->_response($result);
}
/**
* Method used to update/edit a category by its term_id
*
* @param array $param An array containing the "term_id" and "cat_name" parameters needed to edit the category
* @return array - Containing information as a result fo the process. True on success, false on failure.
*
* @link {https://developer.wordpress.org/reference/functions/wp_udpate_category/}
* @link {https://developer.wordpress.org/reference/functions/current_user_can/}
* @link {https://developer.wordpress.org/reference/functions/switch_to_blog/}
*/
public function edit_requested_category($param) {
/**
* Include admin taxonomy.php file to enable us to use all necessary functions for post category
*/
$this->_admin_include('taxonomy.php');
$result = array();
// Check if term_id is set
if (empty($param['term_id']) && empty($param['cat_name'])) {
return $this->_generic_error_response('term_id_or_category_not_set', array($params));
}
$term_id = $param['term_id'];
$cat_name = $param['cat_name'];
// Check user capability
if (!current_user_can('manage_categories')) {
return $this->_generic_error_response('user_no_permission_to_edit_category', array());
}
// Do term update
$response = wp_update_term($term_id, 'category', array('name' => $cat_name, 'slug' => sanitize_title($cat_name)));
// Check if response is false
if (is_wp_error($response)) {
$error_data = array(
'message' => __('Error updating category')
);
return $this->_generic_error_response('category_update_error', $error_data);
}
$result = array(
'category' => $cat_name,
'message' => "category_updated_to"
);
return $this->_response($result);
}
/**
* Method used to delete a category by term_id
*
* @param array $param An array containing the "term_id" needed to delete the category
* @return array - Containing information as a result fo the process. True on success, false on failure.
*
* @link {https://developer.wordpress.org/reference/functions/wp_delete_category/}
* @link {https://developer.wordpress.org/reference/functions/current_user_can/}
* @link {https://developer.wordpress.org/reference/functions/switch_to_blog/}
*/
public function delete_requested_category($param) {
/**
* Include admin taxonomy.php file to enable us to use all necessary functions for post category
*/
$this->_admin_include('taxonomy.php');
$result = array();
// Check if term_id is set
if (empty($param['term_id'])) {
return $this->_generic_error_response('term_id_not_set', array($params));
}
$term_id = $param['term_id'];
// Check user capability
if (!current_user_can('manage_categories')) {
return $this->_generic_error_response('user_no_permission_to_delete_category', array());
}
// Do wp_delete_category
$response = wp_delete_category($term_id);
if (is_wp_error($response)) {
$error_data = array(
'message' => __('Error deleting category')
);
return $this->_generic_error_response('user_no_permission_to_delete_category', $error_data);
}
$result = array(
'error' => false,
'message' => "category_deleted"
);
return $this->_response($result);
}
/**
* Method to fetch post search by post title
* Will return all posts if no match was found
*
* @param array $params An array containing the keyword to be used to search all available posts
* @return array - Containing the result of the process. Post object on success, a no matched message on failure.
*
* @link {https://developer.wordpress.org/reference/functions/get_post/}
* @link {https://developer.wordpress.org/reference/functions/switch_to_blog/}
*/
public function find_post_by_title($params) {
$paged = !empty($params['paged']) ? (int) $params['paged'] : 1;
// Check if keyword is empty or null
if (empty($params['s'])) {
return $this->_generic_error_response('search_generated_no_result', array());
}
// Set an array with post_type to search only post
$query_string = array(
's' => $params['s'],
'post_type' => 'post',
'posts_per_page' => $params['numberposts'],
'paged' => $paged
);
$query = new WP_Query($query_string);
$postdata = array();
$count_posts = $query->found_posts;
if ((int) $count_posts > 0) {
if (empty($params['numberposts'])) return $this->_generic_error_response('numberposts_parameter_missing', $params);
$page_count = absint((int) $count_posts / (int) $params['numberposts']);
$remainder = absint((int) $count_posts % (int) $params['numberposts']);
$page_count = ($remainder > 0) ? ++$page_count : $page_count;
}
$info = array(
'page' => $paged,
'pages' => $page_count,
'results' => $count_posts
);
$response = array();
if ($query->have_posts()) {
foreach ($query->posts as $post) {
// initialize stdclass variable data
$data = new stdClass();
// get the author name
$author = get_the_author_meta('display_name', $post->post_author);
// get categories associated with the post
$categories = get_the_category($post->ID);
$cat_array = array();
foreach ($categories as $category) {
$cat_array[] = $category->name;
}
// Adding author name and category assigned to the post object
$data->author_name = $author;
$data->category_name = $cat_array;
$data->post_title = $post->post_title;
$data->post_status = $post->post_status;
$postdata[] = $data;
}
$response = array(
'categories' => $this->get_requested_categories(array('parent' => 0, 'return_object' => true)),
'posts' => $postdata,
'n' => $arr,
'count' => $count_posts,
'paged' => $paged,
'message' => "found_post",
'info' => $info
);
} else {
$error_data = array(
'count' => $count_posts,
'paged' => $paged,
'info' => $info
);
return $this->_generic_error_response('post_not_found_with_keyword', $error_data);
}
return $this->_response($response);
}
/**
* Recursively get taxonomy and its children
*
* @param string $taxonomy name e.g. category, post_tags
* @param int $parent id of the category to be fetch
* @return array Containing all the categories with children
*
* @link {https://developer.wordpress.org/reference/functions/get_terms/}
*/
public function get_taxonomy_hierarchy($taxonomy, $parent = 0) {
// only 1 taxonomy
$taxonomy = is_array($taxonomy) ? array_shift($taxonomy) : $taxonomy;
// get all direct decendants of the $parent
$terms = get_terms($taxonomy, array( 'parent' => $parent, 'hide_empty' => false));
// prepare a new array. these are the children of $parent
// we'll ultimately copy all the $terms into this new array, but only after they
// find their own children
$children = array();
// go through all the direct decendants of $parent, and gather their children
foreach ($terms as $term) {
// recurse to get the direct decendants of "this" term
$term->children = $this->get_taxonomy_hierarchy($taxonomy, $term->term_id);
// add the term to our new array
$children[] = $term;
}
// send the results back to the caller
return $children;
}
/**
* Recursively get all taxonomies as complete hierarchies
*
* @param array $taxonomies array of taxonomy slugs
* @param int $parent starting id to fetch
*
* @return array Containing all the taxonomies
*/
public function get_taxonomy_hierarchy_multiple($taxonomies, $parent = 0) {
if (!is_array($taxonomies)) {
$taxonomies = array($taxonomies);
}
$results = array();
foreach ($taxonomies as $taxonomy) {
$terms = $this->get_taxonomy_hierarchy($taxonomy, $parent);
if ($terms) {
$results[$taxonomy] = $terms;
}
}
return $results;
}
}

View File

@@ -0,0 +1,559 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No access.');
/**
* Handles UpdraftCentral Theme Commands which basically handles
* the installation and activation of a theme
*/
class UpdraftCentral_Theme_Commands extends UpdraftCentral_Commands {
private $switched = false;
/**
* Function that gets called before every action
*
* @param string $command a string that corresponds to UDC command to call a certain method for this class.
* @param array $data an array of data post or get fields
* @param array $extra_info extrainfo use in the udrpc_action, e.g. user_id
*
* link to udrpc_action main function in class UpdraftPlus_UpdraftCentral_Listener
*/
public function _pre_action($command, $data, $extra_info) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
// Here we assign the current blog_id to a variable $blog_id
$blog_id = get_current_blog_id();
if (!empty($data['site_id'])) $blog_id = $data['site_id'];
if (function_exists('switch_to_blog') && is_multisite() && $blog_id) {
$this->switched = switch_to_blog($blog_id);
}
}
/**
* Function that gets called after every action
*
* @param string $command a string that corresponds to UDC command to call a certain method for this class.
* @param array $data an array of data post or get fields
* @param array $extra_info extrainfo use in the udrpc_action, e.g. user_id
*
* link to udrpc_action main function in class UpdraftPlus_UpdraftCentral_Listener
*/
public function _post_action($command, $data, $extra_info) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
// Here, we're restoring to the current (default) blog before we switched
if ($this->switched) restore_current_blog();
}
/**
* Constructor
*/
public function __construct() {
$this->_admin_include('theme.php', 'file.php', 'template.php', 'class-wp-upgrader.php', 'theme-install.php', 'update.php');
}
/**
* Checks whether the theme is currently installed and activated.
*
* @param array $query Parameter array containing the name of the theme to check
* @return array Contains the result of the current process
*/
public function is_theme_installed($query) {
if (!isset($query['theme']))
return $this->_generic_error_response('theme_name_required');
$result = $this->_get_theme_info($query['theme']);
return $this->_response($result);
}
/**
* Applies currently requested action for theme processing
*
* @param string $action The action to apply (e.g. activate or install)
* @param array $query Parameter array containing information for the currently requested action
*
* @return array
*/
private function _apply_theme_action($action, $query) {
$result = array();
switch ($action) {
case 'activate':
$info = $this->_get_theme_info($query['theme']);
if ($info['installed']) {
switch_theme($info['slug']);
if (wp_get_theme()->get_stylesheet() === $info['slug']) {
$result = array('activated' => true);
} else {
$result = $this->_generic_error_response('theme_not_activated', array($query['theme']));
}
} else {
$result = $this->_generic_error_response('theme_not_installed', array($query['theme']));
}
break;
case 'network_enable':
$info = $this->_get_theme_info($query['theme']);
if ($info['installed']) {
// Make sure that network_enable_theme is present and callable since
// it is only available at 4.6. If not, we'll do things the old fashion way
if (is_callable(array('WP_Theme', 'network_enable_theme'))) {
WP_Theme::network_enable_theme($info['slug']);
} else {
$allowed_themes = get_site_option('allowedthemes');
$allowed_themes[$info['slug']] = true;
update_site_option('allowedthemes', $allowed_themes);
}
$allowed = WP_Theme::get_allowed_on_network();
if (is_array($allowed) && !empty($allowed[$info['slug']])) {
$result = array('enabled' => true);
} else {
$result = $this->_generic_error_response('theme_not_enabled', array($query['theme']));
}
} else {
$result = $this->_generic_error_response('theme_not_installed', array($query['theme']));
}
break;
case 'network_disable':
$info = $this->_get_theme_info($query['theme']);
if ($info['installed']) {
// Make sure that network_disable_theme is present and callable since
// it is only available at 4.6. If not, we'll do things the old fashion way
if (is_callable(array('WP_Theme', 'network_disable_theme'))) {
WP_Theme::network_disable_theme($info['slug']);
} else {
$allowed_themes = get_site_option('allowedthemes');
if (isset($allowed_themes[$info['slug']])) {
unset($allowed_themes[$info['slug']]);
}
update_site_option('allowedthemes', $allowed_themes);
}
$allowed = WP_Theme::get_allowed_on_network();
if (is_array($allowed) && empty($allowed[$info['slug']])) {
$result = array('disabled' => true);
} else {
$result = $this->_generic_error_response('theme_not_disabled', array($query['theme']));
}
} else {
$result = $this->_generic_error_response('theme_not_installed', array($query['theme']));
}
break;
case 'install':
$api = themes_api('theme_information', array(
'slug' => $query['slug'],
'fields' => array(
'description' => true,
'sections' => false,
'rating' => true,
'ratings' => true,
'downloaded' => true,
'downloadlink' => true,
'last_updated' => true,
'screenshot_url' => true,
'parent' => true,
)
));
if (is_wp_error($api)) {
$result = $this->_generic_error_response('generic_response_error', array($api->get_error_message()));
} else {
$info = $this->_get_theme_info($query['theme']);
$installed = $info['installed'];
if (!$installed) {
// WP < 3.7
if (!class_exists('Automatic_Upgrader_Skin')) include_once(UPDRAFTPLUS_DIR.'/central/classes/class-automatic-upgrader-skin.php');
$skin = new Automatic_Upgrader_Skin();
$upgrader = new Theme_Upgrader($skin);
$download_link = $api->download_link;
$installed = $upgrader->install($download_link);
}
if (!$installed) {
$result = $this->_generic_error_response('theme_install_failed', array($query['theme']));
} else {
$result = array('installed' => true);
}
}
break;
}
return $result;
}
/**
* Preloads the submitted credentials to the global $_POST variable
*
* @param array $query Parameter array containing information for the currently requested action
*/
private function _preload_credentials($query) {
if (!empty($query) && isset($query['filesystem_credentials'])) {
parse_str($query['filesystem_credentials'], $filesystem_credentials);
if (is_array($filesystem_credentials)) {
foreach ($filesystem_credentials as $key => $value) {
// Put them into $_POST, which is where request_filesystem_credentials() checks for them.
$_POST[$key] = $value;
}
}
}
}
/**
* Checks whether we have the required fields submitted and the user has
* the capabilities to execute the requested action
*
* @param array $query The submitted information
* @param array $fields The required fields to check
* @param array $capabilities The capabilities to check and validate
*
* @return array|string
*/
private function _validate_fields_and_capabilities($query, $fields, $capabilities) {
$error = '';
if (!empty($fields)) {
for ($i=0; $i<count($fields); $i++) {
$field = $fields[$i];
if (!isset($query[$field])) {
if ('keyword' === $field) {
$error = $this->_generic_error_response('keyword_required');
} else {
$error = $this->_generic_error_response('theme_'.$query[$field].'_required');
}
break;
}
}
}
if (empty($error) && !empty($capabilities)) {
for ($i=0; $i<count($capabilities); $i++) {
if (!current_user_can($capabilities[$i])) {
$error = $this->_generic_error_response('theme_insufficient_permission');
break;
}
}
}
return $error;
}
/**
* Activates the theme
*
* @param array $query Parameter array containing the name of the theme to activate
* @return array Contains the result of the current process
*/
public function activate_theme($query) {
$error = $this->_validate_fields_and_capabilities($query, array('theme'), array('switch_themes'));
if (!empty($error)) {
return $error;
}
$result = $this->_apply_theme_action('activate', $query);
if (empty($result['activated'])) {
return $result;
}
return $this->_response($result);
}
/**
* Enables theme for network
*
* @param array $query Parameter array containing the name of the theme to activate
* @return array Contains the result of the current process
*/
public function network_enable_theme($query) {
$error = $this->_validate_fields_and_capabilities($query, array('theme'), array('switch_themes'));
if (!empty($error)) {
return $error;
}
$result = $this->_apply_theme_action('network_enable', $query);
if (empty($result['enabled'])) {
return $result;
}
return $this->_response($result);
}
/**
* Disables theme from network
*
* @param array $query Parameter array containing the name of the theme to activate
* @return array Contains the result of the current process
*/
public function network_disable_theme($query) {
$error = $this->_validate_fields_and_capabilities($query, array('theme'), array('switch_themes'));
if (!empty($error)) {
return $error;
}
$result = $this->_apply_theme_action('network_disable', $query);
if (empty($result['disabled'])) {
return $result;
}
return $this->_response($result);
}
/**
* Download, install and activates the theme
*
* @param array $query Parameter array containing the filesystem credentials entered by the user along with the theme name and slug
* @return array Contains the result of the current process
*/
public function install_activate_theme($query) {
$error = $this->_validate_fields_and_capabilities($query, array('theme', 'slug'), array('install_themes', 'switch_themes'));
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$result = $this->_apply_theme_action('install', $query);
if (!empty($result['installed']) && $result['installed']) {
$result = $this->_apply_theme_action('activate', $query);
if (empty($result['activated'])) {
return $result;
}
} else {
return $result;
}
return $this->_response($result);
}
/**
* Download, install the theme
*
* @param array $query Parameter array containing the filesystem credentials entered by the user along with the theme name and slug
* @return array Contains the result of the current process
*/
public function install_theme($query) {
$error = $this->_validate_fields_and_capabilities($query, array('theme', 'slug'), array('install_themes'));
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$result = $this->_apply_theme_action('install', $query);
if (empty($result['installed'])) {
return $result;
}
return $this->_response($result);
}
/**
* Uninstall/delete the theme
*
* @param array $query Parameter array containing the filesystem credentials entered by the user along with the theme name and slug
* @return array Contains the result of the current process
*/
public function delete_theme($query) {
$error = $this->_validate_fields_and_capabilities($query, array('theme'), array('delete_themes'));
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$info = $this->_get_theme_info($query['theme']);
if ($info['installed']) {
$deleted = delete_theme($info['slug']);
if ($deleted) {
$result = array('deleted' => true);
} else {
$result = $this->_generic_error_response('delete_theme_failed', array($query['theme']));
}
} else {
$result = $this->_generic_error_response('theme_not_installed', array($query['theme']));
}
return $this->_response($result);
}
/**
* Updates/upgrade the theme
*
* @param array $query Parameter array containing the filesystem credentials entered by the user along with the theme name and slug
* @return array Contains the result of the current process
*/
public function update_theme($query) {
$error = $this->_validate_fields_and_capabilities($query, array('theme'), array('update_themes'));
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$info = $this->_get_theme_info($query['theme']);
// Make sure that we still have the theme installed before running
// the update process
if ($info['installed']) {
// Load the updates command class if not existed
if (!class_exists('UpdraftCentral_Updates_Commands')) include_once('updates.php');
$update_command = new UpdraftCentral_Updates_Commands($this->rc);
$result = $update_command->update_theme($info['slug']);
if (!empty($result['error'])) {
$result['values'] = array($query['theme']);
}
} else {
return $this->_generic_error_response('theme_not_installed', array($query['theme']));
}
return $this->_response($result);
}
/**
* Gets the theme information along with its active and install status
*
* @internal
* @param array $theme The name of the theme to pull the information from
* @return array Contains the theme information
*/
private function _get_theme_info($theme) {
$info = array(
'active' => false,
'installed' => false
);
// Clear theme cache so that newly installed/downloaded themes
// gets reflected when calling "get_themes"
wp_clean_themes_cache();
// Gets all themes available.
$themes = wp_get_themes();
$current_theme_slug = basename(get_stylesheet_directory());
// Loops around each theme available.
foreach ($themes as $slug => $value) {
$name = $value->get('Name');
$theme_name = !empty($name) ? $name : $slug;
// If the theme name matches that of the specified name, it will gather details.
if ($theme_name === $theme) {
$info['installed'] = true;
$info['active'] = ($slug === $current_theme_slug) ? true : false;
$info['slug'] = $slug;
$info['data'] = $value;
break;
}
}
return $info;
}
/**
* Loads all available themes with additional attributes and settings needed by UpdraftCentral
*
* @param array $query Parameter array Any available parameters needed for this action
* @return array Contains the result of the current process
*/
public function load_themes($query) {
$error = $this->_validate_fields_and_capabilities($query, array(), array('install_themes', 'switch_themes'));
if (!empty($error)) {
return $error;
}
$website = get_bloginfo('name');
$results = array();
// Load the updates command class if not existed
if (!class_exists('UpdraftCentral_Updates_Commands')) include_once('updates.php');
$updates = new UpdraftCentral_Updates_Commands($this->rc);
// Get themes for update
$theme_updates = (array) $updates->get_item_updates('themes');
// Get all themes
$themes = wp_get_themes();
$current_theme_slug = basename(get_stylesheet_directory());
foreach ($themes as $slug => $value) {
$name = $value->get('Name');
$theme_name = !empty($name) ? $name : $slug;
$theme = new stdClass();
$theme->name = $theme_name;
$theme->description = $value->get('Description');
$theme->slug = $slug;
$theme->version = $value->get('Version');
$theme->author = $value->get('Author');
$theme->status = ($slug === $current_theme_slug) ? 'active' : 'inactive';
$template = $value->get('Template');
$theme->child_theme = !empty($template) ? true : false;
$theme->website = $website;
$theme->multisite = is_multisite();
if ($theme->child_theme) {
$parent_theme = wp_get_theme($template);
$parent_name = $parent_theme->get('Name');
$theme->parent = !empty($parent_name) ? $parent_name : $parent_theme->get_stylesheet();
}
if (!empty($theme_updates[$slug])) {
$update_info = $theme_updates[$slug];
if (version_compare($theme->version, $update_info->update['new_version'], '<')) {
if (!empty($update_info->update['new_version'])) $theme->latest_version = $update_info->update['new_version'];
if (!empty($update_info->update['package'])) $theme->download_link = $update_info->update['package'];
}
}
if (empty($theme->short_description) && !empty($theme->description)) {
// Only pull the first sentence as short description, it should be enough rather than displaying
// an empty description or a full blown one which the user can access anytime if they press on
// the view details link in UpdraftCentral.
$temp = explode('.', $theme->description);
$short_description = $temp[0];
// Adding the second sentence wouldn't hurt, in case the first sentence is too short.
if (isset($temp[1])) $short_description .= '.'.$temp[1];
$theme->short_description = $short_description.'.';
}
$results[] = $theme;
}
$result = array(
'themes' => $results,
'theme_updates' => $theme_updates,
);
$result = array_merge($result, $this->_get_backup_credentials_settings(get_theme_root()));
return $this->_response($result);
}
/**
* Gets the backup and security credentials settings for this website
*
* @param array $query Parameter array Any available parameters needed for this action
* @return array Contains the result of the current process
*/
public function get_theme_requirements() {
return $this->_response($this->_get_backup_credentials_settings(get_theme_root()));
}
}

View File

@@ -0,0 +1,865 @@
<?php
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
class UpdraftCentral_Updates_Commands extends UpdraftCentral_Commands {
public function do_updates($updates) {
if (!is_array($updates)) $this->_generic_error_response('invalid_data');
if (!empty($updates['plugins']) && !current_user_can('update_plugins')) return $this->_generic_error_response('updates_permission_denied', 'update_plugins');
if (!empty($updates['themes']) && !current_user_can('update_themes')) return $this->_generic_error_response('updates_permission_denied', 'update_themes');
if (!empty($updates['core']) && !current_user_can('update_core')) return $this->_generic_error_response('updates_permission_denied', 'update_core');
if (!empty($updates['translations']) && !$this->user_can_update_translations()) return $this->_generic_error_response('updates_permission_denied', 'update_translations');
$this->_admin_include('plugin.php', 'update.php', 'file.php', 'template.php');
$this->_frontend_include('update.php');
if (!empty($updates['meta']) && isset($updates['meta']['filesystem_credentials'])) {
parse_str($updates['meta']['filesystem_credentials'], $filesystem_credentials);
if (is_array($filesystem_credentials)) {
foreach ($filesystem_credentials as $key => $value) {
// Put them into $_POST, which is where request_filesystem_credentials() checks for them.
$_POST[$key] = $value;
}
}
}
$plugins = empty($updates['plugins']) ? array() : $updates['plugins'];
$plugin_updates = array();
foreach ($plugins as $plugin_info) {
$plugin_file = $plugin_info['plugin'];
$plugin_updates[] = $this->_update_plugin($plugin_info['plugin'], $plugin_info['slug']);
}
$themes = empty($updates['themes']) ? array() : $updates['themes'];
$theme_updates = array();
foreach ($themes as $theme_info) {
$theme = $theme_info['theme'];
$theme_updates[] = $this->_update_theme($theme);
}
$cores = empty($updates['core']) ? array() : $updates['core'];
$core_updates = array();
foreach ($cores as $core) {
$core_updates[] = $this->_update_core(null);
// Only one (and always we go to the latest version) - i.e. we ignore the passed parameters
break;
}
$translation_updates = array();
if (!empty($updates['translations'])) {
$translation_updates[] = $this->_update_translation();
}
return $this->_response(array(
'plugins' => $plugin_updates,
'themes' => $theme_updates,
'core' => $core_updates,
'translations' => $translation_updates,
));
}
/**
* Updates a plugin. A facade method that exposes a private updates
* feature for other modules to consume.
*
* @param string $plugin Specific plugin to be updated
* @param string $slug Unique key passed for updates
*
* @return array
*/
public function update_plugin($plugin, $slug) {
return $this->_update_plugin($plugin, $slug);
}
/**
* Updates a theme. A facade method that exposes a private updates
* feature for other modules to consume.
*
* @param string $theme Specific theme to be updated
*
* @return array
*/
public function update_theme($theme) {
return $this->_update_theme($theme);
}
/**
* Gets available updates for a certain entity (e.g. plugin or theme). A facade method that
* exposes a private updates feature for other modules to consume.
*
* @param string $entity The name of the entity that this request is intended for (e.g. themes or plugins)
*
* @return array
*/
public function get_item_updates($entity) {
$updates = array();
switch ($entity) {
case 'themes':
wp_update_themes();
$updates = $this->maybe_add_third_party_items(get_theme_updates(), 'theme');
break;
case 'plugins':
wp_update_plugins();
$updates = $this->maybe_add_third_party_items(get_plugin_updates(), 'plugin');
break;
}
return $updates;
}
/**
* Mostly from wp_ajax_update_plugin() in wp-admin/includes/ajax-actions.php (WP 4.5.2)
* Code-formatting style has been retained from the original, for ease of comparison/updating
*
* @param string $plugin Specific plugin to be updated
* @param string $slug Unique key passed for updates
* @return array
*/
private function _update_plugin($plugin, $slug) {
$status = array(
'update' => 'plugin',
'plugin' => $plugin,
'slug' => sanitize_key($slug),
'oldVersion' => '',
'newVersion' => '',
);
if (false !== strpos($plugin, '..') || false !== strpos($plugin, ':') || !preg_match('#^[^\/]#i', $plugin)) {
$status['error'] = 'not_found';
return $status;
}
$plugin_data = get_plugin_data(WP_PLUGIN_DIR . '/' . $plugin);
if (!isset($plugin_data['Name']) || !isset($plugin_data['Author']) || ('' == $plugin_data['Name'] && '' == $plugin_data['Author'])) {
$status['error'] = 'not_found';
return $status;
}
if ($plugin_data['Version']) {
$status['oldVersion'] = $plugin_data['Version'];
}
if (!current_user_can('update_plugins')) {
$status['error'] = 'updates_permission_denied';
return $status;
}
include_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php');
wp_update_plugins();
// WP < 3.7
if (!class_exists('Automatic_Upgrader_Skin')) include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/classes/class-automatic-upgrader-skin.php');
$skin = new Automatic_Upgrader_Skin();
$upgrader = new Plugin_Upgrader($skin);
$result = $upgrader->bulk_upgrade(array($plugin));
if (is_array($result) && empty($result[$plugin]) && is_wp_error($skin->result)) {
$result = $skin->result;
}
$status['messages'] = $upgrader->skin->get_upgrade_messages();
if (is_array($result) && !empty($result[$plugin])) {
$plugin_update_data = current($result);
/*
* If the `update_plugins` site transient is empty (e.g. when you update
* two plugins in quick succession before the transient repopulates),
* this may be the return.
*
* Preferably something can be done to ensure `update_plugins` isn't empty.
* For now, surface some sort of error here.
*/
if (true === $plugin_update_data) {
$status['error'] = 'update_failed';
return $status;
}
$plugin_data = get_plugins('/' . $result[$plugin]['destination_name']);
$plugin_data = reset($plugin_data);
if ($plugin_data['Version']) {
$status['newVersion'] = $plugin_data['Version'];
}
return $status;
} elseif (is_wp_error($result)) {
$status['error'] = $result->get_error_code();
$status['error_message'] = $result->get_error_message();
return $status;
} elseif (is_bool($result) && !$result) {
$status['error'] = 'unable_to_connect_to_filesystem';
global $wp_filesystem;
// Pass through the error from WP_Filesystem if one was raised
if (isset($wp_filesystem->errors) && is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
$status['error'] = $wp_filesystem->errors->get_error_code();
$status['error_message'] = $wp_filesystem->errors->get_error_message();
}
return $status;
} else {
// An unhandled error occured
$status['error'] = 'update_failed';
return $status;
}
}
/**
* Adapted from _update_theme (above)
*
* @param string $core
* @return array
*/
private function _update_core($core) {
global $wp_filesystem;
$status = array(
'update' => 'core',
'core' => $core,
'oldVersion' => '',
'newVersion' => '',
);
include(ABSPATH.WPINC.'/version.php');
$status['oldVersion'] = $wp_version;
if (!current_user_can('update_core')) {
$status['error'] = 'updates_permission_denied';
return $status;
}
include_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php');
wp_version_check();
$locale = get_locale();
$core_update_key = false;
$core_update_latest_version = false;
$get_core_updates = get_core_updates();
@include(ABSPATH.WPINC.'/version.php');
foreach ($get_core_updates as $k => $core_update) {
if (isset($core_update->version) && version_compare($core_update->version, $wp_version, '>') && version_compare($core_update->version, $core_update_latest_version, '>')) {
$core_update_latest_version = $core_update->version;
$core_update_key = $k;
}
}
if (false === $core_update_key) {
$status['error'] = 'no_update_found';
return $status;
}
$update = $get_core_updates[$core_update_key];
// WP < 3.7
if (!class_exists('Automatic_Upgrader_Skin')) include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/classes/class-automatic-upgrader-skin.php');
$skin = new Automatic_Upgrader_Skin();
$upgrader = new Core_Upgrader($skin);
$result = $upgrader->upgrade($update);
$status['messages'] = $upgrader->skin->get_upgrade_messages();
if (is_wp_error($result)) {
$status['error'] = $result->get_error_code();
$status['error_message'] = $result->get_error_message();
return $status;
} elseif (is_bool($result) && !$result) {
$status['error'] = 'unable_to_connect_to_filesystem';
// Pass through the error from WP_Filesystem if one was raised
if (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
$status['error'] = $wp_filesystem->errors->get_error_code();
$status['error_message'] = $wp_filesystem->errors->get_error_message();
}
return $status;
} elseif (preg_match('/^[0-9]/', $result)) {
$status['newVersion'] = $result;
return $status;
} else {
// An unhandled error occured
$status['error'] = 'update_failed';
return $status;
}
}
private function _update_theme($theme) {
global $wp_filesystem;
$status = array(
'update' => 'theme',
'theme' => $theme,
'oldVersion' => '',
'newVersion' => '',
);
if (false !== strpos($theme, '/') || false !== strpos($theme, '\\')) {
$status['error'] = 'not_found';
return $status;
}
$theme_version = $this->get_theme_version($theme);
if (false === $theme_version) {
$status['error'] = 'not_found';
return $status;
}
$status['oldVersion'] = $theme_version;
if (!current_user_can('update_themes')) {
$status['error'] = 'updates_permission_denied';
return $status;
}
include_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php');
wp_update_themes();
// WP < 3.7
if (!class_exists('Automatic_Upgrader_Skin')) include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/classes/class-automatic-upgrader-skin.php');
$skin = new Automatic_Upgrader_Skin();
$upgrader = new Theme_Upgrader($skin);
$upgrader->init();
$result = $upgrader->bulk_upgrade(array($theme));
if (is_array($result) && empty($result[$theme]) && is_wp_error($skin->result)) {
$result = $skin->result;
}
$status['messages'] = $upgrader->skin->get_upgrade_messages();
if (is_array($result) && !empty($result[$theme])) {
$theme_update_data = current($result);
/*
* If the `update_themes` site transient is empty (e.g. when you update
* two plugins in quick succession before the transient repopulates),
* this may be the return.
*
* Preferably something can be done to ensure `update_themes` isn't empty.
* For now, surface some sort of error here.
*/
if (true === $theme_update_data) {
$status['error'] = 'update_failed';
return $status;
}
$new_theme_version = $this->get_theme_version($theme);
if (false === $new_theme_version) {
$status['error'] = 'update_failed';
return $status;
}
$status['newVersion'] = $new_theme_version;
return $status;
} elseif (is_wp_error($result)) {
$status['error'] = $result->get_error_code();
$status['error_message'] = $result->get_error_message();
return $status;
} elseif (is_bool($result) && !$result) {
$status['error'] = 'unable_to_connect_to_filesystem';
// Pass through the error from WP_Filesystem if one was raised
if (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
$status['error'] = $wp_filesystem->errors->get_error_code();
$status['error_message'] = $wp_filesystem->errors->get_error_message();
}
return $status;
} else {
// An unhandled error occured
$status['error'] = 'update_failed';
return $status;
}
}
/**
* Updates available translations for this website
*
* @return Array
*/
private function _update_translation() {
global $wp_filesystem;
include_once(ABSPATH . 'wp-admin/includes/class-wp-upgrader.php');
if (!class_exists('Automatic_Upgrader_Skin')) include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/classes/class-automatic-upgrader-skin.php');
$skin = new Automatic_Upgrader_Skin();
$upgrader = new Language_Pack_Upgrader($skin);
$result = $upgrader->bulk_upgrade();
if (is_array($result) && !empty($result)) {
$status['success'] = true;
} elseif (is_wp_error($result)) {
$status['error'] = $result->get_error_code();
$status['error_message'] = $result->get_error_message();
} elseif (is_bool($result) && !$result) {
$status['error'] = 'unable_to_connect_to_filesystem';
// Pass through the error from WP_Filesystem if one was raised
if (is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
$status['error'] = $wp_filesystem->errors->get_error_code();
$status['error_message'] = $wp_filesystem->errors->get_error_message();
}
} elseif (is_bool($result) && $result) {
$status['error'] = 'up_to_date';
} else {
// An unhandled error occured
$status['error'] = 'update_failed';
}
return $status;
}
private function get_theme_version($theme) {
if (function_exists('wp_get_theme')) {
// Since WP 3.4.0
$theme = wp_get_theme($theme);
if (is_a($theme, 'WP_Theme')) {
return $theme->Version;
} else {
return false;
}
} else {
$theme_data = get_theme_data(WP_CONTENT_DIR . '/themes/'.$theme.'/style.css');
if (isset($theme_data['Version'])) {
return $theme_data['Version'];
} else {
return false;
}
}
}
/**
* Adding third-party plugins/theme for UDC automatic updates, for some updaters which store their information when the transient is set, instead of (like most) when it is fetched
*
* @param Array $items A collection of plugins or themes for updates
* @param String $type A string indicating which type of collection to process (e.g. 'plugin' or 'theme')
* @return Array An updated collection of plugins or themes for updates
*/
private function maybe_add_third_party_items($items, $type) {
// Here we're preparing a dummy transient object that will be pass to the filter
// and gets populated by those plugins or themes that hooked into the "pre_set_site_transient_*" filter.
//
// We're setting some default properties so that plugins and themes won't be able to bypass populating them,
// because most of the plugins and themes updater scripts checks whether or not these properties are set and
// non-empty or passed the 12 hour period (where WordPress re-starts the process of checking updates for
// these plugins and themes), otherwise, they bypass populating the update/upgrade info for these items.
$transient = (object) array(
'last_checked' => time() - (13 * 3600), /* Making sure that we passed the 12 hour period check */
'checked' => array('default' => 'none'),
'response' => array('default' => 'none')
);
// Most of the premium plugin developers are hooking into the "pre_set_site_transient_update_plugins" and
// "pre_set_site_transient_update_themes" filters if they want their plugins or themes to support automatic
// updates. Thus, we're making sure here that if for some reason, those plugins or themes didn't get through
// and added to the "update_plugins" or "update_themes" transients when calling the get_site_transient('update_plugins')
// or get_site_transient('update_themes') we add them here manually.
$filters = apply_filters("pre_set_site_transient_update_{$type}s", $transient, "update_{$type}s");
$all_items = array();
switch ($type) {
case 'plugin':
$all_items = get_plugins();
break;
case 'theme':
$this->_frontend_include('theme.php');
if (function_exists('wp_get_themes')) {
$themes = wp_get_themes();
if (!empty($themes)) {
// We make sure that the return key matched the previous
// key from "get_themes", otherwise, no updates will be found
// even if it does have one. "get_themes" returns the name of the
// theme as the key while "wp_get_themes" returns the slug.
foreach ($themes as $slug => $theme) {
$all_items[$theme->Name] = $theme;
}
}
} else {
$all_items = get_themes();
}
break;
default:
break;
}
if (!empty($all_items)) {
$all_items = (array) $all_items;
foreach ($all_items as $key => $data) {
if (!isset($items[$key]) && isset($filters->response[$key])) {
$update_info = ('plugin' === $type) ? $filters->response[$key] : $data;
// If "package" is empty, it means that this plugin or theme does not support automatic updates
// currently, since the "package" field is the one holding the download link of these plugins/themes
// and WordPress is using this field to download the latest version of these items.
//
// Most of the time, this "package" field is not empty, but for premium plugins/themes this can be
// conditional, only then if the user provides a legit access or api key can this field be populated or available.
//
// We set this variable to "false" by default, as plugins/themes hosted in wordpress.org always sets this
// to the downloadable zip file of the plugin/theme.
//
// N.B. We only add premium plugins/themes that has this "package" field set and non-empty, otherwise, it
// does not support automatic updates as explained above.
$is_package_empty = false;
if (is_object($update_info)) {
if (!isset($update_info->package) || empty($update_info->package)) {
$is_package_empty = true;
}
} elseif (is_array($update_info)) {
if (!isset($update_info['package']) || empty($update_info['package'])) {
$is_package_empty = true;
}
}
// Add this plugin/theme to the current updates collection
if (!$is_package_empty) {
$items[$key] = ('plugin' === $type) ? (object) $data : $this->get_theme_info($key);
$items[$key]->update = $update_info;
}
}
}
}
return $this->prep_items_for_updates($items, $type);
}
/**
* Extracts theme's data or information
*
* @param string $theme A string representing a theme's name or slug.
* @return object|boolean If successful, an object containing the theme data or information, "false" otherwise.
*/
private function get_theme_info($theme) {
if (function_exists('wp_get_theme')) {
$theme = wp_get_theme($theme);
if (is_a($theme, 'WP_Theme')) {
return $theme;
}
} else {
$theme_data = get_theme_data(WP_CONTENT_DIR.'/themes/'.$theme.'/style.css');
if (isset($theme_data['Version'])) {
if (!isset($theme_data['ThemeURI'])) $theme_data['ThemeURI'] = $theme_data['URI'];
return (object) $theme_data;
}
}
return false;
}
/**
* Fix items for update with missing "plugin" or "theme" field if applicable
*
* @param Array $items A collection of plugins or themes for updates
* @param String $type A string indicating which type of collection to process (e.g. 'plugin' or 'theme')
* @return Array An updated collection of plugins or themes for updates
*/
private function prep_items_for_updates($items, $type) {
foreach ($items as $key => $data) {
$update_info = $data->update;
// Some plugins and/or themes does not adhere to the standard WordPress updates meta
// properties/fields. Thus, missing some fields such as "plugin" or "theme"
// in their update information results in "Automatic updates is unavailable for this item"
// in UDC since we're using these fields to process the updates.
//
// As a workaround, we're filling these missing fields in order to solve the above issue
// in case the developer of these plugins/themes forgot to include them.
if (is_object($update_info)) {
$update_info = (array) $update_info;
if (!isset($update_info[$type])) {
$update_info[$type] = $key;
}
$update_info = (object) $update_info;
} elseif (is_array($update_info)) {
if (!isset($update_info[$type])) {
$update_info[$type] = $key;
}
}
// Re-assign the updated info to the original "update" property
$items[$key]->update = $update_info;
}
return $items;
}
/**
* Custom validation for translation permission. Since the 'install_languages' capability insn't available until 4.9
* therefore, we wrapped the validation check in this block to support older version of WP.
*
* @return Boolean
*/
private function user_can_update_translations() {
global $updraftplus;
$wp_version = $updraftplus->get_wordpress_version();
if (version_compare($wp_version, '4.9', '<')) {
if (current_user_can('update_core') || current_user_can('update_plugins') || current_user_can('update_themes')) return true;
} else {
if (current_user_can('install_languages')) return true;
}
return false;
}
public function get_updates($options) {
// Forcing Elegant Themes (Divi) updates component to load if it exist.
if (function_exists('et_register_updates_component')) et_register_updates_component();
if (!current_user_can('update_plugins') && !current_user_can('update_themes') && !current_user_can('update_core')) return $this->_generic_error_response('updates_permission_denied');
$this->_admin_include('plugin.php', 'update.php', 'file.php', 'template.php');
$this->_frontend_include('update.php');
if (!is_array($options)) $options = array();
// Normalise it
$plugin_updates = array();
if (current_user_can('update_plugins')) {
// Detect if refresh needed
$transient = get_site_transient('update_plugins');
if (!empty($options['force_refresh']) || false === $transient) {
delete_site_transient('update_plugins');
wp_update_plugins();
}
$get_plugin_updates = $this->maybe_add_third_party_items(get_plugin_updates(), 'plugin');
if (is_array($get_plugin_updates)) {
foreach ($get_plugin_updates as $update) {
// For some reason, some 3rd-party (premium) plugins are returning the same version
// with that of the currently installed version in WordPress. Thus, we're making sure here to
// only return those items for update that has new versions greater than the currently installed version.
if (version_compare($update->Version, $update->update->new_version, '>=')) continue;
$plugin_updates[] = array(
'name' => $update->Name,
'plugin_uri' => $update->PluginURI,
'version' => $update->Version,
'description' => $update->Description,
'author' => $update->Author,
'author_uri' => $update->AuthorURI,
'title' => $update->Title,
'author_name' => $update->AuthorName,
'update' => array(
// With Affiliates-WP, if you have not connected, this is null.
'plugin' => isset($update->update->plugin) ? $update->update->plugin : null,
'slug' => $update->update->slug,
'new_version' => $update->update->new_version,
'package' => $update->update->package,
'tested' => isset($update->update->tested) ? $update->update->tested : null,
'compatibility' => isset($update->update->compatibility) ? (array) $update->update->compatibility : null,
'sections' => isset($update->update->sections) ? (array) $update->update->sections : null,
),
);
}
}
}
$theme_updates = array();
if (current_user_can('update_themes')) {
// Detect if refresh needed
$transient = get_site_transient('update_themes');
if (!empty($options['force_refresh']) || false === $transient) {
delete_site_transient('update_themes');
wp_update_themes();
}
$get_theme_updates = $this->maybe_add_third_party_items(get_theme_updates(), 'theme');
if (is_array($get_theme_updates)) {
foreach ($get_theme_updates as $update) {
// We're making sure here to only return those items for update that has new
// versions greater than the currently installed version.
if (version_compare($update->Version, $update->update['new_version'], '>=')) continue;
$name = $update->Name;
$theme_name = !empty($name) ? $name : $update->update['theme'];
$theme_updates[] = array(
'name' => $theme_name,
'theme_uri' => $update->ThemeURI,
'version' => $update->Version,
'description' => $update->Description,
'author' => $update->Author,
'author_uri' => $update->AuthorURI,
'update' => array(
'theme' => $update->update['theme'],
'new_version' => $update->update['new_version'],
'package' => $update->update['package'],
'url' => $update->update['url'],
),
);
}
}
}
$core_updates = array();
if (current_user_can('update_core')) {
// Detect if refresh needed
$transient = get_site_transient('update_core');
if (!empty($options['force_refresh']) || false === $transient) {
// The next line is only needed for older WP versions - otherwise, the parameter to wp_version_check forces a check.
delete_site_transient('update_core');
wp_version_check(array(), true);
}
$get_core_updates = get_core_updates();
if (is_array($get_core_updates)) {
$core_update_key = false;
$core_update_latest_version = false;
@include(ABSPATH.WPINC.'/version.php');
foreach ($get_core_updates as $k => $core_update) {
if (isset($core_update->version) && version_compare($core_update->version, $wp_version, '>') && version_compare($core_update->version, $core_update_latest_version, '>')) {
$core_update_latest_version = $core_update->version;
$core_update_key = $k;
}
}
if (false !== $core_update_key) {
$update = $get_core_updates[$core_update_key];
global $wpdb;
$mysql_version = $wpdb->db_version();
$is_mysql = (file_exists(WP_CONTENT_DIR . '/db.php') && empty($wpdb->is_mysql)) ? false : true;
// We're making sure here to only return those items for update that has new
// versions greater than the currently installed version.
if (version_compare($wp_version, $update->version, '<')) {
$core_updates[] = array(
'download' => $update->download,
'version' => $update->version,
'php_version' => $update->php_version,
'mysql_version' => $update->mysql_version,
'installed' => array(
'version' => $wp_version,
'mysql' => $mysql_version,
'php' => PHP_VERSION,
'is_mysql' => $is_mysql,
),
'sufficient' => array(
'mysql' => version_compare($mysql_version, $update->mysql_version, '>='),
'php' => version_compare(PHP_VERSION, $update->php_version, '>='),
),
);
}
}
}
}
$translation_updates = array();
if (function_exists('wp_get_translation_updates') && $this->user_can_update_translations()) {
$translations = wp_get_translation_updates();
$translation_updates = array(
'items' => $translations
);
}
// Do we need to ask the user for filesystem credentials?
$request_filesystem_credentials = array();
$check_fs = array(
'plugins' => WP_PLUGIN_DIR,
'themes' => WP_CONTENT_DIR.'/themes',
'core' => untrailingslashit(ABSPATH)
);
if (!empty($translation_updates)) {
// 'en_US' don't usually have the "languages" folder, thus, we
// check if there's a need to ask for filesystem credentials for that
// folder if it exists, most especially for locale other than 'en_US'.
$language_dir = WP_CONTENT_DIR.'/languages';
if ('en_US' !== get_locale() && is_dir($language_dir)) {
$check_fs['translations'] = $language_dir;
}
}
foreach ($check_fs as $entity => $dir) {
$filesystem_method = get_filesystem_method(array(), $dir);
ob_start();
$filesystem_credentials_are_stored = request_filesystem_credentials(site_url());
$filesystem_form = strip_tags(ob_get_contents(), '<div><h2><p><input><label><fieldset><legend><span><em>');
ob_end_clean();
$request_filesystem_credentials[$entity] = ('direct' != $filesystem_method && !$filesystem_credentials_are_stored);
}
$automatic_backups = (class_exists('UpdraftPlus_Options') && class_exists('UpdraftPlus_Addon_Autobackup') && UpdraftPlus_Options::get_updraft_option('updraft_autobackup_default', true)) ? true : false;
return $this->_response(array(
'plugins' => $plugin_updates,
'themes' => $theme_updates,
'core' => $core_updates,
'translations' => $translation_updates,
'meta' => array(
'request_filesystem_credentials' => $request_filesystem_credentials,
'filesystem_form' => $filesystem_form,
'automatic_backups' => $automatic_backups
),
));
}
}

View File

@@ -0,0 +1,632 @@
<?php
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
/**
* Handles Users Commands
*/
class UpdraftCentral_Users_Commands extends UpdraftCentral_Commands {
/**
* Compares two user object whether one is lesser than, equal to, greater than the other
*
* @internal
* @param array $a First user in the comparison
* @param array $b Second user in the comparison
* @return integer Comparison results (0 = equal, -1 = less than, 1 = greater than)
*/
private function compare_user_id($a, $b) {
if ($a->ID === $b->ID) {
return 0;
}
return ($a->ID < $b->ID) ? -1 : 1;
}
/**
* Searches users based from the keyword submitted
*
* @internal
* @param array $query Parameter array containing the filter and keyword fields
* @return array Contains the list of users found as well as the total users count
*/
private function _search_users($query) {
$this->_admin_include('user.php');
$query1 = new WP_User_Query(array(
'orderby' => 'ID',
'order' => 'ASC',
'role'=> $query["role"],
'search' => '*' . esc_attr($query["search"]) . '*',
'search_columns' => array('user_login', 'user_email')
));
$query2 = new WP_User_Query(array(
'orderby' => 'ID',
'order' => 'ASC',
'role'=> $query["role"],
'meta_query'=>array(
'relation' => 'OR',
array(
'key' => 'first_name',
'value' => $query["search"],
'compare' => 'LIKE'
),
array(
'key' => 'last_name',
'value' => $query["search"],
'compare' => 'LIKE'
),
)
));
if (empty($query1->results) && empty($query2->results)) {
return array("message" => "users_not_found");
} else {
$found_users = array_merge($query1->results, $query2->results);
$temp = array();
foreach ($found_users as $new_user) {
if (!isset($temp[$new_user->ID])) {
$temp[$new_user->ID] = $new_user;
}
};
$users = array_values($temp);
// Sort users:
usort($users, array($this, 'compare_user_id'));
$offset = (intval($query['page_no']) * intval($query['per_page'])) - intval($query['per_page']);
$user_list = array_slice($users, $offset, $query['per_page']);
return array(
'users' => $user_list,
'total_users' => count($users)
);
}
}
/**
* Calculates the number of pages needed to construct the pagination links
*
* @internal
* @param array $query
* @param array $total_users The total number of users found from the WP_User_Query query
* @return array Contains information needed to construct the pagination links
*/
private function _calculate_pages($query, $total_users) {
$per_page_options = array(10, 20, 30, 40, 50);
if (!empty($query)) {
$pages = array();
$page_count = ceil($total_users / $query["per_page"]);
if ($page_count > 1) {
for ($i = 0; $i < $page_count; $i++) {
if ($i + 1 == $query['page_no']) {
$paginator_item = array(
"value"=>$i+1,
"setting"=>"disabled"
);
} else {
$paginator_item = array(
"value"=>$i+1
);
}
array_push($pages, $paginator_item);
};
if ($query['page_no'] >= $page_count) {
$page_next = array(
"value"=>$page_count,
"setting"=>"disabled"
);
} else {
$page_next = array(
"value"=>$query['page_no'] + 1
);
};
if (1 === $query['page_no']) {
$page_prev = array(
"value"=>1,
"setting"=>"disabled"
);
} else {
$page_prev = array(
"value"=>$query['page_no'] - 1
);
};
return array(
"page_no" => $query['page_no'],
"per_page" => $query["per_page"],
"page_count" => $page_count,
"pages" => $pages,
"page_next" => $page_next,
"page_prev" => $page_prev,
"total_results" => $total_users,
"per_page_options" => $per_page_options
);
} else {
return array(
"page_no" => $query['page_no'],
"per_page" => $query["per_page"],
"page_count" => $page_count,
"total_results" => $total_users,
"per_page_options" => $per_page_options
);
}
} else {
return array(
"per_page_options" => $per_page_options
);
}
}
/**
* Validates whether the username exists
*
* @param array $params Contains the user name to check and validate
* @return array An array containing the result of the current process
*/
public function check_username($params) {
$this->_admin_include('user.php');
$username = $params['user_name'];
$blog_id = get_current_blog_id();
if (!empty($params['site_id'])) {
$blog_id = $params['site_id'];
}
// Here, we're switching to the actual blog that we need
// to pull users from.
$switched = function_exists('switch_to_blog') ? switch_to_blog($blog_id) : false;
if (username_exists($username) && is_user_member_of_blog(username_exists($username), $blog_id)) {
$result = array("valid" => false, "message" => 'username_exists');
return $this->_response($result);
}
if (!validate_username($username)) {
$result = array("valid" => false, "message" => 'username_invalid');
return $this->_response($result);
}
// Here, we're restoring to the current (default) blog before we
// do the switched.
if (function_exists('restore_current_blog') && $switched) {
restore_current_blog();
}
$result = array("valid" => true, "message" => 'username_valid');
return $this->_response($result);
}
/**
* Pulls blog sites available
* for the current WP instance.
* If the site is a multisite, then sites under the network
* will be pulled, otherwise, it will return an empty array.
*
* @return Array - an array of sites
*/
private function _get_blog_sites() {
if (!is_multisite()) return array();
// Initialize array container
$sites = $network_sites = array();
// Check to see if latest get_sites (available on WP version >= 4.6) function is
// available to pull any available sites from the current WP instance. If not, then
// we're going to use the fallback function wp_get_sites (for older version).
if (function_exists('get_sites') && class_exists('WP_Site_Query')) {
$network_sites = get_sites();
} else {
if (function_exists('wp_get_sites')) {
$network_sites = wp_get_sites();
}
}
// We only process if sites array is not empty, otherwise, bypass
// the next block.
if (!empty($network_sites)) {
foreach ($network_sites as $site) {
// Here we're checking if the site type is an array, because
// we're pulling the blog_id property based on the type of
// site returned.
// get_sites returns an array of object, whereas the wp_get_sites
// function returns an array of array.
$blog_id = is_array($site) ? $site['blog_id'] : $site->blog_id;
// We're saving the blog_id and blog name as an associative item
// into the sites array, that will be used as "Sites" option in
// the frontend.
$sites[$blog_id] = get_blog_details($blog_id)->blogname;
}
}
return $sites;
}
/**
* Validates whether the email exists
*
* @param array $params Contains the email to check and validate
* @return array An array containing the result of the current process
*/
public function check_email($params) {
$this->_admin_include('user.php');
$email = $params['email'];
$blog_id = get_current_blog_id();
if (isset($params['site_id']) && 0 !== $params['site_id']) {
$blog_id = $params['site_id'];
}
// Here, we're switching to the actual blog that we need
// to pull users from.
$switched = false;
if (function_exists('switch_to_blog')) {
$switched = switch_to_blog($blog_id);
}
if (is_email($email) === false) {
$result = array("valid" => false, "message" => 'email_invalid');
return $this->_response($result);
}
if (email_exists($email) && is_user_member_of_blog(email_exists($email), $blog_id)) {
$result = array("valid" => false, "message" => 'email_exists');
return $this->_response($result);
}
// Here, we're restoring to the current (default) blog before we
// do the switched.
if (function_exists('restore_current_blog') && $switched) {
restore_current_blog();
}
$result = array("valid" => true, "message" => 'email_valid');
return $this->_response($result);
}
/**
* The get_users function pull all the users from the database
* based on the current search parameters/filters. Please see _search_users
* for the breakdown of these parameters.
*
* @param array $query Parameter array containing the filter and keyword fields
* @return array An array containing the result of the current process
*/
public function get_users($query) {
$this->_admin_include('user.php');
// Here, we're getting the current blog id. If blog id
// is passed along with the parameters then we override
// that current (default) value with the parameter blog id value.
$blog_id = get_current_blog_id();
if (isset($query['site_id']) && 0 !== $query['site_id']) $blog_id = $query['site_id'];
// Here, we're switching to the actual blog that we need
// to pull users from.
$switched = false;
if (function_exists('switch_to_blog')) {
$switched = switch_to_blog($blog_id);
}
// Set default:
if (empty($query["per_page"])) {
$query["per_page"] = 10;
}
if (empty($query['page_no'])) {
$query['page_no'] = 1;
}
if (empty($query["role"])) {
$query["role"] = "";
}
$users = array();
$total_users = 0;
if (!empty($query["search"])) {
$search_results = $this->_search_users($query);
if (isset($search_results['users'])) {
$users = $search_results['users'];
$total_users = $search_results['total_users'];
}
} else {
$user_query = new WP_User_Query(array(
'orderby' => 'ID',
'order' => 'ASC',
'number' => $query["per_page"],
'paged'=> $query['page_no'],
'role'=> $query["role"]
));
if (empty($user_query->results)) {
$result = array("message" => 'users_not_found');
return $this->_response($result);
}
$users = $user_query->results;
$total_users = $user_query->get_total();
}
foreach ($users as &$user) {
$user_object = get_userdata($user->ID);
if (method_exists($user_object, 'to_array')) {
$user = $user_object->to_array();
$user["roles"] = $user_object->roles;
$user["first_name"] = $user_object->first_name;
$user["last_name"] = $user_object->last_name;
$user["description"] = $user_object->description;
} else {
$user = $user_object;
}
}
$result = array(
"users"=>$users,
"paging" => $this->_calculate_pages($query, $total_users)
);
// Here, we're restoring to the current (default) blog before we
// do the switched.
if (function_exists('restore_current_blog') && $switched) {
restore_current_blog();
}
return $this->_response($result);
}
/**
* Creates new user for the current blog
*
* @param array $user User information to add
* @return array An array containing the result of the current process
*/
public function add_user($user) {
$this->_admin_include('user.php');
// Here, we're getting the current blog id. If blog id
// is passed along with the parameters then we override
// that current (default) value with the parameter blog id value.
$blog_id = get_current_blog_id();
if (isset($user['site_id']) && 0 !== $user['site_id']) $blog_id = $user['site_id'];
// Here, we're switching to the actual blog that we need
// to pull users from.
$switched = false;
if (function_exists('switch_to_blog')) {
$switched = switch_to_blog($blog_id);
}
if (!current_user_can('create_users') && !is_super_admin()) {
$result = array('error' => true, 'message' => 'user_create_no_permission', 'data' => array('multisite' => is_multisite()));
return $this->_response($result);
}
if (is_email($user["user_email"]) === false) {
$result = array("error" => true, "message" => "email_invalid");
return $this->_response($result);
}
if (email_exists($user["user_email"]) && is_user_member_of_blog(email_exists($user["user_email"]), $blog_id)) {
$result = array("error" => true, "message" => "email_exists");
return $this->_response($result);
}
if (username_exists($user["user_login"]) && is_user_member_of_blog(username_exists($user["user_login"]), $blog_id)) {
$result = array("error" => true, "message" => "username_exists");
return $this->_response($result);
}
if (!validate_username($user["user_login"])) {
$result = array("error" => true, "message" => 'username_invalid');
return $this->_response($result);
}
if (isset($user['site_id']) && !current_user_can('manage_network_users')) {
$result = array("error" => true, "message" => 'user_create_no_permission');
return $this->_response($result);
}
if (email_exists($user["user_email"]) && !is_user_member_of_blog(email_exists($user["user_email"]), $blog_id)) {
$user_id = email_exists($user["user_email"]);
} else {
$user_id = wp_insert_user($user);
}
$role = $user['role'];
if (is_multisite()) {
add_existing_user_to_blog(array('user_id' => $user_id, 'role' => $role));
}
// Here, we're restoring to the current (default) blog before we
// do the switched.
if (function_exists('restore_current_blog') && $switched) {
restore_current_blog();
}
if ($user_id > 0) {
$result = array("error" => false, "message" => "user_created_with_user_name", "values" => array($user['user_login']));
return $this->_response($result);
} else {
$result = array("error" => true, "message" => "user_create_failed", "values" => array($user));
}
return $this->_response($result);
}
/**
* [delete_user - UCP: users.delete_user]
*
* This function is used to check to make sure the user_id is valid and that it has has user delete permissions.
* If there are no issues, the user is deleted.
*
* current_user_can: This check the user permissons from UCP
* get_userdata: This get the user data on the data from user_id in the $user_id array
* wp_delete_user: Deleting users on the User ID (user_id) and, IF Specified, the Assigner ID (assign_user_id).
*
* @param [type] $params [description] THis is an Array of params sent over from UpdraftCentral
* @return [type] Array [description] This will send back an error array along with message if there are any issues with the user_id
*/
public function delete_user($params) {
$this->_admin_include('user.php');
$user_id = $params['user_id'];
$assign_user_id = $params["assign_user_id"];
// Here, we're getting the current blog id. If blog id
// is passed along with the parameters then we override
// that current (default) value with the parameter blog id value.
$blog_id = get_current_blog_id();
if (isset($params['site_id']) && 0 !== $params['site_id']) $blog_id = $params['site_id'];
$switched = false;
if (function_exists('switch_to_blog')) {
$switched = switch_to_blog($blog_id);
}
if (!current_user_can('delete_users') && !is_super_admin()) {
$result = array('error' => true, 'message' => 'user_delete_no_permission', 'data' => array('multisite' => is_multisite()));
return $this->_response($result);
}
if (get_userdata($user_id) === false) {
$result = array("error" => true, "message" => "user_not_found");
return $this->_response($result);
}
if (wp_delete_user($user_id, $assign_user_id)) {
$result = array("error" => false, "message" => "user_deleted");
} else {
$result = array("error" => true, "message" => "user_delete_failed");
}
// Here, we're restoring to the current (default) blog before we
// do the switched.
if (function_exists('restore_current_blog') && $switched) {
restore_current_blog();
}
return $this->_response($result);
}
/**
* Edits existing user information
*
* @param array $user User information to save
* @return array An array containing the result of the current process
*/
public function edit_user($user) {
$this->_admin_include('user.php');
// Here, we're getting the current blog id. If blog id
// is passed along with the parameters then we override
// that current (default) value with the parameter blog id value.
$blog_id = get_current_blog_id();
if (isset($user['site_id']) && 0 !== $user['site_id']) $blog_id = $user['site_id'];
// Here, we're switching to the actual blog that we need
// to apply our changes.
$switched = false;
if (function_exists('switch_to_blog')) {
$switched = switch_to_blog($blog_id);
}
if (!current_user_can('edit_users') && !is_super_admin() && get_current_user_id() !== $user["ID"]) {
$result = array('error' => true, 'message' => 'user_edit_no_permission', 'data' => array('multisite' => is_multisite()));
return $this->_response($result);
}
if (false === get_userdata($user["ID"])) {
$result = array("error" => true, "message" => "user_not_found");
return $this->_response($result);
}
if (get_current_user_id() == $user["ID"]) {
unset($user["role"]);
}
/* Validate Username*/
if (!validate_username($user["user_login"])) {
$result = array("error" => true, "message" => 'username_invalid');
return $this->_response($result);
}
/* Validate Email if not the same*/
$remote_user = get_userdata($user["ID"]);
$old_email = $remote_user->user_email;
if ($user['user_email'] !== $old_email) {
if (is_email($user['user_email']) === false) {
$result = array("error" => true, "message" => 'email_invalid');
return $this->_response($result);
}
if (email_exists($user['user_email'])) {
$result = array("error" => true, "message" => 'email_exists');
return $this->_response($result);
}
}
$user_id = wp_update_user($user);
if (is_wp_error($user_id)) {
$result = array("error" => true, "message" => "user_edit_failed_with_error", "values" => array($user_id));
} else {
$result = array("error" => false, "message" => "user_edited_with_user_name", "values" => array($user["user_login"]));
}
// Here, we're restoring to the current (default) blog before we
// do the switched.
if (function_exists('restore_current_blog') && $switched) {
restore_current_blog();
}
return $this->_response($result);
}
/**
* Retrieves available roles to be used as filter options
*
* @return array An array containing all available roles
*/
public function get_roles() {
$this->_admin_include('user.php');
$roles = array_reverse(get_editable_roles());
return $this->_response($roles);
}
/**
* Retrieves information to be use as filters
*
* @return array An array containing the filter fields and their data
*/
public function get_user_filters() {
$this->_admin_include('user.php');
// Pull sites options if available.
$sites = $this->_get_blog_sites();
$result = array(
"sites" => $sites,
"roles" => array_reverse(get_editable_roles()),
"paging" => $this->_calculate_pages(null, 0),
);
return $this->_response($result);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top.shepherd-target-attached-right .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-target-attached-right .shepherd-content:before {
display: none; }

View File

@@ -0,0 +1 @@
.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top.shepherd-target-attached-left .shepherd-content:before,.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top.shepherd-target-attached-right .shepherd-content:before,.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-target-attached-left .shepherd-content:before,.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-target-attached-right .shepherd-content:before{display:none}

View File

@@ -0,0 +1,215 @@
.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top.shepherd-target-attached-right .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-target-attached-right .shepherd-content:before {
display: none; }
.shepherd-element, .shepherd-element:after, .shepherd-element:before, .shepherd-element *, .shepherd-element *:after, .shepherd-element *:before {
box-sizing: border-box; }
.shepherd-element {
position: absolute;
display: none; }
.shepherd-element.shepherd-open {
display: block; }
.shepherd-element.shepherd-theme-arrows-plain-buttons {
max-width: 100%;
max-height: 100%; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content {
border-radius: 5px;
position: relative;
font-family: inherit;
background: #fff;
color: #444;
padding: 1em;
font-size: 1.1em;
line-height: 1.5em;
-webkit-transform: translateZ(0);
transform: translateZ(0);
-webkit-filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2)); }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content:before {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-width: 16px;
border-style: solid;
pointer-events: none; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content:before {
top: 100%;
left: 50%;
margin-left: -16px;
border-top-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content:before {
bottom: 100%;
left: 50%;
margin-left: -16px;
border-bottom-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content:before {
left: 100%;
top: 50%;
margin-top: -16px;
border-left-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content:before {
right: 100%;
top: 50%;
margin-top: -16px;
border-right-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-left.shepherd-target-attached-center .shepherd-content {
left: -32px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-right.shepherd-target-attached-center .shepherd-content {
left: 32px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
top: 16px;
left: 100%;
border-left-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
top: 16px;
right: 100%;
border-right-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
bottom: 16px;
left: 100%;
border-left-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
bottom: 16px;
right: 100%;
border-right-color: #fff; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-center.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before {
border-bottom-color: #eee; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header {
background: #eee;
padding: 1em; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header a.shepherd-cancel-link {
padding: 0;
margin-bottom: 0; }
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-cancel-link .shepherd-content header h3 {
float: left; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content {
padding: 0; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content header {
*zoom: 1;
border-radius: 5px 5px 0 0; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content header:after {
content: "";
display: table;
clear: both; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content header h3 {
margin: 0;
line-height: 1;
font-weight: normal; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content header a.shepherd-cancel-link {
float: right;
text-decoration: none;
font-size: 1.25em;
line-height: .8em;
font-weight: normal;
color: rgba(0, 0, 0, 0.5);
opacity: 0.25;
position: relative;
top: .1em;
padding: .8em;
margin-bottom: -.8em; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content header a.shepherd-cancel-link:hover {
opacity: 1; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content .shepherd-text {
padding: 1em; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content .shepherd-text p {
margin: 0 0 .5em 0;
line-height: 1.3em; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content .shepherd-text p:last-child {
margin-bottom: 0; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content footer {
padding: 0 1em 1em; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content footer .shepherd-buttons {
text-align: right;
list-style: none;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content footer .shepherd-buttons li {
display: inline;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content footer .shepherd-buttons li .shepherd-button {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
cursor: pointer;
margin: 0 .5em 0 0;
text-decoration: none; }
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content footer .shepherd-buttons li:last-child .shepherd-button {
margin-right: 0; }

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,229 @@
.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top.shepherd-target-attached-right .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-target-attached-right .shepherd-content:before {
display: none; }
.shepherd-element, .shepherd-element:after, .shepherd-element:before, .shepherd-element *, .shepherd-element *:after, .shepherd-element *:before {
box-sizing: border-box; }
.shepherd-element {
position: absolute;
display: none; }
.shepherd-element.shepherd-open {
display: block; }
.shepherd-element.shepherd-theme-arrows {
max-width: 100%;
max-height: 100%; }
.shepherd-element.shepherd-theme-arrows .shepherd-content {
border-radius: 5px;
position: relative;
font-family: inherit;
background: #fff;
color: #444;
padding: 1em;
font-size: 1.1em;
line-height: 1.5em;
-webkit-transform: translateZ(0);
transform: translateZ(0);
-webkit-filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2)); }
.shepherd-element.shepherd-theme-arrows .shepherd-content:before {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-width: 16px;
border-style: solid;
pointer-events: none; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content:before {
top: 100%;
left: 50%;
margin-left: -16px;
border-top-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content:before {
bottom: 100%;
left: 50%;
margin-left: -16px;
border-bottom-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content:before {
left: 100%;
top: 50%;
margin-top: -16px;
border-left-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content:before {
right: 100%;
top: 50%;
margin-top: -16px;
border-right-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-left.shepherd-target-attached-center .shepherd-content {
left: -32px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-right.shepherd-target-attached-center .shepherd-content {
left: 32px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
top: 16px;
left: 100%;
border-left-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
top: 16px;
right: 100%;
border-right-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
bottom: 16px;
left: 100%;
border-left-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
bottom: 16px;
right: 100%;
border-right-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-center.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before {
border-bottom-color: #eee; }
.shepherd-element.shepherd-theme-arrows.shepherd-has-title .shepherd-content header {
background: #eee;
padding: 1em; }
.shepherd-element.shepherd-theme-arrows.shepherd-has-title .shepherd-content header a.shepherd-cancel-link {
padding: 0;
margin-bottom: 0; }
.shepherd-element.shepherd-theme-arrows.shepherd-has-cancel-link .shepherd-content header h3 {
float: left; }
.shepherd-element.shepherd-theme-arrows .shepherd-content {
padding: 0; }
.shepherd-element.shepherd-theme-arrows .shepherd-content * {
font-size: inherit; }
.shepherd-element.shepherd-theme-arrows .shepherd-content header {
*zoom: 1;
border-radius: 5px 5px 0 0; }
.shepherd-element.shepherd-theme-arrows .shepherd-content header:after {
content: "";
display: table;
clear: both; }
.shepherd-element.shepherd-theme-arrows .shepherd-content header h3 {
margin: 0;
line-height: 1;
font-weight: normal; }
.shepherd-element.shepherd-theme-arrows .shepherd-content header a.shepherd-cancel-link {
float: right;
text-decoration: none;
font-size: 1.25em;
line-height: .8em;
font-weight: normal;
color: rgba(0, 0, 0, 0.5);
opacity: 0.25;
position: relative;
top: .1em;
padding: .8em;
margin-bottom: -.8em; }
.shepherd-element.shepherd-theme-arrows .shepherd-content header a.shepherd-cancel-link:hover {
opacity: 1; }
.shepherd-element.shepherd-theme-arrows .shepherd-content .shepherd-text {
padding: 1em; }
.shepherd-element.shepherd-theme-arrows .shepherd-content .shepherd-text p {
margin: 0 0 .5em 0;
line-height: 1.3em; }
.shepherd-element.shepherd-theme-arrows .shepherd-content .shepherd-text p:last-child {
margin-bottom: 0; }
.shepherd-element.shepherd-theme-arrows .shepherd-content footer {
padding: 0 1em 1em; }
.shepherd-element.shepherd-theme-arrows .shepherd-content footer .shepherd-buttons {
text-align: right;
list-style: none;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-arrows .shepherd-content footer .shepherd-buttons li {
display: inline;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-arrows .shepherd-content footer .shepherd-buttons li .shepherd-button {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 3px;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }
.shepherd-element.shepherd-theme-arrows .shepherd-content footer .shepherd-buttons li .shepherd-button.shepherd-button-secondary {
background: #eee;
color: #888; }
.shepherd-element.shepherd-theme-arrows .shepherd-content footer .shepherd-buttons li:last-child .shepherd-button {
margin-right: 0; }

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,246 @@
.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top.shepherd-target-attached-right .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-target-attached-right .shepherd-content:before {
display: none; }
.shepherd-element, .shepherd-element:after, .shepherd-element:before, .shepherd-element *, .shepherd-element *:after, .shepherd-element *:before {
box-sizing: border-box; }
.shepherd-element {
position: absolute;
display: none; }
.shepherd-element.shepherd-open {
display: block; }
.shepherd-element.shepherd-theme-dark {
max-width: 100%;
max-height: 100%; }
.shepherd-element.shepherd-theme-dark .shepherd-content {
border-radius: 5px;
position: relative;
font-family: inherit;
background: #232323;
color: #eee;
padding: 1em;
font-size: 1.1em;
line-height: 1.5em; }
.shepherd-element.shepherd-theme-dark .shepherd-content:before {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-width: 16px;
border-style: solid;
pointer-events: none; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content:before {
top: 100%;
left: 50%;
margin-left: -16px;
border-top-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content:before {
bottom: 100%;
left: 50%;
margin-left: -16px;
border-bottom-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content:before {
left: 100%;
top: 50%;
margin-top: -16px;
border-left-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content:before {
right: 100%;
top: 50%;
margin-top: -16px;
border-right-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-left.shepherd-target-attached-center .shepherd-content {
left: -32px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-right.shepherd-target-attached-center .shepherd-content {
left: 32px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
top: 16px;
left: 100%;
border-left-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
top: 16px;
right: 100%;
border-right-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
bottom: 16px;
left: 100%;
border-left-color: #232323; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
bottom: 16px;
right: 100%;
border-right-color: #232323; }
.shepherd-element.shepherd-theme-dark {
z-index: 9999;
max-width: 24em;
font-size: 1em; }
.shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-center.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before {
border-bottom-color: #303030; }
.shepherd-element.shepherd-theme-dark.shepherd-has-title .shepherd-content header {
background: #303030;
padding: 1em; }
.shepherd-element.shepherd-theme-dark.shepherd-has-title .shepherd-content header a.shepherd-cancel-link {
padding: 0;
margin-bottom: 0; }
.shepherd-element.shepherd-theme-dark.shepherd-has-cancel-link .shepherd-content header h3 {
float: left; }
.shepherd-element.shepherd-theme-dark .shepherd-content {
box-shadow: 0 0 1em rgba(0, 0, 0, 0.2);
padding: 0; }
.shepherd-element.shepherd-theme-dark .shepherd-content * {
font-size: inherit; }
.shepherd-element.shepherd-theme-dark .shepherd-content header {
*zoom: 1;
border-radius: 5px 5px 0 0; }
.shepherd-element.shepherd-theme-dark .shepherd-content header:after {
content: "";
display: table;
clear: both; }
.shepherd-element.shepherd-theme-dark .shepherd-content header h3 {
margin: 0;
line-height: 1;
font-weight: normal; }
.shepherd-element.shepherd-theme-dark .shepherd-content header a.shepherd-cancel-link {
float: right;
text-decoration: none;
font-size: 1.25em;
line-height: .8em;
font-weight: normal;
color: rgba(0, 0, 0, 0.5);
opacity: 0.25;
position: relative;
top: .1em;
padding: .8em;
margin-bottom: -.8em; }
.shepherd-element.shepherd-theme-dark .shepherd-content header a.shepherd-cancel-link:hover {
opacity: 1; }
.shepherd-element.shepherd-theme-dark .shepherd-content .shepherd-text {
padding: 1em; }
.shepherd-element.shepherd-theme-dark .shepherd-content .shepherd-text p {
margin: 0 0 .5em 0;
line-height: 1.3em; }
.shepherd-element.shepherd-theme-dark .shepherd-content .shepherd-text p:last-child {
margin-bottom: 0; }
.shepherd-element.shepherd-theme-dark .shepherd-content footer {
padding: 0 1em 1em; }
.shepherd-element.shepherd-theme-dark .shepherd-content footer .shepherd-buttons {
text-align: right;
list-style: none;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-dark .shepherd-content footer .shepherd-buttons li {
display: inline;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-dark .shepherd-content footer .shepherd-buttons li .shepherd-button {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 3px;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }
.shepherd-element.shepherd-theme-dark .shepherd-content footer .shepherd-buttons li .shepherd-button.shepherd-button-secondary {
background: #eee;
color: #888; }
.shepherd-element.shepherd-theme-dark .shepherd-content footer .shepherd-buttons li:last-child .shepherd-button {
margin-right: 0; }
.shepherd-start-tour-button.shepherd-theme-dark {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 3px;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,246 @@
.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top.shepherd-target-attached-right .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-target-attached-right .shepherd-content:before {
display: none; }
.shepherd-element, .shepherd-element:after, .shepherd-element:before, .shepherd-element *, .shepherd-element *:after, .shepherd-element *:before {
box-sizing: border-box; }
.shepherd-element {
position: absolute;
display: none; }
.shepherd-element.shepherd-open {
display: block; }
.shepherd-element.shepherd-theme-default {
max-width: 100%;
max-height: 100%; }
.shepherd-element.shepherd-theme-default .shepherd-content {
border-radius: 5px;
position: relative;
font-family: inherit;
background: #f6f6f6;
color: #444;
padding: 1em;
font-size: 1.1em;
line-height: 1.5em; }
.shepherd-element.shepherd-theme-default .shepherd-content:before {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-width: 16px;
border-style: solid;
pointer-events: none; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content:before {
top: 100%;
left: 50%;
margin-left: -16px;
border-top-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content:before {
bottom: 100%;
left: 50%;
margin-left: -16px;
border-bottom-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content:before {
left: 100%;
top: 50%;
margin-top: -16px;
border-left-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content:before {
right: 100%;
top: 50%;
margin-top: -16px;
border-right-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-left.shepherd-target-attached-center .shepherd-content {
left: -32px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-right.shepherd-target-attached-center .shepherd-content {
left: 32px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
top: 16px;
left: 100%;
border-left-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
top: 16px;
right: 100%;
border-right-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
bottom: 16px;
left: 100%;
border-left-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
bottom: 16px;
right: 100%;
border-right-color: #f6f6f6; }
.shepherd-element.shepherd-theme-default {
z-index: 9999;
max-width: 24em;
font-size: 1em; }
.shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-center.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-default.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before {
border-bottom-color: #e6e6e6; }
.shepherd-element.shepherd-theme-default.shepherd-has-title .shepherd-content header {
background: #e6e6e6;
padding: 1em; }
.shepherd-element.shepherd-theme-default.shepherd-has-title .shepherd-content header a.shepherd-cancel-link {
padding: 0;
margin-bottom: 0; }
.shepherd-element.shepherd-theme-default.shepherd-has-cancel-link .shepherd-content header h3 {
float: left; }
.shepherd-element.shepherd-theme-default .shepherd-content {
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.17);
padding: 0; }
.shepherd-element.shepherd-theme-default .shepherd-content * {
font-size: inherit; }
.shepherd-element.shepherd-theme-default .shepherd-content header {
*zoom: 1;
border-radius: 5px 5px 0 0; }
.shepherd-element.shepherd-theme-default .shepherd-content header:after {
content: "";
display: table;
clear: both; }
.shepherd-element.shepherd-theme-default .shepherd-content header h3 {
margin: 0;
line-height: 1;
font-weight: normal; }
.shepherd-element.shepherd-theme-default .shepherd-content header a.shepherd-cancel-link {
float: right;
text-decoration: none;
font-size: 1.25em;
line-height: .8em;
font-weight: normal;
color: rgba(0, 0, 0, 0.5);
opacity: 0.25;
position: relative;
top: .1em;
padding: .8em;
margin-bottom: -.8em; }
.shepherd-element.shepherd-theme-default .shepherd-content header a.shepherd-cancel-link:hover {
opacity: 1; }
.shepherd-element.shepherd-theme-default .shepherd-content .shepherd-text {
padding: 1em; }
.shepherd-element.shepherd-theme-default .shepherd-content .shepherd-text p {
margin: 0 0 .5em 0;
line-height: 1.3em; }
.shepherd-element.shepherd-theme-default .shepherd-content .shepherd-text p:last-child {
margin-bottom: 0; }
.shepherd-element.shepherd-theme-default .shepherd-content footer {
padding: 0 1em 1em; }
.shepherd-element.shepherd-theme-default .shepherd-content footer .shepherd-buttons {
text-align: right;
list-style: none;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-default .shepherd-content footer .shepherd-buttons li {
display: inline;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-default .shepherd-content footer .shepherd-buttons li .shepherd-button {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 3px;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }
.shepherd-element.shepherd-theme-default .shepherd-content footer .shepherd-buttons li .shepherd-button.shepherd-button-secondary {
background: #eee;
color: #888; }
.shepherd-element.shepherd-theme-default .shepherd-content footer .shepherd-buttons li:last-child .shepherd-button {
margin-right: 0; }
.shepherd-start-tour-button.shepherd-theme-default {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 3px;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,248 @@
.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top.shepherd-target-attached-right .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-target-attached-right .shepherd-content:before {
display: none; }
.shepherd-element, .shepherd-element:after, .shepherd-element:before, .shepherd-element *, .shepherd-element *:after, .shepherd-element *:before {
box-sizing: border-box; }
.shepherd-element {
position: absolute;
display: none; }
.shepherd-element.shepherd-open {
display: block; }
.shepherd-element.shepherd-theme-square-dark {
max-width: 100%;
max-height: 100%; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content {
border-radius: 5px;
position: relative;
font-family: inherit;
background: #232323;
color: #eee;
padding: 1em;
font-size: 1.1em;
line-height: 1.5em; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content:before {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-width: 16px;
border-style: solid;
pointer-events: none; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content:before {
top: 100%;
left: 50%;
margin-left: -16px;
border-top-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content:before {
bottom: 100%;
left: 50%;
margin-left: -16px;
border-bottom-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content:before {
left: 100%;
top: 50%;
margin-top: -16px;
border-left-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content:before {
right: 100%;
top: 50%;
margin-top: -16px;
border-right-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-left.shepherd-target-attached-center .shepherd-content {
left: -32px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-right.shepherd-target-attached-center .shepherd-content {
left: 32px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
top: 16px;
left: 100%;
border-left-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
top: 16px;
right: 100%;
border-right-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
bottom: 16px;
left: 100%;
border-left-color: #232323; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
bottom: 16px;
right: 100%;
border-right-color: #232323; }
.shepherd-element.shepherd-theme-square-dark {
border-radius: 0;
z-index: 9999;
max-width: 24em;
font-size: 1em; }
.shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-center.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-square-dark.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before {
border-bottom-color: #303030; }
.shepherd-element.shepherd-theme-square-dark.shepherd-has-title .shepherd-content header {
background: #303030;
padding: 1em; }
.shepherd-element.shepherd-theme-square-dark.shepherd-has-title .shepherd-content header a.shepherd-cancel-link {
padding: 0;
margin-bottom: 0; }
.shepherd-element.shepherd-theme-square-dark.shepherd-has-cancel-link .shepherd-content header h3 {
float: left; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content {
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.17);
border-radius: 0;
padding: 0; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content * {
font-size: inherit; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content header {
*zoom: 1;
border-radius: 0; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content header:after {
content: "";
display: table;
clear: both; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content header h3 {
margin: 0;
line-height: 1;
font-weight: normal; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content header a.shepherd-cancel-link {
float: right;
text-decoration: none;
font-size: 1.25em;
line-height: .8em;
font-weight: normal;
color: rgba(0, 0, 0, 0.5);
opacity: 0.25;
position: relative;
top: .1em;
padding: .8em;
margin-bottom: -.8em; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content header a.shepherd-cancel-link:hover {
opacity: 1; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content .shepherd-text {
padding: 1em; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content .shepherd-text p {
margin: 0 0 .5em 0;
line-height: 1.3em; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content .shepherd-text p:last-child {
margin-bottom: 0; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content footer {
padding: 0 1em 1em; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content footer .shepherd-buttons {
text-align: right;
list-style: none;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content footer .shepherd-buttons li {
display: inline;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content footer .shepherd-buttons li .shepherd-button {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 0;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content footer .shepherd-buttons li .shepherd-button.shepherd-button-secondary {
background: #eee;
color: #888; }
.shepherd-element.shepherd-theme-square-dark .shepherd-content footer .shepherd-buttons li:last-child .shepherd-button {
margin-right: 0; }
.shepherd-start-tour-button.shepherd-theme-square-dark {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 0;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,248 @@
.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top.shepherd-target-attached-right .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-target-attached-right .shepherd-content:before {
display: none; }
.shepherd-element, .shepherd-element:after, .shepherd-element:before, .shepherd-element *, .shepherd-element *:after, .shepherd-element *:before {
box-sizing: border-box; }
.shepherd-element {
position: absolute;
display: none; }
.shepherd-element.shepherd-open {
display: block; }
.shepherd-element.shepherd-theme-square {
max-width: 100%;
max-height: 100%; }
.shepherd-element.shepherd-theme-square .shepherd-content {
border-radius: 5px;
position: relative;
font-family: inherit;
background: #f6f6f6;
color: #444;
padding: 1em;
font-size: 1.1em;
line-height: 1.5em; }
.shepherd-element.shepherd-theme-square .shepherd-content:before {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-width: 16px;
border-style: solid;
pointer-events: none; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content:before {
top: 100%;
left: 50%;
margin-left: -16px;
border-top-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content:before {
bottom: 100%;
left: 50%;
margin-left: -16px;
border-bottom-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content:before {
left: 100%;
top: 50%;
margin-top: -16px;
border-left-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content:before {
right: 100%;
top: 50%;
margin-top: -16px;
border-right-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-left.shepherd-target-attached-center .shepherd-content {
left: -32px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-right.shepherd-target-attached-center .shepherd-content {
left: 32px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-middle .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-middle .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
top: 16px;
left: 100%;
border-left-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
top: 16px;
right: 100%;
border-right-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
bottom: 16px;
left: 100%;
border-left-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
bottom: 16px;
right: 100%;
border-right-color: #f6f6f6; }
.shepherd-element.shepherd-theme-square {
border-radius: 0;
z-index: 9999;
max-width: 24em;
font-size: 1em; }
.shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-center.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-square.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before {
border-bottom-color: #e6e6e6; }
.shepherd-element.shepherd-theme-square.shepherd-has-title .shepherd-content header {
background: #e6e6e6;
padding: 1em; }
.shepherd-element.shepherd-theme-square.shepherd-has-title .shepherd-content header a.shepherd-cancel-link {
padding: 0;
margin-bottom: 0; }
.shepherd-element.shepherd-theme-square.shepherd-has-cancel-link .shepherd-content header h3 {
float: left; }
.shepherd-element.shepherd-theme-square .shepherd-content {
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.17);
border-radius: 0;
padding: 0; }
.shepherd-element.shepherd-theme-square .shepherd-content * {
font-size: inherit; }
.shepherd-element.shepherd-theme-square .shepherd-content header {
*zoom: 1;
border-radius: 0; }
.shepherd-element.shepherd-theme-square .shepherd-content header:after {
content: "";
display: table;
clear: both; }
.shepherd-element.shepherd-theme-square .shepherd-content header h3 {
margin: 0;
line-height: 1;
font-weight: normal; }
.shepherd-element.shepherd-theme-square .shepherd-content header a.shepherd-cancel-link {
float: right;
text-decoration: none;
font-size: 1.25em;
line-height: .8em;
font-weight: normal;
color: rgba(0, 0, 0, 0.5);
opacity: 0.25;
position: relative;
top: .1em;
padding: .8em;
margin-bottom: -.8em; }
.shepherd-element.shepherd-theme-square .shepherd-content header a.shepherd-cancel-link:hover {
opacity: 1; }
.shepherd-element.shepherd-theme-square .shepherd-content .shepherd-text {
padding: 1em; }
.shepherd-element.shepherd-theme-square .shepherd-content .shepherd-text p {
margin: 0 0 .5em 0;
line-height: 1.3em; }
.shepherd-element.shepherd-theme-square .shepherd-content .shepherd-text p:last-child {
margin-bottom: 0; }
.shepherd-element.shepherd-theme-square .shepherd-content footer {
padding: 0 1em 1em; }
.shepherd-element.shepherd-theme-square .shepherd-content footer .shepherd-buttons {
text-align: right;
list-style: none;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-square .shepherd-content footer .shepherd-buttons li {
display: inline;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-square .shepherd-content footer .shepherd-buttons li .shepherd-button {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 0;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }
.shepherd-element.shepherd-theme-square .shepherd-content footer .shepherd-buttons li .shepherd-button.shepherd-button-secondary {
background: #eee;
color: #888; }
.shepherd-element.shepherd-theme-square .shepherd-content footer .shepherd-buttons li:last-child .shepherd-button {
margin-right: 0; }
.shepherd-start-tour-button.shepherd-theme-square {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 0;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,64 @@
/* CSS for adverts */
.updraft_notice_container {
height: auto;
overflow: hidden;
}
.updraft_advert_content_left {
float: none;
width: 65px;
}
.updraft_advert_content_right {
float: none;
width: auto;
overflow: hidden;
}
.updraft_advert_bottom {
margin: 10px 0;
padding: 10px;
font-size: 140%;
background-color: white;
border-color: #E6DB55;
border: 1px solid;
border-radius: 4px;
}
.updraft-advert-dismiss {
float: right;
font-size: 13px;
font-weight: normal;
}
h3.updraft_advert_heading {
margin-top: 5px !important;
margin-bottom: 5px !important;
}
h4.updraft_advert_heading {
margin-top: 2px !important;
margin-bottom: 3px !important;
}
.updraft_center_content {
text-align: center;
margin-bottom: 5px;
}
.updraft_notice_link {
padding-left: 5px;
}
.updraft_text_center {
text-align: center;
}
@media screen and (min-width: 560px) {
.updraft_advert_content_left {
float: left;
}
}

View File

@@ -0,0 +1,2 @@
.updraft_notice_container{height:auto;overflow:hidden}.updraft_advert_content_left{float:none;width:65px}.updraft_advert_content_right{float:none;width:auto;overflow:hidden}.updraft_advert_bottom{margin:10px 0;padding:10px;font-size:140%;background-color:white;border-color:#e6db55;border:1px solid;border-radius:4px}.updraft-advert-dismiss{float:right;font-size:13px;font-weight:normal}h3.updraft_advert_heading{margin-top:5px !important;margin-bottom:5px !important}h4.updraft_advert_heading{margin-top:2px !important;margin-bottom:3px !important}.updraft_center_content{text-align:center;margin-bottom:5px}.updraft_notice_link{padding-left:5px}.updraft_text_center{text-align:center}@media screen and (min-width:560px){.updraft_advert_content_left{float:left}}
/*# sourceMappingURL=updraftplus-notices.min.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["css/updraftplus-notices.css"],"names":[],"mappings":"AAAA,qBAAqB;;AAErB;CACC,aAAa;CACb,iBAAiB;CACjB;;AAED;CACC,YAAY;CACZ,YAAY;CACZ;;AAED;CACC,YAAY;CACZ,YAAY;CACZ,iBAAiB;CACjB;;AAED;CACC,eAAe;CACf,cAAc;CACd,gBAAgB;CAChB,wBAAwB;CACxB,sBAAsB;CACtB,kBAAkB;CAClB,mBAAmB;CACnB;;AAED;CACC,aAAa;CACb,gBAAgB;CAChB,oBAAoB;CACpB;;AAED;CACC,2BAA2B;CAC3B,8BAA8B;CAC9B;;AAED;CACC,2BAA2B;CAC3B,8BAA8B;CAC9B;;AAED;CACC,mBAAmB;CACnB,mBAAmB;CACnB;;AAED;CACC,kBAAkB;CAClB;;AAED;CACC,mBAAmB;CACnB;;AAED;;CAEC;EACC,YAAY;EACZ;;CAED","file":"updraftplus-notices.min.css","sourcesContent":["/* CSS for adverts */\n\n.updraft_notice_container {\n\theight: auto;\n\toverflow: hidden;\n}\n\n.updraft_advert_content_left {\n\tfloat: none;\n\twidth: 65px;\n}\n\n.updraft_advert_content_right {\n\tfloat: none;\n\twidth: auto;\n\toverflow: hidden;\n}\n\n.updraft_advert_bottom {\n\tmargin: 10px 0;\n\tpadding: 10px;\n\tfont-size: 140%;\n\tbackground-color: white;\n\tborder-color: #E6DB55;\n\tborder: 1px solid;\n\tborder-radius: 4px;\n}\n\n.updraft-advert-dismiss {\n\tfloat: right;\n\tfont-size: 13px;\n\tfont-weight: normal;\n}\n\nh3.updraft_advert_heading {\n\tmargin-top: 5px !important;\n\tmargin-bottom: 5px !important;\n}\n\nh4.updraft_advert_heading {\n\tmargin-top: 2px !important;\n\tmargin-bottom: 3px !important;\n}\n\n.updraft_center_content {\n\ttext-align: center;\n\tmargin-bottom: 5px;\n}\n\n.updraft_notice_link {\n\tpadding-left: 5px;\n}\n\n.updraft_text_center {\n\ttext-align: center;\n}\n\n@media screen and (min-width: 560px) {\n\n\t.updraft_advert_content_left {\n\t\tfloat: left;\n\t}\n\n}\n"]}

View File

@@ -0,0 +1,198 @@
.shepherd-theme-arrows-plain-buttons {
z-index: 99;
max-width: 390px!important;
}
.shepherd-theme-arrows-plain-buttons.super-index {
z-index: 999999;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content {
border-radius: 3px;
filter: none;
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.15), 0px 10px 40px rgba(0, 0, 0, 0.15);
}
.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top.shepherd-target-attached-right .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-target-attached-right .shepherd-content:before {
display: none;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-center.shepherd-has-title .shepherd-content:before,
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before,
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before {
border-bottom-color: #DD6823;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header {
background-color: #DD6823;
border-radius: 3px 3px 0 0;
padding-right: 90px;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content header h3 {
color: #FFF;
font-size: 1.2em;
float: none;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header a.shepherd-cancel-link {
opacity: 0.7;
color: rgba(255, 255, 255, 0);
font-size: 0.8em;
border: 1px solid #FFF;
border-radius: 50%;
width: 22px;
height: 22px;
line-height: 20px;
padding: 0;
text-align: center;
float: none;
position: absolute;
right: 11px;
top: 12px
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header a.shepherd-cancel-link::before {
color: #FFF;
content: attr(data-btntext);
position: absolute;
right: 20px;
padding-right: 10px;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header a.shepherd-cancel-link::after {
content: "\f335";
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: dashicons;
color: #FFF;
position: absolute;
left: 2px;
line-height: 21px;
font-size: 16px;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header a.shepherd-cancel-link:hover,
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header a.shepherd-cancel-link:focus {
border: 1px solid #A04E00;
opacity: 1
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header a.shepherd-cancel-link:hover::before, .shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header a.shepherd-cancel-link:focus::before {
color: #A04E00;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header a.shepherd-cancel-link:hover::after, .shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header a.shepherd-cancel-link:focus::after {
color: #A04E00;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
top: 44px;
}
.shepherd-content .ud-notice {
background: #F0F0F0;
padding: 14px;
border-radius: 4px;
font-size: 90% !important;
line-height: 1.5;
}
.shepherd-content .ud-notice h3 {
margin-top: 0;
padding-top: 0;
margin-bottom: .5em;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content .shepherd-text p {
margin-top: 0.5em;
margin-bottom: 1.3em;
}
.ud-notice span.ud-special-offer {
font-weight: bold;
display: inline-block;
padding: 1px 6px;
border-radius: 3px;
background: rgba(217, 105, 0, 0.09);
}
label[for=updraft_servicecheckbox_updraftvault] {
border: 1px solid rgba(204, 204, 204, 0.4);
transition: border .5s
}
label[for=updraft_servicecheckbox_updraftvault].emphasize {
border-color: #DD6823;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content footer .shepherd-buttons li .shepherd-button.udp-tour-back,
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content footer .shepherd-buttons li .shepherd-button.udp-tour-end {
float: left;
position: relative;
padding-left: 10px;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content footer .shepherd-buttons li .shepherd-button.udp-tour-end {
padding-left: 0;
color: #B7B7B7;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content footer .shepherd-buttons li .shepherd-button.udp-tour-back::before {
content: ' ';
width: 6px;
height: 6px;
display: block;
border-left: 1px solid;
border-bottom: 1px solid;
position: absolute;
left: 0px;
top: 8px;
transform: rotate(45deg);
}
a.shepherd-button.udp-tour-end::before {
display: inline-block;
position: relative;
content: "\f335";
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: dashicons;
font-size: 20px;
line-height: 20px;
vertical-align: middle;
margin-top: -2px;
}
.updraftplus-welcome-logo {
display: block;
width: 70px;
float: left;
margin-top: -11px;
margin-right: 12px;
}
.updraftplus-welcome-logo img {
display: block;
width: auto;
max-width: 100%;
}
.highlight-udp .plugins #the-list tr:not([data-slug="updraftplus"]) {
opacity: 0.3;
}
@media(max-width: 790px) {
.shepherd-element.shepherd-theme-arrows-plain-buttons {
display: none;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,195 @@
$udp_primary: #DD6823;
$wp_blue: #0073AA;
.shepherd-theme-arrows-plain-buttons {
z-index: 99;
max-width: 390px!important;
}
.shepherd-theme-arrows-plain-buttons.super-index {
z-index: 999999;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content {
border-radius: 3px;
filter: none;
box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.15), 0px 10px 40px rgba(0, 0, 0, 0.15);
}
.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top.shepherd-target-attached-right .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-target-attached-left .shepherd-content:before,
.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-target-attached-right .shepherd-content:before {
display: none;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-center.shepherd-has-title .shepherd-content:before,
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before,
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before {
border-bottom-color: $udp_primary;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header {
background-color: $udp_primary;
border-radius: 3px 3px 0 0;
padding-right: 90px;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content header h3 {
color: #FFF;
font-size: 1.2em;
float: none;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header a.shepherd-cancel-link {
opacity: 0.7;
color: rgba(255, 255, 255, 0);
font-size: 0.8em;
border: 1px solid #FFF;
border-radius: 50%;
width: 22px;
height: 22px;
line-height: 20px;
padding: 0;
text-align: center;
float: none;
position: absolute;
right: 11px;
top: 12px;
&::before {
color: #FFF;
content: attr(data-btntext);
position: absolute;
right: 20px;
padding-right: 10px;
}
&::after {
content: "\f335";
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: dashicons;
color: #FFF;
position: absolute;
left: 2px;
line-height: 21px;
font-size: 16px;
}
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header a.shepherd-cancel-link:hover,
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-has-title .shepherd-content header a.shepherd-cancel-link:focus {
border: 1px solid #A04E00;
opacity: 1;
&::before {
color: #A04E00;
}
&::after {
color: #A04E00;
}
}
.shepherd-element.shepherd-theme-arrows-plain-buttons.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
top: 44px;
}
.shepherd-content .ud-notice {
background: #F0F0F0;
padding: 14px;
border-radius: 4px;
font-size: 90% !important;
line-height: 1.5;
h3 {
margin-top: 0;
padding-top: 0;
margin-bottom: .5em;
}
}
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content .shepherd-text p {
margin-top: 0.5em;
margin-bottom: 1.3em;
}
.ud-notice span.ud-special-offer {
font-weight: bold;
display: inline-block;
padding: 1px 6px;
border-radius: 3px;
background: rgba(217, 105, 0, 0.09);
}
label[for=updraft_servicecheckbox_updraftvault] {
border: 1px solid rgba(204, 204, 204, 0.4);
transition: border .5s;
&.emphasize {
border-color: $udp_primary;
}
}
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content footer .shepherd-buttons li .shepherd-button.udp-tour-back,
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content footer .shepherd-buttons li .shepherd-button.udp-tour-end {
float: left;
position: relative;
padding-left: 10px;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content footer .shepherd-buttons li .shepherd-button.udp-tour-end {
padding-left: 0;
color: #B7B7B7;
}
.shepherd-element.shepherd-theme-arrows-plain-buttons .shepherd-content footer .shepherd-buttons li .shepherd-button.udp-tour-back::before {
content: ' ';
width: 6px;
height: 6px;
display: block;
border-left: 1px solid;
border-bottom: 1px solid;
position: absolute;
left: 0px;
top: 8px;
transform: rotate(45deg);
}
a.shepherd-button.udp-tour-end::before {
display: inline-block;
position: relative;
content: "\f335";
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: dashicons;
font-size: 20px;
line-height: 20px;
vertical-align: middle;
margin-top: -2px;
}
.updraftplus-welcome-logo {
display: block;
width: 70px;
float: left;
margin-top: -11px;
margin-right: 12px;
}
.updraftplus-welcome-logo img {
display: block;
width: auto;
max-width: 100%;
}
.highlight-udp .plugins #the-list tr:not([data-slug="updraftplus"]) {
opacity: 0.3;
}
@media(max-width: 790px) {
.shepherd-element.shepherd-theme-arrows-plain-buttons {
display: none;
}
}

View File

@@ -0,0 +1,43 @@
<?php
// @codingStandardsIgnoreStart
/*
To dump the decrypted file using the given key on stdout, call:
rijndael_decrypt_file('../path/to/file.crypt' , 'mykey');
Thus, here are the easy instructions:
1) Add a line like the above into this PHP file (not inside these comments, but outside)
e.g.
rijndael_decrypt_file('/home/myself/myfile.crypt' , 'MYKEY');
2) Run this file (and make sure that includes/Rijndael.php is available, if you are moving this file around)
e.g.
php /home/myself/example-decrypt.php >output.sql.gz
3) You may then want to gunzip the resulting file to have a standard SQL file.
e.g.
gunzip output.sql.gz
*/
// @codingStandardsIgnoreEnd
/**
* An example of how to decrypt a file
*
* @param String $file Full path to file to decrypt
* @param String $key Key or salting to be used
*/
function rijndael_decrypt_file($file, $key) {
include_once(dirname(__FILE__).'/vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php');
$rijndael = new Crypt_Rijndael();
$rijndael->setKey($key);
$ciphertext = file_get_contents($file);
print $rijndael->decrypt($ciphertext);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Some files were not shown because too many files have changed in this diff Show More