Files
2024-07-15 11:28:08 +02:00

751 lines
18 KiB
PHP

<?php
/**
* @package akeebabackupwp
* @copyright Copyright (c)2014-2019 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU GPL version 3 or later
*/
namespace Akeeba\WPCLI\Command;
use Akeeba\Engine\Factory;
use Akeeba\Engine\Platform;
use Awf\Mvc\Model;
use Solo\Application;
use WP_CLI;
use WP_CLI\Utils as CliUtils;
/**
* Add, remove, reset, modify, import or export your Akeeba Backup profiles.
*
* @package Akeeba\WPCLI\Command
*
* @since 3.0.0
*/
class Profile
{
/**
* Lists the Akeeba Backup backup profiles
*
* ## OPTIONS
*
* [--format=<format>]
* : The format for the returned list
* ---
* default: table
* options:
* - table
* - json
* - csv
* - yaml
* - count
* ---
*
* ## EXAMPLES
*
* wp akeeba profile list
*
* wp akeeba profile list --format=json
*
* @when after_wp_load
* @subcommand list
*
* @param array $args Positional arguments (literal arguments)
* @param array $assoc_args Associative arguments (--flag, --no-flag, --key=value)
*
* @return void
*
* @throws WP_CLI\ExitException
*
* @since 3.0.0
*/
public function _list($args, $assoc_args)
{
/** @var Application $akeebaBackupApplication */
global $akeebaBackupApplication;
$format = isset($assoc_args['format']) ? $assoc_args['format'] : 'table';
$container = $akeebaBackupApplication->getContainer();
/** @var \Solo\Model\Profiles $model */
$model = Model::getTmpInstance($container->application_name, 'Profiles', $container);
$profiles = $model->get();
CliUtils\format_items($format, $profiles->toArray(), ['id', 'description', 'quickicon']);
}
/**
* Creates a copy of an Akeeba Backup profile
*
* ## OPTIONS
*
* <id>
* : The numeric ID of the profile to copy
*
* [--filters]
* : Include filters in the copy. Enabled by default. Use --no-filters to disable.
*
* [--description=<description>]
* : Description for the new backup profile. Uses the old profile's description if not specified.
*
* [--quickicon=<quickicon>]
* : Should the new backup profile have a one-click backup icon? Copies the old profile's setting if not specified.
*
* [--format=<format>]
* : The format for the response. Use JSON to get a JSON-parseable numeric ID of the new backup profile.
* ---
* default: text
* options:
* - text
* - json
* ---
*
* ## EXAMPLES
*
* wp akeeba profile copy 1
*
* wp akeeba profile copy 1 --description="Default profile without any filters" --no-filters
*
* wp akeeba profile copy 1 --quickicon=0 --description="Cloned default profile"
*
* @when after_wp_load
*
* @param array $args Positional arguments (literal arguments)
* @param array $assoc_args Associative arguments (--flag, --no-flag, --key=value)
*
* @return void
*
* @throws WP_CLI\ExitException
*
* @since 3.0.0
*/
public function copy($args, $assoc_args)
{
/** @var Application $akeebaBackupApplication */
global $akeebaBackupApplication;
if (!isset($args[0]))
{
WP_CLI::error("You must specify the backup profile ID to copy.");
}
$profileID = (int) $args[0];
if ($profileID <= 0)
{
WP_CLI::error("The backup profile ID to copy from must be a positive integer.");
}
$withFilters = isset($assoc_args['filters']) ? (bool) $assoc_args['filters'] : true;
$container = $akeebaBackupApplication->getContainer();
/** @var \Solo\Model\Profiles $model */
$model = Model::getTmpInstance($container->application_name, 'Profiles', $container);
try
{
$source = $model->findOrFail($profileID);
}
catch (\RuntimeException $e)
{
WP_CLI::error("Cannot copy profile $profileID; profile not found.");
}
$profileData = $source->getData();
unset($profileData['id']);
if (!$withFilters)
{
$profileData['filters'] = '';
}
if (isset($assoc_args['description']))
{
$profileData['description'] = trim($assoc_args['description']);
}
if (isset($assoc_args['quickicon']))
{
$profileData['quickicon'] = (bool) $assoc_args['quickicon'] ? 1 : 0;
}
try
{
$newProfile = $model->create($profileData);
}
catch (\Exception $e)
{
WP_CLI::error("Cannot copy profile #$profileID: {$e->getMessage()}");
}
$format = isset($assoc_args['format']) ? $assoc_args['format'] : 'text';
if ($format == 'json')
{
echo json_encode($newProfile->getData());
return;
}
WP_CLI::success("Copy successful. Created new profile with ID {$newProfile->getId()}.");
}
/**
* Creates a new Akeeba Backup profile
*
* ## OPTIONS
*
* [--description=<description>]
* : Description for the new backup profile. Default: "New backup profile".
*
* [--quickicon=<quickicon>]
* : Should the new backup profile have a one-click backup icon? Default: 1.
*
* [--format=<format>]
* : The format for the response. Use JSON to get a JSON-parseable numeric ID of the new backup profile.
* ---
* default: text
* options:
* - text
* - json
* ---
*
* ## EXAMPLES
*
* wp akeeba profile create
*
* wp akeeba profile create --description="For moving site to example.com"
*
* wp akeeba profile create --description="Backup after post creation" --quickicon=1
*
* @when after_wp_load
*
* @param array $args Positional arguments (literal arguments)
* @param array $assoc_args Associative arguments (--flag, --no-flag, --key=value)
*
* @return void
*
* @throws WP_CLI\ExitException
*
* @since 3.0.0
*/
public function create($args, $assoc_args)
{
/** @var Application $akeebaBackupApplication */
global $akeebaBackupApplication;
$container = $akeebaBackupApplication->getContainer();
/** @var \Solo\Model\Profiles $model */
$model = Model::getTmpInstance($container->application_name, 'Profiles', $container);
// Set up the new profile data
$profileData = [
'description' => 'New backup profile',
'quickicon' => '1',
'configuration' => '',
'filters' => '',
];
if (isset($assoc_args['description']))
{
$profileData['description'] = trim($assoc_args['description']);
}
if (isset($assoc_args['quickicon']))
{
$profileData['quickicon'] = (bool) $assoc_args['quickicon'] ? 1 : 0;
}
// Create the profile
try
{
$newProfile = $model->create($profileData);
}
catch (\Exception $e)
{
WP_CLI::error("Cannot create profile: {$e->getMessage()}");
}
/**
* Create a new profile configuration.
*
* Loading the new profile's empty configuration causes the Platform code to revert to the default options and
* save them automatically to the database.
*/
$profileId = $newProfile->getId();
Platform::getInstance()->load_configuration($profileId);
// Display the results
$format = isset($assoc_args['format']) ? $assoc_args['format'] : 'text';
if ($format == 'json')
{
echo json_encode($newProfile->getId());
return;
}
WP_CLI::success("Copy successful. Created new profile with ID {$newProfile->getId()}.");
}
/**
* Deletes an Akeeba Backup profile
*
* ## OPTIONS
*
* <id>
* : The numeric ID of the profile to copy. It CAN NOT be 1; the default backup profile must always be present.
*
* ## EXAMPLES
*
* wp akeeba profile delete 2
*
* @when after_wp_load
*
* @param array $args Positional arguments (literal arguments)
* @param array $assoc_args Associative arguments (--flag, --no-flag, --key=value)
*
* @return void
*
* @throws WP_CLI\ExitException
*
* @since 3.0.0
*/
public function delete($args, $assoc_args)
{
/** @var Application $akeebaBackupApplication */
global $akeebaBackupApplication;
if (!isset($args[0]))
{
WP_CLI::error("You must specify the backup profile ID to delete.");
}
$profileID = (int) $args[0];
if ($profileID <= 0)
{
WP_CLI::error("The backup profile ID to delete must be a positive integer.");
}
$container = $akeebaBackupApplication->getContainer();
/** @var \Solo\Model\Profiles $model */
$model = Model::getTmpInstance($container->application_name, 'Profiles', $container);
try
{
$profile = $model->findOrFail($profileID);
}
catch (\RuntimeException $e)
{
WP_CLI::error("Cannot delete profile $profileID; profile not found.");
}
try
{
$profile->forceDelete($profile->getId());
}
catch (\Exception $e)
{
WP_CLI::error("Cannot delete profile $profileID; {$e->getMessage()}.");
}
WP_CLI::success("Profile $profileID has been deleted.");
}
/**
* Change the description or quick icon setting of an Akeeba Backup profile.
*
* ## OPTIONS
*
* <id>
* : The numeric ID of the profile to modify
*
* [--description=<description>]
* : Description for the new backup profile. Uses the old profile's description if not specified.
*
* [--quickicon=<quickicon>]
* : Should the new backup profile have a one-click backup icon? Copies the old profile's setting if not specified.
*
* ## EXAMPLES
*
* wp akeeba profile modify 1 --description="Foo bar"
*
* wp akeeba profile modify 1 --quickicon=0
*
* wp akeeba profile modify 1 --description="Foo bar" --quickicon=0
*
* @when after_wp_load
*
* @param array $args Positional arguments (literal arguments)
* @param array $assoc_args Associative arguments (--flag, --no-flag, --key=value)
*
* @return void
*
* @throws WP_CLI\ExitException
*
* @since 3.0.0
*/
public function modify($args, $assoc_args)
{
/** @var Application $akeebaBackupApplication */
global $akeebaBackupApplication;
if (!isset($args[0]))
{
WP_CLI::error("You must specify the backup profile ID to modify.");
}
$profileID = (int) $args[0];
if ($profileID <= 0)
{
WP_CLI::error("The backup profile ID to modify must be a positive integer.");
}
$container = $akeebaBackupApplication->getContainer();
/** @var \Solo\Model\Profiles $model */
$model = Model::getTmpInstance($container->application_name, 'Profiles', $container);
try
{
$profile = $model->findOrFail($profileID);
}
catch (\RuntimeException $e)
{
WP_CLI::error("Cannot modify profile $profileID; profile not found.");
}
if (isset($assoc_args['description']))
{
$profile->description = trim($assoc_args['description']);
}
if (isset($assoc_args['quickicon']))
{
$profile->quickicon = (bool) $assoc_args['quickicon'] ? 1 : 0;
}
try
{
$profile->save();
}
catch (\Exception $e)
{
WP_CLI::error("Cannot save profile #$profileID: {$e->getMessage()}");
}
WP_CLI::success("Profile $profileID modified successfully.");
}
/**
* Reset the filters and / or backup engine configuration of an Akeeba Backup profile.
*
* ## OPTIONS
*
* <id>
* : The numeric ID of the profile to reset
*
* [--filters]
* : Reset filters. Enabled by default. Use --no-filters to disable.
*
* [--configuration]
* : Reset the engine configuration. Enabled by default. Use --no-configuration to disable.
*
* ## EXAMPLES
*
* wp akeeba profile reset 1
*
* wp akeeba profile reset 1 --no-filters
*
* wp akeeba profile reset 1 --no-configuration
*
* @when after_wp_load
*
* @param array $args Positional arguments (literal arguments)
* @param array $assoc_args Associative arguments (--flag, --no-flag, --key=value)
*
* @return void
*
* @throws WP_CLI\ExitException
*
* @since 3.0.0
*/
public function reset($args, $assoc_args)
{
/** @var Application $akeebaBackupApplication */
global $akeebaBackupApplication;
if (!isset($args[0]))
{
WP_CLI::error("You must specify the backup profile ID to reset.");
}
$profileID = (int) $args[0];
if ($profileID <= 0)
{
WP_CLI::error("The backup profile ID to reset must be a positive integer.");
}
$container = $akeebaBackupApplication->getContainer();
/** @var \Solo\Model\Profiles $model */
$model = Model::getTmpInstance($container->application_name, 'Profiles', $container);
try
{
$profile = $model->findOrFail($profileID);
}
catch (\RuntimeException $e)
{
WP_CLI::error("Cannot reset profile $profileID; profile not found.");
}
$filters = isset($assoc_args['filters']) ? $assoc_args['filters'] : true;
$configuration = isset($assoc_args['configuration']) ? $assoc_args['configuration'] : true;
if ($filters)
{
$profile->filters = '';
}
if ($configuration)
{
$profile->configuration = '';
}
if (!$filters && !$configuration)
{
WP_CLI::error("You have chosen to reset neither filters nor configuration. There is nothing for me to do.");
}
try
{
$profile->save();
}
catch (\Exception $e)
{
WP_CLI::error("Cannot save profile #$profileID: {$e->getMessage()}");
}
/**
* Loading the new profile's empty configuration causes the Platform code to revert to the default options and
* save them automatically to the database.
*/
if ($configuration)
{
Platform::getInstance()->load_configuration($profileID);
}
WP_CLI::success("Profile $profileID reset successfully.");
}
/**
* Exports an Akeeba Backup profile as a JSON string.
*
* ## OPTIONS
*
* <id>
* : The numeric ID of the profile to export
*
* [--filters]
* : Include filter settings. Enabled by default. Use --no-filters to disable.
*
* ## EXAMPLES
*
* wp akeeba profile export 1
*
* wp akeeba profile export 1 --no-filters
*
* @when after_wp_load
*
* @param array $args Positional arguments (literal arguments)
* @param array $assoc_args Associative arguments (--flag, --no-flag, --key=value)
*
* @return void
*
* @throws WP_CLI\ExitException
*
* @since 3.0.0
*/
public function export($args, $assoc_args)
{
/** @var Application $akeebaBackupApplication */
global $akeebaBackupApplication;
if (!isset($args[0]))
{
WP_CLI::error("You must specify the backup profile ID to export.");
}
$profileID = (int) $args[0];
if ($profileID <= 0)
{
WP_CLI::error("The backup profile ID to export must be a positive integer.");
}
$container = $akeebaBackupApplication->getContainer();
/** @var \Solo\Model\Profiles $model */
$model = Model::getTmpInstance($container->application_name, 'Profiles', $container);
try
{
$profile = $model->findOrFail($profileID);
}
catch (\RuntimeException $e)
{
WP_CLI::error("Cannot export profile $profileID; profile not found.");
}
$data = $profile->toArray();
$filters = isset($assoc_args['filters']) ? $assoc_args['filters'] : true;
if (!$filters)
{
unset($data['filters']);
}
// The ID must not be included in the export
unset($data['id']);
// Decrypt configuration data if necessary
if (substr($data['configuration'], 0, 12) == '###AES128###')
{
// Load the server key file if necessary
$key = Factory::getSecureSettings()->getKey();
$data['configuration'] = Factory::getSecureSettings()->decryptSettings($data['configuration'], $key);
}
echo json_encode($data);
}
/**
* Exports an Akeeba Backup profile from JSON.
*
* ## OPTIONS
*
* [<fileOrJSON>]
* : A path to an Akeeba Backup profile export JSON file or a literal JSON string. Uses STDIN if ommited.
*
* [--format=<format>]
* : The format for the response. Use JSON to get a JSON-parseable numeric ID of the new backup profile.
* ---
* default: text
* options:
* - text
* - json
* ---
*
* ## EXAMPLES
*
* wp akeeba profile import /path/to/profile.json --format=json
*
* cat /path/to/profile.json | wp akeeba profile
*
* @when after_wp_load
*
* @param array $args Positional arguments (literal arguments)
* @param array $assoc_args Associative arguments (--flag, --no-flag, --key=value)
*
* @return void
*
* @throws WP_CLI\ExitException
*
* @since 3.0.0
*/
public function import($args, $assoc_args)
{
global $akeebaBackupApplication;
$json = null;
$filename = isset($args[0]) ? $args[0] : '';
$json = $this->getJSON($filename);
$decoded = json_decode($json, true);
if (empty($decoded))
{
WP_CLI::error("Cannot process input; invalid JSON string or file not found.");
}
// We must never pass an ID, forcing the model to create a new record
if (isset($decoded['id']))
{
unset($decoded['id']);
}
$container = $akeebaBackupApplication->getContainer();
/** @var \Solo\Model\Profiles $model */
$model = Model::getTmpInstance($container->application_name, 'Profiles', $container);
try
{
$newProfile = $model->create($decoded);
}
catch (\Exception $e)
{
WP_CLI::error("Cannot import profile: {$e->getMessage()}");
}
$profileID = $newProfile->getId();
$format = isset($assoc_args['format']) ? $assoc_args['format'] : 'text';
if ($format == 'json')
{
echo json_encode($profileID);
return;
}
WP_CLI::success("Successfully imported JSON as profile #$profileID");
}
/**
* Get the JSON input for import()
*
* @param string $filename The filename to read from, raw JSON data or an empty string
*
* @return string The JSON data
*
* @since 3.0.0
*/
private function getJSON($filename)
{
// No filename or JSON string passed to script; use STDIN
if (empty($filename))
{
$json = '';
while (!feof(STDIN))
{
$json .= fgets(STDIN) . "\n";
}
return rtrim($json);
}
// An existing file path was passed. Return the contents of the file.
if (@file_exists($filename))
{
$ret = @file_get_contents($filename);
if ($ret === false)
{
return '';
}
}
// Otherwise assume raw JSON was passed back to us.
return $filename;
}
}