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

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);
}
}