Σ(゚Д゚;≡;゚д゚)Σ(゚Д゚;≡;゚д゚)Σ(゚Д゚;≡;゚д゚)始,故人唐宰相鲁公,🆚开府南服,余以布衣从戎。明年,别公漳水湄。后明年,公以事过张睢阳庙及颜杲卿所尝往来处,悲歌慷慨,卒不负其言而从之游。今其诗具在,可考也。😭
wp-fastest-cache/wp-fastest-cache.php 0000644 00000002341 15233365742 0013563 0 ustar 00 <?php
// exit if accessed directly
if ( ! defined( 'ABSPATH' ) )
exit;
/**
* Cookie Notice Modules WP Fastest Cache class.
*
* Compatibility since: 1.0.0
*
* @class Cookie_Notice_Modules_WPFastestCache
*/
class Cookie_Notice_Modules_WPFastestCache {
/**
* Constructor.
*
* @return void
*/
public function __construct() {
add_action( 'plugins_loaded', [ $this, 'check_wpfc' ], 11 );
}
/**
* Add compatibility to WP Fastest Cache plugin.
*
* @return void
*/
public function check_wpfc() {
// is caching enabled?
if ( isset( $GLOBALS['wp_fastest_cache_options']->wpFastestCacheStatus ) ) {
// update 2.4.9+
if ( version_compare( Cookie_Notice()->db_version, '2.4.9', '<=' ) )
$this->delete_cache();
// delete cache files after updating settings or status
add_action( 'cn_configuration_updated', [ $this, 'delete_cache' ] );
}
}
/**
* Delete all cache files.
*
* @return void
*/
public function delete_cache( $type ) {
if ( isset( $GLOBALS['wp_fastest_cache'] ) && method_exists( $GLOBALS['wp_fastest_cache'], 'deleteCache' ) )
$GLOBALS['wp_fastest_cache']->deleteCache( false );
}
}
new Cookie_Notice_Modules_WPFastestCache(); elementor/elementor.php 0000644 00000001200 15233365742 0011244 0 ustar 00 <?php
// exit if accessed directly
if ( ! defined( 'ABSPATH' ) )
exit;
/**
* Cookie Notice Modules Elementor class.
*
* Compatibility since: 1.3.0
*
* @class Cookie_Notice_Modules_Elementor
*/
class Cookie_Notice_Modules_Elementor {
/**
* Constructor.
*
* @return void
*/
public function __construct() {
add_filter( 'cn_is_preview_mode', [ $this, 'is_preview_mode' ] );
}
/**
* Whether elementor editor is active.
*
* @return bool
*/
function is_preview_mode() {
return \Elementor\Plugin::$instance->preview->is_preview_mode();
}
}
new Cookie_Notice_Modules_Elementor(); wp-optimize/wp-optimize.php 0000644 00000002133 15233365742 0012036 0 ustar 00 <?php
// exit if accessed directly
if ( ! defined( 'ABSPATH' ) )
exit;
/**
* Cookie Notice Modules WP-Optimize class.
*
* Compatibility since: 3.0.12
*
* @class Cookie_Notice_Modules_WPOptimize
*/
class Cookie_Notice_Modules_WPOptimize {
/**
* Constructor.
*
* @return void
*/
public function __construct() {
// has to be executed on plugins_loaded with priority 0
$this->check_wpo();
}
/**
* Add compatibility to WP-Optimize plugin.
*
* @return void
*/
public function check_wpo() {
// get wp-optimize configuration
if ( class_exists( 'WPO_Cache_Config' ) ) {
$options = WPO_Cache_Config::instance()->get();
// is caching enabled?
if ( ! empty( $options['enable_page_caching'] ) )
add_filter( 'wpo_purge_cache_hooks', [ $this, 'add_purge_cache' ] );
}
}
/**
* Add action when cache is purged.
*
* @param array $actions
* @return array
*/
public function add_purge_cache( $actions ) {
$actions[] = 'cn_configuration_updated';
return $actions;
}
}
new Cookie_Notice_Modules_WPOptimize(); contact-form-7/recaptcha.js 0000644 00000004066 15233365742 0011614 0 ustar 00 ( function( window, document, undefined ) {
'use strict';
/**
* Initialize recaptcha.
*
* @return {void}
*/
function initRecaptcha() {
wpcf7_recaptcha = {
...( wpcf7_recaptcha ?? {} )
};
const siteKey = wpcf7_recaptcha.sitekey;
const { homepage, contactform } = wpcf7_recaptcha.actions;
const execute = options => {
const { action, func, params } = options;
grecaptcha.execute( siteKey, {
action,
} ).then( token => {
const event = new CustomEvent( 'wpcf7grecaptchaexecuted', {
detail: {
action,
token
}
} );
document.dispatchEvent( event );
} ).then( () => {
if ( typeof func === 'function' ) {
func( ...params );
}
} ).catch( error => console.error( error ) );
};
grecaptcha.ready( () => {
execute( {
action: homepage
} );
} );
document.addEventListener( 'change', event => {
execute( {
action: contactform
} );
} );
if ( typeof wpcf7 !== 'undefined' && typeof wpcf7.submit === 'function' ) {
const submit = wpcf7.submit;
wpcf7.submit = ( form, options = {} ) => {
execute( {
action: contactform,
func: submit,
params: [ form, options ]
} );
};
}
document.addEventListener( 'wpcf7grecaptchaexecuted', event => {
const fields = document.querySelectorAll( 'form.wpcf7-form input[name="_wpcf7_recaptcha_response"]' );
for ( let i = 0; i < fields.length; i++ ) {
let field = fields[ i ];
field.setAttribute( 'value', event.detail.token );
}
} );
}
/**
* Handle cookies-unblocked event.
*
* @return {void}
*/
document.addEventListener( 'cookies-unblocked.hu', function( e ) {
e.detail.data.scripts.forEach( function( script ) {
// find google recaptcha in valid category
if ( script.id === 'google-recaptcha-js' && e.detail.categories[script.dataset.huCategory] === true ) {
script.onload = initRecaptcha;
script.onreadystatechange = initRecaptcha;
}
} );
}, false );
} )( window, document ); contact-form-7/contact-form-7.php 0000644 00000002627 15233365742 0012576 0 ustar 00 <?php
// exit if accessed directly
if ( ! defined( 'ABSPATH' ) )
exit;
/**
* Cookie Notice Modules Contact Form 7 class.
*
* Compatibility since: 5.1.0 (recaptcha v3 only)
*
* @class Cookie_Notice_Modules_ContactForm7
*/
class Cookie_Notice_Modules_ContactForm7 {
private $service;
/**
* Constructor.
*
* @return void
*/
public function __construct() {
$this->service = WPCF7_RECAPTCHA::get_instance();
if ( $this->service->is_active() )
add_action( 'wp_enqueue_scripts', [ $this, 'contact_form_7_recaptcha' ], 21 );
}
/**
* Replace original recaptcha script from Contact Form 7.
*
* @return void
*/
public function contact_form_7_recaptcha() {
// deregister original script
wp_deregister_script( 'wpcf7-recaptcha' );
// register new script
wp_register_script(
'wpcf7-recaptcha',
COOKIE_NOTICE_URL . '/includes/modules/contact-form-7/recaptcha.js',
[
'google-recaptcha',
'wp-polyfill'
],
WPCF7_VERSION,
true
);
wp_enqueue_script( 'wpcf7-recaptcha' );
wp_localize_script(
'wpcf7-recaptcha',
'wpcf7_recaptcha',
[
'sitekey' => $this->service->get_sitekey(),
'actions' => apply_filters(
'wpcf7_recaptcha_actions',
[
'homepage' => 'homepage',
'contactform' => 'contactform'
]
)
]
);
}
}
new Cookie_Notice_Modules_ContactForm7(); litespeed-cache/litespeed-cache.php 0000644 00000001577 15233365742 0013337 0 ustar 00 <?php
// exit if accessed directly
if ( ! defined( 'ABSPATH' ) )
exit;
/**
* Cookie Notice Modules LiteSpeed Cache class.
*
* Compatibility since: 3.0.0
*
* @class Cookie_Notice_Modules_LiteSpeedCache
*/
class Cookie_Notice_Modules_LiteSpeedCache {
/**
* Constructor.
*
* @return void
*/
public function __construct() {
add_filter( 'litespeed_optimize_js_excludes', [ $this, 'exclude_js' ] );
add_filter( 'litespeed_optm_js_defer_exc ', [ $this, 'exclude_js' ] );
}
/**
* Exclude JavaScript external file and inline code.
*
* @param array $excludes
* @return array
*/
function exclude_js( $excludes ) {
// add widget url
$excludes[] = basename( Cookie_Notice()->get_url( 'widget' ) );
// add widget inline code
$excludes[] = 'huOptions';
return $excludes;
}
}
new Cookie_Notice_Modules_LiteSpeedCache(); autoptimize/autoptimize.php 0000644 00000002275 15233365742 0012221 0 ustar 00 <?php
// exit if accessed directly
if ( ! defined( 'ABSPATH' ) )
exit;
/**
* Cookie Notice Modules Autoptimize class.
*
* Compatibility since: 2.4.0
*
* @class Cookie_Notice_Modules_Autoptimize
*/
class Cookie_Notice_Modules_Autoptimize {
/**
* Constructor.
*
* @return void
*/
public function __construct() {
add_filter( 'autoptimize_filter_js_exclude', [ $this, 'exclude' ] );
}
/**
* Exclude JavaScript files or inline code.
*
* @param string $excludes
* @return string
*/
function exclude( $excludes ) {
if ( empty( $excludes ) )
$new_excludes = [];
else {
$new_excludes = explode( ',', $excludes );
$new_excludes = array_filter( $new_excludes );
$new_excludes = array_map( 'trim', $new_excludes );
}
// not found huOptions?
if ( strpos( $excludes, 'huOptions' ) === false )
$new_excludes[] = 'huOptions';
// get widget url
$widget_url = basename( Cookie_Notice()->get_url( 'widget' ) );
// not found widget url?
if ( strpos( $excludes, $widget_url ) === false )
$new_excludes[] = $widget_url;
return implode( ', ', $new_excludes );
}
}
new Cookie_Notice_Modules_Autoptimize(); wp-rocket/wp-rocket.php 0000644 00000002005 15233365742 0011112 0 ustar 00 <?php
// exit if accessed directly
if ( ! defined( 'ABSPATH' ) )
exit;
/**
* Cookie Notice Modules WP Rocket Optimizer class.
*
* Compatibility since: 3.8.0
*
* @class Cookie_Notice_Modules_WPRocket
*/
class Cookie_Notice_Modules_WPRocket {
/**
* Constructor.
*
* @return void
*/
public function __construct() {
add_filter( 'rocket_exclude_defer_js', [ $this, 'exclude_script' ] );
add_filter( 'rocket_defer_inline_exclusions ', [ $this, 'exclude_code' ] );
}
/**
* Exclude JavaScript file.
*
* @param array $excludes
* @return array
*/
function exclude_script( $excludes ) {
// add widget url
$excludes[] = basename( Cookie_Notice()->get_url( 'widget' ) );
return $excludes;
}
/**
* Exclude JavaScript inline code.
*
* @param array $excludes
* @return array
*/
function exclude_code( $excludes ) {
// add widget inline code
$excludes[] = 'huOptions';
return $excludes;
}
}
new Cookie_Notice_Modules_WPRocket(); wp-super-cache/wp-super-cache.php 0000644 00000000630 15233365742 0012734 0 ustar 00 <?php
// exit if accessed directly
if ( ! defined( 'ABSPATH' ) )
exit;
/**
* Cookie Notice Modules WP Super Cache class.
*
* Compatibility since: 1.6.9
*
* @class Cookie_Notice_Modules_WPSuperCache
*/
class Cookie_Notice_Modules_WPSuperCache {
/**
* Constructor.
*
* @return void
*/
public function __construct() {
}
}
new Cookie_Notice_Modules_WPSuperCache(); sg-optimizer/sg-optimizer.php 0000644 00000002065 15233365742 0012354 0 ustar 00 <?php
// exit if accessed directly
if ( ! defined( 'ABSPATH' ) )
exit;
/**
* Cookie Notice Modules SiteGround Optimizer class.
*
* Compatibility since: 5.5.0
*
* @class Cookie_Notice_Modules_SGOptimizer
*/
class Cookie_Notice_Modules_SGOptimizer {
/**
* Constructor.
*
* @return void
*/
public function __construct() {
add_filter( 'sgo_javascript_combine_excluded_external_paths', [ $this, 'exclude_script' ] );
add_filter( 'sgo_javascript_combine_excluded_inline_content', [ $this, 'exclude_code' ] );
}
/**
* Exclude JavaScript file.
*
* @param array $excludes
* @return array
*/
function exclude_script( $excludes ) {
// add widget url
$excludes[] = basename( Cookie_Notice()->get_url( 'widget' ) );
return $excludes;
}
/**
* Exclude JavaScript inline code.
*
* @param array $excludes
* @return array
*/
function exclude_code( $excludes ) {
// add widget inline code
$excludes[] = 'huOptions';
return $excludes;
}
}
new Cookie_Notice_Modules_SGOptimizer(); amp/amp.php 0000644 00000010423 15233365742 0006621 0 ustar 00 <?php
// exit if accessed directly
if ( ! defined( 'ABSPATH' ) )
exit;
/**
* Cookie Notice Modules AMP class.
*
* Compatibility since: 2.0.0
*
* @class Cookie_Notice_Modules_AMP
*/
class Cookie_Notice_Modules_AMP {
private $nonce = '';
/**
* Constructor.
*
* @return void
*/
public function __construct() {
$this->nonce = wp_create_nonce( 'cookie-compliance-amp-consent' );
add_action( 'init', [ $this, 'handle_iframe' ] );
add_action( 'wp_head', [ $this, 'load_amp_consent' ] );
}
/**
* Load AMP consent module.
*
* @return void
*/
public function load_amp_consent() {
if ( function_exists( 'amp_is_request' ) && amp_is_request() ) {
// load styles
echo '
<style amp-custom>
#cnConsentContainer {
background: none;
border: none;
box-shadow: none;
border-radius: 0;
height: 100vh;
width: 100%;
}
#cnConsentContainer amp-iframe {
height: 100vh;
width: 100%;
}
#cnConsentWidget {
height: 100vh;
width: 100%;
}
</style>';
// load scripts
echo '
<script async custom-element="amp-script" src="https://cdn.ampproject.org/v0/amp-script-0.1.js"></script>
<script async custom-element="amp-consent" src="https://cdn.ampproject.org/v0/amp-consent-0.1.js"></script>
<script async custom-element="amp-iframe" src="https://cdn.ampproject.org/v0/amp-iframe-0.1.js"></script>';
// get iframe url
$url = apply_filters( 'cn_cookie_compliance_amp_iframe_url', $this->add_subdomain_to_url( get_site_url(), 'www' ) );
// load consent iframe
echo '
<amp-consent id="cnConsentContainer" layout="nodisplay">
<script type="application/json">
{
"consentInstanceId": "cnConsent",
"consentRequired": true,
"purposeConsentRequired": [ "basic_operations", "content_personalization", "site_optimization", "ad_personalization" ],
"promptUI": "cnConsentWidget"
}
</script>
<div id="cnConsentWidget">
<amp-iframe layout="fill" sandbox="allow-scripts allow-same-origin" src="' . esc_url( $url . '/?cn-amp-consent-iframe=' . $this->nonce ) . '">
<div placeholder></div>
</amp-iframe>
</div>
</amp-consent>';
}
}
/**
* Add subdomain to url.
*
* @param string $url
* @param string $subdomain
* @return string
*/
public function add_subdomain_to_url( $url, $subdomain ) {
// parse url
$parts = parse_url( $url );
// subdomain does not exist?
if ( substr( $parts['host'], 0, strlen( $subdomain ) + 1 ) !== $subdomain . '.' ) {
// find host
$pos = strpos( $url, $parts['host'] );
// update url and add subdomain
$url = substr_replace( $url, $subdomain . '.' . $parts['host'], $pos, strlen( $parts['host'] ) );
}
return $url;
}
/**
* Generate consent iframe.
*
* @return void
*/
public function handle_iframe() {
if ( isset( $_GET['cn-amp-consent-iframe'] ) && $_GET['cn-amp-consent-iframe'] === $this->nonce ) {
wp_ob_end_flush_all();
// display iframe
echo $this->generate_iframe_html();
exit;
}
}
/**
* Generate consent iframe.
*
* @return string
*/
public function generate_iframe_html() {
// get main instance
$cn = Cookie_Notice();
// get options
$options = $cn->frontend->get_cc_options();
// get output
$cc_output = $cn->frontend->get_cc_output( $options );
// get allowed html for cookie compliance html output
$allowed_html = array_merge(
wp_kses_allowed_html( 'post' ),
[
'script' => [
'type' => true,
'src' => true
]
]
);
$html = '
<!DOCTYPE html>
<html ' . get_language_attributes( 'html' ) . '>
<head>
<meta charset="' . esc_attr( get_bloginfo( 'charset', 'display' ) ) . '">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex">
<title>' . esc_html__( 'Cookie Compliance AMP Consent', 'cookie-notice' ) . '</title>
' . wp_kses( $cc_output, $allowed_html ) . '
<script type="text/javascript" src="' . esc_url( COOKIE_NOTICE_URL . '/includes/modules/amp/iframe.js' ) . '"></script>
</head>
<body></body>
</html>';
return $html;
}
}
new Cookie_Notice_Modules_AMP(); amp/iframe.js 0000644 00000002646 15233365742 0007144 0 ustar 00 function cnDisableRevoke() {
// force revoke button to be disabled
hu.options.config.revokeConsent = false;
}
function cnConsentResponse( event ) {
// allow this event to run only once
if ( event.type === 'set-consent.hu' )
document.removeEventListener( 'hide.hu', cnConsentResponse );
document.addEventListener( 'request-finished.hu', cnConsentSet( event.detail.categories ) );
}
function cnConsentSet( categories ) {
return function cnRequestFinished( event ) {
if ( event.detail.request === 'save-consent' && event.detail.endpoint === '/api/transactional/event/save' ) {
var action = 'accept';
// only basic operations?
if ( categories[1] && ! categories[2] && ! categories[3] && ! categories[4] )
action = 'reject';
// inform amp to save consent
window.parent.postMessage(
{
type: 'consent-response',
action: action,
purposeConsents: {
'basic_operations': categories[1],
'content_personalization': categories[2],
'site_optimization': categories[3],
'ad_personalization': categories[4]
}
},
'*'
);
}
}
}
// set consent
document.addEventListener( 'hide.hu', cnConsentResponse );
document.addEventListener( 'set-consent.hu', cnConsentResponse );
// disable revoke button
document.addEventListener( 'init-ready.hu', cnDisableRevoke );
document.addEventListener( 'config-loaded.hu', cnDisableRevoke );
core.php 0000644 00000055600 15233402364 0006215 0 ustar 00 <?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 {
/**
* Retrieve site icon (favicon)
*
* @return array An array containing the site icon (favicon) byte string if available
*/
public function get_site_icon() {
if (!function_exists('get_site_icon_url')) {
include_once(ABSPATH.'wp-includes/general-template.php');
}
if (!class_exists('UpdraftCentral_Media_Commands') && defined('UPDRAFTCENTRAL_CLIENT_DIR')) {
include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/modules/media.php');
}
$media = new UpdraftCentral_Media_Commands($this);
$icon_details = array();
$site_icon_url = get_site_icon_url();
// If none is set in WordPress, let's try to search for the default favicon
// within the site's directory
if (empty($site_icon_url)) {
if (!function_exists('get_site_url')) {
include_once(ABSPATH.'wp-includes/link-template.php');
}
// Common favicon locations to check
$potential_locations = array(
'/favicon.ico',
'/favicon.png',
'/favicon.svg',
'/assets/favicon.ico',
'/assets/images/favicon.ico',
'/apple-touch-icon.png',
'/apple-touch-icon-precomposed.png',
);
foreach ($potential_locations as $location) {
$path = rtrim(ABSPATH, '/\\').$location;
if (file_exists($path)) {
$site_icon_url = get_site_url().$location;
break;
}
}
} else {
$site_icon_id = (int) get_option('site_icon');
if ($site_icon_id) $icon_details = $media->get_media_item(array('id' => $site_icon_id), null, true);
}
// We are returning the site icon as byte string instead of URL in order to avoid
// any hotlink protection that might prevent us to show the icon in UpdraftCentral
// dashboard successfully.
$site_icon = '';
if (!empty($site_icon_url)) {
$content = file_get_contents($site_icon_url);
$mime_type = '';
foreach ($http_response_header as $value) {
if (false !== stripos($value, 'content-type:')) {
list(, $mime_type) = explode(':', preg_replace('/\s+/', '', $value));
break;
}
}
if ($content && !empty($mime_type)) {
$site_icon = 'data: '.$mime_type.';base64,'.base64_encode($content);
}
}
return $this->_response(array('site_icon' => $site_icon, 'icon_details' => $icon_details));
}
/**
* Handles site icon upload
*
* @param Array $query An array containing the image data.
*
* @return Array
*/
public function handle_site_icon_upload($query) {
if (!current_user_can('upload_files')) {
return $this->_generic_error_response('insufficient_permission', array('error_message' => __('You do not have the necessary permissions to upload files.', 'updraftplus')));
}
$data_uri = sanitize_text_field($query['data_uri']);
$filename = sanitize_text_field(basename($query['filename']));
$file_ext = pathinfo($filename, PATHINFO_EXTENSION);
$history = sanitize_text_field($query['history']);
$preview = sanitize_text_field($query['preview']);
$pattern = '/^data:(image\/[^;]+);base64,([A-Za-z0-9+\/=]+)$/i';
if (!empty($data_uri) && preg_match($pattern, $data_uri, $matches)) {
list(, $data) = explode(';', $data_uri);
list(, $data) = explode(',', $data);
$decoded_data = base64_decode($data);
if (!class_exists('wp_tempnam')) include_once(ABSPATH.'/wp-admin/includes/file.php');
$temp_img_file = wp_tempnam();
@file_put_contents($temp_img_file, $decoded_data); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the call.
$mime_type = wp_get_image_mime($temp_img_file);
@unlink($temp_img_file); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise if the file doesn't exist.
$allowed_ext = array('jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif', 'webp', 'avif', 'heic');
if (false !== $mime_type && !empty($matches[1]) && strtolower($mime_type) === strtolower($matches[1]) && !empty($file_ext) && in_array(strtolower($file_ext), $allowed_ext)) {
$upload = wp_upload_bits($filename, null, $decoded_data);
} else {
$upload = array('error' => __("Couldn't verify the actual MIME type of the given site icon image data.", 'updraftplus'));
}
if (!$upload['error']) {
$attachment = array(
'guid' => $upload['url'],
'post_mime_type' => $upload['type'],
'post_title' => sanitize_file_name($filename),
'post_content' => '',
'post_status' => 'inherit',
);
$attach_id = wp_insert_attachment($attachment, $upload['file']);
if (!function_exists('wp_generate_attachment_metadata')) {
require_once(ABSPATH.'wp-admin/includes/image.php');
}
// Generate metadata and thumbnails
$attach_data = wp_generate_attachment_metadata($attach_id, $upload['file']);
wp_update_attachment_metadata($attach_id, $attach_data);
if (update_option('site_icon', $attach_id)) {
if (1 == intval($preview)) {
if (!class_exists('UpdraftCentral_Media_Commands') && defined('UPDRAFTCENTRAL_CLIENT_DIR')) {
include_once(UPDRAFTCENTRAL_CLIENT_DIR.'/modules/media.php');
}
$media = new UpdraftCentral_Media_Commands($this);
$icon_details = $media->get_media_item(array('id' => $attach_id), null, true);
$params = array(
'_ajax_nonce' => $icon_details->nonce,
'postid' => $attach_id,
'history' => $history,
'rand' => $icon_details->misc['rand'],
);
$result = $media->image_preview($params);
$result['data']['icon_details'] = $icon_details;
return $result;
} else {
return $this->get_site_icon();
}
} else {
return $this->_generic_error_response('upload_error', array('error_message' => __('Unable to set uploaded file as site icon.', 'updraftplus')));
}
} else {
return $this->_generic_error_response('upload_error', array('error_message' => $upload['error']));
}
} else {
return $this->_generic_error_response('data_uri_field_empty_or_invalid', array('error_message' => __('Required data URI is either missing or invalid.', 'updraftplus')));
}
}
/**
* 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
*/
public function get_blog_sites() {
if (!is_multisite()) {
return $this->_generic_error_response('not_multisite');
}
$sites = array();
$network_sites = array();
// Use `get_sites` for WP version >= 4.6 else use old `wp_get_sites`.
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();// phpcs:ignore WordPress.WP.DeprecatedFunctions.wp_get_sitesFound -- This function was only intended for backward compatibility with versions below 4.6.
}
}
if (!empty($network_sites)) {
foreach ($network_sites as $site) {
// Check if the site type is an array,
// because `wp_get_sites` returns site data as associative array while,
// `get_sites` returns the data as WP_Site object.
$blog_id = is_array($site) ? $site['blog_id'] : $site->blog_id;
$sites[] = array(
'site_id' => $blog_id,
'name' => get_blog_details($blog_id)->blogname,
);
}
}
return $this->_response(array(
'sites' => $sites,
));
}
/**
* 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 {
$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) || is_a($instance, 'UpdraftCentral_UpdraftPlus_Commands') || is_a($instance, 'UpdraftCentral_WP_Optimize_Commands')) {
$params = empty($params) ? array() : $params;
$call_result = call_user_func(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)
);
if ('translations' === $entity) {
// '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)) {
$entity_directories['translations'] = $language_dir;
}
}
$url = wp_nonce_url(site_url());
$passed = false;
if (isset($entity_directories[$entity])) {
$directory = $entity_directories[$entity];
// Check if credentials are valid and have sufficient
// privileges to create and delete (e.g. write)
ob_start();
$credentials = request_filesystem_credentials($url, '', false, $directory);
ob_end_clean();
// The "WP_Filesystem" will suffice in validating the inputted credentials
// from UpdraftCentral, as it is already attempting to connect to the filesystem
// using the chosen transport (e.g. ssh, ftp, etc.)
$passed = WP_Filesystem($credentials, $directory);
}
if ($passed) {
$result = array('error' => false, 'message' => 'credentials_ok', 'values' => array());
} else {
// We're adding some useful error information to help troubleshooting any problems
// that may arise in the future. If the user submitted a wrong password or username
// it usually falls through here.
global $wp_filesystem;
$errors = array();
if (isset($wp_filesystem->errors) && is_wp_error($wp_filesystem->errors)) {
$errors = $wp_filesystem->errors->errors;
}
$result = array('error' => true, 'message' => 'failed_credentials', 'values' => array('errors' => $errors));
}
} 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).'_'.wp_rand(0, 999999999);
if (!defined('SECURE_AUTH_KEY')) return false;
$hash_it = $user_id.'_'.microtime(true).'_'.wp_rand(0, 999999999).'_'.$secure_auth_key;
$hash = hash('sha256', $hash_it);
return $hash;
}
public function site_info() {
global $wpdb;
// THis is included so we can get $wp_version
@include(ABSPATH.WPINC.'/version.php');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
$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,// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- The variable is defined inside the ABSPATH.WPINC.'/version.php'.
'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() {
if (!function_exists('phpinfo')) return null;
ob_start();
phpinfo(INFO_GENERAL|INFO_CREDITS|INFO_MODULES); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_phpinfo -- we call the phpinfo() function to display PHP information in the advanced tools.
$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;
updraft_try_include_file('admin.php', 'include_once');
global $updraftplus_admin;
return $updraftplus_admin;
}
}
analytics.php 0000644 00000032610 15233402364 0007250 0 ustar 00 <?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_GOOGLE_ANALYTICS_CALLBACK_URL') ? UPDRAFTPLUS_GOOGLE_ANALYTICS_CALLBACK_URL : 'https://auth.updraftplus.com/auth/googleanalytics';
$this->client_id = defined('UPDRAFTPLUS_GOOGLE_ANALYTICS_CLIENT_ID') ? UPDRAFTPLUS_GOOGLE_ANALYTICS_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($result['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($result['tracking_id'])) {
set_transient($this->tracking_id_key, $result['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()); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should be happening when the exception is printed
} 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()); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The escaping should be happening when the exception is printed
} 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);
}
}
plugin.php 0000644 00000060627 15233402364 0006570 0 ustar 00 <?php
if (!defined('UPDRAFTCENTRAL_CLIENT_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 UpdraftCentral_Listener
*/
public function _pre_action($command, $data, $extra_info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This function is called from listener.php and $extra_info is being sent.
// 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 UpdraftCentral_Listener
*/
public function _post_action($command, $data, $extra_info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused parameter is present because the caller from UpdraftCentral_Listener class uses 3 arguments.
// 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');
}
/**
* Installs and activates a plugin through upload
*
* @param array $params Parameter array containing information pertaining the currently uploaded plugin
* @return array Contains the result of the current process
*/
public function upload_plugin($params) {
return $this->process_chunk_upload($params, 'plugin');
}
/**
* 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);
return $this->_response($result);
}
/**
* Checks whether the plugins are currently installed and activated.
*
* @param array $query Parameter array containing the names of the plugins to check
* @return array Contains the result of the current process
*/
public function are_plugins_installed($query) {
if (!isset($query['plugins']) || !is_array($query['plugins'])) {
return $this->_generic_error_response('plugin_name_required');
}
$results = array();
// Process each requested plugin
foreach ($query['plugins'] as $key => $plugin) {
if (!isset($plugin['plugin'])) {
continue;
}
$info = $this->_get_plugin_info($plugin);
$results[$key] = $info;
}
return $this->_response($results);
}
/**
* 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);
if ($info['installed']) {
$activate = activate_plugin($info['plugin_path']);
if (is_wp_error($activate)) {
$result = $this->_generic_error_response('generic_response_error', array(
'plugin' => $query['plugin'],
'error_code' => 'generic_response_error',
'error_message' => $activate->get_error_message(),
'info' => $this->_get_plugin_info($query)
));
} else {
$result = array('activated' => true, 'info' => $this->_get_plugin_info($query), 'last_state' => $info);
}
} else {
$result = $this->_generic_error_response('plugin_not_installed', array(
'plugin' => $query['plugin'],
'error_code' => 'plugin_not_installed',
'error_message' => __('The plugin you wish to activate is either not installed or has been removed recently.', 'updraftplus'),
'info' => $info
));
}
break;
case 'deactivate':
case 'network_deactivate':
$info = $this->_get_plugin_info($query);
if ($info['active']) {
deactivate_plugins($info['plugin_path']);
if (!is_plugin_active($info['plugin_path'])) {
$result = array('deactivated' => true, 'info' => $this->_get_plugin_info($query), 'last_state' => $info);
} else {
$result = $this->_generic_error_response('deactivate_plugin_failed', array(
'plugin' => $query['plugin'],
'error_code' => 'deactivate_plugin_failed',
'error_message' => __('There appears to be a problem deactivating the intended plugin.', 'updraftplus').' '.__('Please check your permissions and try again.', 'updraftplus'),
'info' => $this->_get_plugin_info($query)
));
}
} else {
$result = $this->_generic_error_response('not_active', array(
'plugin' => $query['plugin'],
'error_code' => 'not_active',
'error_message' => __('The plugin you wish to deactivate is currently not active or is already deactivated.', 'updraftplus'),
'info' => $info
));
}
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,
)
));
$info = $this->_get_plugin_info($query);
if (is_wp_error($api)) {
$result = $this->_generic_error_response('generic_response_error', array(
'plugin' => $query['plugin'],
'error_code' => 'generic_response_error',
'error_message' => $api->get_error_message(),
'info' => $info
));
} else {
$installed = $info['installed'];
$error_code = $error_message = '';
if (!$installed) {
// WP < 3.7
if (!class_exists('Automatic_Upgrader_Skin')) include_once(dirname(dirname(__FILE__)).'/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 (is_wp_error($installed)) {
$error_code = $installed->get_error_code();
$error_message = $installed->get_error_message();
} elseif (is_wp_error($skin->result)) {
$error_code = $skin->result->get_error_code();
$error_message = $skin->result->get_error_message();
$error_data = $skin->result->get_error_data($error_code);
if (!empty($error_data)) {
if (is_array($error_data)) $error_data = json_encode($error_data);
$error_message .= ' '.$error_data;
}
} elseif (is_null($installed) || !$installed) {
global $wp_filesystem;
$upgrade_messages = $skin->get_upgrade_messages();
if (!class_exists('WP_Filesystem_Base')) include_once(ABSPATH.'/wp-admin/includes/class-wp-filesystem-base.php');
// Pass through the error from WP_Filesystem if one was raised.
if ($wp_filesystem instanceof WP_Filesystem_Base && is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
$error_code = $wp_filesystem->errors->get_error_code();
$error_message = $wp_filesystem->errors->get_error_message();
} elseif (!empty($upgrade_messages)) {
// We're only after for the last feedback that we received from the install process. Mostly,
// that is where the last error has been inserted.
$messages = $skin->get_upgrade_messages();
$error_code = 'install_failed';
$error_message = end($messages);
} else {
$error_code = 'unable_to_connect_to_filesystem';
$error_message = __('Unable to connect to the filesystem.', 'updraftplus').' '.__('Please confirm your credentials.', 'updraftplus');
}
}
}
if (!$installed || is_wp_error($installed)) {
$result = $this->_generic_error_response('plugin_install_failed', array(
'plugin' => $query['plugin'],
'error_code' => $error_code,
'error_message' => $error_message,
'info' => $this->_get_plugin_info($query)
));
} else {
$result = array('installed' => true, 'info' => $this->_get_plugin_info($query), 'last_state' => $info);
}
}
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;
}
/**
* Processing an action for multiple items
*
* @param array $query Parameter array containing a list of plugins to process
* @return array Contains the results of the bulk process
*/
public function process_action_in_bulk($query) {
$action = isset($query['action']) ? $query['action'] : '';
$items = isset($query['args']) ? $query['args']['items'] : array();
$results = array();
if (!empty($action) && !empty($items) && is_array($items)) {
foreach ($items as $value) {
if (method_exists($this, $action)) {
$results[] = $this->$action($value);
}
}
}
return $this->_response($results);
}
/**
* 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) {
$fields = array('plugin');
$permissions = array('activate_plugins');
$error = $this->_validate_fields_and_capabilities($query, $fields, $permissions);
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$result = $this->_apply_plugin_action((!empty($query['multisite']) && (bool) $query['multisite']) ? 'network_activate' : 'activate', $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) {
$fields = array('plugin');
$permissions = array('activate_plugins');
$error = $this->_validate_fields_and_capabilities($query, $fields, $permissions);
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$result = $this->_apply_plugin_action((!empty($query['multisite']) && (bool) $query['multisite']) ? 'network_deactivate' : 'deactivate', $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) {
$fields = array('plugin', 'slug');
$permissions = array('install_plugins', 'activate_plugins');
$error = $this->_validate_fields_and_capabilities($query, $fields, $permissions);
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$result = $this->_apply_plugin_action('install', $query);
if (!empty($result['installed']) && $result['installed']) {
$result = $this->_apply_plugin_action((!empty($query['multisite']) && (bool) $query['multisite']) ? 'network_activate' : 'activate', $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) {
$fields = array('plugin', 'slug');
$permissions = array('install_plugins');
$error = $this->_validate_fields_and_capabilities($query, $fields, $permissions);
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) {
$fields = array('plugin');
$permissions = array('delete_plugins');
$error = $this->_validate_fields_and_capabilities($query, $fields, $permissions);
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$info = $this->_get_plugin_info($query);
if ($info['installed']) {
// Deactivate first before delete to invalidate the activate
// state/status prior to deleting the item. Otherwise, WordPress will keep
// that state, and as soon as you install the same plugin it will be automatically
// activated since it's previous state was kept.
deactivate_plugins($info['plugin_path']);
$deleted = delete_plugins(array($info['plugin_path']));
if ($deleted) {
$result = array('deleted' => true, 'info' => $this->_get_plugin_info($query), 'last_state' => $info);
} else {
return $this->_generic_error_response('delete_plugin_failed', array(
'plugin' => $query['plugin'],
'error_code' => 'delete_plugin_failed',
'info' => $info
));
}
} else {
return $this->_generic_error_response('plugin_not_installed', array(
'plugin' => $query['plugin'],
'error_code' => 'plugin_not_installed',
'info' => $info
));
}
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) {
$fields = array('plugin', 'slug');
$permissions = array('update_plugins');
$error = $this->_validate_fields_and_capabilities($query, $fields, $permissions);
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
// Make sure that we still have the plugin installed before running
// the update process
$info = $this->_get_plugin_info($query);
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('plugin' => $query['plugin'], 'info' => $info);
}
} else {
return $this->_generic_error_response('plugin_not_installed', array(
'plugin' => $query['plugin'],
'error_code' => 'plugin_not_installed',
'info' => $info
));
}
return $this->_response($result);
}
/**
* Gets the plugin information along with its active and install status
*
* @internal
* @param array $query Contains either the plugin name or slug or both to be used when retrieving information
* @return array
*/
private function _get_plugin_info($query) {
$info = array(
'active' => false,
'installed' => false
);
// Clear plugin cache so that newly installed/downloaded plugins
// gets reflected when calling "get_plugins"
if (function_exists('wp_clean_plugins_cache')) {
wp_clean_plugins_cache();
}
// Gets all plugins available.
$get_plugins = get_plugins();
// Loops around each plugin available.
foreach ($get_plugins as $key => $value) {
$slug = $this->extract_slug_from_info($key, $value);
// If the plugin name matches that of the specified name, it will gather details.
// In case name check isn't enough, we'll use slug to verify if the plugin being queried is actually installed.
//
// Reason for name check failure:
// Due to plugin name inconsistencies - where wordpress.org registered plugin name is different
// from the actual plugin files's metadata (found inside the plugin's PHP file itself).
if ((!empty($query['plugin']) && html_entity_decode($value['Name']) === html_entity_decode($query['plugin'])) || (!empty($query['slug']) && $slug === $query['slug'])) {
$info['installed'] = true;
$info['active'] = is_plugin_active($key);
$info['plugin_path'] = $key;
$info['data'] = $value;
$info['name'] = $value['Name'];
$info['slug'] = $slug;
break;
}
}
return $info;
}
/**
* Extract the slug from the plugin data
*
* @param string $key They key of the current info
* @param array $info Data pulled from the plugin file
*
* @return string
*/
private function extract_slug_from_info($key, $info) {
if (!is_array($info) || empty($info) || empty($key)) return '';
$temp = explode('/', $key);
// With WP standards textdomain must always be equal to the plugin's folder name
// but for premium plugins this may not always be the case thus, we extract the folder
// name from the key as the default slug.
$slug = basename($temp[0], '.php');
if (!empty($info['TextDomain']) && 1 === count($temp)) {
// For plugin without folder we compare the extracted slug with the 'TextDomain'
// and if they're not equal then 'TextDomain' will assume as slug.
if ($slug != $info['TextDomain']) $slug = $info['TextDomain'];
}
// If in case the user kept the hello-dolly plugin then we'll make sure that it gets
// the proper slug for it, otherwise, we'll end up with the wrong slug 'hello' instead of
// 'hello-dolly'. Wrong slug will produce error in UpdraftCentral when running it against
// wordpress.org for further information retrieval.
$slug = ('Hello Dolly' === $info['Name']) ? 'hello-dolly' : $slug;
return $slug;
}
/**
* 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) {
$permissions = array('install_plugins', 'activate_plugins');
if (is_multisite() && !is_super_admin(get_current_user_id())) $permissions = array('activate_plugins');
$error = $this->_validate_fields_and_capabilities($query, array(), $permissions);
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();
if (is_multisite() && !is_super_admin(get_current_user_id())) {
// If the "Plugins" menu is disabled for the subsites on a multisite
// network then we return an empty "plugins" array.
$menu_items = get_site_option('menu_items');
if (empty($menu_items) || !isset($menu_items['plugins'])) {
$plugins = array();
} else {
$show_network_active = apply_filters('show_network_active_plugins', current_user_can('manage_network_plugins'));
$filtered_plugins = array();
foreach ($plugins as $file => $data) {
if (is_network_only_plugin($file) && !is_plugin_active($file)) {
if ($show_network_active) $filtered_plugins[$file] = $data;
} elseif (is_plugin_active_for_network($file)) {
if ($show_network_active) $filtered_plugins[$file] = $data;
} else {
$filtered_plugins[$file] = $data;
}
}
$plugins = $filtered_plugins;
}
}
foreach ($plugins as $key => $value) {
$slug = $this->extract_slug_from_info($key, $value);
$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();
$plugin->site_url = trailingslashit(get_bloginfo('url'));
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,
'is_super_admin' => is_super_admin(),
);
$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));
}
}
rest.php 0000644 00000006067 15233402364 0006245 0 ustar 00 <?php
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
/**
* Handles commands to access the REST API.
*
* This action is used to relay any REST API request through UDC command.
* From UDC we don't have direct authentication to child site's REST APIs, therefore this middleware.
*
* UDC authentication logic automatically authenticates the user with which you have added the site to UDC dashboard.
* And then the request is just relayed to the WordPress rest api handler, which takes care of permission callback and everything as usual.
*/
class UpdraftCentral_REST_API_Access_Commands extends UpdraftCentral_Commands {
protected $switched = false;
/**
* Function that gets called before every action
*
* Link to udrpc_action main function in class UpdraftCentral_Listener
*
* @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
*/
public function _pre_action($command, $data) {
$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
*
* Link to udrpc_action main function in class UpdraftCentral_Listener
*/
public function _post_action() {
// Here, we're restoring to the current (default) blog before we switched
if ($this->switched) restore_current_blog();
}
/**
* Relays the REST API request.
*
* @param array $params The parameters for the request.
*
* @return array The response from the REST API, wrapped in udrpc response structure.
*/
public function handle_request($params) {
$route = untrailingslashit(!empty($params['route']) ? $params['route'] : '');
$method = !empty($params['method']) ? $params['method'] : 'GET';
$body = !empty($params['body']) ? $params['body'] : null;
// Return early if the route is empty.
if (empty($route)) {
return $this->_generic_error_response('route_empty', array(
'prefix' => 'updraftcentral',
'command' => 'handle_request',
'class' => 'UpdraftCentral_REST_API_Access_Commands'
));
}
if (!class_exists('WP_REST_Request')) {
return $this->_generic_error_response('rest_api_not_available_on_this_wordpress_version', array(
'prefix' => 'updraftcentral',
'command' => 'handle_request',
'class' => 'UpdraftCentral_REST_API_Access_Commands'
));
}
$request = new WP_REST_Request($method, '/' . $route);
if (!empty($body)) {
$request->set_body(json_encode($body));
$request->set_header('Content-Type', 'application/json');
}
// Do the request.
$response = rest_do_request($request);
// Return if error.
if (true === $response->is_error()) {
return $this->_generic_error_response('rest_request_failed', $response->as_error());
}
// `get_data` should always return JSON-serializable data.
return $this->_response(array('rest_data' => $response->get_data(), 'headers' => $response->headers));
}
}
theme.php 0000644 00000100461 15233402364 0006363 0 ustar 00 <?php
if (!defined('UPDRAFTCENTRAL_CLIENT_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 UpdraftCentral_Listener
*/
public function _pre_action($command, $data, $extra_info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This function is called from listener.php and $extra_info is being sent.
// 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 UpdraftCentral_Listener
*/
public function _post_action($command, $data, $extra_info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused parameter is present because the caller from UpdraftCentral_Listener class uses 3 arguments.
// 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');
}
/**
* Installs and activates a theme through upload
*
* @param array $params Parameter array containing information pertaining the currently uploaded theme
* @return array Contains the result of the current process
*/
public function upload_theme($params) {
return $this->process_chunk_upload($params, 'theme');
}
/**
* 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, 'info' => $this->_get_theme_info($query['theme']), 'last_state' => $info);
} else {
$result = $this->_generic_error_response('theme_not_activated', array(
'theme' => $query['theme'],
'error_code' => 'theme_not_activated',
'error_message' => __('There appears to be a problem activating or switching to the intended theme.', 'updraftplus').' '.__('Please check your permissions and try again.', 'updraftplus'),
'info' => $this->_get_theme_info($query['theme'])
));
}
} else {
$result = $this->_generic_error_response('theme_not_installed', array(
'theme' => $query['theme'],
'error_code' => 'theme_not_installed',
'error_message' => __('The theme you wish to activate is either not installed or has been removed recently.', 'updraftplus'),
'info' => $info
));
}
break;
case 'network_enable':
$info = $this->_get_theme_info($query['theme']);
if ($info['installed']) {
if (current_user_can('manage_network_themes')) {
// 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, 'info' => $this->_get_theme_info($query['theme']), 'last_state' => $info);
} else {
$result = $this->_generic_error_response('theme_not_enabled', array(
'theme' => $query['theme'],
'error_code' => 'theme_not_enabled',
'error_message' => __('There appears to be a problem enabling the intended theme on your network.', 'updraftplus').' '.__('Please kindly check your permission and try again.', 'updraftplus'),
'info' => $this->_get_theme_info($query['theme'])
));
}
} else {
$result = $this->_generic_error_response('theme_not_installed', array(
'theme' => $query['theme'],
'error_code' => 'theme_not_installed',
'error_message' => __('The theme you wish to enable on your network is either not installed or has been removed recently.', 'updraftplus'),
'info' => $info
));
}
break;
case 'network_disable':
$info = $this->_get_theme_info($query['theme']);
if ($info['installed']) {
if (current_user_can('manage_network_themes')) {
// 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, 'info' => $this->_get_theme_info($query['theme']), 'last_state' => $info);
} else {
$result = $this->_generic_error_response('theme_not_disabled', array(
'theme' => $query['theme'],
'error_code' => 'theme_not_disabled',
'error_message' => __('There appears to be a problem disabling the intended theme from your network.', 'updraftplus').' '.__('Please kindly check your permission and try again.', 'updraftplus'),
'info' => $this->_get_theme_info($query['theme'])
));
}
} else {
$result = $this->_generic_error_response('theme_not_installed', array(
'theme' => $query['theme'],
'error_code' => 'theme_not_installed',
'error_message' => __('The theme you wish to disable from your network is either not installed or has been removed recently.', 'updraftplus'),
'info' => $info
));
}
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,
)
));
$info = $this->_get_theme_info($query['theme']);
if (is_wp_error($api)) {
$result = $this->_generic_error_response('generic_response_error', array(
'theme' => $query['theme'],
'error_code' => 'theme_not_installed',
'error_message' => $api->get_error_message(),
'info' => $info
));
} else {
$installed = $info['installed'];
$error_code = $error_message = '';
if (!$installed) {
// WP < 3.7
if (!class_exists('Automatic_Upgrader_Skin')) include_once(dirname(dirname(__FILE__)).'/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 (is_wp_error($installed)) {
$error_code = $installed->get_error_code();
$error_message = $installed->get_error_message();
} elseif (is_wp_error($skin->result)) {
$error_code = $skin->result->get_error_code();
$error_message = $skin->result->get_error_message();
$error_data = $skin->result->get_error_data($error_code);
if (!empty($error_data)) {
if (is_array($error_data)) $error_data = json_encode($error_data);
$error_message .= ' '.$error_data;
}
} elseif (is_null($installed) || !$installed) {
global $wp_filesystem;
$upgrade_messages = $skin->get_upgrade_messages();
if (!class_exists('WP_Filesystem_Base')) include_once(ABSPATH.'/wp-admin/includes/class-wp-filesystem-base.php');
// Pass through the error from WP_Filesystem if one was raised.
if ($wp_filesystem instanceof WP_Filesystem_Base && is_wp_error($wp_filesystem->errors) && $wp_filesystem->errors->get_error_code()) {
$error_code = $wp_filesystem->errors->get_error_code();
$error_message = $wp_filesystem->errors->get_error_message();
} elseif (!empty($upgrade_messages)) {
// We're only after for the last feedback that we received from the install process. Mostly,
// that is where the last error has been inserted.
$messages = $skin->get_upgrade_messages();
$error_code = 'install_failed';
$error_message = end($messages);
} else {
$error_code = 'unable_to_connect_to_filesystem';
$error_message = __('Unable to connect to the filesystem.', 'updraftplus').' '.__('Please confirm your credentials.', 'updraftplus');
}
}
}
if (!$installed || is_wp_error($installed)) {
$result = $this->_generic_error_response('theme_install_failed', array(
'theme' => $query['theme'],
'error_code' => $error_code,
'error_message' => $error_message,
'info' => $this->_get_theme_info($query['theme'])
));
} else {
$result = array('installed' => true, 'info' => $this->_get_theme_info($query['theme']), 'last_state' => $info);
}
}
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;
}
/**
* Processing an action for multiple items
*
* @param array $query Parameter array containing a list of themes to process
* @return array Contains the results of the bulk process
*/
public function process_action_in_bulk($query) {
$action = isset($query['action']) ? $query['action'] : '';
$items = isset($query['args']) ? $query['args']['items'] : array();
$results = array();
if (!empty($action) && !empty($items) && is_array($items)) {
foreach ($items as $value) {
if (method_exists($this, $action)) {
$results[] = $this->$action($value);
}
}
}
return $this->_response($results);
}
/**
* 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) {
$fields = array('theme');
$permissions = array('switch_themes');
$error = $this->_validate_fields_and_capabilities($query, $fields, $permissions);
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$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) {
$fields = array('theme');
$permissions = array('switch_themes');
$error = $this->_validate_fields_and_capabilities($query, $fields, $permissions);
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$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) {
$fields = array('theme');
$permissions = array('switch_themes');
$error = $this->_validate_fields_and_capabilities($query, $fields, $permissions);
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
$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) {
$fields = array('theme', 'slug');
$permissions = array('install_themes', 'switch_themes');
$error = $this->_validate_fields_and_capabilities($query, $fields, $permissions);
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) {
$fields = array('theme', 'slug');
$permissions = array('install_themes');
$error = $this->_validate_fields_and_capabilities($query, $fields, $permissions);
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) {
$fields = array('theme');
$permissions = array('delete_themes');
$error = $this->_validate_fields_and_capabilities($query, $fields, $permissions);
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, 'info' => $this->_get_theme_info($query['theme']), 'last_state' => $info);
} else {
return $this->_generic_error_response('delete_theme_failed', array(
'theme' => $query['theme'],
'error_code' => 'delete_theme_failed',
'info' => $info
));
}
} else {
return $this->_generic_error_response('theme_not_installed', array(
'theme' => $query['theme'],
'error_code' => 'theme_not_installed',
'info' => $info
));
}
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) {
$fields = array('theme');
$permissions = array('update_themes');
$error = $this->_validate_fields_and_capabilities($query, $fields, $permissions);
if (!empty($error)) {
return $error;
}
$this->_preload_credentials($query);
// Make sure that we still have the theme installed before running
// the update process
$info = $this->_get_theme_info($query['theme']);
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('theme' => $query['theme'], 'info' => $info);
}
} else {
return $this->_generic_error_response('theme_not_installed', array(
'theme' => $query['theme'],
'error_code' => 'theme_not_installed',
'info' => $info
));
}
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"
if (function_exists('wp_clean_themes_cache')) {
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;
$info['name'] = $theme_name;
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) {
$permissions = array('install_themes', 'switch_themes');
$args = array();
if (is_multisite() && !is_super_admin(get_current_user_id())) {
$permissions = array('switch_themes');
$args = array('allowed' => true, 'blog_id' => get_current_blog_id());
}
$error = $this->_validate_fields_and_capabilities($query, array(), $permissions);
if (!empty($error)) {
return $error;
}
// Get pagination parameters early
$updates_only = isset($query['updates_only']) ? filter_var($query['updates_only'], FILTER_VALIDATE_BOOLEAN) : false;
$include_updates_pagination = isset($query['include_updates_pagination']) && $query['include_updates_pagination'];
$per_page = isset($query['per_page']) ? (int) $query['per_page'] : 10;
$page = isset($query['page']) ? (int) $query['page'] : 1;
$offset = ($page - 1) * $per_page;
// A call is considered legacy if neither updates_only nor include_updates_pagination flags are set
$is_legacy_call = !isset($query['updates_only']) && !isset($query['include_updates_pagination']) && !isset($query['per_page']);
$sort_direction = isset($query['sort_direction']) ? strtolower($query['sort_direction']) : 'asc';
// Initialize arrays
$themes_with_updates = array();
$themes_without_updates = array();
$installed_slugs = array();
$snoozed_themes_info = array();
// Get current theme info once
$current_theme_slug = basename(get_stylesheet_directory());
$website = get_bloginfo('name');
// Load updates
if (!class_exists('UpdraftCentral_Updates_Commands')) include_once('updates.php');
$updates = new UpdraftCentral_Updates_Commands($this->rc);
$theme_updates = (array) $updates->get_item_updates('themes');
// Get all themes
$themes = wp_get_themes($args);
// Sort themes by name
if (!empty($themes)) {
uasort($themes, array($this, '_sort_themes_by_name'));
}
// Get list of snoozed theme slugs if provided
$snoozed_slugs = isset($query['snoozed_themes']) ? $query['snoozed_themes'] : array();
// Prepare config object for theme data
$config = array(
'website' => $website,
'current_theme_slug' => $current_theme_slug,
'is_legacy_call' => $is_legacy_call,
'snoozed_slugs' => $snoozed_slugs
);
// Process themes efficiently
foreach ($themes as $slug => $value) {
// Skip processing if we're only looking for updates and this theme has none
if ($updates_only && empty($theme_updates[$slug])) {
continue;
}
$config['slug'] = $slug;
$theme = $this->_prepare_theme_data($value, $config);
// Track installed slugs if needed
if (!empty($query['get_installed_slugs'])) {
$installed_slugs[] = $slug;
}
// Check for updates and categorize
if (!empty($theme_updates[$slug]) && version_compare($theme->version, $theme_updates[$slug]->update['new_version'], '<')) {
$theme = $this->_add_update_info($theme, $theme_updates[$slug], $is_legacy_call);
// Add to themes_with_updates regardless of snooze status
if ($slug === $current_theme_slug) {
$themes_with_updates = array($theme->slug => $theme) + $themes_with_updates;
} else {
$themes_with_updates[$theme->slug] = $theme;
}
// Track snoozed status separately
if (in_array($slug, $snoozed_slugs)) {
$snoozed_themes_info[$slug] = $theme;
}
} else {
if ($slug === $current_theme_slug) {
$themes_without_updates = array($theme->slug => $theme) + $themes_without_updates;
} else {
$themes_without_updates[$theme->slug] = $theme;
}
}
}
// Handle response based on call type
if ($is_legacy_call) {
$result = $this->_prepare_legacy_response($themes_with_updates, $themes_without_updates, $theme_updates);
} else {
$result = $this->_prepare_paginated_response(
$themes_with_updates,
$themes_without_updates,
$updates_only,
$include_updates_pagination,
$per_page,
$page,
$offset,
$current_theme_slug,
$sort_direction
);
}
// Add installed slugs if requested
if (!empty($query['get_installed_slugs'])) {
$result['installed_slugs'] = $installed_slugs;
}
// Add snoozed themes info if any were requested
if (!empty($snoozed_themes_info)) {
$result['snoozed_themes_info'] = $snoozed_themes_info;
}
return $this->_response(array_merge($result, $this->_get_backup_credentials_settings(get_theme_root())));
}
/**
* Prepare basic theme data
*
* @param WP_Theme $value Theme object
* @param array $config Configuration array containing:
* - slug: Theme slug
* - website: Website name
* - current_theme_slug: Current active theme slug
* - is_legacy_call: Whether this is a legacy call
* - snoozed_slugs: Array of snoozed theme slugs
* @return stdClass Theme data object
*/
private function _prepare_theme_data($value, $config) {
$theme = new stdClass();
$theme->name = $value->get('Name') ? $value->get('Name') : $config['slug'];
$theme->description = $value->get('Description');
$theme->slug = $config['slug'];
$theme->version = $value->get('Version');
$theme->author = $value->get('Author');
$theme->status = ($config['slug'] === $config['current_theme_slug']) ? 'active' : 'inactive';
// Set is_snoozed property for all themes
$theme->is_snoozed = !empty($config['snoozed_slugs']) && in_array($config['slug'], $config['snoozed_slugs']);
if (!$config['is_legacy_call']) {
$screenshot = $value->get_screenshot();
$theme->screenshot = $screenshot ? $screenshot : null;
$theme->has_update = false;
}
$template = $value->get('Template');
$theme->child_theme = !empty($template);
$theme->website = $config['website'];
$theme->multisite = is_multisite();
$theme->site_url = trailingslashit(get_bloginfo('url'));
if ($theme->child_theme) {
$parent_theme = wp_get_theme($template);
$theme->parent = $parent_theme->get('Name') ? $parent_theme->get('Name') : $parent_theme->get_stylesheet();
}
if (empty($theme->short_description) && !empty($theme->description)) {
$temp = explode('.', $theme->description);
$theme->short_description = $temp[0] . (isset($temp[1]) ? '.' . $temp[1] : '') . '.';
}
return $theme;
}
/**
* Add update information to theme object
*
* @param stdClass $theme Theme object
* @param object $update_info Update information
* @param bool $is_legacy_call Whether this is a legacy call
* @return stdClass Updated theme object
*/
private function _add_update_info($theme, $update_info, $is_legacy_call) {
$theme->latest_version = $update_info->update['new_version'];
$theme->download_link = $update_info->update['package'];
if (!$is_legacy_call) {
$theme->update_url = $update_info->update['url'];
$theme->requires = $update_info->update['requires'];
$theme->requires_php = $update_info->update['requires_php'];
$theme->has_update = true;
}
return $theme;
}
/**
* Prepare legacy response format
*
* @param array $themes_with_updates Themes with updates
* @param array $themes_without_updates Themes without updates
* @param array $theme_updates Theme updates data
* @return array Legacy format response
*/
private function _prepare_legacy_response($themes_with_updates, $themes_without_updates, $theme_updates) {
return array(
'themes' => array_merge(array_values($themes_with_updates), array_values($themes_without_updates)),
'theme_updates' => $theme_updates,
'is_super_admin' => is_super_admin(),
);
}
/**
* Prepare paginated response format
*
* @param array $themes_with_updates Themes with updates
* @param array $themes_without_updates Themes without updates
* @param bool $updates_only Whether only updates are requested
* @param bool $include_updates_pagination Whether to include updates pagination
* @param int $per_page Number of items per page
* @param int $page Current page number
* @param int $offset Offset for pagination
* @param string $current_theme_slug Current active theme slug
* @param string $sort_direction Sort direction
* @return array Paginated response
*/
private function _prepare_paginated_response($themes_with_updates, $themes_without_updates, $updates_only, $include_updates_pagination, $per_page, $page, $offset, $current_theme_slug, $sort_direction) {
// If only requesting updates, filter out snoozed themes here
if ($updates_only) {
$filtered_updates = array_filter($themes_with_updates, array($this, '_filter_non_snoozed'));
$results = array_slice($filtered_updates, $offset, $per_page);
return array(
'theme_updates' => $results,
'pagination' => array(
'total' => count($filtered_updates),
'per_page' => $per_page,
'current_page' => $page,
'total_pages' => ceil(count($filtered_updates) / $per_page)
)
);
}
// For all themes view, merge and sort
$all_themes = array_merge($themes_with_updates, $themes_without_updates);
// Sort themes
global $current_theme_slug, $sort_direction;
$current_theme_slug = $current_theme_slug;
$sort_direction = $sort_direction;
uasort($all_themes, array($this, '_sort_themes_with_active'));
// Get paginated results for all themes
$results = array_slice($all_themes, $offset, $per_page);
// Filter out snoozed themes from updates list
$filtered_updates = array_filter($themes_with_updates, array($this, '_filter_non_snoozed'));
// For theme_updates, use the filtered list
$paginated_updates = $include_updates_pagination ? array_slice($filtered_updates, 0, $per_page) : $filtered_updates;
// Return with all the original data
$total_themes = count($all_themes);
$result = array(
'themes' => $results, // Contains ALL themes including snoozed ones
'theme_updates' => $paginated_updates, // Contains only non-snoozed themes with updates
'is_super_admin' => is_super_admin(),
'pagination' => array(
'total' => $total_themes,
'per_page' => $per_page,
'current_page' => $page,
'total_pages' => ceil($total_themes / $per_page)
),
'updates_count' => count($filtered_updates)
);
if ($include_updates_pagination) {
$result['updates_pagination'] = array(
'total' => count($filtered_updates),
'per_page' => $per_page,
'current_page' => 1,
'total_pages' => ceil(count($filtered_updates) / $per_page)
);
}
return $result;
}
/**
* Sort themes by name
*
* @param object $a Theme object
* @param object $b Theme object
* @return int Comparison result
*/
private function _sort_themes_by_name($a, $b) {
global $sort_direction;
$result = strcasecmp($a->name, $b->name);
return 'desc' === $sort_direction ? -$result : $result;
}
/**
* Sort all themes while keeping active theme first
*
* @param object $a Theme object
* @param object $b Theme object
* @return int Comparison result
*/
private function _sort_themes_with_active($a, $b) {
global $current_theme_slug, $sort_direction;
// Active theme always first
if ($a->slug === $current_theme_slug) return -1;
if ($b->slug === $current_theme_slug) return 1;
// Sort by name respecting direction
$result = strcasecmp($a->name, $b->name);
return 'desc' === $sort_direction ? -$result : $result;
}
/**
* Filter out snoozed themes
*
* @param object $theme Theme object
* @return bool Whether the theme is not snoozed
*/
private function _filter_non_snoozed($theme) {
return !$theme->is_snoozed;
}
/**
* 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()));
}
}
posts.php 0000644 00000207775 15233402364 0006451 0 ustar 00 <?php
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Direct $wpdb query is required for this operation.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- some query operations need to always receive the most up-to-date or actual data directly from the database, reducing the risk of serving stale information.
if (!defined('ABSPATH')) die('No direct access allowed');
/**
* Handles Posts Commands
*/
class UpdraftCentral_Posts_Commands extends UpdraftCentral_Commands {
protected $switched = false;
protected $post_type = 'post';
/**
* 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 UpdraftCentral_Listener
*/
public function _pre_action($command, $data, $extra_info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This function is called from listener.php and $extra_info is being sent.
// 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 UpdraftCentral_Listener
*/
public function _post_action($command, $data, $extra_info) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
// Here, we're restoring to the current (default) blog before we switched
if ($this->switched) restore_current_blog();
}
/**
* Returns the keys and fields names that are associated to a particular module type
*
* @param string $type The type of the module that the current request is processing
*
* @return array
*/
private function get_state_fields_by_type($type) {
$state_fields = array(
'post' => array(
'validation_fields' => array('publish_posts', 'edit_posts', 'delete_posts'),
'items_key' => 'posts',
'count_key' => 'posts_count',
'list_key' => 'posts',
'result_key' => 'get',
'error_key' => 'post_state_change_failed'
),
'page' => array(
'validation_fields' => array('publish_pages', 'edit_pages', 'delete_pages'),
'items_key' => 'pages',
'count_key' => 'pages_count',
'list_key' => 'pages',
'result_key' => 'get',
'error_key' => 'page_state_change_failed'
)
);
if (!isset($state_fields[$type])) return array();
return $state_fields[$type];
}
/**
* Fetch and retrieves posts based from the submitted parameters
*
* @param array $params Containing all the needed information to filter the results of the current request
* @return array
*/
public function get($params) {
$state_fields = $this->get_state_fields_by_type($this->post_type);
if (empty($state_fields)) return $this->_generic_error_response('unsupported_type_on_get_posts');
$error = $this->_validate_capabilities($state_fields['validation_fields']);
if (!empty($error)) return $error;
// check paged parameter; if empty set to defaults
$paged = !empty($params['paged']) ? (int) $params['paged'] : 1;
$numberposts = !empty($params['numberposts']) ? (int) $params['numberposts'] : 10;
$offset = ($paged - 1) * $numberposts;
$args = array(
'posts_per_page' => $numberposts,
'paged' => $paged,
'offset' => $offset,
'post_type' => $this->post_type,
'post_status' => 'publish,private,draft,pending,future',
);
if (!empty($params['keyword'])) {
$args['s'] = $params['keyword'];
}
if ('post' == $this->post_type) {
if (!empty($params['category'])) {
$args['cat'] = (int) $params['category'];
}
}
if (!empty($params['date'])) {
list($monthnum, $year) = explode(':', $params['date']);
$args['monthnum'] = $monthnum;
$args['year'] = $year;
}
if (!empty($params['status']) && 'all' !== $params['status']) {
$args['post_status'] = $params['status'];
}
$query = new WP_Query($args);
$result = $query->posts;
$count_posts = (int) $query->found_posts;
$page_count = 0;
if ($count_posts > 0) {
$page_count = absint($count_posts / $numberposts);
$remainder = absint($count_posts % $numberposts);
$page_count = ($remainder > 0) ? ++$page_count : $page_count;
}
$info = array(
'page' => $paged,
'pages' => $page_count,
'results' => $count_posts,
'items_from' => (($paged * $numberposts) - $numberposts) + 1,
'items_to' => ($paged == $page_count) ? $count_posts : $paged * $numberposts,
);
$posts = array();
if (!empty($result)) {
foreach ($result as $post) {
// Pulling any other relevant and additional information regarding
// the post before returning it in the response.
$postdata = $this->get_postdata($post, false);
if (!empty($postdata)) {
array_push($posts, $postdata);
}
}
}
$response = array(
$state_fields['items_key'] => $posts,
'options' => $this->get_options($this->post_type),
'info' => $info,
$state_fields['count_key'] => $this->get_post_status_counts($this->post_type)
);
// Load any additional information if preload parameter is set. Will only be
// requested on initial load of items in UpdraftCentral.
if (isset($params['preload']) && $params['preload']) {
$timeout = !empty($params['timeout']) ? $params['timeout'] : 30;
$response = array_merge($response, $this->get_preload_data($timeout, $this->post_type));
}
return $this->_response($response);
}
/**
* Extracts public properties from complex object and return a simple
* object (stdClass) that contains the public properties of the original object.
*
* @param object $obj Any type of complex objects that needs converting (e.g. WP_Taxonomy, WP_Term or WP_User)
* @return stdClass
*/
protected function trim_object($obj) {
// To preserve the object's accessibility through its properties we recreate
// the object using the stdClass and fill it with the public properties
// that will be extracted from the original object ($obj).
$newObj = new stdClass();
if (is_object($obj)) {
// Making sure that we only extract those publicly accessible properties excluding
// the private, protected, static ones and methods.
$props = get_object_vars($obj);
if (!empty($props)) {
foreach ($props as $key => $value) {
$newObj->{$key} = $value;
}
}
}
return $newObj;
}
/**
* Retrieves information that will be preloaded in UC for quick and easy access
* when editing a certain page or post
*
* @param int $timeout The user-defined timeout from UpdraftCentral
* @param string $type The type of the module that the current request is processing
*
* @return array
*/
protected function get_preload_data($timeout, $type = 'post') {
global $updraftcentral_host_plugin, $updraftcentral_main;
if (!function_exists('get_page_templates')) {
require_once(ABSPATH.'wp-admin/includes/theme.php');
}
if (!function_exists('get_block_editor_server_block_settings')) {
require_once(ABSPATH.'wp-admin/includes/post.php');
}
$templates = ('post' == $type) ? get_page_templates(null, 'post') : get_page_templates();
if (!empty($templates)) {
$templates = array_flip($templates);
if (!isset($templates['default'])) {
$templates['default'] = $updraftcentral_host_plugin->retrieve_show_message('default_template');
}
}
// Preloading elements saves time and avoid unnecessary round trips to fetch
// these information individually.
$authors = $this->get_authors();
$parent_pages = $this->get_parent_pages();
$data = array(
'authors' => $authors['data']['authors'],
'parent_pages' => $parent_pages['data']['pages'],
'templates' => $templates,
'editor_styles' => $this->get_editor_styles($timeout),
'wp_version' => $updraftcentral_main->get_wordpress_version()
);
if ('post' == $type) {
$categories = $this->get_categories();
$tags = $this->get_tags();
$data['taxonomies'] = $this->get_taxonomies();
$data['categories'] = $categories['data'];
$data['tags'] = $tags['data'];
}
global $post;
$context = class_exists('WP_Block_Editor_Context') ? new WP_Block_Editor_Context(array('post' => $post)) : $post;
// Load block patterns from w.org.
if (function_exists('_load_remote_block_patterns')) _load_remote_block_patterns();
if (function_exists('_load_remote_featured_patterns')) _load_remote_featured_patterns();
$block_types = class_exists('WP_Block_Type_Registry') ? WP_Block_Type_Registry::get_instance()->get_all_registered() : array();
$block_patterns = class_exists('WP_Block_Patterns_Registry') ? WP_Block_Patterns_Registry::get_instance()->get_all_registered() : array();
$block_pattern_categories = class_exists('WP_Block_Pattern_Categories_Registry') ? WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered() : array();
$block_styles = class_exists('WP_Block_Styles_Registry') ? WP_Block_Styles_Registry::get_instance()->get_all_registered() : array();
$block_data = array(
'block_categories' => get_block_categories($context),
'block_definitions' => get_block_editor_server_block_settings(),
'block_types' => $block_types,
'block_patterns' => $block_patterns,
'block_pattern_categories' => $block_pattern_categories,
'block_styles' => $block_styles
);
$data = array_merge($data, $block_data);
return array(
'preloaded' => json_encode($data)
);
}
/**
* Extract content from the given css path
*
* @param string $style CSS file path
* @param int $timeout The user-defined timeout from UpdraftCentral
* @return array
*/
protected function extract_css_content($style, $timeout) {
$content = '';
if (1 === preg_match('~^(https?:)?//~i', $style)) {
$response = wp_remote_get($style, array('timeout' => $timeout));
if (!is_wp_error($response)) {
$result = trim(wp_remote_retrieve_body($response));
if (!empty($result)) $content = $result;
}
} else {
// Editor styles that resides in "css/dist"
if (false !== ($pos = stripos($style, 'css/dist'))) {
$file = ABSPATH.WPINC.substr_replace($style, '/', 0, $pos);
} else {
// Styles that resides in "wp-content/themes" (coming from $editor_styles global var)
$file = get_theme_file_path($style);
}
$is_valid = (function_exists('is_file')) ? is_file($file) : file_exists($file);
if ($is_valid) {
$result = trim(file_get_contents($file));
if (!empty($result)) $content = $result;
}
}
return $this->extract_custom_fonts($this->filter_url($content));
}
/**
* Extract custom fonts defined within the css content. Basically,
* separating custom font (@font-face) rules from the Style/css content.
*
* @param string $content Style content
* @return array
*/
protected function extract_custom_fonts($content) {
$fonts = array();
while ($start = strpos($content, '@font-face')) {
$end = strpos($content, '}', $start) + 1;
$length = $end - $start;
$font = substr($content, $start, $length);
$fonts[]= $this->update_font_src($font);
$content = str_replace($font, '', $content);
}
return array(
'content' => $content,
'fonts' => $fonts
);
}
/**
* Updates the font URL to point to the UpdraftCentral "load_font" action
*
* @param string $font Font-face definition/content
* @return string
*/
protected function update_font_src($font) {
$start = strpos($font, 'src:') + 4;
$end = strpos($font, ';', $start);
$length = $end - $start;
$src = trim(substr($font, $start, $length));
$temp = explode(' ', $src);
preg_match('/^url\((.*)\)$/i', $temp[0], $matches);
$url = '';
if (!empty($matches)) {
$url = trim(trim($matches[1], "'"), '"');
if (strlen($url)) {
$font_url = 'CENTRAL_URL/?udcentral_action=load_font&font='.urlencode($url);
$font = str_replace($url, $font_url, $font);
}
}
return $font;
}
/**
* Convert URL entries contained in the CSS content to absolute URLs
*
* @param string $content The content of the CSS file
* @return string
*/
protected function filter_url($content) {
// Replace with valid URL (absolute)
preg_match_all('~url\((.+?)\)~i', $content, $all_matches);
if (!empty($all_matches) && isset($all_matches[1])) {
$urls = array_unique($all_matches[1]);
foreach ($urls as $url) {
$url = str_replace('"', '', $url);
if (false !== strpos($url, 'data:')) continue;
if (1 !== preg_match('~^(https?:)?//~i', $url)) {
if (1 === preg_match('~(plugins|themes)~i', $url, $matches)) {
if (false !== ($pos = stripos($url, $matches[1]))) {
if (!function_exists('content_url')) {
require_once ABSPATH.WPINC.'/link-template.php';
}
$absolute_url = rtrim(content_url(), '/').substr_replace($url, '/', 0, $pos);
$content = str_replace($url, $absolute_url, $content);
}
} else {
$path = preg_replace('~(\.+\/)~', '', $url);
$dirpath = trailingslashit(get_stylesheet_directory());
if (!file_exists($dirpath.$url)) $path = $this->resolve_path($path);
$absolute_url = (!empty($path)) ? trailingslashit(get_stylesheet_directory_uri()).ltrim($path, '/') : '';
$content = str_replace($url, $absolute_url, $content);
}
}
}
}
return $content;
}
/**
* Resolve URL to its actual absolute path
*
* @param string $path Some relative path to check
* @return string
*/
protected function resolve_path($path) {
$dir = trailingslashit(get_stylesheet_directory());
// Some relative paths declared within the css file (e.g. only has '../fonts/etc/', called deep down from a subfolder) where parent
// subfolder is not articulated needs to be resolve further to get its actual absolute path. Using glob will pinpoint its actual location
// rather than iterating through a series of sublevels just to find the actual file.
$result = str_replace($dir, '', glob($dir.'{,*/}{'.$path.'}', GLOB_BRACE));
if (!empty($result)) return $result[0];
return false;
}
/**
* Retrieves block editor assets for iframe.
*
* @return string
*/
protected function get_iframed_editor_assets() {
$script_handles = array();
$style_handles = array(
'wp-block-editor',
'wp-block-library',
'wp-block-library-theme',
'wp-edit-blocks',
);
if (class_exists('WP_Block_Type_Registry')) {
$block_registry = WP_Block_Type_Registry::get_instance();
foreach ($block_registry->get_all_registered() as $block_type) {
if (!empty($block_type->style)) {
if (is_array($block_type->style)) {
$style_handles = array_merge($style_handles, $block_type->style);
} else {
$style_handles[] = $block_type->style;
}
}
if (!empty($block_type->editor_style)) {
if (is_array($block_type->editor_style)) {
$style_handles = array_merge($style_handles, $block_type->editor_style);
} else {
$style_handles[] = $block_type->editor_style;
}
}
if (!empty($block_type->script)) {
if (is_array($block_type->script)) {
$script_handles = array_merge($script_handles, $block_type->script);
} else {
$script_handles[] = $block_type->script;
}
}
}
}
$style_handles = array_unique($style_handles);
$done = wp_styles()->done;
ob_start();
// We do not need reset styles for the iframed editor.
wp_styles()->done = array('wp-reset-editor-styles');
wp_styles()->do_items($style_handles);
wp_styles()->done = $done;
$styles = ob_get_clean();
$script_handles = array_unique($script_handles);
$done = wp_scripts()->done;
ob_start();
wp_scripts()->done = array();
wp_scripts()->do_items($script_handles);
wp_scripts()->done = $done;
$scripts = ob_get_clean();
return wp_json_encode(array(
'styles' => $styles,
'scripts' => $scripts,
));
}
/**
* Retrieve the editor styles/assets to be use by UpdraftCentral when editing a post
*
* @param int $timeout The user-defined timeout from UpdraftCentral
* @return array()
*/
protected function get_editor_styles($timeout) {
global $editor_styles, $wp_styles;
$editing_styles = $loaded = array();
$fonts = '';
$required = array('css/dist/editor/style.css', 'css/dist/block-library/style.css', 'css/dist/block-library/theme.css');
foreach ($required as $style) {
$result = $this->extract_css_content($style, $timeout);
if (!empty($result['fonts'])) $fonts .= implode('', $result['fonts']);
$editing_styles[] = array('css' => $result['content'], 'inline' => '');
};
do_action('enqueue_block_assets');
do_action('enqueue_block_editor_assets');
do_action('wp_enqueue_scripts');
// Checking for editor styles support since styles may vary from theme to theme
if ($editor_styles) {
foreach ($editor_styles as $style) {
if (false !== array_search($style, $loaded)) continue;
$result = $this->extract_css_content($style, $timeout);
if (!empty($result['fonts'])) $fonts .= implode('', $result['fonts']);
$editing_styles[] = array('css' => $result['content'], 'inline' => '');
$loaded[] = $style;
}
}
if ($wp_styles) {
foreach ($wp_styles->queue as $handle) {
$style = $wp_styles->registered[$handle]->src;
if (false !== array_search($style, $loaded)) continue;
$result = $this->extract_css_content($style, $timeout);
if (!empty($result['fonts'])) $fonts .= implode('', $result['fonts']);
$inline_style = $wp_styles->print_inline_style($handle, false);
if ($inline_style) {
$inline_result = $this->extract_custom_fonts($inline_style);
if (!empty($inline_result['fonts'])) $fonts .= implode('', $inline_result['fonts']);
}
$editing_styles[] = array(
'css' => $result['content'],
'inline' => (!$inline_style) ? '' : $inline_result['content']
);
$loaded[] = $style;
}
}
// Introduced in 5.9.0
if (function_exists('wp_get_global_stylesheet')) {
$result = $this->extract_custom_fonts(wp_get_global_stylesheet());
if (!empty($result['fonts'])) $fonts .= implode('', $result['fonts']);
$editing_styles[] = array('css' => $result['content'], 'inline' => '');
}
// Introduced in 5.8.0
if (function_exists('get_block_editor_settings')) {
$block_editor_context = new WP_Block_Editor_Context();
$settings = get_block_editor_settings(array(), $block_editor_context);
// Don't render but instead attached to the editor before load.
// We let the editor render these kind of styles as they need to be prefixed
// by the editor based on the current context.
//
// N.B. Leave the 'css' property empty. It is used for downward compatibility.
$editing_styles[] = array('editor_css' => $settings['styles'], 'inline' => '', 'css' => '');
// Get editor assets (e.g. styles) for iframe, mostly used for previewing blocks and patterns
$editing_styles[] = array('editor_assets' => $this->get_iframed_editor_assets(), 'inline' => '', 'css' => '');
}
$result = $this->extract_css_content('/style.css', $timeout);
if (!empty($result['fonts'])) $fonts .= implode('', $result['fonts']);
$editing_styles[] = array('css' => $result['content'], 'inline' => '');
if (strlen($fonts)) {
$editing_styles[] = array('font_css' => $fonts, 'inline' => '', 'css' => '');
}
// These styles are used if the "no theme styles" options is triggered or on
// themes without their own editor styles.
$default_editor_styles_file = ABSPATH.WPINC.'/css/dist/block-editor/default-editor-styles.css';
if (file_exists($default_editor_styles_file)) {
$editing_styles[] = array('default_editor_css' => file_get_contents($default_editor_styles_file), 'inline' => '', 'css' => '');
}
// Extract fonts from theme.json if the current theme supports it
$resolver = ABSPATH.WPINC.'/class-wp-theme-json-resolver.php';
if (!class_exists('WP_Theme_JSON_Resolver') && file_exists($resolver)) {
require_once($resolver);
}
$theme_has_support = false;
if (function_exists('wp_theme_has_theme_json')) {
$theme_has_support = wp_theme_has_theme_json();
} else {
if (class_exists('WP_Theme_JSON_Resolver')) {
$theme_has_support = WP_Theme_JSON_Resolver::theme_has_support();
}
}
if (class_exists('WP_Theme_JSON_Resolver') && $theme_has_support) {
$theme_json = ABSPATH.WPINC.'/class-wp-theme-json.php';
if (!class_exists('WP_Theme_JSON') && file_exists($theme_json)) require_once($theme_json);
$theme_json_instance = WP_Theme_JSON_Resolver::get_theme_data();
if ($theme_json_instance) {
$settings = $theme_json_instance->get_settings();
$theme_fonts = '';
if (isset($settings['typography']) && isset($settings['typography']['fontFamilies'])) {
$font_families = $settings['typography']['fontFamilies'];
if (isset($font_families['theme'])) {
foreach ($font_families['theme'] as $theme) {
if (isset($theme['fontFace'])) {
foreach ($theme['fontFace'] as $font) {
$theme_fonts .= '@font-face {';
$keys = array_keys($font);
foreach ($keys as $key) {
if (false !== stripos($key, 'font')) {
$prop = 'font-'.strtolower(str_replace('font', '', $key));
$theme_fonts .= $prop.': '.$font[$key].';';
} elseif (false !== stripos($key, 'src')) {
foreach ($font['src'] as $src_file) {
$url = trailingslashit(get_stylesheet_directory_uri()).str_replace('file:./', '', $src_file);
$theme_fonts .= 'src: url(CENTRAL_URL/?udcentral_action=load_font&font='.urlencode($url).');';
}
}
}
$theme_fonts .= '}';
}
}
}
}
}
$editing_styles[] = array('theme_json_fonts' => $theme_fonts, 'inline' => '', 'css' => '');
}
}
return $editing_styles;
}
/**
* Retrieves the total number of items found under each post statuses
*
* @param string $type The type of the module that the current request is processing
*
* @return array
*/
protected function get_post_status_counts($type = 'post') {
$posts = wp_count_posts($type);
$publish = (int) $posts->publish;
$private = (int) $posts->private;
$draft = (int) $posts->draft;
$pending = (int) $posts->pending;
$future = (int) $posts->future;
$trash = (int) $posts->trash;
// We exclude "trash" from the overall total as WP doesn't actually
// consider or include it in the total count.
$all = $publish + $private + $draft + $pending + $future;
return array(
'all' => $all,
'publish' => $publish,
'private' => $private,
'draft' => $draft,
'pending' => $pending,
'future' => $future,
'trash' => $trash,
);
}
/**
* Retrieves a collection of formatted dates found for the given post statuses.
* It will be used as options for the date filter when managing the posts in UpdraftCentral.
*
* @param string $type The type of the module that the current request is processing
*
* @return array
*/
protected function get_date_options($type = 'post') {
global $wpdb;
$date_options = $wpdb->get_col(
$wpdb->prepare(
"SELECT DATE_FORMAT(`post_date`, '%%M %%Y') AS `formatted_post_date`
FROM {$wpdb->posts}
WHERE `post_type` = %s
AND `post_status` IN ('publish', 'private', 'draft', 'pending', 'future')
GROUP BY `formatted_post_date`
ORDER BY `post_date` DESC",
$type
)
);
return $date_options;
}
/**
* Make sure that we have the required fields to use in UpdraftCentral for
* displaying the categories and tags sections. Add if missing.
*
* @param object $item Taxonomy item to check
* @return object
*/
protected function map_tax($item) {
$taxs = array('category' => 'categories', 'post_tag' => 'tags');
if (array_key_exists($item->name, $taxs)) {
if (!isset($item->show_in_rest)) $item->show_in_rest = true;
if (!isset($item->rest_base)) $item->rest_base = $taxs[$item->name];
}
return $item;
}
/**
* Fetch and retrieves available taxonomies for this site and some capabilities specific
* to tags and categories when managing them.
*
* @return array
*/
protected function get_taxonomies() {
$taxonomies = get_taxonomies(array(), 'objects');
$taxonomies = array_map(array($this, 'map_tax'), $taxonomies);
$response = array(
'taxonomies' => $taxonomies,
'current_user_cap' => array(
'manage_categories' => current_user_can('manage_categories'),
'edit_categories' => current_user_can('edit_categories'),
'delete_categories' => current_user_can('delete_categories'),
'assign_categories' => current_user_can('assign_categories'),
'manage_post_tags' => current_user_can('manage_post_tags'),
'edit_post_tags' => current_user_can('edit_post_tags'),
'delete_post_tags' => current_user_can('delete_post_tags'),
'assign_post_tags' => current_user_can('assign_post_tags'),
)
);
return $response;
}
/**
* Fetch and retrieves categories based from the submitted parameters
*
* @param array $query Containing all the needed information to filter the results of the current request
* @return array
*/
public function get_categories($query = array()) {
$page = !empty($query['page']) ? (int) $query['page'] : 1;
$items_per_page = !empty($query['per_page']) ? (int) $query['per_page'] : 100;
$offset = ($page - 1) * $items_per_page;
$order = !empty($query['order']) ? $query['order'] : 'asc';
$orderby = !empty($query['orderby']) ? $query['orderby'] : 'name';
$args = array(
'hide_empty' => false,
'orderby' => $orderby,
'order' => $order,
'number' => $items_per_page,
'offset' => $offset
);
$categories = get_categories($args);
$category_options = array();
if (!empty($categories)) {
foreach ($categories as $key => $term) {
$parent_term = get_term((int) $term->parent, $term->taxonomy);
if (!is_wp_error($parent_term) && !is_null($parent_term)) {
$parent_term = json_encode($this->trim_object($parent_term));
} else {
$parent_term = '';
}
$category_options[] = array(
'id' => $term->term_id,
'name' => $term->name,
'parent' => $term->parent
);
$categories[$key] = array(
'term' => json_encode($this->trim_object($term)),
'misc' => array(
'link' => get_term_link($term),
'parent_term' => $parent_term,
'taxonomy' => $term->taxonomy
)
);
}
}
$categorytax = get_taxonomy('category');
$parent_dropdown_args = array(
'taxonomy' => 'category',
'hide_empty' => 0,
'name' => 'newcategory_parent',
'orderby' => 'name',
'hierarchical' => 1,
'show_option_none' => '— '.$categorytax->labels->parent_item.' —',
'echo' => false
);
$parent_dropdown_args = apply_filters('post_edit_category_parent_dropdown_args', $parent_dropdown_args);
$parent_dropdown = wp_dropdown_categories($parent_dropdown_args);
if (!function_exists('wp_popular_terms_checklist')) {
require_once ABSPATH . 'wp-admin/includes/template.php';
}
ob_start();
wp_popular_terms_checklist('category');
$popular_terms_checklist = ob_get_contents();
ob_end_clean();
return $this->_response(array(
'terms' => $categories,
'misc' => array(
'formatted' => $category_options,
'raw' => $categories,
'tax' => json_encode($this->trim_object($categorytax)),
'popular' => $popular_terms_checklist,
'parent_dropdown' => $parent_dropdown,
'capabilities' => array(
'can_edit_terms' => current_user_can($categorytax->cap->edit_terms)
)
)
));
}
/**
* Fetch and retrieves tags based from the submitted parameters
*
* @param array $query Containing all the needed information to filter the results of the current request
* @return array
*/
public function get_tags($query = array()) {
$page = !empty($query['page']) ? (int) $query['page'] : 1;
$items_per_page = !empty($query['per_page']) ? (int) $query['per_page'] : 100;
$offset = ($page - 1) * $items_per_page;
$order = !empty($query['order']) ? $query['order'] : 'desc';
$orderby = !empty($query['orderby']) ? $query['orderby'] : 'count';
$args = array(
'hide_empty' => false,
'orderby' => $orderby,
'order' => $order,
'number' => $items_per_page,
'offset' => $offset
);
$tags = get_tags($args);
$tag_options = array();
$tag_cloud = '';
if (!empty($tags)) {
$tags_for_cloud = array();
foreach ($tags as $key => $term) {
if (!isset($term->link)) $term->link = get_tag_link($term->term_id);
array_push($tags_for_cloud, $term);
$parent_term = get_term((int) $term->parent, $term->taxonomy);
if (!is_wp_error($parent_term) && !is_null($parent_term)) {
$parent_term = json_encode($this->trim_object($parent_term));
} else {
$parent_term = '';
}
$tag_options[] = array(
'id' => $term->term_id,
'name' => $term->name,
);
$tags[$key] = array(
'term' => json_encode($this->trim_object($term)),
'misc' => array(
'link' => get_term_link($term),
'parent_term' => $parent_term,
'taxonomy' => $term->taxonomy
)
);
}
add_filter('tag_cloud_sort', array($this, 'sort_tag_cloud'), 9, 2);
if (!function_exists('wp_generate_tag_cloud')) {
require_once ABSPATH.WPINC.'/category-template.php';
}
$tag_cloud = wp_generate_tag_cloud($tags_for_cloud, array(
'smallest' => 10,
'largest' => 22,
'unit' => 'pt',
'number' => 10,
'format' => 'flat',
'separator' => " ",
'orderby' => 'count',
'order' => 'DESC',
'show_count' => 1,
'echo' => false
));
}
$tagtax = get_taxonomy('post_tag');
return $this->_response(array(
'terms' => $tags,
'misc' => array(
'formatted' => $tag_options,
'raw' => $tags,
'tax' => json_encode($this->trim_object($tagtax)),
'tag_cloud' => $tag_cloud,
'capabilities' => array(
'can_assign_terms' => current_user_can($tagtax->cap->assign_terms)
)
)
));
}
/**
* Sorts the tag items that are to be shown within the tag cloud
*
* @param array $tags The array to be sorted. Contains the tag items
* @param array $args Additional parameters needed for the sorting process
* @return array
*/
public function sort_tag_cloud($tags, $args) {
uasort($tags, array($this, '_wp_object_count_sort_cb'));
if ('DESC' === $args['order']) {
$tags = array_reverse($tags, true);
}
return $tags;
}
/**
* Serves as a callback for comparing objects based on count. Copied from WordPress 5.7
* core (wp-includes/category-template.php) and tweaked to return integer instead of boolean
* because returning boolean using uasort is now DEPRECATED in PHP 8.
*
* Used with `uasort()`.
*
* @since 3.1.0
* @access private
*
* @param object $a The first object to compare.
* @param object $b The second object to compare.
* @return bool Whether the count value for `$a` is greater than the count value for `$b`.
*/
public function _wp_object_count_sort_cb($a, $b) {
if ($a->count == $b->count) {
return 0;
}
return ( $a->count > $b->count ) ? 1 : -1;
}
/**
* Fetch all available taxonomies and terms information for the given post object
*
* @param array $post The "Post" object to use when retrieving the information
* @return array
*/
protected function get_taxonomies_terms($post) {
$taxonomies = get_object_taxonomies($post->post_type, 'objects');
$taxonomies = array_map(array($this, 'map_tax'), $taxonomies);
$taxonomy_names = array();
$taxonomy_terms = array();
$taxonomy_caps = array();
foreach ($taxonomies as $taxonomy) {
$terms = get_the_terms($post->ID, $taxonomy->name);
$terms = !is_array($terms) ? (array) $terms : $terms;
$taxonomy_terms[$taxonomy->name] = $terms;
$taxonomy_caps[$taxonomy->name] = array(
'hierarchical' => is_taxonomy_hierarchical($taxonomy->name),
'edit_terms' => current_user_can($taxonomy->cap->edit_terms),
'assign_terms' => current_user_can($taxonomy->cap->assign_terms),
);
array_push($taxonomy_names, $taxonomy->name);
}
return array(
'objects' => $taxonomies,
'names' => $taxonomy_names,
'terms' => $taxonomy_terms,
'caps' => $taxonomy_caps,
);
}
/**
* Take over the current editing of the post
*
* @param array $params An array of data that serves as parameters for the given request
* @return array
*/
public function take_over($params) {
$error = $this->_validate_capabilities(array('edit_'.$this->post_type.'s'));
if (!empty($error)) return $error;
$result = array('lock_acquired' => false);
if (!empty($params['post_id'])) {
if (!function_exists('wp_set_post_lock')) {
require_once ABSPATH.'wp-admin/includes/post.php';
}
$lock = wp_set_post_lock($params['post_id']);
if (!empty($lock)) {
$result = array(
'lock_acquired' => true,
'details' => $lock
);
}
}
return $this->_response($result);
}
/**
* Retrieves the underlying data for the given post. Some extra information are
* passed along that will be consumed by the editor in UpdraftCentral
*
* @param int|object $param Post object or a post ID
* @param boolean $encode True to encode the post object, false otherwise
* @return array
*/
public function get_postdata($param, $encode = true) {
$response = array();
if (is_object($param) && isset($param->ID)) {
$post = $param;
} elseif (is_numeric($param)) {
$post = get_post($param);
}
if ($post) {
$post_type_obj = get_post_type_object($post->post_type);
$is_post_type_viewable = false;
if (!empty($post_type_obj)) {
$is_post_type_viewable = $post_type_obj->publicly_queryable || ($post_type_obj->_builtin && $post_type_obj->public);
}
if (!function_exists('get_sample_permalink')) {
require_once ABSPATH.'wp-admin/includes/post.php';
}
// Validate template exists on the current theme, otherwise,
// reset the template to default.
$template = get_page_template_slug($post->ID);
if (!empty($template)) {
$page_templates = wp_get_theme()->get_page_templates($post);
if ('default' != $template && !isset($page_templates[$template])) {
update_post_meta($post->ID, '_wp_page_template', 'default');
}
}
// phpcs:disable WordPress.DateTime.RestrictedFunctions.date_date -- post_date is stored in WP local timezone; gmdate() would return incorrect UTC values
$published_date = array(
'jj' => date('d', strtotime($post->post_date)),
'mm' => date('m', strtotime($post->post_date)),
'aa' => date('Y', strtotime($post->post_date)),
'hh' => date('H', strtotime($post->post_date)),
'mn' => date('i', strtotime($post->post_date)),
'ss' => date('s', strtotime($post->post_date))
);
// phpcs:enable WordPress.DateTime.RestrictedFunctions.date_date
$sample_permalink = get_sample_permalink($post->ID, $post->post_title, '');
$permalink = get_permalink($post->ID);
$slug = $post->post_name;
if (!empty($sample_permalink) && !empty($slug)) {
if (isset($sample_permalink[0])) {
if (false !== stripos($sample_permalink[0], '%pagename%/') || false !== stripos($sample_permalink[0], '%postname%/')) {
$token = (false !== stripos($sample_permalink[0], '%pagename%/')) ? '%pagename%/' : '%postname%/';
$permalink = str_replace($token, '', $sample_permalink[0]).$slug;
}
}
}
$editor = null;
$editor_id = wp_check_post_lock($post->ID);
if ($editor_id) {
$editor = get_userdata($editor_id);
if (!$editor) {
// The user with lock does not exist. This can happen if you created a backup or clone
// where you excluded the users table during the process and you restore this backup to
// a different site or the user was deleted or removed more recently. Thus, we will
// release the lock so that other users with the right permission can edit the post.
delete_post_meta($post->ID, '_edit_lock');
}
}
$response = array(
'post' => $encode ? json_encode($post) : $post,
'misc' => array(
'guid_rendered' => apply_filters('get_the_guid', $post->guid, $post->ID),
'link' => $permalink,
'slug' => $slug,
'site_url' => site_url('/'),
'title_rendered' => get_the_title($post->ID),
'content_rendered' => apply_filters('the_content', $post->post_content),
'excerpt' => $post->post_excerpt,
'featured_media' => 0,
'sticky' => is_sticky($post->ID),
'template' => get_page_template_slug($post->ID),
'permalink_template' => get_permalink($post->ID, true),
'author_name' => get_the_author_meta('display_name', $post->post_author),
'publish_month_year' => date('F Y', strtotime($post->post_date)), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- post_date is stored in WP local timezone; gmdate() would return incorrect UTC values
'publish_month_year_date' => date('d F Y', strtotime($post->post_date)), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- post_date is stored in WP local timezone; gmdate() would return incorrect UTC values
'post_status_object' => get_post_status_object(get_post_status($post->ID)),
'published_date' => $published_date,
'format' => get_post_format($post->ID),
'post_type_name' => $post_type_obj->name,
'post_type_viewable' => $is_post_type_viewable,
'post_type_public' => $post_type_obj->public,
'post_type_hierarchical' => $post_type_obj->hierarchical,
'sample_permalink' => get_sample_permalink($post->ID, $post->post_title, ''),
'post_password_required' => post_password_required($post),
'post_type_supports_authors' => post_type_supports($post->post_type, 'author'),
'post_type_supports_comments' => post_type_supports($post->post_type, 'comments'),
'post_type_supports_revisions' => post_type_supports($post->post_type, 'revisions'),
'post_revisions' => array(), // N.B. We're not going to allow revisions editing for now
'post_thumbnail_id' => get_post_thumbnail_id($post->ID),
'can_publish_posts' => current_user_can($post_type_obj->cap->publish_posts),
'can_edit_others_posts' => current_user_can($post_type_obj->cap->edit_others_posts),
'can_unfiltered_html' => current_user_can('unfiltered_html'),
'is_edited' => $editor ? 1 : 0,
'editor_id' => $editor_id,
'editor' => $editor,
'edited_by_id' => $editor ? $editor->ID : 0,
'edited_by_display_name' => $editor ? $editor->display_name : '',
)
);
if ('post' == $post->post_type) {
$taxonomies = $this->get_taxonomies_terms($post);
$response['misc']['taxonomy_objects'] = $taxonomies['objects'];
$response['misc']['taxonomy_names'] = $taxonomies['names'];
$response['misc']['taxonomy_terms'] = $taxonomies['terms'];
$response['misc']['taxonomy_caps'] = $taxonomies['caps'];
if (!function_exists('wp_popular_terms_checklist') || !function_exists('get_terms_to_edit')) {
require_once ABSPATH . 'wp-admin/includes/template.php';
require_once ABSPATH . 'wp-admin/includes/taxonomy.php';
}
if (!function_exists('wp_get_post_categories')) {
require_once(ABSPATH.WPINC.'/post.php');
}
$categories = wp_get_post_categories($post->ID, array('fields' => 'ids'));
if (!is_wp_error($categories)) {
$response['misc']['categories'] = empty($categories) ? array() : $categories;
$terms_to_edit = get_terms_to_edit($post->ID, 'category');
if (!empty($terms_to_edit)) {
$response['misc']['categories_list'] = str_replace(',', ', ', $terms_to_edit);
}
$popular_ids = wp_popular_terms_checklist('category', 0, 10, false);
// On WP 3.4 the "wp_terms_checklist" doesn't have an "echo" parameter and will automatically
// display the rendered checklist. Therefore, we're going to pull the terms so that all
// versions starting from WP 3.4 will pull the content instead of displaying them.
ob_start();
// In this call we'll have to set the "echo" parameter to true so that later version of WP
// will be able to catch and process it.
wp_terms_checklist($post->ID, array('taxonomy' => 'category', 'popular_cats' => $popular_ids, 'echo' => true));
$popular_checklist = ob_get_contents();
ob_end_clean();
$response['misc']['categories_checklist'] = $popular_checklist;
ob_start();
wp_terms_checklist($post->ID, array('taxonomy' => 'category', 'checked_ontop' => 0, 'echo' => true));
$quickedit_checklist = ob_get_contents();
ob_end_clean();
$response['misc']['categories_quickedit_checklist'] = $quickedit_checklist;
}
$tags = wp_get_post_tags($post->ID, array('fields' => 'ids'));
if (!is_wp_error($tags)) {
$response['misc']['tags'] = empty($tags) ? array() : $tags;
$terms_to_edit = get_terms_to_edit($post->ID, 'post_tag');
if (!empty($terms_to_edit)) {
$response['misc']['tags_list'] = str_replace(',', ', ', $terms_to_edit);
}
}
}
// Naturally, the "featured_media" will suffice when loading the image (media) in
// UpdraftCentral since the value in this field is the actual image id of the featured
// media used in UC. If we currently don't have an entry in the "featured_media_updraftcentral" meta,
// then UC will need to download the featured media (image) for this current post/page
// using the "featured_media_url" field (below) if not empty.
$featured_media = get_post_meta($post->ID, 'featured_media_updraftcentral', true);
if (!empty($featured_media)) {
$response['misc']['featured_media'] = $featured_media;
}
// Retrieve featured media if currently present for the given post/page.
// If present, we pull the image (media) URL in case there's a need for
// UpdraftCentral to download the image upon loading the editor (e.g. the featured_media id
// above no longer exists).
$media_id = (int) get_post_thumbnail_id($post->ID);
if (!empty($media_id)) {
$response['misc']['featured_media_url'] = wp_get_attachment_url($media_id);
} else {
// The post/page no longer has a "featured_media" or doesn't have one currently, therefore,
// we're going to set the "featured_media" and "featured_media_url" fields to both empty to
// to avoid any further actions (e.g. download media).
$response['misc']['featured_media'] = 0;
$response['misc']['featured_media_url'] = '';
}
}
return $response;
}
/**
* Changes the state/status of the submitted post(s)
*
* @param array $params An array of data that serves as parameters for the given request
* @return array
*/
public function set_state($params) {
$state_fields = $this->get_state_fields_by_type($this->post_type);
if (empty($state_fields)) return $this->_generic_error_response('unsupported_type_on_set_state');
$error = $this->_validate_capabilities($state_fields['validation_fields']);
if (!empty($error)) return $error;
$result = array();
if (!empty($params['list'])) {
$posts = array();
foreach ($params['list'] as $id) {
$post = $this->apply_state($id, $params['action'], $this->post_type);
if (!empty($post)) {
array_push($posts, $post);
}
}
if (!empty($posts)) {
$result = array($state_fields['list_key'] => $posts);
}
} elseif (!empty($params['id'])) {
$post = $this->apply_state($params['id'], $params['action'], $this->post_type);
if (!empty($post)) $result = $post;
}
if (!empty($result)) {
$response = $this->get($params);
if (!empty($response['response']) && 'rpcok' === $response['response']) {
$result[$state_fields['result_key']] = $response['data'];
}
return $this->_response($result);
} else {
return $this->_generic_error_response($state_fields['error_key'], array('action' => $params['action']));
}
}
/**
* Creates new category
*
* @param array $params An array of data that serves as parameters for the given request
* @param boolean $wrap_response Indicates whether to wrap the response based on local or UpdraftCentral calls. Default true.
* @return array
*/
public function add_category($params, $wrap_response = true) {
$error = $this->_validate_capabilities(array('manage_categories'));
if (!empty($error)) return $error;
$name = sanitize_text_field($params['name']);
$args = array();
if (!empty($params['parent'])) {
$args['parent'] = $params['parent'];
}
$result = wp_insert_term($name, 'category', $args);
if (!is_wp_error($result)) {
$term_id = $result['term_id'];
$term = get_term($term_id, 'category');
$data = array();
if (!is_wp_error($term)) {
$data = array(
'id' => $term->term_id,
'count' => $term->count,
'description' => $term->description,
'link' => get_term_link($term->term_id, 'category'),
'name' => $term->name,
'slug' => $term->slug,
'taxonomy' => $term->taxonomy,
'parent' => $term->parent,
'meta' => array()
);
$categories = $this->get_categories();
if ($wrap_response) $data['categories'] = json_encode($categories['data']);
}
return $wrap_response ? $this->_response($data) : $data;
} else {
$error = array(
'message' => $result->get_error_message()
);
return $wrap_response ? $this->_generic_error_response('post_add_category_failed', $error) : $error;
}
}
/**
* Assigns categories to a certain post object
*
* @param int $post_id The ID of the post object
* @param array $category_ids A collection of category IDs to assign to the post object
* @return void
*/
protected function assign_category_to_post($post_id, $category_ids) {
if (!empty($category_ids)) {
// Making sure that we have the correct type to use and we
// don't have any redundant IDs before saving.
$category_ids = array_unique(array_map('intval', $category_ids));
// Attach (new) categories to post
wp_set_object_terms($post_id, $category_ids, 'category');
} else {
wp_set_object_terms($post_id, intval(get_option('default_category')), 'category');
}
}
/**
* Creates new tag
*
* @param array $params An array of data that serves as parameters for the given request
* @param boolean $wrap_response Indicates whether to wrap the response based on local or UpdraftCentral calls. Default true.
* @return array
*/
public function add_tag($params, $wrap_response = true) {
// N.B. Since the "manage_post_tags" capability does not exist in WP 3.4. We'll use the "manage_categories" instead. Besides, the "manage_post_tags" along with the other tag-related capabilities in the latest versions are actually mapped to the "manage_categories" capability (refer to wp-includes/capabilities.php under the "map_meta_cap" function).
$error = $this->_validate_capabilities(array('manage_categories'));
if (!empty($error)) return $error;
$name = sanitize_text_field($params['name']);
$result = wp_insert_term($name, 'post_tag');
if (!is_wp_error($result)) {
$term_id = $result['term_id'];
$term = get_term($term_id, 'post_tag');
$data = array();
if (!is_wp_error($term)) {
$data = array(
'id' => $term->term_id,
'count' => $term->count,
'description' => $term->description,
'link' => get_term_link($term->term_id, 'post_tag'),
'name' => $term->name,
'slug' => $term->slug,
'taxonomy' => $term->taxonomy,
'meta' => array()
);
$tags = $this->get_tags();
if ($wrap_response) $data['tags'] = json_encode($tags['data']);
}
return $wrap_response ? $this->_response($data) : $data;
} else {
$error = array(
'message' => $result->get_error_message()
);
return $wrap_response ? $this->_generic_error_response('post_add_tag_failed', $error) : $error;
}
}
/**
* Assigns tags to a certain post object
*
* @param int $post_id The ID of the post object
* @param array $tag_ids A collection of tag IDs to assign to the post object
* @return void
*/
protected function assign_tag_to_post($post_id, $tag_ids) {
if (!empty($tag_ids)) {
// Making sure that we have the correct type to use and we
// don't have any redundant IDs before saving.
$tag_ids = array_unique(array_map('intval', $tag_ids));
// Attach (new) tags to post
wp_set_object_terms($post_id, $tag_ids, 'post_tag');
} else {
wp_set_object_terms($post_id, null, 'post_tag');
}
}
/**
* Pre-validates data before running the save process
*
* @param WP_Post $post The post object to validate
* @param array $params An array of data that serves as parameters for the given request
*
* @return array|void
*/
private function pre_validation($post, $params) {
if (empty($post) || empty($params)) return;
if (!empty($params['password'])) {
if (!empty($params['sticky'])) {
return $this->_generic_error_response('post_save_failed', array(
'message' => __('A post can not be sticky and have a password.'),// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core.
'args' => $params
));
}
if (!isset($params['sticky']) && is_sticky($post->ID)) {
return $this->_generic_error_response('post_save_failed', array(
'message' => __('A sticky post can not be password protected.'),// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core.
'args' => $params
));
}
}
if (!empty($params['sticky'])) {
if (!isset($params['password']) && post_password_required($post->ID)) {
return $this->_generic_error_response('post_save_failed', array(
'message' => __('A password protected post can not be set to sticky.'),// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core.
'args' => $params
));
}
}
}
/**
* Saves or updates post/page information based from the submitted data
*
* @param array $params An array of data that serves as parameters for the given request
* @return array
*/
public function save($params) {
global $updraftcentral_host_plugin;
$validation_fields = array(
'post' => array('publish_posts', 'edit_posts', 'delete_posts'),
'page' => array('publish_pages', 'edit_pages', 'delete_pages')
);
if (!isset($validation_fields[$this->post_type])) return $this->_generic_error_response('unsupported_type_on_save_post');
$error = $this->_validate_capabilities($validation_fields[$this->post_type]);
if (!empty($error)) return $error;
if (!empty($params['id']) || !empty($params['new'])) {
$args = array();
if (!empty($params['id'])) {
$post = get_post($params['id']);
if (!empty($post)) {
$result = $this->pre_validation($post, $params);
if (isset($result['response']) && 'rpcerror' == $result['response']) {
return $result;
}
}
}
// post_content
if (!empty($params['content']))
$args['post_content'] = $params['content'];
// post_excerpt
if (!empty($params['excerpt']))
$args['post_excerpt'] = $params['excerpt'];
// menu_order
if (isset($params['order']))
$args['menu_order'] = (int) $params['order'];
// post_parent
if (isset($params['parent'])) {
$args['post_parent'] = empty($params['parent']) ? 0 : $params['parent'];
}
// post_name
if (!empty($params['slug']))
$args['post_name'] = $params['slug'];
// post_status
if (!empty($params['status'])) {
$args['post_status'] = $params['status'];
}
// post_title
if (!empty($params['title']))
$args['post_title'] = $params['title'];
// post_author
if (!empty($params['author']))
$args['post_author'] = $params['author'];
// comment_status
if (!empty($params['comment_status']))
$args['comment_status'] = $params['comment_status'];
// ping_status
if (!empty($params['ping_status']))
$args['ping_status'] = $params['ping_status'];
// visibility
if (!empty($params['visibility'])) {
switch ($params['visibility']) {
case 'public':
$args['post_status'] = 'publish';
$args['post_password'] = '';
break;
case 'password':
$args['post_status'] = 'publish';
$args['post_password'] = $params['password'];
break;
case 'private':
$args['post_status'] = 'private';
$args['post_password'] = '';
break;
default:
break;
}
} else {
if (!empty($params['password'])) {
$args['post_status'] = 'publish';
$args['post_password'] = $params['password'];
} elseif (isset($params['password']) && '' == $params['password']) {
$args['post_status'] = 'publish';
$args['post_password'] = '';
}
}
// post/publish date
if (!empty($params['date'])) {
$datetime = strtotime($params['date']);
$post_date = date('Y-m-d H:i:s', $datetime); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- post_date must be stored in WP local timezone, not UTC
$args['post_date'] = $post_date;
$args['post_date_gmt'] = gmdate('Y-m-d H:i:s', $datetime);
// We only change the status to "future" based from the submitted date if the post status
// is not empty and equal to 'publish' and the date is for the coming future.
if (!empty($params['status']) && 'publish' == $params['status']) {
if (strtotime($post_date) > strtotime(date('Y-m-d H:i:s'))) $args['post_status'] = 'future'; // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- comparing local-timezone post_date against current local time intentionally
}
}
// Make sure we have a slug/post_name generated before insert/update
if (empty($params['slug']) && !empty($params['title'])) {
$args['post_name'] = sanitize_title_with_dashes($params['title']);
}
if (!empty($params['new'])) {
$args['post_type'] = $this->post_type;
$post_id = wp_insert_post($args, true);
} else {
$args['ID'] = $params['id'];
$args['post_modified'] = date('Y-m-d H:i:s'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- post_modified must be stored in WP local timezone, not UTC
$args['post_modified_gmt'] = gmdate('Y-m-d H:i:s');
$post_id = wp_update_post($args, true);
}
// We have successfully created/updated a post at this point, thus, we'll continue
// with implementing the other requested processes and return the result.
if (!is_wp_error($post_id)) {
// sticky post
if (isset($params['sticky'])) {
$sticky = (bool) $params['sticky'];
if ($sticky) {
stick_post($post_id);
} else {
if (is_sticky($post_id)) {
unstick_post($post_id);
}
}
}
// template
if (!empty($params['template'])) {
update_post_meta($post_id, '_wp_page_template', $params['template']);
}
// featured_media
if (isset($params['featured_media'])) {
if (!empty($params['featured_media'])) {
$featured_media = (int) $params['featured_media'];
$attach_continue = true;
$url = wp_get_attachment_url($featured_media);
if (!empty($url) && !empty($params['featured_media_url']) && $url == $params['featured_media_url']) {
set_post_thumbnail($post_id, $featured_media);
update_post_meta($post_id, 'featured_media_updraftcentral', $params['featured_media']);
$attach_continue = false;
}
if ($attach_continue) {
$featured_media_data = !empty($params['featured_media_data']) ? $params['featured_media_data'] : null;
$media_id = $this->attach_remote_image($params['featured_media_url'], $featured_media_data, $post_id);
if (!empty($media_id)) {
// If we have a successful attachment then add reference to UC's media id
update_post_meta($post_id, 'featured_media_updraftcentral', $params['featured_media']);
}
}
} else {
// Remove featured image.
delete_post_meta($post_id, '_thumbnail_id');
delete_post_meta($post_id, 'featured_media_updraftcentral');
}
}
// categories
$categories_updated = false;
if (!empty($params['categories'])) {
$term_ids = array();
foreach ($params['categories'] as $value) {
$category = sanitize_text_field($value);
$parent = 0;
if (false !== strpos($category, ':')) {
list($parent, $category) = explode(':', $category);
$result = $this->add_category(array('name' => $category, 'parent' => $parent), false);
if (!empty($result)) {
array_push($term_ids, $result['id']);
}
} else {
$term = get_term_by('id', $category, 'category');
if (!empty($term)) {
$term_id = $term->term_id;
array_push($term_ids, $term_id);
}
}
}
$this->assign_category_to_post($post_id, $term_ids);
$categories_updated = true;
}
// tags
$tags_updated = false;
if (!empty($params['tags'])) {
$term_ids = array();
foreach ($params['tags'] as $value) {
$tag = sanitize_text_field($value);
$field = is_numeric($tag) ? 'id' : 'name';
$term = get_term_by($field, $tag, 'post_tag');
if (!empty($term)) {
$term_id = $term->term_id;
array_push($term_ids, $term_id);
} else {
$result = $this->add_tag(array('name' => $tag), false);
if (!empty($result)) {
array_push($term_ids, $result['id']);
}
}
}
$this->assign_tag_to_post($post_id, $term_ids);
$tags_updated = true;
}
// Pulling any other relevant and additional information regarding
// the post before returning it in the response.
$postdata = $this->get_postdata($post_id);
if (!empty($params['new'])) {
$timeout = !empty($params['timeout']) ? $params['timeout'] : 30;
$postdata = array_merge($postdata, $this->get_preload_data($timeout, $this->post_type));
} else {
if ($categories_updated || $tags_updated) {
$categories = $this->get_categories();
$tags = $this->get_tags();
$postdata['preloaded'] = json_encode(array(
'categories' => $categories['data'],
'tags' => $tags['data']
));
}
}
$postdata['options'] = $this->get_options($this->post_type);
return $this->_response($postdata);
} else {
// ERROR: error creating or updating post
return $this->_generic_error_response('post_save_failed', array(
'message' => $post_id->get_error_message(),
'args' => $args
));
}
} else {
// ERROR: no id parameter, invalid request
return $this->_generic_error_response('post_invalid_request', array('message' => $updraftcentral_host_plugin->retrieve_show_message('parameters_missing')));
}
}
/**
* Fetch and retrieves authors based from the submitted parameters
*
* @param array $params Containing all the needed information to filter the results of the current request
* @return array
*/
public function get_authors($params = array()) {
global $updraftcentral_main;
// If expected parameters are empty or does not exists then set them to some default values
$page = !empty($params['page']) ? (int) $params['page'] : 1;
$per_page = !empty($params['per_page']) ? (int) $params['per_page'] : 15;
$offset = ($page - 1) * $per_page;
$who = !empty($params['who']) ? $params['who'] : 'authors';
$order = !empty($params['order']) ? strtoupper($params['order']) : 'ASC';
$orderby = !empty($params['orderby']) ? $params['orderby'] : 'display_name';
$get_user_params = array(
'number' => $per_page,
'paged' => $page,
'offset' => $offset,
'order' => $order,
'orderby' => $orderby,
);
// WP 5.9 deprecated the 'who' parameter and introduces the 'capability'
// parameter, thus we'll be replacing the 'who' parameter in 5.9 or higher
if (version_compare($updraftcentral_main->get_wordpress_version(), '5.9', '<')) {
$get_user_params['who'] = $who;
} else {
$get_user_params['capability'] = array('edit_posts');
}
$users = get_users($get_user_params);
$authors = array();
$locale = get_locale();
foreach ($users as $user) {
$data = array(
'user' => json_encode($this->trim_object($user)),
'misc' => array(
'link' => get_author_posts_url($user->ID, $user->user_nicename),
'locale' => function_exists('get_user_locale') ? get_user_locale($user) : $locale,
'registered_date' => date('c', strtotime($user->user_registered)), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- user_registered is stored in UTC; date() used intentionally for ISO 8601 output in local timezone
)
);
array_push($authors, $data);
}
return $this->_response(array(
'authors' => $authors
));
}
/**
* Fetch and retrieves parent pages based from the submitted parameters
*
* @param array $params Containing all the needed information to filter the results of the current request
* @return array
*/
public function get_parent_pages($params = array()) {
// If expected parameters are empty or does not exists then set them to some default values
$page = !empty($params['page']) ? (int) $params['page'] : 1;
$per_page = !empty($params['per_page']) ? (int) $params['per_page'] : 100;
$offset = ($page - 1) * $per_page;
$exclude = !empty($params['exclude']) ? $params['exclude'] : array();
$order = !empty($params['order']) ? strtoupper($params['order']) : 'ASC';
$orderby = !empty($params['orderby']) ? $params['orderby'] : 'menu_order';
$status = !empty($params['status']) ? $params['status'] : 'publish';
$args = array(
'posts_per_page' => $per_page,
'paged' => $page,
'offset' => $offset,
'post__not_in' => $exclude,
'order' => $order,
'orderby' => $orderby,
'post_type' => 'page',
'post_status' => $status,
);
$query = new WP_Query($args);
$posts = $query->posts;
$pages = array();
if (!empty($posts)) {
foreach ($posts as $post) {
// Get additional information and merge with the response
$postdata = $this->get_postdata($post, true);
if (!empty($postdata)) array_push($pages, $this->trim_parent_info($postdata));
}
}
return $this->_response(array(
'pages' => $pages
));
}
/**
* Trim down return data for parent pages
*
* @param array $postdata The array containing the data to process
* @return array
*/
protected function trim_parent_info($postdata) {
if (isset($postdata['post'])) {
$post = json_decode($postdata['post']);
$page = new stdClass();
$page->ID = $post->ID;
$page->post_title = $post->post_title;
$page->post_parent = $post->post_parent;
$page->post_type = $post->post_type;
$page->post_status = $post->post_status;
$postdata['post'] = json_encode($page);
}
return $postdata;
}
/**
* Retrieves pages, templates, authors, categories and tags data that will be
* used as options when displayed on the editor in UpdraftCentral
*
* @param string $type The type of the module that the current request is processing
*
* @return array
*/
protected function get_options($type = 'post') {
// Primarily used for editor consumption so we don't include trash here. Besides,
// trash posts/pages aren't included as parent options.
$parent_pages = $this->get_parent_pages();
$pages = $parent_pages['data']['pages'];
// Add flexibility by letting users filter the default roles and add their own
// custom page/post "author" role(s) if need be.
$author_roles = apply_filters('updraftcentral_author_roles', array('administrator', 'editor', 'author', 'contributor'));
$authors = get_users(array('role__in' => $author_roles));
if (!function_exists('get_page_templates')) {
require_once(ABSPATH.'wp-admin/includes/theme.php');
}
$templates = ('post' == $type) ? get_page_templates(null, 'post') : get_page_templates();
$template_options = array();
foreach ($templates as $template => $filename) {
$item = array(
'filename' => $filename,
'template' => $template,
);
$template_options[] = $item;
}
$page_options = array();
foreach ($pages as $page_item) {
if (isset($page_item['post'])) {
$page = json_decode($page_item['post']);
$item = array(
'id' => $page->ID,
'title' => $page->post_title,
'parent' => $page->post_parent
);
$page_options[] = $item;
}
}
$author_options = array();
foreach ($authors as $user) {
$item = array(
'id' => $user->ID,
'name' => $user->display_name,
);
$author_options[] = $item;
}
$response = array(
'page' => $page_options,
'author' => $author_options,
'template' => $template_options,
'date' => $this->get_date_options($type),
);
if ('post' == $type) {
$categories = get_categories(array('hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC'));
$tags = get_tags(array('hide_empty' => false));
$category_options = array();
foreach ($categories as $category) {
$item = array(
'id' => $category->term_id,
'name' => $category->name,
'parent' => $category->parent
);
$category_options[] = $item;
}
$tag_options = array();
foreach ($tags as $tag) {
$item = array(
'id' => $tag->term_id,
'name' => $tag->name,
);
$tag_options[] = $item;
}
$response['category'] = $category_options;
$response['tag'] = $tag_options;
}
return $response;
}
/**
* Changes the state/status of the given post based from the submitted action/request
*
* @param int $id The ID of the current page to work on
* @param string $action The type of change that the current request is going to apply
* @param string $type The type of the module that the current request is processing
*
* @return array
*/
protected function apply_state($id, $action, $type = 'post') {
if (empty($id)) return false;
$post = get_post($id);
if (!empty($post)) {
$previous_status = $post->post_status;
$deleted = false;
switch ($action) {
case 'draft':
$args = array('ID' => $id, 'post_status' => 'draft');
wp_update_post($args);
break;
case 'trash':
wp_trash_post($id);
break;
case 'publish':
$args = array('ID' => $id, 'post_status' => 'publish');
wp_update_post($args);
break;
case 'restore':
$args = array('ID' => $id, 'post_status' => 'pending');
wp_update_post($args);
break;
case 'delete':
$result = wp_delete_post($id, true);
if (!empty($result)) $deleted = true;
break;
default:
break;
}
$postdata = $this->get_postdata($post);
if (!empty($postdata) || $deleted) {
$data = $deleted ? $id : $postdata;
$result = array(
'id' => $id,
'previous_status' => $previous_status
);
$result[$type] = $data;
return $result;
}
}
return false;
}
/**
* Imports image from UpdraftCentral's page/post editor
*
* @param string $image_url The URL of the image to import
* @param string $image_data The image data to save. If empty, image_url will be used to download the image
* @param int $post_id The ID of the page where this image is to be attached
*
* @return integer
*/
protected function attach_remote_image($image_url, $image_data, $post_id) {
if (empty($image_url) || empty($post_id)) return;
$image = pathinfo($image_url);
$image_name = $image['basename'];
$upload_dir = wp_upload_dir();
if (empty($image_data)) {
$response = wp_remote_get($image_url);
if (!is_wp_error($response)) {
$image_data = wp_remote_retrieve_body($response);
}
} else {
$image_data = base64_decode($image_data);
}
$media_id = 0;
if (!empty($image_data)) {
$unique_file_name = wp_unique_filename($upload_dir['path'], $image_name);
$filename = basename($unique_file_name);
if (wp_mkdir_p($upload_dir['path'])) {
$file = $upload_dir['path'] . '/' . $filename;
} else {
$file = $upload_dir['basedir'] . '/' . $filename;
}
file_put_contents($file, $image_data);
$wp_filetype = wp_check_filetype($filename, null);
$attachment = array(
'post_mime_type' => $wp_filetype['type'],
'post_title' => sanitize_file_name($filename),
'post_content' => '',
'post_status' => 'inherit'
);
$media_id = wp_insert_attachment($attachment, $file, $post_id);
require_once(ABSPATH . 'wp-admin/includes/image.php');
$attach_data = wp_generate_attachment_metadata($media_id, $file);
wp_update_attachment_metadata($media_id, $attach_data);
set_post_thumbnail($post_id, $media_id);
}
return $media_id;
}
/**
* Checks whether we have the required fields submitted and the user has
* the capabilities to execute the requested action
*
* @param array $capabilities The capabilities to check and validate
*
* @return array|void
*/
protected function _validate_capabilities($capabilities) {
foreach ($capabilities as $capability) {
if (!current_user_can($capability)) return $this->_generic_error_response('insufficient_permission');
}
}
}
backups.php 0000644 00000036112 15233402364 0006712 0 ustar 00 <?php
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
/**
* Handles Backups Commands
*/
class UpdraftCentral_Backups_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 UpdraftCentral_Listener
*/
public function _pre_action($command, $data, $extra_info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This function is called from listener.php and $extra_info is being sent.
// 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 UpdraftCentral_Listener
*/
public function _post_action($command, $data, $extra_info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused parameter is present because the caller from UpdraftCentral_Listener class uses 3 arguments.
// Here, we're restoring to the current (default) blog before we switched
if ($this->switched) restore_current_blog();
}
/**
* Retrieves the UpdraftPlus plugin status, UpdraftVault storage usage status, Next backup
* schedule, etc. Used primarily by UpdraftCentral background process.
*
* @return array
*/
public function get_status() {
if (!current_user_can('manage_options')) {
$response = array(
'status' => 'error',
'error_code' => 'insufficient_permission',
);
} else {
if (!function_exists('get_mu_plugins')) include_once(ABSPATH.'wp-admin/includes/plugin.php');
$mu_plugins = get_mu_plugins();
$is_premium = false;
if (defined('UPDRAFTPLUS_DIR') && file_exists(UPDRAFTPLUS_DIR.'/udaddons')) $is_premium = true;
// Set default response
$response = array(
'updraftplus_version' => '',
'is_premium' => $is_premium,
'installed' => false,
'active' => false,
'backup_count' => 0,
'has_mu_plugins' => !empty($mu_plugins) ? true : false,
'last_backup' => array(
'backup_nonce' => '',
'has_errors' => false,
'has_warnings' => false,
'has_succeeded' => false,
),
'updraftvault' => array(
'site_connected' => false,
'storage' => array('quota_used' => '0 MB', 'quota' => '0 MB', 'percentage_usage' => '0.0%'),
),
'meta' => array(),
);
if (class_exists('UpdraftPlus')) {
global $updraftplus;
$response['updraftplus_version'] = $updraftplus->version;
$response['updraftvault'] = $this->get_updraftvault_status();
$response['installed'] = true;
$response['active'] = true;
$response['meta'] = $this->get_filesystem_credentials_info();
$schedule = $this->get_next_backup_schedule();
if ($schedule) {
$response['next_backup_schedule'] = $schedule;
}
$backup_history = UpdraftPlus_Backup_History::add_jobdata(UpdraftPlus_Backup_History::get_history());
$response['backup_count'] = count($backup_history);
$updraft_last_backup = UpdraftPlus_Options::get_updraft_option('updraft_last_backup');
if ($updraft_last_backup) {
$response['last_backup']['backup_nonce'] = $updraft_last_backup['backup_nonce'];
if (isset($updraft_last_backup['backup_time'])) {
$response['last_backup']['backup_date'] = gmdate('n/j/Y', $updraft_last_backup['backup_time']);
$response['last_backup']['backup_time'] = $updraft_last_backup['backup_time'];
}
$errors = 0;
$warnings = 0;
if (is_array($updraft_last_backup['errors'])) {
foreach ($updraft_last_backup['errors'] as $err) {
$level = (is_array($err)) ? $err['level'] : 'error';
if ('warning' == $level) {
$warnings++;
} elseif ('error' == $level) {
$errors++;
}
}
}
if ($errors > 0) $response['last_backup']['has_errors'] = true;
if ($warnings > 0) $response['last_backup']['has_warnings'] = true;
if (isset($updraft_last_backup['success']) && $updraft_last_backup['success']) $response['last_backup']['has_succeeded'] = true;
}
} else {
if (!function_exists('get_plugins')) require_once(ABSPATH.'wp-admin/includes/plugin.php');
$plugins = get_plugins();
$key = 'updraftplus/updraftplus.php';
if (array_key_exists($key, $plugins)) {
$response['installed'] = true;
if (is_plugin_active($key)) $response['active'] = true;
}
}
}
return $this->_response($response);
}
/**
* Retrieves the next backup schedule for Files and Database backups
*
* @return string
*/
private function get_next_backup_schedule() {
// Get the next (nearest) scheduled backups
$files = wp_next_scheduled('updraft_backup');
$db = wp_next_scheduled('updraft_backup_database');
if ($files && $db) {
$timestamp = min($files, $db); // Get the nearest schedule among the two schedules
} elseif ($files && !$db) {
$timestamp = $files;
} elseif (!$files && $db) {
$timestamp = $db;
} else {
$timestamp = null;
}
if (!empty($timestamp)) {
return gmdate('g:i A - D', $timestamp);
}
return false;
}
/**
* Retrieves the UpdrafVault storage usage status
*
* @return array
*/
private function get_updraftvault_status() {
if (!class_exists('UpdraftCentral_UpdraftVault_Commands')) {
include_once(UPDRAFTPLUS_DIR.'/includes/updraftvault.php');
}
$updraftvault = new UpdraftCentral_UpdraftVault_Commands($this->rc);
$creds = $updraftvault->get_credentials();
$site_connected = false;
$storage = array('quota_used' => '0 MB', 'quota' => '0 MB', 'percentage_usage' => '0.0%');
$remote_service = false;
if (isset($creds['data'])) {
if (!isset($creds['data']['error']) && isset($creds['data']['accesskey'])) {
$site_connected = true;
$storage_objects_and_ids = UpdraftPlus_Storage_Methods_Interface::get_storage_objects_and_ids(array('updraftvault'));
if (isset($storage_objects_and_ids['updraftvault']['instance_settings'])) {
$instance_settings = $storage_objects_and_ids['updraftvault']['instance_settings'];
$instance_id = key($instance_settings);
$opts = $instance_settings[$instance_id];
if (!class_exists('UpdraftPlus_BackupModule_updraftvault')) {
include_once(UPDRAFTPLUS_DIR.'/methods/updraftvault.php');
}
$vault = new UpdraftPlus_BackupModule_updraftvault();
$vault->set_options($opts, false, $instance_id);
$quota_root = $opts['quota_root'];
$quota = $opts['quota'];
if (empty($quota_root)) {
// This next line is wrong: it lists the files *in this site's sub-folder*, rather than the whole Vault
$current_files = $vault->listfiles('');
} else {
$current_files = $vault->listfiles_with_path($quota_root, '', true);
}
if (!is_wp_error($current_files) && is_array($current_files)) {
$quota_used = 0;
foreach ($current_files as $file) {
$quota_used += $file['size'];
}
$storage = array(
'quota_used' => round($quota_used / 1048576, 1).' MB',
'quota' => round($quota / 1048576, 1).' MB',
'percentage_usage' => sprintf('%.1f', 100*$quota_used / $quota).'%',
);
$remote_service = array(
'name' => 'updraft_include_remote_service_updraftvault',
'value' => $instance_id,
);
}
}
}
}
return array(
'site_connected' => $site_connected,
'storage' => $storage,
'remote_service' => $remote_service,
);
}
/**
* Retrieves information whether filesystem credentials (e.g. FTP/SSH) are required
* when updating plugins
*
* @return array
*/
private function get_filesystem_credentials_info() {
if (!function_exists('get_filesystem_method')) {
include_once(ABSPATH.'/wp-admin/includes/file.php');
}
$filesystem_method = get_filesystem_method(array(), WP_PLUGIN_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 = ('direct' != $filesystem_method && !$filesystem_credentials_are_stored);
return array(
'request_filesystem_credentials' => $request_filesystem_credentials,
'filesystem_form' => base64_encode($filesystem_form),
);
}
/**
* Retrieves the backup progress in terms of entities completed. Used primarily by UpdraftCentral
* for polling backup progress in the background.
*
* @param array $params Submitted arguments for the current request
* @return array
*/
public function get_backup_progress($params) {
$nonce = isset($params['nonce']) ? $params['nonce'] : false;
$response = array('nonce' => $params['nonce']);
if (!current_user_can('manage_options')) {
$response['status'] = 'error';
$response['error_code'] = 'insufficient_permission';
} else {
global $updraftplus;
if ($nonce && $updraftplus && is_a($updraftplus, 'UpdraftPlus')) {
// Check the job is not still running.
$jobdata = $updraftplus->jobdata_getarray($nonce);
if (!empty($jobdata)) {
$response['status'] = 'in-progress';
$file_entities = 0;
$db_entities = 0;
$processed = 0;
if (isset($jobdata['backup_database']) && 'no' != $jobdata['backup_database']) {
$backup_database = $jobdata['backup_database'];
$db_entities += count($backup_database);
foreach ($backup_database as $whichdb => $info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- In this check we only need the status contained in the $info for now.
$status = $info; // For default: 'wp'
if (is_array($info)) {
$status = $info['status'];
}
if ('finished' == $status) {
$processed++;
}
}
}
if (isset($jobdata['backup_files']) && 'no' != $jobdata['backup_files']) {
$file_entities = count($jobdata['job_file_entities']);
$backup_files = $jobdata['backup_files'];
if ('finished' == $backup_files) {
$processed += $file_entities;
} elseif (isset($jobdata['filecreating_substatus'])) {
$substatus = $jobdata['filecreating_substatus'];
$processed += max(0, intval($substatus['i']) - 1);
}
}
$response['progress'] = array(
'file_entities' => $file_entities,
'db_entities' => $db_entities,
'total_entities' => $file_entities+$db_entities,
'processed' => $processed,
'percentage' => floor(($processed/($file_entities+$db_entities))*100),
'nonce' => $nonce,
);
UpdraftPlus_Options::update_updraft_option('updraft_central_last_backup_progress', $response['progress'], false);
} else {
$last_backup = UpdraftPlus_Options::get_updraft_option('updraft_last_backup');
if ($nonce == $last_backup['backup_nonce']) {
$response['status'] = 'finished';
$response['progress'] = array('percentage' => 100);
$response['progress']['errors'] = $last_backup['errors'];
$response['progress']['backup_time'] = $last_backup['backup_time'];
$response['progress']['completed_time'] = gmdate('g:ia', $last_backup['backup_time']);
$response['progress']['completed_date'] = gmdate('M d, Y', $last_backup['backup_time']);
$errors = 0;
$warnings = 0;
if (!empty($last_backup['errors']) && is_array($last_backup['errors'])) {
foreach ($last_backup['errors'] as $err) {
$level = (is_array($err)) ? $err['level'] : 'error';
if ('warning' == $level) {
$warnings++;
} elseif ('error' == $level) {
$errors++;
}
}
}
$response['progress']['has_errors'] = ($errors > 0) ? true : false;
$response['progress']['has_warnings'] = ($warnings > 0) ? true : false;
} else {
// We might be too early to check the `updraft_last_backup` thus, we'll
// give it a few rounds to check by setting the status to "in-progress"
// and returning the last backup progress (if applicable).
$last_progress = UpdraftPlus_Options::get_updraft_option('updraft_central_last_backup_progress');
$response['status'] = 'in-progress';
if (!empty($last_progress) && isset($last_progress['nonce'])) {
$response['progress'] = $last_progress;
if ($nonce == $last_progress['nonce']) {
UpdraftPlus_Options::delete_updraft_option('updraft_central_last_backup_progress');
}
}
}
}
}
}
return $this->_response($response);
}
/**
* Retrieves backup report data for a given time range. Used primarily by UpdraftCentral
* for backup reports.
*
* @return array
*/
public function get_report_data() {
global $updraftplus;
global $updraftcentral_host_plugin;
$start_time = strtotime('first day of this month 00:00:00');
$end_time = strtotime('last day of this month 23:59:59');
$backup_history = UpdraftPlus_Backup_History::get_history();
$report_data = array();
foreach ($backup_history as $time => $backup) {
if ($time >= $start_time && $time <= $end_time) {
$external_storage = __('N/A', 'updraftplus');
if (!empty($backup['service'])) {
$external_storage = array();
foreach ($backup['service'] as $service) {
$external_storage[] = $updraftplus->backup_methods[$service];
}
$external_storage = implode(', ', $external_storage);
}
$report_data[$time] = array(
'date' => get_date_from_gmt(gmdate('Y-m-d H:i:s', (int) $time), 'M d, Y G:i'),
'total_size' => UpdraftPlus_Manipulation_Functions::convert_numeric_size_to_text($updraftplus->get_total_backup_size($backup), 1048577),
'external_storage' => $external_storage,
);
}
}
// Get the latest 10 report data.
$truncated = count($report_data) > 10;
if (true === $truncated) {
$report_data = array_slice($report_data, 0, 10, true);
}
$updraftplus_admin = $updraftcentral_host_plugin->get_admin_instance();
$files_backup_intervals = $updraftplus_admin->get_intervals('files');
$database_backup_intervals = $updraftplus_admin->get_intervals('db');
$file_backup_interval = UpdraftPlus_Options::get_updraft_option('updraft_interval', 'manual');
$database_backup_interval = UpdraftPlus_Options::get_updraft_option('updraft_interval_database', 'manual');
return $this->_response(array(
'report_data' => array(
'backups' => $report_data,
'truncated' => $truncated,
),
'variables' => array(
'files_backup_frequency' => $files_backup_intervals[$file_backup_interval],
'database_backup_frequency' => $database_backup_intervals[$database_backup_interval],
),
));
}
}
pages.php 0000644 00000000713 15233402364 0006357 0 ustar 00 <?php
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
// Load the posts command class since we're going to be extending it for our page module service/command
// class in order to minimize redundant shareable methods.
if (!class_exists('UpdraftCentral_Posts_Commands')) require_once('posts.php');
/**
* Handles Pages Commands
*/
class UpdraftCentral_Pages_Commands extends UpdraftCentral_Posts_Commands {
protected $post_type = 'page';
}
updates.php 0000644 00000110165 15233402364 0006730 0 ustar 00 <?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_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) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- We dont use $core but we need the AS in the foreach so it needs to stay
$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;
}
if (is_wp_error($result[$plugin])) {
$status['error'] = $result[$plugin]->get_error_code();
$status['error_message'] = $result[$plugin]->get_error_message();
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 occurred
$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' => '',
);
// THis is included so we can get $wp_version
include(ABSPATH.WPINC.'/version.php');
$status['oldVersion'] = $wp_version;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- The variable is defined inside the ABSPATH.WPINC.'/version.php'.
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();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused variable is for future use.
$core_update_key = false;
$core_update_latest_version = false;
$get_core_updates = get_core_updates();
// THis is included so we can get $wp_version
@include(ABSPATH.WPINC.'/version.php');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
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, '>')) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- The variable is defined inside the ABSPATH.WPINC.'/version.php'.
$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 occurred
$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 occurred
$status['error'] = 'update_failed';
return $status;
}
}
/**
* Updates available translations for this website
*
* @return Array
*/
private function _update_translation() {
global $wp_filesystem;
$status = array();
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 occurred
$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');// phpcs:ignore WordPress.WP.DeprecatedFunctions.get_theme_dataFound -- This function was only intended for backward compatibility with versions below 3.4.0.
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 $theme) {
$all_items[$theme->Name] = $theme;
}
}
} else {
$all_items = get_themes();// phpcs:ignore WordPress.WP.DeprecatedFunctions.get_themesFound -- This function was only intended for backward compatibility with versions below 3.4.0.
}
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');// phpcs:ignore WordPress.WP.DeprecatedFunctions.get_theme_dataFound -- This function was only intended for backward compatibility with versions below 3.4.0.
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 $updraftcentral_main;
$wp_version = $updraftcentral_main->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;
}
/**
* Checks basic WP and PHP compatibility for plugins and themes
*
* @param string $type The type of the entity to check (e.g. 'plugin' or 'theme')
* @param array $info Data or information to check
* @return array
*/
private function is_compatible($type, $info) {
global $updraftcentral_main;
$wp_version = $updraftcentral_main->get_wordpress_version();
$is_compatible = null;
$message = '';
if (isset($info['update'])) {
// Check for WP Compatibility based from the update information
if (!empty($info['update']['requires'])) {
if (version_compare($wp_version, $info['update']['requires'], '<')) {
$is_compatible = false;
$message1 = sprintf(
/* translators: %s: Plugin/theme type */
__('The latest update for this %s is not compatible with the WordPress version installed on the remote site.', 'updraftplus'),
$type
);
$message2 = sprintf(
/* translators: 1: Plugin/theme type, 2: Required WordPress version */
__('The minimum WordPress version supported by this %1$s is %2$s.', 'updraftplus'),
$type,
$info['update']['requires']
);
$message = esc_attr($message1 . ' ' . $message2);
} else {
$is_compatible = true;
}
}
// Check for PHP Compatibility based from the update information
if (!empty($info['update']['requires_php'])) {
if (version_compare(PHP_VERSION, $info['update']['requires_php'], '<')) {
$is_compatible = false;
$message1 = sprintf(
/* translators: %s: Plugin/theme type */
__('The latest update for this %s is not compatible with the PHP version installed on the remote site.', 'updraftplus'),
$type
);
$message2 = sprintf(
/* translators: 1: Plugin/theme type, 2: Required PHP version */
__('The minimum PHP version supported by this %1$s is %2$s.', 'updraftplus'),
$type,
$info['update']['requires_php']
);
$message = esc_attr($message1 . ' ' . $message2);
} else {
$is_compatible = true;
}
}
// Check whether Plugin/Theme has been tested based from the update information
if (!empty($info['update']['tested'])) {
if (version_compare($wp_version, $info['update']['tested'], '>')) {
$is_compatible = false;
// translators: %s: Plugin/theme type
$message = esc_attr(sprintf(__('The latest update for this %s has not been tested with the WordPress version installed on the remote site and may have compatibility issues when used.', 'updraftplus'), $type));
} else {
$is_compatible = true;
}
}
}
if (is_null($is_compatible)) {
$is_compatible = false;
$message = esc_attr(sprintf(__('This % does not provide information to allow determining whether the latest version is compatible with your WordPress or PHP installation.', 'updraftplus'), $type).' '.__('If installing, then proceed with caution by first doing a backup.', 'updraftplus'));
}
return array(
'compatible' => $is_compatible,
'compatible_message' => $message,
);
}
/**
* Retrieve icons for plugin and screenshot for theme
*
* @param string $type The type of the entity to process (e.g. 'plugin' or 'theme')
* @param string $slug The entity slug
* @return string
*/
private function get_icons_screenshot($type, $slug) {
if (!in_array($type, array('plugin', 'theme'))) return '';
if ('plugin' == $type) {
if (!function_exists('plugins_api') && file_exists(ABSPATH.'wp-admin/includes/plugin-install.php')) {
include_once(ABSPATH.'wp-admin/includes/plugin-install.php');
}
// We make sure that the function now exists before we use it because
// the definition of this function maybe located in other places given
// the varying versions and changes to the WordPress platform.
if (function_exists('plugins_api')) {
$api = plugins_api('plugin_information', array(
'slug' => $slug,
'fields' => array('icons' => true),
));
if (!is_wp_error($api) && property_exists($api, 'icons')) {
return $api->icons;
}
}
} elseif ('theme' == $type) {
if (!function_exists('themes_api') && file_exists(ABSPATH.'wp-admin/includes/theme.php')) {
include_once(ABSPATH.'wp-admin/includes/theme.php');
}
// We make sure that the function now exists before we use it because
// the definition of this function maybe located in other places given
// the varying versions and changes to the WordPress platform.
if (function_exists('themes_api')) {
$api = themes_api('theme_information', array(
'slug' => $slug,
'fields' => array('screenshot_url' => true),
));
if (!is_wp_error($api) && property_exists($api, 'screenshot_url')) {
return $api->screenshot_url;
}
}
}
return '';
}
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;
$info = 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,
'requires' => isset($update->update->requires) ? $update->update->requires : null,
'requires_php' => isset($update->update->requires_php) ? $update->update->requires_php : null,
'icons' => isset($update->update->icons) ? $update->update->icons : $this->get_icons_screenshot('plugin', $update->update->slug),
),
);
// Check for compatibility and merge result into info
$result = $this->is_compatible('plugin', $info);
$plugin_updates[] = array_merge($info, $result);
}
}
}
$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'];
$info = 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'],
'tested' => isset($update->update['tested']) ? $update->update['tested'] : null,
'requires' => $update->update['requires'],
'requires_php' => $update->update['requires_php'],
'screenshot_url' => isset($update->update['screenshot_url']) ? $update->update['screenshot_url'] : $this->get_icons_screenshot('theme', $update->update['theme']),
),
);
// Check for compatibility and merge result into info
$result = $this->is_compatible('theme', $info);
$theme_updates[] = array_merge($info, $result);
}
}
}
$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;
// THis is included so we can get $wp_version
@include(ABSPATH.WPINC.'/version.php');// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
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, '>')) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- The variable is defined inside the ABSPATH.WPINC.'/version.php'.
$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, '<')) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- The variable is defined inside the ABSPATH.WPINC.'/version.php'.
$core_updates[] = array(
'download' => $update->download,
'version' => $update->version,
'php_version' => $update->php_version,
'mysql_version' => $update->mysql_version,
'installed' => array(
'version' => $wp_version,// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- The variable is defined inside the ABSPATH.WPINC.'/version.php'.
'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 : 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
),
));
}
}
users.php 0000644 00000045565 15233402364 0006437 0 ustar 00 <?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 = ((int) $query['page_no'] * (int) $query['per_page']) - (int) $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();// phpcs:ignore WordPress.WP.DeprecatedFunctions.wp_get_sitesFound -- This function was only intended for backward compatibility with versions below 4.6.
}
}
// 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(
"users" => array(),
"paging" => $this->_calculate_pages($query, 0),
);
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 permissions 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);
}
}
comments.php 0000644 00000067351 15233402364 0007120 0 ustar 00 <?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();// phpcs:ignore WordPress.WP.DeprecatedFunctions.wp_get_sitesFound -- This function was only intended for backward compatibility with versions below 4.6.
}
}
// 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'])); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- comment_date is stored in WP local timezone; gmdate() would produce incorrect values.
$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'),// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core.
'pings' => __('Pings'),// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core.
));
// Options for comment_status field
$comment_statuses = array(
'approve' => __('Approve'),// phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- The string exists within the WordPress core.
'hold' => __('Hold or Unapprove', 'updraftplus'),
'trash' => __('Trash', 'updraftplus'),
'spam' => __('Spam', 'updraftplus'),
);
// 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) {
global $updraftcentral_main;
// 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_max_links' => $this->_get_wp_option($blog_id, 'comment_max_links'),
'moderation_keys' => $this->_get_wp_option($blog_id, 'moderation_keys'),
),
'sites' => $sites,
);
$wp_version = $updraftcentral_main->get_wordpress_version();
if (version_compare($wp_version, '5.5.0', '<')) {
$result['settings']['comment_whitelist'] = $this->_get_wp_option($blog_id, 'comment_whitelist');
$result['settings']['blacklist_keys'] = $this->_get_wp_option($blog_id, 'blacklist_keys');
} else {
$result['settings']['comment_previously_approved'] = $this->_get_wp_option($blog_id, 'comment_previously_approved');
$result['settings']['disallowed_keys'] = $this->_get_wp_option($blog_id, 'disallowed_keys');
}
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 comment
* @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 comment 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)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- comment_date is stored in WP local timezone; gmdate() would produce incorrect values.
$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);
}
}
media.php 0000644 00000051115 15233402364 0006341 0 ustar 00 <?php
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Direct $wpdb query is required for this operation.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- some query operations need to always receive the most up-to-date or actual data directly from the database, reducing the risk of serving stale information.
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
/**
* Handles Media Commands
*/
class UpdraftCentral_Media_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 UpdraftCentral_Listener
*/
public function _pre_action($command, $data, $extra_info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This function is called from listener.php and $extra_info is being sent.
// 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 UpdraftCentral_Listener
*/
public function _post_action($command, $data, $extra_info) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused parameter is present because the caller from UpdraftCentral_Listener class uses 3 arguments.
// Here, we're restoring to the current (default) blog before we switched
if ($this->switched) restore_current_blog();
}
/**
* Fetch and retrieves posts based from the submitted parameters
*
* @param array $params Containing all the needed information to filter the results of the current request
* @return array
*/
public function get_media_items($params) {
$error = $this->_validate_capabilities(array('upload_files', 'edit_posts'));
if (!empty($error)) return $error;
// check paged parameter; if empty set to defaults
$paged = !empty($params['paged']) ? (int) $params['paged'] : 1;
$numberposts = !empty($params['numberposts']) ? (int) $params['numberposts'] : 10;
$offset = ($paged - 1) * $numberposts;
$args = array(
'posts_per_page' => $numberposts,
'paged' => $paged,
'offset' => $offset,
'post_type' => 'attachment',
'post_status' => 'inherit',
);
if (!empty($params['keyword'])) {
$args['s'] = $params['keyword'];
}
if (!empty($params['category'])) {
if (in_array($params['category'], array('detached', 'unattached'))) {
$attachment_ids = $this->get_unattached_ids();
} else {
$attachment_ids = $this->get_type_ids($params['category']);
}
$args['post__in'] = $attachment_ids;
}
if (!empty($params['date'])) {
list($monthnum, $year) = explode(':', $params['date']);
$args['monthnum'] = $monthnum;
$args['year'] = $year;
}
$query = new WP_Query($args);
$result = $query->posts;
$count_posts = (int) $query->found_posts;
$page_count = 0;
if ($count_posts > 0) {
$page_count = absint($count_posts / $numberposts);
$remainder = absint($count_posts % $numberposts);
$page_count = ($remainder > 0) ? ++$page_count : $page_count;
}
$info = array(
'page' => $paged,
'pages' => $page_count,
'results' => $count_posts,
'items_from' => (($paged * $numberposts) - $numberposts) + 1,
'items_to' => ($paged == $page_count) ? $count_posts : $paged * $numberposts,
);
$media_items = array();
if (!empty($result)) {
foreach ($result as $item) {
$media = $this->get_media_item($item, null, true);
if (!empty($media)) {
array_push($media_items, $media);
}
}
}
$response = array(
'items' => $media_items,
'info' => $info,
'options' => array(
'date' => $this->get_date_options(),
'type' => $this->get_type_options()
)
);
return $this->_response($response);
}
/**
* Check whether we have an image editor (e.g. GD, Imagick, etc.) set in place to handle the basic editing
* functions such as rotate, crop, etc. If not, then we hide that feature in UpdraftCentral
*
* @param object $media The media item/object to check
* @return boolean
*/
private function has_image_editor($media) {
// Most of the time image library are enabled by default in the php.ini but there's a possbility that users don't
// enable them as they have no need for them at the moment or for some other reasons. Thus, we need to confirm
// that here through the wp_get_image_editor method.
$has_image_editor = true;
if (!empty($media)) {
if (!function_exists('wp_get_image_editor')) {
require_once(ABSPATH.'wp-includes/media.php');
}
if (!function_exists('_load_image_to_edit_path')) {
require_once(ABSPATH.'wp-admin/includes/image.php');
}
$image_editor = wp_get_image_editor(_load_image_to_edit_path($media->ID));
if (is_wp_error($image_editor)) {
$has_image_editor = false;
}
}
return $has_image_editor;
}
/**
* Fetch a single media item information
*
* @param array $params Containing all the needed information to filter the results of the current request
* @param array|null $extra_info Additional information from the current request
* @param boolean $raw If set, returns the result of the fetch process unwrapped by the response array
* @return array
*/
public function get_media_item($params, $extra_info = null, $raw = false) {
$error = $this->_validate_capabilities(array('upload_files', 'edit_posts'));
if (!empty($error)) return $error;
// Raw means that we need to return the result without wrapping it
// with the "$this->_response" function which indicates that the call
// was done locally (within the class) and not directly from UpdraftCentral.
if ($raw && is_object($params) && isset($params->ID)) {
$media = $params;
} elseif (is_array($params) && !empty($params['id'])) {
$media = get_post($params['id']);
}
if (!function_exists('get_post_mime_types')) {
global $updraftcentral_main;
// For a much later version of WP the "get_post_mime_types" is located
// in a different folder. So, we make sure that we have it loaded before
// actually using it.
if (version_compare($updraftcentral_main->get_wordpress_version(), '3.5', '>=')) {
require_once(ABSPATH.WPINC.'/post.php');
} else {
// For WP 3.4, the "get_post_mime_types" is located in the location provided below.
require_once(ABSPATH.'wp-admin/includes/post.php');
}
}
if (!function_exists('wp_image_editor')) {
require_once(ABSPATH.'wp-admin/includes/image-edit.php');
}
if (!function_exists('get_media_item')) {
require_once(ABSPATH.'wp-admin/includes/template.php');
require_once(ABSPATH.'wp-admin/includes/media.php');
}
if ($media) {
$thumb = wp_get_attachment_image_src($media->ID, 'thumbnail', true);
if (!empty($thumb)) $media->thumb_url = $thumb[0];
$media->url = wp_get_attachment_url($media->ID);
$media->parent_post_title = get_the_title($media->post_parent);
$media->author = get_the_author_meta('display_name', $media->post_author);
$media->filename = basename($media->url);
$media->date = date('Y/m/d', strtotime($media->post_date)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- post_date is stored in WP local timezone; gmdate() would produce incorrect values.
$media->upload_date = mysql2date(get_option('date_format'), $media->post_date);
$media->filesize = 0;
$file = get_attached_file($media->ID);
if (!empty($file) && file_exists($file)) {
$media->filesize = size_format(filesize($file));
}
$media->nonce = wp_create_nonce('image_editor-'.$media->ID);
if (false !== strpos($media->post_mime_type, 'image/')) {
$meta = wp_get_attachment_metadata($media->ID);
$thumb = image_get_intermediate_size($media->ID, 'thumbnail');
$sub_sizes = isset($meta['sizes']) && is_array($meta['sizes']);
// Pulling details
$sizer = 1;
if (isset($meta['width'], $meta['height'])) {
$big = max($meta['width'], $meta['height']);
$sizer = $big > 400 ? 400 / $big : 1;
}
$constrained_dims = array();
if ($thumb && $sub_sizes) {
$constrained_dims = wp_constrain_dimensions($thumb['width'], $thumb['height'], 160, 120);
}
$rotate_supported = false;
if (function_exists('imagerotate') || wp_image_editor_supports(array('mime_type' => get_post_mime_type($media->ID), 'methods' => array('rotate')))) {
$rotate_supported = true;
}
// Check for alternative text if present
$alt = get_post_meta($media->ID, '_wp_attachment_image_alt', true);
$media->alt = !empty($alt) ? $alt : '';
// Check whether edited images are restorable
$backup_sizes = get_post_meta($media->ID, '_wp_attachment_backup_sizes', true);
$can_restore = !empty($backup_sizes) && isset($backup_sizes['full-orig']) && basename($meta['file']) != $backup_sizes['full-orig']['file'];
$image_edit_overwrite = (!defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE) ? 0 : 1;
$media->misc = array(
'sizer' => $sizer,
'rand' => wp_rand(1, 99999),
'constrained_dims' => $constrained_dims,
'rotate_supported' => (int) $rotate_supported,
'thumb' => $thumb,
'meta' => $meta,
'alt_text' => $alt,
'can_restore' => $can_restore,
'image_edit_overwrite' => $image_edit_overwrite
);
$media->has_image_editor = $this->has_image_editor($media);
}
}
return $raw ? $media : $this->_response(array('item' => $media));
}
/**
* Fetch and retrieves posts based from the submitted parameters
*
* @param array $params Containing all the needed information to filter the results of the current request
* @return array
*/
public function get_posts($params) {
$error = $this->_validate_capabilities(array('edit_posts'));
if (!empty($error)) return $error;
// check paged parameter; if empty set to defaults
$paged = !empty($params['paged']) ? (int) $params['paged'] : 1;
$numberposts = !empty($params['numberposts']) ? (int) $params['numberposts'] : 10;
$offset = ($paged - 1) * $numberposts;
$args = array(
'posts_per_page' => $numberposts,
'paged' => $paged,
'offset' => $offset,
'post_type' => 'post',
'post_status' => 'publish,private,draft,pending,future',
);
if (!empty($params['keyword'])) {
$args['s'] = $params['keyword'];
}
$query = new WP_Query($args);
$result = $query->posts;
$count_posts = (int) $query->found_posts;
$page_count = 0;
if ($count_posts > 0) {
$page_count = absint($count_posts / $numberposts);
$remainder = absint($count_posts % $numberposts);
$page_count = ($remainder > 0) ? ++$page_count : $page_count;
}
$info = array(
'page' => $paged,
'pages' => $page_count,
'results' => $count_posts,
'items_from' => (($paged * $numberposts) - $numberposts) + 1,
'items_to' => ($paged == $page_count) ? $count_posts : $paged * $numberposts,
);
$posts = array();
if (!empty($result)) {
foreach ($result as $post) {
array_push($posts, array(
'ID' => $post->ID,
'title' => $post->post_title,
'post_status' => $post->post_status,
'post_date_gmt' => $post->post_date_gmt,
'post_modified_gmt' => $post->post_modified_gmt,
));
}
}
$response = array(
'posts' => $posts,
'info' => $info
);
return $this->_response($response);
}
/**
* Saves media changes from UpdraftCentral
*
* @param array $params Containing all the needed information to filter the results of the current request
* @return array
*/
public function save_media_item($params) {
$error = $this->_validate_capabilities(array('upload_files', 'edit_posts'));
if (!empty($error)) return $error;
$args = array(
'post_title' => $params['image_title'],
'post_excerpt' => $params['image_caption'],
'post_content' => $params['image_description']
);
if (!empty($params['new'])) {
$args['post_type'] = 'attachment';
$media_id = wp_insert_post($args, true);
} else {
$args['ID'] = $params['id'];
$args['post_modified'] = date('Y-m-d H:i:s'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- post_modified is stored in WP local timezone; gmdate() would produce incorrect values.
$args['post_modified_gmt'] = gmdate('Y-m-d H:i:s');
$media_id = wp_update_post($args, true);
}
if (!empty($media_id)) {
// Update alternative text if not empty
if (!empty($params['image_alternative_text'])) {
update_post_meta($media_id, '_wp_attachment_image_alt', $params['image_alternative_text']);
}
$result = array(
'status' => 'success',
'item' => $this->get_media_item(array('id' => $media_id), null, true)
);
} else {
$result = array('status' => 'failed');
}
return $this->_response($result);
}
/**
* Executes media action (e.g. attach, detach and delete)
*
* @param array $params Containing all the needed information to filter the results of the current request
* @return array
*/
public function execute_media_action($params) {
global $updraftcentral_host_plugin;
$error = $this->_validate_capabilities(array('upload_files', 'edit_posts'));
if (!empty($error)) return $error;
$result = array();
switch ($params['do']) {
case 'attach':
global $wpdb;
$query_result = $wpdb->query($wpdb->prepare("UPDATE {$wpdb->posts} SET `post_parent` = %d WHERE `post_type` = 'attachment' AND ID = %d", $params['parent_id'], $params['id']));
if (false === $query_result) {
$result['error'] = $updraftcentral_host_plugin->retrieve_show_message('failed_to_attach_media');
} else {
$result['msg'] = $updraftcentral_host_plugin->retrieve_show_message('media_attached');
}
break;
case 'detach':
global $wpdb;
$query_result = $wpdb->query($wpdb->prepare("UPDATE {$wpdb->posts} SET `post_parent` = 0 WHERE `post_type` = 'attachment' AND ID = %d", $params['id']));
if (false === $query_result) {
$result['error'] = $updraftcentral_host_plugin->retrieve_show_message('failed_to_detach_media');
} else {
$result['msg'] = $updraftcentral_host_plugin->retrieve_show_message('media_detached');
}
break;
case 'delete':
$failed_items = array();
foreach ($params['ids'] as $id) {
// Delete permanently
if (false === wp_delete_attachment($id, true)) {
$failed_items[] = $id;
}
}
if (!empty($failed_items)) {
$result['error'] = $updraftcentral_host_plugin->retrieve_show_message('failed_to_delete_media');
$result['items'] = $failed_items;
} else {
$result['msg'] = $updraftcentral_host_plugin->retrieve_show_message('selected_media_deleted');
}
break;
default:
break;
}
return $this->_response($result);
}
/**
* Retrieves a collection of formatted dates found for the given post statuses.
* It will be used as options for the date filter when managing the media items in UpdraftCentral.
*
* @return array
*/
private function get_date_options() {
global $wpdb;
$options = array();
$date_options = $wpdb->get_col("SELECT DATE_FORMAT(`post_date`, '%M %Y') as `formatted_post_date` FROM {$wpdb->posts} WHERE `post_type` = 'attachment' AND `post_status` = 'inherit' GROUP BY `formatted_post_date` ORDER BY `post_date` DESC");
if (!empty($date_options)) {
foreach ($date_options as $monthyear) {
$timestr = strtotime($monthyear);
$options[] = array('label' => date('F Y', $timestr), 'value' => date('n:Y', $timestr)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- Post dates are stored in WP local timezone; gmdate() would produce incorrect month/year values.
}
}
return $options;
}
/**
* Retrieves mime types that will be use as filter option in UpdraftCentral
*
* @return array
*/
private function get_type_options() {
global $wpdb, $updraftcentral_host_plugin, $updraftcentral_main;
$options = array();
if (!function_exists('get_post_mime_types')) {
// For a much later version of WP the "get_post_mime_types" is located
// in a different folder. So, we make sure that we have it loaded before
// actually using it.
if (version_compare($updraftcentral_main->get_wordpress_version(), '3.5', '>=')) {
require_once(ABSPATH.WPINC.'/post.php');
} else {
// For WP 3.4, the "get_post_mime_types" is located in the location provided below.
require_once(ABSPATH.'wp-admin/includes/post.php');
}
}
$post_mime_types = get_post_mime_types();
$type_options = $wpdb->get_col("SELECT `post_mime_type` FROM {$wpdb->posts} WHERE `post_type` = 'attachment' AND `post_status` = 'inherit' GROUP BY `post_mime_type` ORDER BY `post_mime_type` DESC");
foreach ($post_mime_types as $mime_type => $label) {
if (!wp_match_mime_types($mime_type, $type_options)) continue;
$options[] = array('label' => $label[0], 'value' => esc_attr($mime_type));
}
$options[] = array('label' => $updraftcentral_host_plugin->retrieve_show_message('unattached'), 'value' => 'detached');
return $options;
}
/**
* Retrieves media items that haven't been attached to any posts
*
* @return array
*/
private function get_unattached_ids() {
global $wpdb;
return $wpdb->get_col("SELECT `ID` FROM {$wpdb->posts} WHERE `post_type` = 'attachment' AND `post_status` = 'inherit' AND `post_parent` = '0'");
}
/**
* Retrieves IDs of media items that has the given mime type
*
* @param string $type The mime type to search for
* @return array
*/
private function get_type_ids($type) {
global $wpdb;
return $wpdb->get_col($wpdb->prepare("SELECT `ID` FROM {$wpdb->posts} WHERE `post_type` = 'attachment' AND `post_status` = 'inherit' AND `post_mime_type` LIKE %s", $type.'/%'));
}
/**
* Checks whether we have the required fields submitted and the user has
* the capabilities to execute the requested action
*
* @param array $capabilities The capabilities to check and validate
*
* @return array|void
*/
private function _validate_capabilities($capabilities) {
foreach ($capabilities as $capability) {
if (!current_user_can($capability)) {
return $this->_generic_error_response('insufficient_permission');
}
}
}
/**
* Populates the $_REQUEST global variable with the submitted data
*
* @param array $params Submitted data received from UpdraftCentral
* @return array
*/
private function populate_request($params) {
if (!empty($params)) {
foreach ($params as $key => $value) {
$_REQUEST[$key] = $value;
}
}
}
/**
* Handles image editing requests coming from UpdraftCentral
*
* @param array $params Containing all the needed information to filter the results of the current request
* @return array
*/
public function image_editor($params) {
$error = $this->_validate_capabilities(array('edit_posts'));
if (!empty($error)) return $error;
$attachment_id = (int) $params['postid'];
$this->populate_request($params);
if (!function_exists('load_image_to_edit')) {
require_once(ABSPATH.'wp-admin/includes/image.php');
}
include_once(ABSPATH.'wp-admin/includes/image-edit.php');
$msg = false;
switch ($params['do']) {
case 'save':
case 'scale':
$msg = wp_save_image($attachment_id);
break;
case 'restore':
$msg = wp_restore_image($attachment_id);
break;
}
$msg = (false !== $msg) ? json_encode($msg) : $msg;
$full_image = wp_get_attachment_image_src($attachment_id, 'full');
$full_image_url = $full_image ? $full_image[0] : '';
return $this->_response(array('content' => $msg, 'url' => $full_image_url));
}
/**
* Handles image preview requests coming from UpdraftCentral
*
* @param array $params Containing all the needed information to filter the results of the current request
* @return array
*/
public function image_preview($params) {
$error = $this->_validate_capabilities(array('edit_posts'));
if (!empty($error)) return $error;
if (!function_exists('load_image_to_edit')) {
require_once(ABSPATH.'wp-admin/includes/image.php');
}
include_once(ABSPATH.'wp-admin/includes/image-edit.php');
$this->populate_request($params);
$post_id = (int) $params['postid'];
ob_start();
stream_preview_image($post_id);
$content = ob_get_contents();
ob_end_clean();
return $this->_response(array('content' => base64_encode($content)));
}
}
reporting.php 0000644 00000027605 15233402364 0007302 0 ustar 00 <?php
if (!defined('UPDRAFTCENTRAL_CLIENT_DIR')) die('No access.');
/**
* - A container for RPC commands (white label reporting 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_Reporting_Commands extends UpdraftCentral_Commands {
private $valid_statuses = array(
'recurring',
'manual',
);
private $max_number_of_recipients = 100;
/**
* Update existing reports.
*
* @param array $reports Reports data which will be saved.
*
* @return void
*/
private function _update_existing_reports($reports) {
update_option('updraftcentral_reporting_reports', $reports);
}
/**
* Normalize email address - lowercase and sanitize.
*
* @param string $email Email address to normalize.
*
* @return string Normalized email.
*/
private function _normalize_email($email) {
return strtolower(sanitize_email($email));
}
/**
* Get existing reports.
*
* @return array List of all reports.
*/
private function _get_existing_reports() {
return (array) get_option('updraftcentral_reporting_reports', array());
}
/**
* Update existing sent reports.
*
* @param array $sent_reports Sent reports data which will be saved.
*
* @return void
*/
private function _update_existing_sent_reports($sent_reports) {
update_option('updraftcentral_reporting_sent_reports', $sent_reports);
}
/**
* Get existing sent reports.
*
* @return array List of all sent reports.
*/
private function _get_existing_sent_reports() {
return (array) get_option('updraftcentral_reporting_sent_reports', array());
}
/**
* Get all the reports (scheduled or not scheduled).
*
* @param array $data Data array containing report IDs to fetch.
*
* @return array An array of reports.
*/
public function get_reports($data) {
$all_reports = $this->_get_existing_reports();
$sent_reports = array_reverse($this->_get_existing_sent_reports());
foreach ($sent_reports as $key => $sent_report) {
$sent_report['download_url'] = '';
if (!empty($sent_report['pdf_attachment_id'])) {
$sent_report['download_url'] = wp_get_attachment_url(absint($sent_report['pdf_attachment_id']));
}
$sent_reports[$key] = $sent_report;
}
if (empty($data['report_ids']) || !is_array($data['report_ids'])) {
return $this->_response(array(
'reports' => $all_reports,
'sent_reports' => $sent_reports,
));
}
$report_ids = array();
foreach ($data['report_ids'] as $report_id) {
$report_ids[] = sanitize_key(strval($report_id));
}
return $this->_response((array(
'reports' => array_intersect_key($all_reports, array_flip($report_ids)),
'sent_reports' => $sent_reports,
)));
}
/**
* Delete a report.
*
* @param array $data Data array containing report ID to delete.
*
* @return array An array containing the result of the current process
*/
public function delete_report($data) {
// Permission check.
if (!current_user_can('manage_options')) {
$result = array("error" => true, "message" => "not_allowed");
return $this->_response($result);
}
// Return early if valid data structure is not present.
if (!is_array($data)) {
$result = array("error" => true, "message" => "invalid_data");
return $this->_response($result);
}
$report_id = empty($data['id']) ? '' : sanitize_key(strval($data['id']));
// Return early if ID not supplied.
if (empty($report_id)) {
$result = array('error' => true, 'message' => 'missing_id');
return $this->_response($result);
}
// Get the reports from options table.
$reports = $this->_get_existing_reports();
// Check if the ID to delete is present.
if (!isset($reports[$report_id])) {
$result = array('error' => true, 'message' => 'invalid_id');
return $this->_response($result);
}
unset($reports[$report_id]);
$this->_update_existing_reports($reports);
$result = array("error" => false, "message" => "reports_updated");
return $this->_response($result);
}
/**
* Add a new report.
*
* @param array $data Report information to add
*
* @return array An array containing the result of the current process
*/
public function add_report($data) {
// Permission check.
if (!current_user_can('manage_options')) {
$result = array("error" => true, "message" => "not_allowed");
return $this->_response($result);
}
// Return early if valid report structure is not present.
if (!is_array($data)) {
$result = array("error" => true, "message" => "invalid_report_data");
return $this->_response($result);
}
$report_name = isset($data['name']) ? sanitize_text_field(strval($data['name'])) : '';
if ('' === $report_name) {
$result = array("error" => true, "message" => "empty_name");
return $this->_response($result);
}
$report_status = empty($data['status']) ? '' : sanitize_text_field(strval($data['status']));
if (!in_array($report_status, $this->valid_statuses)) {
$result = array("error" => true, "message" => "status_invalid");
return $this->_response($result);
}
$template_id = empty($data['template_id']) ? '' : sanitize_key($data['template_id']);
if (empty($template_id)) {
$result = array("error" => true, "message" => "template_id_invalid");
return $this->_response($result);
}
$recipients = (isset($data['recipients']) && is_array($data['recipients'])) ? $data['recipients'] : array();
if (count($recipients) > $this->max_number_of_recipients) {
$result = array("error" => true, "message" => "max_number_of_recipients_exceeded");
return $this->_response($result);
}
$recipients = array_unique(array_map(array($this, '_normalize_email'), $recipients));
// Sanity check for invalid email.
foreach ($recipients as $email) {
if (!is_email($email)) {
$result = array(
"error" => true,
"message" => "recipients_invalid",
"values" => array(
'invalid_email' => $email,
)
);
return $this->_response($result);
}
}
if (empty($recipients)) {
$result = array("error" => true, "message" => "no_recipients_provided");
return $this->_response($result);
}
// Get existing reports from options table.
$existing_reports = $this->_get_existing_reports();
// Report.
$report = array();
$report_id = empty($data['id']) ? '' : sanitize_key(strval($data['id']));
if (empty($report_id)) {
// First report timestamp and formatted date.
$next_report_timestamp = strtotime("+1 month", time());
$next_report_formatted_date = gmdate("j M, g:i a", $next_report_timestamp);
$report_id = UpdraftPlus_Manipulation_Functions::generate_random_string(10);
$report_id_generation_loops = 0;
// Sanity check to check if the report ID exists.
while (isset($existing_reports[$report_id])) {
$report_id = UpdraftPlus_Manipulation_Functions::generate_random_string(10);
++$report_id_generation_loops;
// If we somehow exceed the max generation loops then return error - which will not happen in almost any case.
if ($report_id_generation_loops > 10) {
$result = array("error" => true, "message" => "report_id_generation_failed");
return $this->_response($result);
}
}
$report = array(
'id' => $report_id,
'name' => $report_name,
'status' => $report_status,
'template_id' => $template_id,
'recipients' => $recipients,
'last_report_timestamp' => 0,
'last_report_formatted_date' => __('N/A', 'updraftplus'),
'next_report_timestamp' => $next_report_timestamp,
'next_report_formatted_date' => $next_report_formatted_date,
);
} elseif (!empty($existing_reports[$report_id])) {
$report = $existing_reports[$report_id];
$report['name'] = $report_name;
$report['status'] = $report_status;
$report['template_id'] = $template_id;
$report['recipients'] = $recipients;
} else {
$result = array("error" => true, "message" => "report_does_not_exist");
return $this->_response($result);
}
// Add the new report.
$existing_reports[$report_id] = $report;
// Update the reports.
$this->_update_existing_reports($existing_reports);
$result = array(
"error" => false,
"message" => "reports_updated",
"values" => array(
'report' => $report,
)
);
return $this->_response($result);
}
/**
* Add a sent report.
*
* @param array $data Report information to add
*
* @return array An array containing the result of the current process
*/
public function add_sent_reports($data) {
// Permission check.
if (!current_user_can('manage_options')) {
$result = array("error" => true, "message" => "not_allowed");
return $this->_response($result);
}
// Return early if valid report structure is not present.
if (!is_array($data) || empty($data['sent_reports_data'])) {
$result = array("error" => true, "message" => "invalid_sent_report_data");
return $this->_response($result);
}
$reports = $this->_get_existing_reports();
$sent_reports = $this->_get_existing_sent_reports();
$return_data = array();
// Loop through all the sent reports.
foreach ($data['sent_reports_data'] as $report_data) {
$report_id = sanitize_key(strval($report_data['report_id']));
// Skip if no report of this ID exists.
if (empty($reports[$report_id])) {
continue;
}
$report_sent_at_timestamp = time();
$report_sent_at_formatted_date = gmdate("j M, g:i a", $report_sent_at_timestamp);
// Change the last report time of the report.
$reports[$report_id]['last_report_timestamp'] = $report_sent_at_timestamp;
$reports[$report_id]['last_report_formatted_date'] = $report_sent_at_formatted_date;
// Save the PDF as attachment.
$pdf_attachment_id = 0;
$uploads_dir = wp_upload_dir();
$custom_upload_directory = trailingslashit($uploads_dir['basedir']) . 'updraftcentral-white-label-reporting-pdfs/';
$custom_upload_url = trailingslashit($uploads_dir['baseurl']) . 'updraftcentral-white-label-reporting-pdfs/';
$filename = sanitize_text_field($reports[$report_id]['name']) . '-' . $report_sent_at_timestamp . '-' . UpdraftPlus_Manipulation_Functions::generate_random_string(5) . '.pdf';
$full_path = $custom_upload_directory . basename($filename);
// Sanity check to test directory exists.
wp_mkdir_p(dirname($full_path));
file_put_contents($full_path, base64_decode($report_data['pdf_content']));
$wp_filetype = wp_check_filetype($filename, null);
$attachment = array(
'guid' => $custom_upload_url . basename($filename),
'post_mime_type' => $wp_filetype['type'],
'post_title' => sanitize_file_name(pathinfo($filename, PATHINFO_FILENAME)),
'post_content' => '',
'post_status' => 'inherit',
);
$pdf_attachment_id = wp_insert_attachment($attachment, $full_path);
require_once(ABSPATH . 'wp-admin/includes/image.php');
$attach_data = wp_generate_attachment_metadata($pdf_attachment_id, $full_path);
wp_update_attachment_metadata($pdf_attachment_id, $attach_data);
$new_sent_report = array(
'report' => $reports[$report_id]['name'],
'sent' => (bool) $report_data['sent'],
'template_id_used' => sanitize_key($report_data['template_id']),
'template_name_used' => sanitize_text_field($report_data['template_name']),
'services' => array_map('sanitize_text_field', $report_data['services']),
'sent_at' => $report_sent_at_formatted_date,
'number_of_recipients' => absint($report_data['number_of_recipients']),
'pdf_attachment_id' => $pdf_attachment_id,
);
$sent_reports[] = $new_sent_report;
$return_data[] = $new_sent_report;
}
$this->_update_existing_reports($reports);
$this->_update_existing_sent_reports($sent_reports);
$result = array(
"error" => false,
"message" => "sent_reports_updated",
"data" => $return_data,
);
return $this->_response($result);
}
}