Σ(゚Д゚;≡;゚д゚)Σ(゚Д゚;≡;゚д゚)Σ(゚Д゚;≡;゚д゚)始,故人唐宰相鲁公,🆚开府南服,余以布衣从戎。明年,别公漳水湄。后明年,公以事过张睢阳庙及颜杲卿所尝往来处,悲歌慷慨,卒不负其言而从之游。今其诗具在,可考也。😭 HEX
HEX
Server: Apache
System: Linux webm006.cluster120.gra.hosting.ovh.net 6.18.39-ovh-vps-grsec-zfs+ #1 SMP PREEMPT_DYNAMIC Tue Jul 21 12:03:15 CEST 2026 x86_64
User: studionolh (122383)
PHP: 7.3.33
Disabled: _dyuweyrj4,_dyuweyrj4r,dl
Upload Files
File: /home/s/t/u/studionolh/www/wp-includes/Requests/Cookie/reusable-blocks/modules.tar
wp-fastest-cache/wp-fastest-cache.php000064400000002341152333657420013563 0ustar00<?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.php000064400000001200152333657420011244 0ustar00<?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.php000064400000002133152333657420012036 0ustar00<?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.js000064400000004066152333657420011614 0ustar00( 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.php000064400000002627152333657420012576 0ustar00<?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.php000064400000001577152333657420013337 0ustar00<?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.php000064400000002275152333657420012221 0ustar00<?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.php000064400000002005152333657420011112 0ustar00<?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.php000064400000000630152333657420012734 0ustar00<?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.php000064400000002065152333657420012354 0ustar00<?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.php000064400000010423152333657420006621 0ustar00<?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.js000064400000002646152333657420007144 0ustar00function 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.php000064400000055600152334023640006215 0ustar00<?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.php000064400000032610152334023640007250 0ustar00<?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.php000064400000060627152334023640006570 0ustar00<?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.php000064400000006067152334023640006245 0ustar00<?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.php000064400000100461152334023640006363 0ustar00<?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.php000064400000207775152334023640006451 0ustar00<?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' => '&mdash; '.$categorytax->labels->parent_item.' &mdash;',
			'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.php000064400000036112152334023640006712 0ustar00<?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.php000064400000000713152334023640006357 0ustar00<?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.php000064400000110165152334023640006730 0ustar00<?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.php000064400000045565152334023640006437 0ustar00<?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.php000064400000067351152334023640007120 0ustar00<?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.php000064400000051115152334023640006341 0ustar00<?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.php000064400000027605152334023640007302 0ustar00<?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);
	}
}