HEX
Server: LiteSpeed
System: Linux s787.bom1.mysecurecloudhost.com 4.18.0-477.13.1.lve.el8.x86_64 #1 SMP Thu Jun 1 16:40:47 EDT 2023 x86_64
User: mobilech (5348)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: //proc/self/cwd/wp-content/plugins/backup/src/JetBackup/Wordpress/Abilities.php
<?php

namespace JetBackup\Wordpress;

use JetBackup\Ajax\iAjax;
use JetBackup\Exception\JBException;
use JetBackup\Factory;
use JetBackup\JetBackup;

if (!defined( '__JETBACKUP__')) die('Direct access is not allowed');

class Abilities {

	const CAPABILITY = 'manage_options';

	const CATEGORY = 'jetbackup';
	const CATEGORY_LABEL = 'JetBackup';
	const CATEGORY_DESCRIPTION = 'JetBackup backup, restore and status capabilities.';

	const INPUT_NONE = 0;
	const INPUT_PAGINATION = 1;
	const INPUT_ID = 2;

	const KEY_SKIP = 'skip';
	const KEY_LIMIT = 'limit';
	const LIMIT_DEFAULT = 50;
	const LIMIT_MAX = 100;

	const ARG_LABEL = 'label';
	const ARG_DESCRIPTION = 'description';
	const ARG_CATEGORY = 'category';
	const ARG_INPUT_SCHEMA = 'input_schema';
	const ARG_OUTPUT_SCHEMA = 'output_schema';
	const ARG_EXECUTE_CALLBACK = 'execute_callback';
	const ARG_PERMISSION_CALLBACK = 'permission_callback';
	const ARG_META = 'meta';
	const ARG_SHOW_IN_REST = 'show_in_rest';
	const ARG_MCP = 'mcp';
	const ARG_PUBLIC = 'public';

	const SCHEMA_TYPE = 'type';
	const SCHEMA_PROPERTIES = 'properties';
	const SCHEMA_ADDITIONAL_PROPERTIES = 'additionalProperties';
	const SCHEMA_DEFAULT = 'default';
	const SCHEMA_MINIMUM = 'minimum';
	const SCHEMA_MAXIMUM = 'maximum';
	const SCHEMA_TYPE_OBJECT = 'object';
	const SCHEMA_TYPE_INTEGER = 'integer';

	const RESPONSE_MESSAGE = 'message';
	const RESPONSE_DATA = 'data';

	const ERROR_INVALID = 'jetbackup_invalid_ability';
	const ERROR_FAILED = 'jetbackup_ability_failed';
	const MESSAGE_FAILED = 'JetBackup ability execution failed';

	const SANITIZE_OUTPUT = [
		'getSystemInfo' => ['secured_dir', 'data_dir', 'wordpress_path', 'jetbackup_data_dir'],
	];

	const ABILITIES = [
		'listBackups'      => ['List Backups',      'List available backup snapshots.',   self::INPUT_PAGINATION],
		'listBackupJobs'   => ['List Backup Jobs',  'List configured backup jobs.',       self::INPUT_PAGINATION],
		'listDestinations' => ['List Destinations', 'List configured backup destinations.', self::INPUT_PAGINATION],
		'listSchedules'    => ['List Schedules',    'List configured backup schedules.',  self::INPUT_PAGINATION],
		'listQueueItems'   => ['List Queue Items',  'List queue items.',                  self::INPUT_PAGINATION],
		'getBackup'        => ['Get Backup',        'Get a single backup snapshot by id.', self::INPUT_ID],
		'getBackupJob'     => ['Get Backup Job',    'Get a single backup job by id.',     self::INPUT_ID],
		'getQueueItem'     => ['Get Queue Item',    'Get a single queue item by id.',     self::INPUT_ID],
		'getDashboard'     => ['Get Dashboard',     'Get the dashboard summary.',         self::INPUT_NONE],
		'getSystemInfo'    => ['Get System Info',   'Get system information.',            self::INPUT_NONE],
	];

	private function __construct() {}

	public static function registerCategories(): void {

		try {
			if (!Factory::getSettingsSecurity()->isAbilitiesEnabled()) return;
		} catch (\Throwable $e) {
			return;
		}

		Wordpress::registerAbilityCategory(self::CATEGORY, [
			self::ARG_LABEL       => self::CATEGORY_LABEL,
			self::ARG_DESCRIPTION => self::CATEGORY_DESCRIPTION,
		]);
	}

	public static function register(): void {

		try {
			if (!Factory::getSettingsSecurity()->isAbilitiesEnabled()) return;
		} catch (\Throwable $e) {
			return;
		}

		foreach (self::ABILITIES as $action => $ability) {

			[$label, $description, $input] = $ability;

			Wordpress::registerAbility(self::_name($action), [
				self::ARG_LABEL               => $label,
				self::ARG_DESCRIPTION         => $description,
				self::ARG_CATEGORY            => self::CATEGORY,
				self::ARG_INPUT_SCHEMA        => self::_inputSchema($input),
				self::ARG_OUTPUT_SCHEMA       => [self::SCHEMA_TYPE => self::SCHEMA_TYPE_OBJECT],
				self::ARG_EXECUTE_CALLBACK    => static function ($input=[]) use ($action) { return self::_execute($action, is_array($input) ? $input : []); },
				self::ARG_PERMISSION_CALLBACK => [self::class, 'permission'],
				self::ARG_META                => [self::ARG_SHOW_IN_REST => true, self::ARG_MCP => [self::ARG_PUBLIC => true]],
			]);
		}
	}

	private static function _name(string $action): string {
		return self::CATEGORY . '/' . strtolower(preg_replace('/([a-z0-9])([A-Z])/', '$1-$2', $action));
	}

	public static function permission(): bool {
		try {
			if (!function_exists('current_user_can') || !function_exists('is_user_logged_in')) return false;
			if (!is_user_logged_in()) return false;
			if (!current_user_can(self::CAPABILITY)) return false;
			if (Helper::isMultisite() && !Helper::isNetworkAdminUser()) return false;

			$security = Factory::getSettingsSecurity();
			if (!$security->isAbilitiesEnabled()) return false;
			if ($security->isMFAEnabled() && !UI::validateMFA() && !$security->isMFAAllowAbilities()) return false;

			return true;

		} catch (\Throwable $e) {
			if (Wordpress::isDebugModeEnabled()) error_log('[JetBackup] ability permission check failed: ' . $e->getMessage());
			return false;
		}
	}

	private static function _inputSchema(int $input): array {

		$properties = [];

		if ($input === self::INPUT_PAGINATION) $properties = [
			self::KEY_SKIP  => [self::SCHEMA_TYPE => self::SCHEMA_TYPE_INTEGER, self::SCHEMA_MINIMUM => 0],
			self::KEY_LIMIT => [
				self::SCHEMA_TYPE    => self::SCHEMA_TYPE_INTEGER,
				self::SCHEMA_MINIMUM => 1,
				self::SCHEMA_MAXIMUM => self::LIMIT_MAX,
				self::SCHEMA_DEFAULT => self::LIMIT_DEFAULT,
			],
		];
		else if ($input === self::INPUT_ID) $properties = [
			JetBackup::ID_FIELD => [self::SCHEMA_TYPE => self::SCHEMA_TYPE_INTEGER, self::SCHEMA_MINIMUM => 1],
		];

		return [
			self::SCHEMA_TYPE                  => self::SCHEMA_TYPE_OBJECT,
			self::SCHEMA_PROPERTIES            => $properties,
			self::SCHEMA_ADDITIONAL_PROPERTIES => false,
			self::SCHEMA_DEFAULT               => (object) [],
		];
	}

	private static function _execute(string $action, array $input) {

		$class = "\\JetBackup\\Ajax\\Calls\\" . ucfirst($action);

		if (!class_exists($class)) return new \WP_Error(self::ERROR_INVALID, 'Unknown JetBackup ability', ['status' => 404]);

		try {
			/** @var iAjax $call */
			$call = new $class();
			$call->setData(self::_boundInput($action, $input));
			$call->execute();

			$data = $call->getResponseData();
			if (isset(self::SANITIZE_OUTPUT[$action])) $data = self::_sanitize($data, self::SANITIZE_OUTPUT[$action]);

			return [
				self::RESPONSE_MESSAGE => $call->getResponseMessage(),
				self::RESPONSE_DATA    => $data,
			];

		} catch (JBException $e) {
			if (Wordpress::isDebugModeEnabled()) error_log('[JetBackup] ability failed (' . $action . '): ' . $e->getMessage());
			return new \WP_Error(self::ERROR_FAILED, self::MESSAGE_FAILED, ['status' => 400]);
		} catch (\Throwable $e) {
			if (Wordpress::isDebugModeEnabled()) error_log('[JetBackup] ability error (' . $action . '): ' . $e->getMessage());
			return new \WP_Error(self::ERROR_FAILED, self::MESSAGE_FAILED, ['status' => 500]);
		}
	}

	private static function _boundInput(string $action, array $input): array {
		if ((self::ABILITIES[$action][2] ?? self::INPUT_NONE) !== self::INPUT_PAGINATION) return $input;
		$limit = isset($input[self::KEY_LIMIT]) ? (int) $input[self::KEY_LIMIT] : self::LIMIT_DEFAULT;
		if ($limit < 1) $limit = self::LIMIT_DEFAULT;
		if ($limit > self::LIMIT_MAX) $limit = self::LIMIT_MAX;
		$input[self::KEY_LIMIT] = $limit;
		return $input;
	}

	private static function _sanitize(array $data, array $keys): array {
		foreach ($data as $key => $value) {
			if (in_array($key, $keys, true)) { unset($data[$key]); continue; }
			if (is_array($value)) $data[$key] = self::_sanitize($value, $keys);
		}
		return $data;
	}
}