mirror of
https://gh.wpcy.net/https://github.com/CaptainCore/do.git
synced 2026-04-17 21:52:19 +08:00
428 lines
No EOL
14 KiB
Bash
428 lines
No EOL
14 KiB
Bash
# ----------------------------------------------------
|
||
# Cron Commands
|
||
# Manages scheduled tasks for the _do script.
|
||
# ----------------------------------------------------
|
||
|
||
# ----------------------------------------------------
|
||
# (Helper) PHP script to manage cron events.
|
||
# ----------------------------------------------------
|
||
function _get_cron_manager_php_script() {
|
||
read -r -d '' php_script <<'PHP'
|
||
<?php
|
||
|
||
$argv = WP_CLI::get_runner()->arguments;
|
||
array_shift( $argv );
|
||
// This script is a self-contained manager for cron events stored in a WP option.
|
||
// It is designed to be called with specific actions and arguments.
|
||
|
||
// Prevent direct execution.
|
||
if (empty($argv) || !isset($argv[1])) {
|
||
return;
|
||
}
|
||
|
||
$action = $argv[1] ?? null;
|
||
// The main function for this script is to get the option, unserialize it,
|
||
// perform an action, then serialize and save the result.
|
||
function get_events() {
|
||
// get_option will return the value of the option, already unserialized.
|
||
// The second argument is the default value if the option does not exist.
|
||
$events = get_option("captaincore_do_cron", []);
|
||
return is_array($events) ? $events : [];
|
||
}
|
||
|
||
function save_events($events) {
|
||
// update_option will create the option if it does not exist.
|
||
// The third argument 'no' sets autoload to false.
|
||
update_option( "captaincore_do_cron", $events, 'no');
|
||
}
|
||
|
||
// --- Action Router ---
|
||
|
||
if ($action === 'list_all') {
|
||
$events = get_events();
|
||
if (empty($events)) {
|
||
return;
|
||
}
|
||
// Sort events by the next_run timestamp to show the soonest first.
|
||
usort($events, function($a, $b) {
|
||
return ($a['next_run'] ?? 0) <=> ($b['next_run'] ?? 0);
|
||
});
|
||
|
||
// MODIFICATION: Get the WordPress timezone once before the loop.
|
||
$wp_timezone = wp_timezone();
|
||
|
||
foreach ($events as $event) {
|
||
|
||
// MODIFICATION: Convert the stored UTC timestamp to the WP timezone for display.
|
||
$next_run_formatted = 'N/A';
|
||
if (isset($event['next_run'])) {
|
||
// Create a DateTime object from the UTC timestamp
|
||
$next_run_dt = new DateTime("@" . $event['next_run']);
|
||
// Set the object's timezone to the WordPress configured timezone
|
||
$next_run_dt->setTimezone($wp_timezone);
|
||
// Format for output
|
||
$next_run_formatted = $next_run_dt->format('Y-m-d H:i:s T');
|
||
}
|
||
|
||
// Output in CSV format for gum table
|
||
echo implode(',', [
|
||
$event['id'] ?? 'N/A',
|
||
'"' . ($event['command'] ?? 'N/A') . '"', // Quote command in case it has spaces
|
||
$next_run_formatted,
|
||
$event['frequency'] ?? 'N/A'
|
||
]) . "\n";
|
||
}
|
||
}
|
||
|
||
elseif ($action === 'list_due') {
|
||
$now = time();
|
||
$due_events = [];
|
||
foreach (get_events() as $event) {
|
||
if (isset($event['next_run']) && $event['next_run'] <= $now) {
|
||
$due_events[] = $event;
|
||
}
|
||
}
|
||
echo json_encode($due_events);
|
||
}
|
||
|
||
elseif ($action === 'add') {
|
||
$id = uniqid('event_');
|
||
$command = $argv[2] ?? null;
|
||
$next_run_str = $argv[3] ?? null;
|
||
$frequency = $argv[4] ?? null;
|
||
|
||
if (!$command || !$next_run_str || !$frequency) {
|
||
error_log('Error: Missing arguments for add action.');
|
||
return;
|
||
}
|
||
|
||
// --- Frequency Translation ---
|
||
$freq_lower = strtolower($frequency);
|
||
if ($freq_lower === 'weekly') {
|
||
$frequency = '1 week';
|
||
} elseif ($freq_lower === 'daily') {
|
||
$frequency = '1 day';
|
||
} elseif ($freq_lower === 'monthly') {
|
||
$frequency = '1 month';
|
||
} elseif ($freq_lower === 'hourly') {
|
||
$frequency = '1 hour';
|
||
}
|
||
// --- End Translation ---
|
||
|
||
try {
|
||
// Use the WordPress configured timezone for parsing the date string.
|
||
$wp_timezone = wp_timezone();
|
||
$next_run_dt = new DateTime($next_run_str, $wp_timezone);
|
||
$next_run_timestamp = $next_run_dt->getTimestamp();
|
||
} catch (Exception $e) {
|
||
error_log('Error: Invalid date/time string for next_run: ' . $e->getMessage());
|
||
return;
|
||
}
|
||
|
||
$events = get_events();
|
||
$events[] = [
|
||
'id' => $id,
|
||
'command' => $command,
|
||
'next_run' => $next_run_timestamp,
|
||
'frequency' => $frequency,
|
||
];
|
||
save_events($events);
|
||
echo "✅ Event '$id' added. Input '{$next_run_str}' interpreted using WordPress timezone ({$wp_timezone->getName()}). Next run: " . date('Y-m-d H:i:s T', $next_run_timestamp) . "\n";
|
||
}
|
||
|
||
elseif ($action === 'delete') {
|
||
$id_to_delete = $argv[2] ?? null;
|
||
if (!$id_to_delete) {
|
||
error_log('Error: No ID provided for delete action.');
|
||
return;
|
||
}
|
||
|
||
$events = get_events();
|
||
$updated_events = [];
|
||
$found = false;
|
||
foreach ($events as $event) {
|
||
if (isset($event['id']) && $event['id'] === $id_to_delete) {
|
||
$found = true;
|
||
} else {
|
||
$updated_events[] = $event;
|
||
}
|
||
}
|
||
|
||
if ($found) {
|
||
save_events($updated_events);
|
||
echo "✅ Event '$id_to_delete' deleted successfully.\n";
|
||
} else {
|
||
echo "❌ Error: Event with ID '$id_to_delete' not found.\n";
|
||
}
|
||
}
|
||
|
||
elseif ($action === 'update_next_run') {
|
||
$id = $argv[2] ?? null;
|
||
if (!$id) {
|
||
error_log('Error: No ID provided to update_next_run.');
|
||
return;
|
||
}
|
||
|
||
$events = get_events();
|
||
$found = false;
|
||
foreach ($events as &$event) {
|
||
if (isset($event['id']) && $event['id'] === $id) {
|
||
try {
|
||
$last_run_ts = $event['next_run'] ??
|
||
time();
|
||
$next_run_dt = new DateTime("@{$last_run_ts}", new DateTimeZone('UTC'));
|
||
|
||
do {
|
||
$next_run_dt->modify('+ ' . $event['frequency']);
|
||
} while ($next_run_dt->getTimestamp() <= time());
|
||
|
||
$event['next_run'] = $next_run_dt->getTimestamp();
|
||
$found = true;
|
||
break;
|
||
} catch (Exception $e) {
|
||
error_log('Error: Invalid frequency string "' . ($event['frequency'] ?? '') . '": ' . $e->getMessage());
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
if ($found) {
|
||
save_events($events);
|
||
}
|
||
}
|
||
PHP
|
||
echo "$php_script"
|
||
}
|
||
|
||
# ----------------------------------------------------
|
||
# Configures the global cron runner by installing the latest script.
|
||
# ----------------------------------------------------
|
||
function cron_enable() {
|
||
echo "Attempting to configure cron runner..."
|
||
|
||
if ! setup_wp_cli || ! "$WP_CLI_CMD" core is-installed --quiet; then
|
||
echo "❌ Error: This command must be run from within a WordPress directory." >&2
|
||
return 1
|
||
fi
|
||
if ! command -v realpath &> /dev/null || ! command -v md5sum &> /dev/null; then
|
||
echo "❌ Error: 'realpath' and 'md5sum' commands are required." >&2
|
||
return 1
|
||
fi
|
||
|
||
# Determine the absolute path of the WordPress installation
|
||
local wp_path
|
||
wp_path=$(realpath ".")
|
||
if [[ ! -f "$wp_path/wp-load.php" ]]; then
|
||
echo "❌ Error: Could not confirm WordPress root at '$wp_path'." >&2
|
||
return 1
|
||
fi
|
||
|
||
local private_dir
|
||
if ! private_dir=$(_get_private_dir); then return 1; fi
|
||
local script_path="$private_dir/_do.sh"
|
||
|
||
echo "ℹ️ Downloading the latest version of the _do script..."
|
||
if ! command -v curl &> /dev/null; then
|
||
echo "❌ Error: 'curl' is required to download the script." >&2; return 1;
|
||
fi
|
||
if ! curl -sL "https://captaincore.io/do" -o "$script_path"; then
|
||
echo "❌ Error: Failed to download the _do script." >&2; return 1;
|
||
fi
|
||
chmod +x "$script_path"
|
||
echo "✅ Script installed/updated at: $script_path"
|
||
|
||
# Make the marker unique to the path to allow multiple cron jobs
|
||
local path_hash
|
||
path_hash=$(echo "$wp_path" | md5sum | cut -d' ' -f1)
|
||
local cron_marker="#_DO_CRON_RUNNER_$path_hash"
|
||
local cron_command="bash \"$script_path\" cron run --path=\"$wp_path\""
|
||
local cron_job="*/10 * * * * $cron_command $cron_marker"
|
||
|
||
# Atomically update the crontab
|
||
local current_crontab
|
||
current_crontab=$(crontab -l 2>/dev/null | grep -v "$cron_marker")
|
||
(echo "$current_crontab"; echo "$cron_job") | crontab -
|
||
|
||
if [ $? -eq 0 ]; then
|
||
local site_url
|
||
site_url=$("$WP_CLI_CMD" option get home --skip-plugins --skip-themes 2>/dev/null)
|
||
echo "✅ Cron runner enabled for site: $site_url ($wp_path)"
|
||
else
|
||
echo "❌ Error: Could not modify crontab. Please check your permissions." >&2
|
||
return 1
|
||
fi
|
||
|
||
echo "Current crontab:"
|
||
crontab -l
|
||
}
|
||
|
||
# ----------------------------------------------------
|
||
# Runs the cron process, executing any due events.
|
||
# ----------------------------------------------------
|
||
function cron_run() {
|
||
# If a path is provided, change to that directory first.
|
||
if [[ -n "$path_flag" ]]; then
|
||
if [ -d "$path_flag" ]; then
|
||
cd "$path_flag" || { echo "[$(date)] Cron Error: Could not change directory to '$path_flag'." >> /tmp/_do_cron.log; return 1; }
|
||
else
|
||
echo "[$(date)] Cron Error: Provided path '$path_flag' does not exist." >> /tmp/_do_cron.log;
|
||
return 1
|
||
fi
|
||
fi
|
||
|
||
# Call the setup function to ensure the wp command path is known.
|
||
if ! setup_wp_cli; then
|
||
echo "[$(date)] Cron Error: WP-CLI setup failed." >> /tmp/_do_cron.log
|
||
return 1
|
||
fi
|
||
|
||
# Check if this is a WordPress installation using the full command path.
|
||
if ! "$WP_CLI_CMD" core is-installed --quiet; then
|
||
return 1
|
||
fi
|
||
|
||
local php_script;
|
||
php_script=$(_get_cron_manager_php_script)
|
||
|
||
local due_events_json;
|
||
# Use the full command path for all wp-cli calls.
|
||
due_events_json=$(echo "$php_script" | "$WP_CLI_CMD" eval-file - 'list_due' 2>&1)
|
||
if [ $? -ne 0 ];
|
||
then
|
||
echo "[$(date)] Cron run failed: $due_events_json" >> /tmp/_do_cron.log
|
||
return 1
|
||
fi
|
||
|
||
if [ -z "$due_events_json" ] || [[ "$due_events_json" == "[]" ]];
|
||
then
|
||
return 0
|
||
fi
|
||
|
||
local php_parser='
|
||
$json = file_get_contents("php://stdin");
|
||
$events = json_decode($json, true);
|
||
if (is_array($events)) {
|
||
foreach($events as $event) {
|
||
echo $event["id"] . "|" . $event["command"] . "\n";
|
||
}
|
||
}
|
||
'
|
||
local due_events_list;
|
||
due_events_list=$(echo "$due_events_json" | php -r "$php_parser")
|
||
|
||
if [ -z "$due_events_list" ];
|
||
then
|
||
return 0
|
||
fi
|
||
|
||
echo "Found due events, processing..."
|
||
while IFS='|' read -r id command; do
|
||
if [ -z "$id" ] || [ -z "$command" ]; then
|
||
continue
|
||
fi
|
||
echo "-> Running event '$id': _do $command"
|
||
local script_path
|
||
script_path=$(realpath "$0")
|
||
bash "$script_path" $command
|
||
echo "-> Updating next run time for event '$id'"
|
||
# Use the full command path here as well.
|
||
echo "$php_script" | "$WP_CLI_CMD" eval-file - 'update_next_run' "$id"
|
||
done <<< "$due_events_list"
|
||
echo "Cron run complete."
|
||
}
|
||
|
||
# ----------------------------------------------------
|
||
# Adds a new command to the cron schedule.
|
||
# ----------------------------------------------------
|
||
function cron_add() {
|
||
local command="$1"
|
||
local next_run="$2"
|
||
local frequency="$3"
|
||
|
||
if [ -z "$command" ] || [ -z "$next_run" ] || [ -z "$frequency" ]; then
|
||
echo "❌ Error: Missing arguments." >&2
|
||
show_command_help "cron"
|
||
return 1
|
||
fi
|
||
|
||
if ! setup_wp_cli; then echo "❌ Error: WP-CLI not found." >&2; return 1; fi
|
||
if ! "$WP_CLI_CMD" core is-installed --quiet; then echo "❌ Error: Not in a WordPress installation." >&2; return 1; fi
|
||
|
||
echo "Adding new cron event..."
|
||
local php_script; php_script=$(_get_cron_manager_php_script)
|
||
|
||
# Capture both stdout and stderr to a variable
|
||
local output; output=$(echo "$php_script" | "$WP_CLI_CMD" eval-file - "add" "$command" "$next_run" "$frequency" 2>&1)
|
||
|
||
# Check the exit code of the wp-cli command
|
||
if [ $? -ne 0 ]; then
|
||
echo "❌ Error: The wp-cli command failed to execute."
|
||
echo " Output:"
|
||
# Indent the output for readability
|
||
echo "$output" | sed 's/^/ /'
|
||
else
|
||
# Print the success message from the PHP script
|
||
echo "$output"
|
||
fi
|
||
}
|
||
|
||
# ----------------------------------------------------
|
||
# Deletes a scheduled cron event by its ID.
|
||
# ----------------------------------------------------
|
||
function cron_delete() {
|
||
local event_id="$1"
|
||
|
||
if [ -z "$event_id" ]; then
|
||
echo "❌ Error: No event ID provided." >&2
|
||
show_command_help "cron"
|
||
return 1
|
||
fi
|
||
|
||
if ! setup_wp_cli; then echo "❌ Error: WP-CLI not found." >&2; return 1; fi
|
||
if ! "$WP_CLI_CMD" core is-installed --quiet; then echo "❌ Error: Not in a WordPress installation." >&2; return 1; fi
|
||
|
||
echo "Attempting to delete event '$event_id'..."
|
||
local php_script
|
||
php_script=$(_get_cron_manager_php_script)
|
||
|
||
# Capture and display output from the PHP script
|
||
local output
|
||
output=$(echo "$php_script" | "$WP_CLI_CMD" eval-file - "delete" "$event_id" 2>&1)
|
||
|
||
# The PHP script now prints success or error, so just display it.
|
||
echo "$output"
|
||
}
|
||
|
||
# ----------------------------------------------------
|
||
# Lists all scheduled cron events in a table.
|
||
# ----------------------------------------------------
|
||
function cron_list() {
|
||
if ! setup_wp_cli; then echo "❌ Error: WP-CLI not found." >&2; return 1; fi
|
||
if ! "$WP_CLI_CMD" core is-installed --quiet; then echo "❌ Error: Not in a WordPress installation." >&2; return 1; fi
|
||
if ! setup_gum; then return 1; fi
|
||
|
||
echo "🔎 Fetching scheduled events..."
|
||
|
||
local php_script; php_script=$(_get_cron_manager_php_script)
|
||
|
||
# Capture stdout and stderr
|
||
local events_csv; events_csv=$(echo "$php_script" | "$WP_CLI_CMD" eval-file - 'list_all' 2>&1)
|
||
|
||
# Check exit code
|
||
if [ $? -ne 0 ]; then
|
||
echo "❌ Error: The wp-cli command failed while listing events."
|
||
echo " Output:"
|
||
echo "$events_csv" | sed 's/^/ /'
|
||
return 1
|
||
fi
|
||
|
||
if [ -z "$events_csv" ]; then
|
||
echo "ℹ️ No scheduled cron events found."
|
||
return 0
|
||
fi
|
||
|
||
local table_header="ID,Command,Next Run,Frequency"
|
||
|
||
# Prepend the header and pipe to gum table for a formatted view
|
||
(echo "$table_header"; echo "$events_csv") | "$GUM_CMD" table --print --separator ","
|
||
} |