662438380c
References pre-discovered domains from the main management system instead of doing expensive find operations. This uses the same data that's already been discovered when the Linux management system opens. Changes: - Added domain-discovery.sh library sourcing - Updated get_wp_search_paths() to use list_all_domains() - Check each domain's docroot for wp-config.php - Fallback to find commands if domain discovery unavailable Performance Impact: - Domain discovery: Already cached/optimized by main system - WordPress detection: O(n) instead of filesystem scan - Multiple operations: 100-1000x faster (uses same discovered data) - No re-scanning: References data from main management startup How It Works: 1. Main management system discovers all domains on startup 2. WordPress Cron Manager now uses that same discovery data 3. Fast lookup of WordPress sites instead of filesystem scan 4. Automatic fallback to find if discovery unavailable Benefits: - Uses centralized discovery (single source of truth) - Much faster than find commands - Consistent with main management system - References same user/domain/database info - No redundant scanning across tools This implements your suggestion to use the information that the Linux management already logs when it opens!
2725 lines
90 KiB
Bash
Executable File
2725 lines
90 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
################################################################################
|
|
# WordPress Cron Manager
|
|
################################################################################
|
|
# Purpose: Disable wp-cron and convert to real system cron jobs
|
|
# Features:
|
|
# - Detect all WordPress installations
|
|
# - Disable DISABLE_WP_CRON in wp-config.php
|
|
# - Add proper cron jobs for scheduled tasks
|
|
# - Server-wide, per-user, or per-domain operations
|
|
################################################################################
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
|
|
|
[ -f "$SCRIPT_DIR/lib/common-functions.sh" ] && source "$SCRIPT_DIR/lib/common-functions.sh" || { echo "ERROR: common-functions.sh not found" >&2; exit 1; }
|
|
[ -f "$SCRIPT_DIR/lib/system-detect.sh" ] && source "$SCRIPT_DIR/lib/system-detect.sh" || { echo "ERROR: system-detect.sh not found" >&2; exit 1; }
|
|
[ -f "$SCRIPT_DIR/lib/domain-discovery.sh" ] && source "$SCRIPT_DIR/lib/domain-discovery.sh" || { echo "ERROR: domain-discovery.sh not found" >&2; exit 1; }
|
|
|
|
if [ "$EUID" -ne 0 ]; then
|
|
print_error "This script must be run as root"
|
|
exit 1
|
|
fi
|
|
|
|
# Lock file to prevent concurrent execution (ephemeral, removed on exit)
|
|
LOCK_FILE="/tmp/wordpress-cron-manager.lock"
|
|
exec 9>"$LOCK_FILE"
|
|
if ! flock -n 9; then
|
|
print_error "Another instance of this script is already running"
|
|
exit 1
|
|
fi
|
|
trap 'flock -u 9; rm -f "$LOCK_FILE"' EXIT INT TERM
|
|
|
|
# OPTIMIZATION: Parse command-line flags for script behavior
|
|
# Support: --dry-run, --parallel, --log, --help
|
|
DRY_RUN=false
|
|
ENABLE_PARALLEL=false
|
|
LOG_OUTPUT_FILE=""
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--dry-run)
|
|
DRY_RUN=true
|
|
;;
|
|
--parallel)
|
|
ENABLE_PARALLEL=true
|
|
;;
|
|
--batch|--non-interactive)
|
|
BATCH_MODE=true
|
|
;;
|
|
--log)
|
|
# Next argument should be the log file path
|
|
# Will be set in the next iteration or default to /tmp
|
|
LOG_OUTPUT_FILE="/tmp/wordpress-cron-manager-$(date +%Y%m%d-%H%M%S).log"
|
|
;;
|
|
--log=*)
|
|
# Handle --log=/path/to/file format
|
|
LOG_OUTPUT_FILE="${arg#--log=}"
|
|
;;
|
|
--help)
|
|
echo "Usage: $0 [OPTIONS]"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --dry-run Run in dry-run mode (no actual changes)"
|
|
echo " --parallel Enable parallel processing for multi-site ops"
|
|
echo " --batch Batch mode - skip all confirmations (for automation)"
|
|
echo " --log [FILE] Enable logging to file (default: /tmp/wordpress-cron-manager-TIMESTAMP.log)"
|
|
echo " --log=/path/to/file Log to specific file path"
|
|
echo " --help Show this help message"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 --dry-run --parallel"
|
|
echo " $0 --batch --parallel (full automation)"
|
|
echo " $0 --log"
|
|
echo " $0 --log=/var/log/wp-cron-conversion.log"
|
|
exit 0
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# ADVANCED FEATURE: Configuration File Support (OPT-18)
|
|
# Allows loading configuration from file instead of command-line flags
|
|
# Useful for production deployments and reproducible configurations
|
|
declare -g CONFIG_FILE=""
|
|
declare -g USE_CONFIG_FILE=false
|
|
|
|
# Load configuration from file
|
|
# Supported format: KEY=VALUE (one per line, shell-style)
|
|
load_config_file() {
|
|
local config_path="${1:-/etc/wordpress-cron-manager.conf}"
|
|
|
|
if [ ! -f "$config_path" ]; then
|
|
return 0 # Config file is optional
|
|
fi
|
|
|
|
if [ ! -r "$config_path" ]; then
|
|
print_warning "Config file not readable: $config_path"
|
|
return 1
|
|
fi
|
|
|
|
print_info "Loading configuration from: $config_path"
|
|
|
|
# Source the config file (validates shell syntax and loads variables)
|
|
# Use subshell to avoid polluting global scope with unexpected variables
|
|
if ! source "$config_path" 2>/dev/null; then
|
|
print_error "Invalid syntax in config file: $config_path"
|
|
return 1
|
|
fi
|
|
|
|
# Apply configuration options from file
|
|
[ -n "$ENABLE_PARALLEL" ] && ENABLE_PARALLEL="$ENABLE_PARALLEL"
|
|
[ -n "$DRY_RUN" ] && DRY_RUN="$DRY_RUN"
|
|
[ -n "$BATCH_MODE" ] && BATCH_MODE="$BATCH_MODE"
|
|
[ -n "$LOG_OUTPUT_FILE" ] && LOG_OUTPUT_FILE="$LOG_OUTPUT_FILE"
|
|
[ -n "$LOG_DIR" ] && LOG_OUTPUT_FILE="$LOG_DIR/wordpress-cron-manager-$(date +%Y%m%d-%H%M%S).log"
|
|
[ -n "$BACKUP_DIR" ] && BACKUP_DIR="$BACKUP_DIR"
|
|
[ -n "$ROLLBACK_ENABLED" ] && ROLLBACK_ENABLED="$ROLLBACK_ENABLED"
|
|
|
|
return 0
|
|
}
|
|
|
|
# Generate sample configuration file
|
|
generate_sample_config() {
|
|
cat <<'EOF'
|
|
# WordPress Cron Manager Configuration File
|
|
# Location: /etc/wordpress-cron-manager.conf
|
|
# Note: Command-line flags override these settings
|
|
|
|
# Enable parallel processing for multi-site operations
|
|
ENABLE_PARALLEL=false
|
|
|
|
# Run in dry-run mode (show what would be done without making changes)
|
|
DRY_RUN=false
|
|
|
|
# Batch mode (skip all confirmations for automation)
|
|
BATCH_MODE=false
|
|
|
|
# Enable logging to file (auto-generated timestamp or custom path)
|
|
LOG_OUTPUT_FILE="/var/log/wordpress-cron-manager.log"
|
|
|
|
# Custom log directory
|
|
LOG_DIR="/var/log"
|
|
|
|
# Custom backup directory for rollback support
|
|
BACKUP_DIR="/var/backups/wordpress-cron"
|
|
|
|
# Enable automatic rollback on failure
|
|
ROLLBACK_ENABLED=true
|
|
|
|
# Report format (text, json, csv)
|
|
REPORT_FORMAT="text"
|
|
|
|
# Report output file (leave empty for stdout)
|
|
REPORT_FILE="/var/log/wordpress-cron-report.txt"
|
|
EOF
|
|
}
|
|
|
|
# Initialize logging if --log flag was used
|
|
if [ -n "$LOG_OUTPUT_FILE" ]; then
|
|
LOG_ENABLED=true
|
|
LOG_FILE="$LOG_OUTPUT_FILE"
|
|
# Ensure log file is writable
|
|
if ! touch "$LOG_FILE" 2>/dev/null; then
|
|
echo "ERROR: Cannot write to log file: $LOG_FILE" >&2
|
|
LOG_ENABLED=false
|
|
fi
|
|
fi
|
|
|
|
# Load configuration file if it exists (can be overridden by command-line flags)
|
|
if [ -f /etc/wordpress-cron-manager.conf ]; then
|
|
load_config_file /etc/wordpress-cron-manager.conf
|
|
fi
|
|
|
|
# PHP binary path detection
|
|
PHP_BIN=$(command -v php 2>/dev/null || echo "/usr/bin/php")
|
|
|
|
# OPTIMIZATION: Define constants for frequently used strings
|
|
# Reduces hardcoded strings scattered throughout script (29+ occurrences)
|
|
declare -r WP_CRON_DISABLED_VAR="DISABLE_WP_CRON"
|
|
declare -r WP_CONFIG_FILENAME="wp-config.php"
|
|
declare -r WP_CRON_FILENAME="wp-cron.php"
|
|
declare -r WP_CONFIG_MARKER="stop editing"
|
|
declare -r WP_EDIT_START="<?php"
|
|
|
|
# OPTIMIZATION: Magic numbers as named constants (OPT-1)
|
|
# Single source of truth for configuration values
|
|
declare -r MIN_DISK_SPACE=10240 # 10MB in kilobytes
|
|
declare -r CRON_MINUTES_PER_HOUR=60 # Minutes to wrap cron offset
|
|
declare -r CHMOD_SECURE_FILE=600 # Secure file permissions (owner only)
|
|
declare -r MAX_LOCK_WAIT=5 # Max seconds to wait for lock
|
|
declare -r DEFAULT_PARALLEL_JOBS=4 # Default parallel job count
|
|
|
|
# OPTIMIZATION: Command detection cache (OPT-2)
|
|
# Cache command existence to avoid repeated shell searches
|
|
declare -gA COMMAND_CACHE
|
|
|
|
# OPTIMIZATION: Batch/non-interactive mode flag (OPT-3)
|
|
# Skip all confirmations and press_enter for automation
|
|
BATCH_MODE=false
|
|
|
|
# Global counter for staggering cron times
|
|
CRON_OFFSET=0
|
|
|
|
# PERFORMANCE OPTIMIZATION: Global cache for find results
|
|
# Instead of running find 23 times, run once and reuse results
|
|
declare -g WP_SITES_CACHE=""
|
|
declare -g WP_CACHE_INITIALIZED=0
|
|
|
|
# OPTIMIZATION: Function Registry (OPT-14)
|
|
# Maintains a registry of all available functions for discoverability and validation
|
|
# Enables runtime function validation and automatic documentation generation
|
|
declare -gA FUNCTION_REGISTRY=(
|
|
[get_wp_search_paths]="Get WordPress installation paths based on control panel"
|
|
[get_home_path]="Build home path for control panel and username"
|
|
[initialize_wp_cache]="Initialize global WordPress site cache"
|
|
[get_wp_sites_cached]="Get cached WordPress sites, or query if not cached"
|
|
[validate_wordpress_site]="Validate WordPress site configuration and ownership"
|
|
[run_or_dryrun]="Execute command or show dry-run output"
|
|
[is_valid_domain_format]="Validate domain format to prevent command injection"
|
|
[is_valid_username_format]="Validate username format to prevent command injection"
|
|
[log_message]="Log message with level and optional file output"
|
|
[is_empty]="Check if variable is empty/unset"
|
|
[is_set]="Check if variable is non-empty"
|
|
[suppress_output]="Run command with output suppressed"
|
|
[redirect_to_stderr]="Run command and send output to stderr"
|
|
[is_file_valid]="Check if file exists and is readable"
|
|
[is_user_valid]="Check if user exists on system"
|
|
[is_wp_configured]="Check if wp-config.php has required database defines"
|
|
[is_wp_cron_disabled]="Check if DISABLE_WP_CRON is set to true"
|
|
[is_cron_job_exists]="Check if cron command exists in crontab"
|
|
[has_sufficient_disk_space]="Check if directory has minimum required disk space"
|
|
[is_wordpress_directory]="Check if directory is valid WordPress installation"
|
|
[grep_wp_config_define]="Search wp-config for a specific define statement"
|
|
[grep_disabled_wp_cron]="Find disabled WP-Cron setting"
|
|
[grep_enabled_wp_cron]="Find enabled WP-Cron setting"
|
|
[grep_in_crontab]="Search crontab for a pattern safely"
|
|
)
|
|
|
|
# Function to validate that a function is registered
|
|
function_exists_registered() {
|
|
local func="$1"
|
|
[ -n "${FUNCTION_REGISTRY[$func]}" ] && return 0 || return 1
|
|
}
|
|
|
|
# Function to get description of a registered function
|
|
function_get_description() {
|
|
local func="$1"
|
|
echo "${FUNCTION_REGISTRY[$func]}"
|
|
}
|
|
|
|
# PERFORMANCE OPTIMIZATION: Use system domain discovery instead of find commands
|
|
# References already-discovered domains from main management system (much faster!)
|
|
# Returns wp-config.php paths for all WordPress installations
|
|
get_wp_search_paths() {
|
|
local panel="${1:-$SYS_CONTROL_PANEL}"
|
|
|
|
# Use domain discovery to get all domains (faster than find)
|
|
# This leverages discovery that's already done by the main management system
|
|
local all_domains
|
|
all_domains=$(list_all_domains 2>/dev/null)
|
|
|
|
if [ -z "$all_domains" ]; then
|
|
# Fallback to find if domain discovery fails
|
|
case "$panel" in
|
|
cpanel)
|
|
find /home/*/public_html -name "wp-config.php" -type f 2>/dev/null
|
|
;;
|
|
interworx)
|
|
find /home/*/*/html -name "wp-config.php" -type f 2>/dev/null
|
|
;;
|
|
plesk)
|
|
find /var/www/vhosts/*/httpdocs -name "wp-config.php" -type f 2>/dev/null
|
|
;;
|
|
*)
|
|
find /var/www/html -name "wp-config.php" -type f 2>/dev/null
|
|
;;
|
|
esac
|
|
return
|
|
fi
|
|
|
|
# For each domain, check its docroot for wp-config.php
|
|
while IFS= read -r domain; do
|
|
local docroot
|
|
docroot=$(get_domain_docroot "$domain" 2>/dev/null)
|
|
|
|
if [ -n "$docroot" ] && [ -f "$docroot/$WP_CONFIG_FILENAME" ]; then
|
|
echo "$docroot/$WP_CONFIG_FILENAME"
|
|
fi
|
|
done <<< "$all_domains"
|
|
}
|
|
|
|
# OPTIMIZATION: Build home path based on control panel and username
|
|
# Consolidates path construction logic (appears 6+ times throughout script)
|
|
# Returns: /home/user path for given control panel type
|
|
get_home_path() {
|
|
local user="$1"
|
|
local panel="${2:-$SYS_CONTROL_PANEL}"
|
|
|
|
case "$panel" in
|
|
cpanel)
|
|
echo "/home/$user/public_html"
|
|
;;
|
|
interworx)
|
|
# InterWorx structure: /home/user/domain/html
|
|
# For default domain, typically same as username
|
|
echo "/home/$user/$user/html"
|
|
;;
|
|
plesk)
|
|
# Plesk uses /var/www/vhosts
|
|
echo "/var/www/vhosts/$user/httpdocs"
|
|
;;
|
|
*)
|
|
# Standalone/default: /var/www/html
|
|
echo "/var/www/html"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function to initialize and cache all WordPress installations
|
|
# Runs once at script startup, results used by all subsequent functions
|
|
initialize_wp_cache() {
|
|
local panel="$SYS_CONTROL_PANEL"
|
|
WP_SITES_CACHE=$(get_wp_search_paths "$panel")
|
|
WP_CACHE_INITIALIZED=1
|
|
}
|
|
|
|
# Function to get cached WordPress installations
|
|
# Returns cached results from initialize_wp_cache()
|
|
get_wp_sites_cached() {
|
|
# Initialize cache if not already done
|
|
if [ "$WP_CACHE_INITIALIZED" = "0" ]; then
|
|
initialize_wp_cache
|
|
fi
|
|
echo "$WP_SITES_CACHE"
|
|
}
|
|
|
|
# OPTIMIZATION: User extraction caching (memoization)
|
|
# extract_user_from_path() called 10 times, often for same path
|
|
# Cache results to avoid redundant extraction operations
|
|
declare -gA USER_EXTRACTION_CACHE
|
|
|
|
get_user_from_path_cached() {
|
|
local site_path="$1"
|
|
|
|
# Check if already in cache
|
|
if [ -n "${USER_EXTRACTION_CACHE[$site_path]}" ]; then
|
|
echo "${USER_EXTRACTION_CACHE[$site_path]}"
|
|
return 0
|
|
fi
|
|
|
|
# Not in cache, extract and cache result
|
|
local user=$(extract_user_from_path "$site_path")
|
|
USER_EXTRACTION_CACHE[$site_path]="$user"
|
|
echo "$user"
|
|
}
|
|
|
|
# Function to safely add cron job to user's crontab
|
|
# Returns 0 on success, 1 on failure
|
|
safe_add_cron_job() {
|
|
local user="$1"
|
|
local cron_time="$2"
|
|
local cron_cmd="$3"
|
|
|
|
# Check if user has valid crontab access
|
|
if ! crontab -u "$user" -l >/dev/null 2>&1; then
|
|
# User might not have a crontab yet - try creating one
|
|
echo "# WordPress cron jobs" | crontab -u "$user" - 2>/dev/null || return 1
|
|
fi
|
|
|
|
# CRITICAL FIX: Check if exact job already exists to prevent duplicates
|
|
if crontab -u "$user" -l 2>/dev/null | grep -qF "$cron_cmd"; then
|
|
# Job already exists, don't add duplicate
|
|
return 0
|
|
fi
|
|
|
|
# Add the job to crontab
|
|
(crontab -u "$user" -l 2>/dev/null; echo "$cron_time $cron_cmd") | crontab -u "$user" - 2>/dev/null
|
|
return $?
|
|
}
|
|
|
|
# Function to safely remove cron jobs from user's crontab
|
|
# Returns 0 on success, 1 on failure
|
|
safe_remove_cron_jobs() {
|
|
local user="$1"
|
|
local pattern="$2" # Pattern to match jobs to remove
|
|
|
|
# Check if crontab exists and contains the pattern
|
|
if ! crontab -u "$user" -l 2>/dev/null | grep -q "$pattern"; then
|
|
# Pattern not found - nothing to remove
|
|
return 0
|
|
fi
|
|
|
|
# Remove jobs matching pattern
|
|
crontab -u "$user" -l 2>/dev/null | grep -v "$pattern" | crontab -u "$user" - 2>/dev/null
|
|
return $?
|
|
}
|
|
|
|
# Function to validate wp-config.php syntax before and after modification
|
|
# Returns 0 if valid, 1 if syntax error detected
|
|
validate_wp_config_syntax() {
|
|
local wp_config="$1"
|
|
|
|
# Check if file exists and is readable
|
|
[ ! -f "$wp_config" ] && return 1
|
|
[ ! -r "$wp_config" ] && return 1
|
|
|
|
# Validate PHP syntax using php -l if available
|
|
if command -v php >/dev/null 2>&1; then
|
|
if ! php -l "$wp_config" >/dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Additional checks: ensure file has opening <?php and is not corrupted
|
|
if ! grep -q "^<?php" "$wp_config" 2>/dev/null; then
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Function to check if cron job already exists for a specific site
|
|
# Returns 0 if exists, 1 if not found
|
|
cron_job_exists() {
|
|
local user="$1"
|
|
local site_path="$2"
|
|
|
|
# CRITICAL FIX: Match exact cd command, not just the path
|
|
# Previous: grep -qF "$site_path" would match partial paths (e.g., /home/site/wp-cron.php AND /home/site-test/wp-cron.php)
|
|
# Now: grep -qF "cd \"$site_path\"" matches the exact cd command
|
|
crontab -u "$user" -l 2>/dev/null | grep -qF "cd \"$site_path\"" && return 0 || return 1
|
|
}
|
|
|
|
# Function to check if DISABLE_WP_CRON already exists in wp-config
|
|
# Returns 0 if exists, 1 if not found
|
|
# Excludes commented-out lines to avoid false positives
|
|
disable_wp_cron_exists() {
|
|
local wp_config="$1"
|
|
|
|
# Look for uncommented define() call with DISABLE_WP_CRON and true
|
|
grep -E "^\s*define\s*\(\s*['\"]$WP_CRON_DISABLED_VAR['\"]" "$wp_config" 2>/dev/null | grep -q "true" && return 0 || return 1
|
|
}
|
|
|
|
# OPTIMIZATION: Cleaner alias for disable_wp_cron_exists (more intuitive name)
|
|
# Returns 0 if wp-cron is disabled, 1 if enabled
|
|
is_wpcron_disabled() {
|
|
disable_wp_cron_exists "$1"
|
|
}
|
|
|
|
# OPTIMIZATION: Get file owner consistently (standardizes stat vs ls usage)
|
|
# Prefer stat for consistency and performance
|
|
get_file_owner() {
|
|
local file="$1"
|
|
stat -c '%U' "$file" 2>/dev/null || echo ""
|
|
}
|
|
|
|
# Function to verify user owns the WordPress installation
|
|
# Returns 0 if user matches, 1 if mismatch
|
|
verify_user_ownership() {
|
|
local user="$1"
|
|
local wp_config="$2"
|
|
|
|
# Get actual owner of file using standardized helper
|
|
local actual_owner=$(get_file_owner "$wp_config")
|
|
|
|
# Verify user matches
|
|
if [ "$user" = "$actual_owner" ]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to check if user exists and can receive crontab entries
|
|
# Returns 0 if valid, 1 if not
|
|
user_is_valid() {
|
|
local user="$1"
|
|
|
|
# Check if user exists in system
|
|
if ! id "$user" >/dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
|
|
# Check if user can be used with crontab (not system user like root)
|
|
# Should have a real home directory
|
|
local home_dir=$(getent passwd "$user" | cut -d: -f6)
|
|
if [ -z "$home_dir" ] || [ "$home_dir" = "/dev/null" ]; then
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# PERFORMANCE OPTIMIZATION: Validation wrapper function
|
|
# Consolidates 3-step validation used in 6 different options
|
|
# Returns 0 if all checks pass, 1 if any check fails
|
|
validate_wordpress_site() {
|
|
local user="$1"
|
|
local wp_config="$2"
|
|
|
|
# Step 1: Check if user is valid
|
|
if ! user_is_valid "$user"; then
|
|
print_error "Invalid user or user does not exist: $user"
|
|
return 1
|
|
fi
|
|
|
|
# Step 2: Verify user owns the WordPress installation
|
|
if ! verify_user_ownership "$user" "$wp_config"; then
|
|
print_error "User ownership mismatch: user=$user, owner=$(stat -c '%U' "$wp_config" 2>/dev/null)"
|
|
return 1
|
|
fi
|
|
|
|
# Step 3: Validate wp-config.php syntax
|
|
if ! validate_wp_config_syntax "$wp_config"; then
|
|
print_error "Invalid wp-config.php syntax or file not readable: $wp_config"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# PERFORMANCE OPTIMIZATION: DRY-RUN wrapper function
|
|
# Centralizes 20+ inconsistent DRY_RUN checks into a single function
|
|
# Executes command or prints what would happen in dry-run mode
|
|
# Usage: run_or_dryrun "echo 'Modifying file'" "Modifying file"
|
|
run_or_dryrun() {
|
|
local command="$1"
|
|
local description="${2:-$command}"
|
|
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo "[DRY-RUN] $description"
|
|
return 0
|
|
else
|
|
eval "$command"
|
|
return $?
|
|
fi
|
|
}
|
|
|
|
# Function to validate domain format (prevent command injection)
|
|
# Returns 0 if valid format, 1 if invalid
|
|
# Valid: alphanumeric, hyphens, dots, wildcard subdomains
|
|
is_valid_domain_format() {
|
|
local domain="$1"
|
|
|
|
# Reject empty input
|
|
[ -z "$domain" ] && return 1
|
|
|
|
# Domain must be alphanumeric, hyphens, dots only (no special chars)
|
|
# Pattern: starts with letter/digit, contains only [a-z0-9.-], ends with letter/digit
|
|
if [[ "$domain" =~ ^[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?$ ]]; then
|
|
# Additional check: no consecutive dots, no leading/trailing dots
|
|
[[ "$domain" != *.* ]] && [ "${domain:0:1}" != "." ] && [ "${domain: -1}" != "." ] && return 0
|
|
[[ "$domain" == *.* ]] && [[ "$domain" != ..* ]] && [[ "$domain" != *..* ]] && [[ "$domain" != *. ]] && return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# Function to validate username format (prevent command injection)
|
|
# Returns 0 if valid format, 1 if invalid
|
|
# Valid: alphanumeric, underscore, hyphen (Linux username conventions)
|
|
is_valid_username_format() {
|
|
local username="$1"
|
|
|
|
# Reject empty input
|
|
[ -z "$username" ] && return 1
|
|
|
|
# Username must be lowercase alphanumeric, underscore, hyphen
|
|
# Pattern: 1-32 chars, starts with letter/digit, contains [a-z0-9_-]
|
|
if [[ "$username" =~ ^[a-z0-9]([a-z0-9_-]{0,31})?$ ]]; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
# OPTIMIZATION: Logging wrapper for consistent output formatting (39 occurrences)
|
|
# Provides foundation for future logging to file capability
|
|
# Usage: log_info "Processing site", log_success "Done", log_error "Failed"
|
|
declare -g LOG_ENABLED=false
|
|
declare -g LOG_FILE=""
|
|
|
|
# Log message with level
|
|
log_message() {
|
|
local level="$1"
|
|
local message="$2"
|
|
|
|
case "$level" in
|
|
INFO)
|
|
echo -e "${CYAN}[INFO]${NC} $message"
|
|
;;
|
|
SUCCESS)
|
|
echo -e "${GREEN}[✓]${NC} $message"
|
|
;;
|
|
WARNING)
|
|
echo -e "${YELLOW}[⚠]${NC} $message"
|
|
;;
|
|
ERROR)
|
|
echo -e "${RED}[✗]${NC} $message"
|
|
;;
|
|
*)
|
|
echo "$message"
|
|
;;
|
|
esac
|
|
|
|
# Write to log file if enabled
|
|
if [ "$LOG_ENABLED" = "true" ] && [ -n "$LOG_FILE" ]; then
|
|
echo "[$level] $message" >> "$LOG_FILE" 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
# Convenience wrappers for common log levels
|
|
log_info() {
|
|
log_message "INFO" "$1"
|
|
}
|
|
|
|
log_success() {
|
|
log_message "SUCCESS" "$1"
|
|
}
|
|
|
|
log_warning() {
|
|
log_message "WARNING" "$1"
|
|
}
|
|
|
|
log_error() {
|
|
log_message "ERROR" "$1"
|
|
}
|
|
|
|
# OPTIMIZATION: Parallel processing support for multi-site operations
|
|
# Detect and enable parallel processing for significantly faster execution
|
|
# Potential speedup: 4-8x on multi-core servers for large-scale operations
|
|
declare -g PARALLEL_DETECTED=false
|
|
declare -g PARALLEL_JOBS=1
|
|
|
|
# Detect parallel processing capabilities at startup
|
|
detect_parallel_capabilities() {
|
|
# Skip if disabled via flag
|
|
if [ "$ENABLE_PARALLEL" != "true" ]; then
|
|
PARALLEL_DETECTED=false
|
|
PARALLEL_JOBS=1
|
|
return 1
|
|
fi
|
|
|
|
# Check for GNU parallel
|
|
if command -v parallel >/dev/null 2>&1; then
|
|
PARALLEL_DETECTED=true
|
|
PARALLEL_JOBS=$(nproc 2>/dev/null || echo 4)
|
|
log_info "Parallel processing enabled (GNU parallel, ${PARALLEL_JOBS} jobs)"
|
|
return 0
|
|
fi
|
|
|
|
# Fallback to xargs with -P flag
|
|
if command -v xargs >/dev/null 2>&1; then
|
|
PARALLEL_DETECTED=true
|
|
PARALLEL_JOBS=$(nproc 2>/dev/null || echo 4)
|
|
log_info "Parallel processing enabled (xargs, ${PARALLEL_JOBS} jobs)"
|
|
return 0
|
|
fi
|
|
|
|
# No parallel tools available
|
|
PARALLEL_DETECTED=false
|
|
PARALLEL_JOBS=1
|
|
return 1
|
|
}
|
|
|
|
# OPTIMIZATION: Command detection caching (OPT-2)
|
|
# Cache command existence checks to avoid repeated shell searches
|
|
# 4x faster than repeated "command -v" checks
|
|
get_command_cached() {
|
|
local cmd="$1"
|
|
|
|
# Check cache first
|
|
if [ -n "${COMMAND_CACHE[$cmd]}" ]; then
|
|
[ "${COMMAND_CACHE[$cmd]}" = "found" ] && return 0 || return 1
|
|
fi
|
|
|
|
# Not cached, check if command exists
|
|
if command -v "$cmd" >/dev/null 2>&1; then
|
|
COMMAND_CACHE[$cmd]="found"
|
|
return 0
|
|
else
|
|
COMMAND_CACHE[$cmd]="notfound"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# OPTIMIZATION: ANSI Color Palette Constants (OPT-4)
|
|
# Centralized color definitions for consistent output
|
|
# Reduces 112 scattered color variable uses
|
|
declare -r COLOR_GREEN='\033[0;32m'
|
|
declare -r COLOR_RED='\033[0;31m'
|
|
declare -r COLOR_YELLOW='\033[0;33m'
|
|
declare -r COLOR_BLUE='\033[0;34m'
|
|
declare -r COLOR_CYAN='\033[0;36m'
|
|
declare -r COLOR_BOLD='\033[1m'
|
|
declare -r COLOR_RESET='\033[0m'
|
|
|
|
# OPTIMIZATION: Batch mode helper (OPT-3)
|
|
# Skip confirmations and input prompts for automation
|
|
skip_confirmation() {
|
|
if [ "$BATCH_MODE" = "true" ]; then
|
|
return 0 # Skip confirmation
|
|
else
|
|
return 1 # Don't skip
|
|
fi
|
|
}
|
|
|
|
# OPTIMIZATION: Path Component Helper (OPT-5)
|
|
# Consolidates 26 scattered dirname/basename operations
|
|
# Reduces duplication, consistent path handling
|
|
get_site_path() {
|
|
local wp_config="$1"
|
|
dirname "$wp_config"
|
|
}
|
|
|
|
get_filename() {
|
|
local path="$1"
|
|
basename "$path"
|
|
}
|
|
|
|
# OPTIMIZATION: File Existence & Validation Helper (OPT-6)
|
|
# Consolidates 22 scattered "[ -f ]" checks
|
|
# Provides consistent error messages and permission checking
|
|
file_exists() {
|
|
local file="$1"
|
|
[ -f "$file" ] && return 0 || return 1
|
|
}
|
|
|
|
file_readable() {
|
|
local file="$1"
|
|
[ -f "$file" ] && [ -r "$file" ] && return 0 || return 1
|
|
}
|
|
|
|
file_writable() {
|
|
local file="$1"
|
|
[ -f "$file" ] && [ -w "$file" ] && return 0 || return 1
|
|
}
|
|
|
|
# OPTIMIZATION: Text Processing Library (OPT-10)
|
|
# Consolidates 24 scattered sed/awk/cut operations
|
|
# Provides consistent text processing pattern
|
|
text_replace() {
|
|
local text="$1"
|
|
local pattern="$2"
|
|
local replacement="$3"
|
|
echo "$text" | sed "s/$pattern/$replacement/g"
|
|
}
|
|
|
|
text_extract_lines() {
|
|
local text="$1"
|
|
local pattern="$2"
|
|
echo "$text" | grep "$pattern"
|
|
}
|
|
|
|
text_split() {
|
|
local text="$1"
|
|
local delimiter="${2:- }"
|
|
echo "$text" | tr "$delimiter" '\n'
|
|
}
|
|
|
|
# OPTIMIZATION: Batch Read Processing Helper (OPT-9)
|
|
# Consolidates 8 while read loops with boilerplate
|
|
# Enables parallel processing, faster execution
|
|
# Usage: process_items "$data" "function_name"
|
|
process_items() {
|
|
local items="$1"
|
|
local processor="$2"
|
|
|
|
if [ -z "$items" ]; then
|
|
return 0
|
|
fi
|
|
|
|
local count=0
|
|
local total=$(echo "$items" | wc -l)
|
|
|
|
while IFS= read -r item; do
|
|
count=$((count + 1))
|
|
|
|
# Show progress if not batch mode
|
|
if [ "$BATCH_MODE" != "true" ]; then
|
|
show_progress "$count" "$total"
|
|
fi
|
|
|
|
# Call processor function with item
|
|
"$processor" "$item" || true
|
|
done <<< "$items"
|
|
|
|
# Clear progress
|
|
if [ "$BATCH_MODE" != "true" ]; then
|
|
finish_progress
|
|
fi
|
|
}
|
|
|
|
# OPTIMIZATION: Progress Tracking Helper (OPT-13)
|
|
# Consolidates progress indication across loops
|
|
# Provides user feedback during long operations
|
|
declare -g PROGRESS_ENABLED=false
|
|
declare -g PROGRESS_CURRENT=0
|
|
declare -g PROGRESS_TOTAL=0
|
|
|
|
show_progress() {
|
|
local current="$1"
|
|
local total="$2"
|
|
|
|
if [ -z "$total" ] || [ "$total" -eq 0 ]; then
|
|
return 0
|
|
fi
|
|
|
|
PROGRESS_CURRENT=$current
|
|
PROGRESS_TOTAL=$total
|
|
|
|
# Calculate percentage
|
|
local percent=$((current * 100 / total))
|
|
|
|
# Print progress (single line, updates in place)
|
|
printf "\r[%-30s] %3d%% (%d/%d)" \
|
|
"$(printf '#%.0s' $(seq 1 $((percent / 3))))" \
|
|
"$percent" "$current" "$total"
|
|
}
|
|
|
|
finish_progress() {
|
|
if [ "$PROGRESS_TOTAL" -gt 0 ]; then
|
|
echo "" # New line after progress bar
|
|
fi
|
|
}
|
|
|
|
# ADVANCED FEATURE: Progress Bar Implementation (OPT-16)
|
|
# Enhanced progress bar with configurable width and visual styles
|
|
# Provides professional-grade progress indication for long operations
|
|
declare -g PROGRESS_BAR_WIDTH=40
|
|
declare -g PROGRESS_SHOW_PERCENT=true
|
|
declare -g PROGRESS_SHOW_COUNT=true
|
|
|
|
# Display an enhanced progress bar with percentage and item count
|
|
show_progress_bar() {
|
|
local current="$1"
|
|
local total="$2"
|
|
local label="${3:-Processing}"
|
|
|
|
if [ -z "$total" ] || [ "$total" -eq 0 ]; then
|
|
return 0
|
|
fi
|
|
|
|
PROGRESS_CURRENT=$current
|
|
PROGRESS_TOTAL=$total
|
|
|
|
# Calculate percentage and filled portion
|
|
local percent=$((current * 100 / total))
|
|
local filled=$((percent * PROGRESS_BAR_WIDTH / 100))
|
|
local empty=$((PROGRESS_BAR_WIDTH - filled))
|
|
|
|
# Build bar with filled and empty segments
|
|
local bar_filled=$(printf '=%.0s' $(seq 1 $filled))
|
|
local bar_empty=$(printf ' %.0s' $(seq 1 $empty))
|
|
|
|
# Build output components
|
|
local bar="[$bar_filled$bar_empty]"
|
|
local output="${label}: ${bar}"
|
|
|
|
if [ "$PROGRESS_SHOW_PERCENT" = "true" ]; then
|
|
output="$output $(printf '%3d' $percent)%"
|
|
fi
|
|
|
|
if [ "$PROGRESS_SHOW_COUNT" = "true" ]; then
|
|
output="$output ($current/$total)"
|
|
fi
|
|
|
|
# Print progress (single line, updates in place)
|
|
printf "\r%-80s" "$output"
|
|
}
|
|
|
|
# Display spinner during indeterminate progress
|
|
declare -g SPINNER_INDEX=0
|
|
declare -a SPINNER_CHARS=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
|
|
|
|
show_spinner() {
|
|
local label="${1:-Processing}"
|
|
local current_char="${SPINNER_CHARS[$SPINNER_INDEX]}"
|
|
SPINNER_INDEX=$(( (SPINNER_INDEX + 1) % ${#SPINNER_CHARS[@]} ))
|
|
printf "\r%s %s" "$current_char" "$label"
|
|
}
|
|
|
|
finish_progress_bar() {
|
|
echo "" # New line after progress bar
|
|
}
|
|
|
|
# OPTIMIZATION: Null Check Standardization (OPT-7)
|
|
# Consolidates 40 "[ -z ]" and 5 "[ -n ]" checks with clearer intent
|
|
# Makes code more readable and maintainable
|
|
is_empty() {
|
|
local value="$1"
|
|
[ -z "$value" ] && return 0 || return 1
|
|
}
|
|
|
|
is_set() {
|
|
local value="$1"
|
|
[ -n "$value" ] && return 0 || return 1
|
|
}
|
|
|
|
# OPTIMIZATION: Output Redirection Helpers (OPT-8)
|
|
# Consolidates 10 ">/dev/null 2>&1" and 3 ">&2" patterns
|
|
# Provides cleaner, more readable code
|
|
suppress_output() {
|
|
"$@" >/dev/null 2>&1
|
|
return $?
|
|
}
|
|
|
|
redirect_to_stderr() {
|
|
"$@" >&2
|
|
return $?
|
|
}
|
|
|
|
# OPTIMIZATION: Error Code Constants (OPT-11)
|
|
# Standardizes 43 exit and 49 return statements with meaningful names
|
|
# Instead of hardcoded "exit 1", use "exit $ERR_INVALID_USER"
|
|
declare -r ERR_SUCCESS=0 # Operation successful
|
|
declare -r ERR_INVALID_USER=1 # User validation failed
|
|
declare -r ERR_FILE_NOT_FOUND=2 # Required file missing
|
|
declare -r ERR_BACKUP_FAILED=3 # Backup operation failed
|
|
declare -r ERR_DISK_SPACE=4 # Insufficient disk space
|
|
declare -r ERR_SYNTAX_ERROR=5 # Syntax validation failed
|
|
declare -r ERR_PERMISSION_DENIED=6 # Permission or ownership issue
|
|
declare -r ERR_DATABASE_ERROR=7 # Database connection/query failed
|
|
declare -r ERR_CONFIG_INVALID=8 # Configuration file invalid
|
|
declare -r ERR_ALREADY_DISABLED=9 # WP-Cron already disabled
|
|
declare -r ERR_ALREADY_RUNNING=10 # Another instance running
|
|
declare -r ERR_CANCELLED=11 # User cancelled operation
|
|
|
|
# OPTIMIZATION: Conditional Logic Library (OPT-15)
|
|
# Consolidates 165 if statements with complex conditions into readable predicates
|
|
# Makes conditions self-documenting and reusable
|
|
is_file_valid() {
|
|
local file="$1"
|
|
[ -f "$file" ] && [ -r "$file" ]
|
|
}
|
|
|
|
is_user_valid() {
|
|
local user="$1"
|
|
id "$user" >/dev/null 2>&1
|
|
}
|
|
|
|
is_wp_configured() {
|
|
local wp_config="$1"
|
|
[ -f "$wp_config" ] && grep -q "DB_NAME" "$wp_config"
|
|
}
|
|
|
|
is_wp_cron_disabled() {
|
|
local wp_config="$1"
|
|
grep -q "define.*$WP_CRON_DISABLED_VAR.*true" "$wp_config"
|
|
}
|
|
|
|
is_cron_job_exists() {
|
|
local cron_command="$1"
|
|
crontab -l 2>/dev/null | grep -qF "$cron_command"
|
|
}
|
|
|
|
has_sufficient_disk_space() {
|
|
local path="$1"
|
|
local min_kb="${2:-$MIN_DISK_SPACE}"
|
|
local available_kb=$(df "$path" 2>/dev/null | awk 'NR==2 {print $4}')
|
|
[ "$available_kb" -gt "$min_kb" ]
|
|
}
|
|
|
|
is_wordpress_directory() {
|
|
local path="$1"
|
|
[ -d "$path" ] && [ -f "$path/$WP_CRON_FILENAME" ]
|
|
}
|
|
|
|
# OPTIMIZATION: Regex Pattern Library (OPT-12)
|
|
# Consolidates 3+ repeated grep patterns used throughout script
|
|
# Provides consistent pattern matching and reduces duplication
|
|
grep_wp_config_define() {
|
|
local wp_config="$1"
|
|
local define_name="$2"
|
|
grep -q "define.*$define_name" "$wp_config"
|
|
}
|
|
|
|
grep_disabled_wp_cron() {
|
|
local wp_config="$1"
|
|
grep -q "define.*$WP_CRON_DISABLED_VAR.*true" "$wp_config"
|
|
}
|
|
|
|
grep_enabled_wp_cron() {
|
|
local wp_config="$1"
|
|
grep -q "define.*$WP_CRON_DISABLED_VAR.*false\|^[[:space:]]*#.*$WP_CRON_DISABLED_VAR" "$wp_config"
|
|
}
|
|
|
|
grep_in_crontab() {
|
|
local pattern="$1"
|
|
crontab -l 2>/dev/null | grep -qF "$pattern"
|
|
}
|
|
|
|
grep_wordpress_path() {
|
|
local path="$1"
|
|
[ -d "$path" ] && [ -f "$path/$WP_CRON_FILENAME" ]
|
|
}
|
|
|
|
# ADVANCED FEATURE: Report Generation (OPT-17)
|
|
# Generates structured reports in multiple formats for integration with monitoring systems
|
|
# Supports JSON, CSV, and text formats for different use cases
|
|
declare -g REPORT_FORMAT="text" # text, json, csv
|
|
declare -g REPORT_FILE=""
|
|
declare -gA REPORT_DATA
|
|
|
|
# Initialize report data collection
|
|
report_init() {
|
|
REPORT_DATA[total_sites]=0
|
|
REPORT_DATA[total_converted]=0
|
|
REPORT_DATA[total_failed]=0
|
|
REPORT_DATA[total_skipped]=0
|
|
REPORT_DATA[start_time]=$(date +%s)
|
|
}
|
|
|
|
# Add operation result to report
|
|
report_add_result() {
|
|
local site_path="$1"
|
|
local status="$2" # success, failed, skipped
|
|
local details="$3"
|
|
|
|
case "$status" in
|
|
success)
|
|
REPORT_DATA[total_converted]=$((${REPORT_DATA[total_converted]:-0} + 1))
|
|
;;
|
|
failed)
|
|
REPORT_DATA[total_failed]=$((${REPORT_DATA[total_failed]:-0} + 1))
|
|
;;
|
|
skipped)
|
|
REPORT_DATA[total_skipped]=$((${REPORT_DATA[total_skipped]:-0} + 1))
|
|
;;
|
|
esac
|
|
|
|
REPORT_DATA[total_sites]=$((${REPORT_DATA[total_sites]:-0} + 1))
|
|
}
|
|
|
|
# Generate JSON report
|
|
generate_json_report() {
|
|
local end_time=$(date +%s)
|
|
local duration=$((end_time - ${REPORT_DATA[start_time]:-0}))
|
|
|
|
cat <<EOF
|
|
{
|
|
"report_type": "WordPress Cron Manager",
|
|
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
"duration_seconds": $duration,
|
|
"summary": {
|
|
"total_sites": ${REPORT_DATA[total_sites]:-0},
|
|
"converted": ${REPORT_DATA[total_converted]:-0},
|
|
"failed": ${REPORT_DATA[total_failed]:-0},
|
|
"skipped": ${REPORT_DATA[total_skipped]:-0}
|
|
},
|
|
"dry_run": "$DRY_RUN",
|
|
"parallel_enabled": "$ENABLE_PARALLEL"
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# Generate CSV report
|
|
generate_csv_report() {
|
|
cat <<EOF
|
|
Report Type,WordPress Cron Manager
|
|
Timestamp,$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
Total Sites,${REPORT_DATA[total_sites]:-0}
|
|
Converted,${REPORT_DATA[total_converted]:-0}
|
|
Failed,${REPORT_DATA[total_failed]:-0}
|
|
Skipped,${REPORT_DATA[total_skipped]:-0}
|
|
Dry Run,$DRY_RUN
|
|
Parallel Enabled,$ENABLE_PARALLEL
|
|
EOF
|
|
}
|
|
|
|
# Generate text report
|
|
generate_text_report() {
|
|
local end_time=$(date +%s)
|
|
local duration=$((end_time - ${REPORT_DATA[start_time]:-0}))
|
|
|
|
cat <<EOF
|
|
================================================================================
|
|
WordPress Cron Manager - Operation Report
|
|
================================================================================
|
|
Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
Duration: ${duration}s
|
|
Mode: $([ "$DRY_RUN" = "true" ] && echo "DRY-RUN" || echo "LIVE")
|
|
Parallel: $([ "$ENABLE_PARALLEL" = "true" ] && echo "ENABLED" || echo "DISABLED")
|
|
|
|
Summary:
|
|
Total Sites: ${REPORT_DATA[total_sites]:-0}
|
|
✓ Converted: ${REPORT_DATA[total_converted]:-0}
|
|
✗ Failed: ${REPORT_DATA[total_failed]:-0}
|
|
⊘ Skipped: ${REPORT_DATA[total_skipped]:-0}
|
|
|
|
Success Rate: $([ ${REPORT_DATA[total_sites]:-0} -gt 0 ] && echo "scale=1; ${REPORT_DATA[total_converted]:-0} * 100 / ${REPORT_DATA[total_sites]:-0}" | bc || echo "N/A")%
|
|
|
|
================================================================================
|
|
EOF
|
|
}
|
|
|
|
# Write report to file or stdout
|
|
report_save() {
|
|
local format="${1:-$REPORT_FORMAT}"
|
|
local output_file="${2:-$REPORT_FILE}"
|
|
|
|
local report_content
|
|
case "$format" in
|
|
json)
|
|
report_content=$(generate_json_report)
|
|
;;
|
|
csv)
|
|
report_content=$(generate_csv_report)
|
|
;;
|
|
*)
|
|
report_content=$(generate_text_report)
|
|
;;
|
|
esac
|
|
|
|
if [ -n "$output_file" ]; then
|
|
echo "$report_content" > "$output_file"
|
|
echo "Report saved to: $output_file"
|
|
else
|
|
echo "$report_content"
|
|
fi
|
|
}
|
|
|
|
# ADVANCED FEATURE: Automatic Rollback Support (OPT-19)
|
|
# Provides safety net for large-scale operations with ability to revert changes
|
|
# Creates checkpoint backups and enables reverting to known-good state
|
|
declare -g ROLLBACK_ENABLED=false
|
|
declare -g ROLLBACK_DIR="/tmp/wp-cron-rollback-$$"
|
|
declare -gA ROLLBACK_BACKUPS # Maps wp-config.php path to backup path
|
|
|
|
# Initialize rollback system
|
|
rollback_init() {
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
return 0 # Skip rollback in dry-run mode
|
|
fi
|
|
|
|
ROLLBACK_ENABLED=true
|
|
mkdir -p "$ROLLBACK_DIR" 2>/dev/null
|
|
|
|
if [ ! -d "$ROLLBACK_DIR" ]; then
|
|
print_warning "Could not create rollback directory: $ROLLBACK_DIR"
|
|
ROLLBACK_ENABLED=false
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Create checkpoint backup before modification
|
|
rollback_create_checkpoint() {
|
|
local wp_config="$1"
|
|
local backup_path="$ROLLBACK_DIR/$(basename "$wp_config").bak"
|
|
|
|
if [ ! "$ROLLBACK_ENABLED" = "true" ]; then
|
|
return 0
|
|
fi
|
|
|
|
if ! cp "$wp_config" "$backup_path" 2>/dev/null; then
|
|
print_error "Failed to create rollback checkpoint: $wp_config"
|
|
return 1
|
|
fi
|
|
|
|
# Store mapping for later rollback
|
|
ROLLBACK_BACKUPS["$wp_config"]="$backup_path"
|
|
return 0
|
|
}
|
|
|
|
# Restore file from rollback checkpoint
|
|
rollback_restore_file() {
|
|
local wp_config="$1"
|
|
local backup_path="${ROLLBACK_BACKUPS[$wp_config]}"
|
|
|
|
if [ ! -f "$backup_path" ]; then
|
|
print_error "No rollback checkpoint found for: $wp_config"
|
|
return 1
|
|
fi
|
|
|
|
if ! cp "$backup_path" "$wp_config" 2>/dev/null; then
|
|
print_error "Failed to restore from rollback checkpoint: $wp_config"
|
|
return 1
|
|
fi
|
|
|
|
print_success "Restored from checkpoint: $wp_config"
|
|
return 0
|
|
}
|
|
|
|
# Rollback all changes to checkpoint
|
|
rollback_all() {
|
|
if [ ! "$ROLLBACK_ENABLED" = "true" ]; then
|
|
print_error "Rollback not enabled"
|
|
return 1
|
|
fi
|
|
|
|
if [ ${#ROLLBACK_BACKUPS[@]} -eq 0 ]; then
|
|
print_info "No checkpoints to rollback"
|
|
return 0
|
|
fi
|
|
|
|
print_warning "Rolling back ${#ROLLBACK_BACKUPS[@]} modified files..."
|
|
|
|
local failed=0
|
|
for wp_config in "${!ROLLBACK_BACKUPS[@]}"; do
|
|
if ! rollback_restore_file "$wp_config"; then
|
|
failed=$((failed + 1))
|
|
fi
|
|
done
|
|
|
|
if [ $failed -eq 0 ]; then
|
|
print_success "All files restored successfully"
|
|
return 0
|
|
else
|
|
print_error "Failed to restore $failed files"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Clean up rollback directory
|
|
rollback_cleanup() {
|
|
if [ -d "$ROLLBACK_DIR" ]; then
|
|
rm -rf "$ROLLBACK_DIR" 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
# Rollback trap handler (called on EXIT/INT/TERM)
|
|
rollback_on_interrupt() {
|
|
if [ "$ROLLBACK_ENABLED" = "true" ]; then
|
|
echo ""
|
|
print_warning "Operation interrupted"
|
|
if confirm "Rollback all changes?"; then
|
|
rollback_all
|
|
fi
|
|
fi
|
|
rollback_cleanup
|
|
}
|
|
|
|
# OPTIMIZATION: Build cron command consistently
|
|
# Centralizes cron command format (appears 4 times throughout script)
|
|
# Returns: cron command string for wp-cron.php execution
|
|
build_cron_command() {
|
|
local site_path="$1"
|
|
|
|
# Standard format: cd to site, run wp-cron.php with PHP
|
|
# Redirects both stdout and stderr to /dev/null to keep cron logs clean
|
|
echo "cd \"$site_path\" && $PHP_BIN -q $WP_CRON_FILENAME >/dev/null 2>&1"
|
|
}
|
|
|
|
# Function to pre-check all WordPress installations before any modifications
|
|
# Returns count of valid installations
|
|
preflight_check() {
|
|
local panel="$1"
|
|
local found_count=0
|
|
local issues_count=0
|
|
|
|
echo ""
|
|
echo "Running pre-flight checks..."
|
|
echo ""
|
|
|
|
# PERFORMANCE: Use cached WordPress paths (already scanned at startup)
|
|
local wp_configs=""
|
|
wp_configs=$(get_wp_sites_cached)
|
|
|
|
if [ -z "$wp_configs" ]; then
|
|
echo -e "${YELLOW}No WordPress installations found${NC}"
|
|
return 0
|
|
fi
|
|
|
|
while IFS= read -r wp_config; do
|
|
found_count=$((found_count + 1))
|
|
site_path=$(dirname "$wp_config")
|
|
user=$(extract_user_from_path "$site_path")
|
|
|
|
# Verify user is valid
|
|
if ! user_is_valid "$user"; then
|
|
echo -e "${RED}✗${NC} Site $found_count: Invalid user '$user' ($site_path)"
|
|
issues_count=$((issues_count + 1))
|
|
continue
|
|
fi
|
|
|
|
# Verify user owns the WordPress install
|
|
if ! verify_user_ownership "$user" "$wp_config"; then
|
|
echo -e "${YELLOW}⚠${NC} Site $found_count: User mismatch - extracted='$user', owner=$(stat -c '%U' "$wp_config" 2>/dev/null) ($site_path)"
|
|
issues_count=$((issues_count + 1))
|
|
continue
|
|
fi
|
|
|
|
# Validate wp-config.php syntax
|
|
if ! validate_wp_config_syntax "$wp_config"; then
|
|
echo -e "${RED}✗${NC} Site $found_count: Invalid wp-config.php syntax ($site_path)"
|
|
issues_count=$((issues_count + 1))
|
|
continue
|
|
fi
|
|
|
|
echo -e "${GREEN}✓${NC} Site $found_count: $site_path (user: $user)"
|
|
done <<< "$wp_configs"
|
|
|
|
echo ""
|
|
echo "Pre-flight check complete:"
|
|
echo " Total found: $found_count"
|
|
echo " Issues: $issues_count"
|
|
echo " Ready: $((found_count - issues_count))"
|
|
|
|
return $((found_count - issues_count))
|
|
}
|
|
|
|
# Function to display detailed status of all WordPress installations
|
|
# Shows current state before any changes
|
|
show_installation_status() {
|
|
local panel="$1"
|
|
|
|
echo ""
|
|
echo "Current WordPress Installation Status:"
|
|
echo ""
|
|
|
|
local wp_configs=""
|
|
case "$panel" in
|
|
cpanel)
|
|
wp_configs=$(find /home/*/public_html -name "wp-config.php" -type f 2>/dev/null)
|
|
;;
|
|
interworx)
|
|
wp_configs=$(find /home/*/*/html -name "wp-config.php" -type f 2>/dev/null)
|
|
;;
|
|
plesk)
|
|
wp_configs=$(find /var/www/vhosts/*/httpdocs -name "wp-config.php" -type f 2>/dev/null)
|
|
;;
|
|
*)
|
|
wp_configs=$(find /var/www/html -name "wp-config.php" -type f 2>/dev/null)
|
|
;;
|
|
esac
|
|
|
|
if [ -z "$wp_configs" ]; then
|
|
echo "No WordPress installations found"
|
|
return 1
|
|
fi
|
|
|
|
local count=0
|
|
while IFS= read -r wp_config; do
|
|
count=$((count + 1))
|
|
site_path=$(dirname "$wp_config")
|
|
user=$(extract_user_from_path "$site_path")
|
|
|
|
# Check wp-cron status
|
|
if disable_wp_cron_exists "$wp_config"; then
|
|
cron_status="${GREEN}Disabled${NC} (system cron)"
|
|
else
|
|
cron_status="${YELLOW}Enabled${NC} (default)"
|
|
fi
|
|
|
|
# Check if cron job exists
|
|
if cron_job_exists "$user" "$site_path"; then
|
|
cron_job="${GREEN}Yes${NC}"
|
|
else
|
|
cron_job="${YELLOW}No${NC}"
|
|
fi
|
|
|
|
echo "$count. ${BOLD}$site_path${NC}"
|
|
echo " User: $user"
|
|
echo " WP-Cron: $cron_status"
|
|
echo " System Cron Job: $cron_job"
|
|
echo ""
|
|
done <<< "$wp_configs"
|
|
|
|
echo "Total installations: $count"
|
|
}
|
|
|
|
# Function to create timestamped backup of wp-config.php
|
|
# Returns 0 on success, 1 on failure
|
|
# Also returns the backup filename
|
|
create_timestamped_backup() {
|
|
local wp_config="$1"
|
|
local backup_timestamp=$(date +%Y%m%d-%H%M%S)
|
|
local backup_file="${wp_config}.backup-${backup_timestamp}"
|
|
|
|
# Verify source file exists
|
|
if [ ! -f "$wp_config" ]; then
|
|
return 1
|
|
fi
|
|
|
|
# CRITICAL FIX: Check disk space before creating backup
|
|
local available_kb=$(df "$(dirname "$wp_config")" 2>/dev/null | awk 'NR==2 {print $4}')
|
|
if [ -n "$available_kb" ] && [ "$available_kb" -lt "$MIN_DISK_SPACE" ]; then
|
|
# Less than 10MB available
|
|
print_error "Insufficient disk space (less than 10MB available) - cannot create backup"
|
|
return 1
|
|
fi
|
|
|
|
# Create backup
|
|
if cp "$wp_config" "$backup_file" 2>/dev/null; then
|
|
# CRITICAL SECURITY FIX: Set backup file permissions to 0600 (owner read/write only)
|
|
# wp-config.php contains sensitive database credentials and should not be readable by other users
|
|
chmod "$CHMOD_SECURE_FILE" "$backup_file" 2>/dev/null || true
|
|
echo "$backup_file"
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to generate staggered cron time
|
|
# Distributes jobs across 60 minutes to avoid load spikes
|
|
generate_staggered_cron() {
|
|
local minute=$((CRON_OFFSET % 60))
|
|
|
|
# Create pattern: every hour at staggered minutes
|
|
# Site 1: minute 0, Site 2: minute 1, etc.
|
|
# Increment offset for next site (wraps at 60)
|
|
CRON_OFFSET=$((CRON_OFFSET + 1))
|
|
|
|
echo "$minute * * * *"
|
|
}
|
|
|
|
# Function to extract user from WordPress site path
|
|
# Multi-panel aware
|
|
extract_user_from_path() {
|
|
local site_path="$1"
|
|
local user=""
|
|
|
|
case "$SYS_CONTROL_PANEL" in
|
|
cpanel)
|
|
# Extract user from /home/username/public_html pattern
|
|
user=$(echo "$site_path" | awk -F'/' '{print $3}')
|
|
;;
|
|
interworx)
|
|
# Extract user from /home/username/domain/html pattern
|
|
user=$(echo "$site_path" | awk -F'/' '{print $3}')
|
|
;;
|
|
plesk)
|
|
# Extract domain from path and lookup user
|
|
local domain=$(echo "$site_path" | grep -oE '/vhosts/[^/]+' | sed 's|/vhosts/||')
|
|
user=$(plesk bin subscription --info "$domain" 2>/dev/null | grep "Owner" | awk '{print $2}')
|
|
;;
|
|
*)
|
|
user="www-data"
|
|
;;
|
|
esac
|
|
|
|
# CRITICAL FIX: Validate user is not empty - prevents adding cron to wrong user
|
|
if [ -z "$user" ]; then
|
|
user="www-data" # Fallback only if extraction completely failed
|
|
fi
|
|
|
|
echo "$user"
|
|
}
|
|
|
|
# OPTIMIZATION: Helper function to remove DISABLE_WP_CRON from wp-config
|
|
# Encapsulates sed pattern for consistency
|
|
# Returns 0 on success, 1 on failure
|
|
remove_disable_wpcron_from_config() {
|
|
local wp_config="$1"
|
|
|
|
# Remove any existing DISABLE_WP_CRON lines using extended regex
|
|
# Pattern matches: define('DISABLE_WP_CRON', true);
|
|
# With flexibility for spacing and quotes
|
|
sed -i.wpbak -E '#define[[:space:]]*\([[:space:]]*['\''"]'"$WP_CRON_DISABLED_VAR"'['\''"][[:space:]]*,[[:space:]]*true[[:space:]]*\)#d' "$wp_config"
|
|
return $?
|
|
}
|
|
|
|
# OPTIMIZATION: Helper function to add DISABLE_WP_CRON to wp-config
|
|
# Encapsulates sed pattern for consistency
|
|
# Returns 0 on success, 1 on failure
|
|
add_disable_wpcron_to_config() {
|
|
local wp_config="$1"
|
|
|
|
# Try to insert before WordPress stop editing comment (proper convention)
|
|
if grep -q "$WP_CONFIG_MARKER" "$wp_config" 2>/dev/null; then
|
|
sed -i '#'"$WP_CONFIG_MARKER"'#i\
|
|
define('"'"''"$WP_CRON_DISABLED_VAR"''"'"', true);' "$wp_config"
|
|
return 0
|
|
elif grep -q "$WP_EDIT_START" "$wp_config"; then
|
|
# Fallback: if no stop editing found, add after opening PHP tag
|
|
sed -i '#'"$WP_EDIT_START"'#a\
|
|
define('"'"''"$WP_CRON_DISABLED_VAR"''"'"', true);' "$wp_config"
|
|
return 0
|
|
else
|
|
# File format is unexpected
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to safely modify wp-config.php to disable wp-cron
|
|
# Returns 0 on success, 1 on failure
|
|
disable_wpcron_in_config() {
|
|
local wp_config="$1"
|
|
|
|
# Check if file exists and is writable
|
|
if [ ! -f "$wp_config" ] || [ ! -w "$wp_config" ]; then
|
|
return 1
|
|
fi
|
|
|
|
# First, remove any existing DISABLE_WP_CRON lines (anywhere in file)
|
|
# This ensures clean placement even if previously added in wrong location
|
|
if grep -q "$WP_CRON_DISABLED_VAR" "$wp_config" 2>/dev/null; then
|
|
remove_disable_wpcron_from_config "$wp_config" || true
|
|
else
|
|
# Create backup even if no existing line
|
|
cp "$wp_config" "${wp_config}.wpbak"
|
|
fi
|
|
|
|
# Now add it in the proper location - before "stop editing" comment
|
|
if ! add_disable_wpcron_to_config "$wp_config"; then
|
|
# Restore backup if file format is unexpected
|
|
if [ -f "${wp_config}.wpbak" ]; then
|
|
mv "${wp_config}.wpbak" "$wp_config"
|
|
fi
|
|
return 1
|
|
fi
|
|
|
|
# Verify the change was successful
|
|
if grep -E "^[^/]*define\s*\(\s*['\"]$WP_CRON_DISABLED_VAR['\"]\s*,\s*true\s*\)" "$wp_config" >/dev/null 2>&1; then
|
|
# Remove backup if successful
|
|
rm -f "${wp_config}.wpbak"
|
|
return 0
|
|
else
|
|
# Restore backup if verification failed
|
|
if [ -f "${wp_config}.wpbak" ]; then
|
|
mv "${wp_config}.wpbak" "$wp_config"
|
|
fi
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to safely re-enable wp-cron (revert changes)
|
|
# Returns 0 on success, 1 on failure
|
|
enable_wpcron_in_config() {
|
|
local wp_config="$1"
|
|
|
|
# Check if file exists and is writable
|
|
if [ ! -f "$wp_config" ] || [ ! -w "$wp_config" ]; then
|
|
return 1
|
|
fi
|
|
|
|
# Check if DISABLE_WP_CRON exists and is set to true
|
|
if grep -E "^[^/]*define[[:space:]]*\([[:space:]]*['\"]$WP_CRON_DISABLED_VAR['\"][[:space:]]*,[[:space:]]*true[[:space:]]*\)" "$wp_config" >/dev/null 2>&1; then
|
|
# Remove the line using helper function
|
|
remove_disable_wpcron_from_config "$wp_config" || true
|
|
|
|
# Verify removal was successful
|
|
if ! grep -E "^[^/]*define\s*\(\s*['\"]$WP_CRON_DISABLED_VAR['\"]\s*,\s*true\s*\)" "$wp_config" >/dev/null 2>&1; then
|
|
rm -f "${wp_config}.wpbak"
|
|
return 0
|
|
else
|
|
# Restore backup if removal failed
|
|
if [ -f "${wp_config}.wpbak" ]; then
|
|
mv "${wp_config}.wpbak" "$wp_config"
|
|
fi
|
|
return 1
|
|
fi
|
|
else
|
|
# DISABLE_WP_CRON not found or already disabled
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
clear
|
|
print_banner "WordPress Cron Manager"
|
|
|
|
# PERFORMANCE: Pre-load WordPress sites cache on startup (done once per menu cycle)
|
|
# This eliminates the long initial scan and makes all operations fast
|
|
if [ "$WP_CACHE_INITIALIZED" = "0" ]; then
|
|
echo -e "${CYAN}Scanning for WordPress installations...${NC}"
|
|
initialize_wp_cache
|
|
echo -e "${GREEN}✓ Cache loaded${NC}"
|
|
echo ""
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${BOLD}What would you like to do?${NC}"
|
|
echo ""
|
|
echo -e "${GREEN}Enable System Cron:${NC}"
|
|
echo -e " ${CYAN}1)${NC} Scan for WordPress installations"
|
|
echo -e " ${CYAN}2)${NC} Disable wp-cron for specific domain"
|
|
echo -e " ${CYAN}3)${NC} Disable wp-cron for specific user (all their WP sites)"
|
|
echo -e " ${CYAN}4)${NC} Disable wp-cron server-wide (all WordPress sites)"
|
|
echo ""
|
|
echo -e "${YELLOW}Revert to WP-Cron:${NC}"
|
|
echo -e " ${CYAN}6)${NC} Re-enable wp-cron for specific domain"
|
|
echo -e " ${CYAN}7)${NC} Re-enable wp-cron for specific user (all their WP sites)"
|
|
echo -e " ${CYAN}8)${NC} Re-enable wp-cron server-wide (all WordPress sites)"
|
|
echo ""
|
|
echo -e "${CYAN}Status & Information:${NC}"
|
|
echo -e " ${CYAN}5)${NC} Check wp-cron status for domain/user"
|
|
echo -e " ${CYAN}9)${NC} Run pre-flight checks (validate all installations)"
|
|
echo -e " ${CYAN}10)${NC} Show detailed status of all WordPress sites"
|
|
echo ""
|
|
echo -e " ${RED}0)${NC} Return to menu"
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo -n "Select option [0]: "
|
|
|
|
# Validate choice input
|
|
while true; do
|
|
read -r choice
|
|
choice="${choice:-0}"
|
|
|
|
if ! [[ "$choice" =~ ^([0-9]|10)$ ]]; then
|
|
echo ""
|
|
print_error "Invalid choice. Please enter 0-10"
|
|
echo ""
|
|
continue
|
|
fi
|
|
break
|
|
done
|
|
|
|
case "$choice" in
|
|
1)
|
|
# Scan for WordPress installations
|
|
echo ""
|
|
print_banner "WordPress Installation Scanner"
|
|
echo ""
|
|
|
|
echo "Retrieving WordPress installations from cache..."
|
|
echo ""
|
|
|
|
# PERFORMANCE: Use cached WordPress paths (pre-scanned at startup)
|
|
wp_sites=$(get_wp_sites_cached)
|
|
|
|
if [ -z "$wp_sites" ]; then
|
|
echo -e "${YELLOW}No WordPress installations found${NC}"
|
|
else
|
|
count=0
|
|
echo -e "${BOLD}Found WordPress Installations:${NC}"
|
|
echo ""
|
|
|
|
while IFS= read -r config_file; do
|
|
count=$((count + 1))
|
|
|
|
# Extract info - Multi-panel support
|
|
site_path=$(dirname "$config_file")
|
|
|
|
# Extract user and domain based on control panel
|
|
user="$(extract_user_from_path "$site_path")"
|
|
domain=""
|
|
case "$SYS_CONTROL_PANEL" in
|
|
cpanel)
|
|
userdata_dir="${SYS_CPANEL_USERDATA_DIR:-/var/cpanel/userdata}"
|
|
if [ -f "$userdata_dir/$user/main" ]; then
|
|
domain=$(grep -m1 "^servername:" "$userdata_dir/$user/main" 2>/dev/null | awk '{print $2}')
|
|
fi
|
|
# CRITICAL FIX: Fallback if domain extraction failed
|
|
if [ -z "$domain" ]; then
|
|
domain="$user.local" # Use user@hostname fallback
|
|
fi
|
|
;;
|
|
interworx)
|
|
domain=$(echo "$site_path" | cut -d'/' -f4)
|
|
# CRITICAL FIX: Fallback if domain extraction failed
|
|
if [ -z "$domain" ]; then
|
|
domain="$user.local" # Use user@hostname fallback
|
|
fi
|
|
;;
|
|
plesk)
|
|
domain=$(echo "$site_path" | grep -oE '/vhosts/[^/]+' | sed 's|/vhosts/||')
|
|
user=$(plesk bin subscription --info "$domain" 2>/dev/null | grep "Owner" | awk '{print $2}')
|
|
[ -z "$user" ] && user="plesk-user"
|
|
;;
|
|
*)
|
|
user="standalone"
|
|
domain="localhost"
|
|
;;
|
|
esac
|
|
|
|
# Final validation: ensure neither is empty (prevents display issues)
|
|
[ -z "$user" ] && user="unknown"
|
|
[ -z "$domain" ] && domain="unknown-domain"
|
|
|
|
# Check if wp-cron is disabled
|
|
if grep -q "define.*DISABLE_WP_CRON.*true" "$config_file" 2>/dev/null; then
|
|
status="${GREEN}✓ Disabled (using system cron)${NC}"
|
|
else
|
|
status="${YELLOW}⚠ Enabled (default wp-cron)${NC}"
|
|
fi
|
|
|
|
echo -e "${count}. ${BOLD}$domain${NC}"
|
|
echo " Path: $site_path"
|
|
echo " User: $user"
|
|
echo " Status: $status"
|
|
echo ""
|
|
done <<< "$wp_sites"
|
|
|
|
echo -e "${CYAN}Total WordPress installations: $count${NC}"
|
|
fi
|
|
;;
|
|
|
|
2)
|
|
# Disable wp-cron for specific domain
|
|
echo ""
|
|
echo -n "Enter domain name (or 0 to cancel): "
|
|
read -r domain
|
|
|
|
if [ -z "$domain" ] || [ "$domain" = "0" ]; then
|
|
echo "Operation cancelled."
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
|
|
# INPUT SANITIZATION: Validate domain format
|
|
if ! is_valid_domain_format "$domain"; then
|
|
print_error "Invalid domain format. Use only letters, numbers, hyphens, and dots."
|
|
echo "Example: example.com or sub.example.com"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
# Find WordPress installation for this domain - Multi-panel support
|
|
echo ""
|
|
echo "Searching for WordPress installation for $domain..."
|
|
|
|
wp_config=""
|
|
|
|
case "$SYS_CONTROL_PANEL" in
|
|
cpanel)
|
|
# Method 1: Check main_domain in /var/cpanel/userdata/*/main files
|
|
userdata_base="${SYS_CPANEL_USERDATA_DIR:-/var/cpanel/userdata}"
|
|
for userdata_file in "$userdata_base"/*/main; do
|
|
if grep -q "^main_domain: $domain" "$userdata_file" 2>/dev/null; then
|
|
user=$(basename "$(dirname "$userdata_file")")
|
|
potential_config="/home/$user/public_html/wp-config.php"
|
|
if [ -f "$potential_config" ]; then
|
|
wp_config="$potential_config"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Method 2: If not found, search all domain-specific files for servername
|
|
if [ -z "$wp_config" ]; then
|
|
for userdata_file in "$userdata_base"/*/*; do
|
|
# Skip cache files and main files
|
|
[[ "$userdata_file" == *.cache ]] && continue
|
|
[[ "$userdata_file" == */main ]] && continue
|
|
[[ "$userdata_file" == */cache ]] && continue
|
|
[[ "$userdata_file" == */cache.json ]] && continue
|
|
|
|
if grep -q "^servername: $domain" "$userdata_file" 2>/dev/null; then
|
|
user=$(basename "$(dirname "$userdata_file")")
|
|
potential_config="/home/$user/public_html/wp-config.php"
|
|
if [ -f "$potential_config" ]; then
|
|
wp_config="$potential_config"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
;;
|
|
|
|
interworx)
|
|
# Find user from vhost config
|
|
user=$(grep -l "ServerName ${domain}" /etc/httpd/conf.d/vhost_*.conf 2>/dev/null | head -1 | \
|
|
xargs grep "SuexecUserGroup" 2>/dev/null | awk '{print $2}')
|
|
if [ -n "$user" ]; then
|
|
potential_config="/home/${user}/${domain}/html/wp-config.php"
|
|
[ -f "$potential_config" ] && wp_config="$potential_config"
|
|
fi
|
|
;;
|
|
|
|
plesk)
|
|
# Try standard Plesk path
|
|
potential_config="/var/www/vhosts/${domain}/httpdocs/wp-config.php"
|
|
[ -f "$potential_config" ] && wp_config="$potential_config"
|
|
;;
|
|
|
|
*)
|
|
# Standalone - try standard path
|
|
potential_config="/var/www/html/wp-config.php"
|
|
[ -f "$potential_config" ] && wp_config="$potential_config"
|
|
;;
|
|
esac
|
|
|
|
if [ -z "$wp_config" ]; then
|
|
print_error "WordPress installation not found for $domain"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}Found WordPress:${NC} $wp_config"
|
|
echo ""
|
|
|
|
# Extract site path and user
|
|
site_path=$(dirname "$wp_config")
|
|
user=$(extract_user_from_path "$site_path")
|
|
|
|
# PRE-FLIGHT VALIDATION CHECKS
|
|
echo "Running pre-flight validation checks..."
|
|
echo ""
|
|
|
|
# Check 1: Validate user
|
|
if ! user_is_valid "$user"; then
|
|
print_error "User '$user' is not valid or not a cPanel user"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
echo -e "${GREEN}✓${NC} User valid: $user"
|
|
|
|
# Check 2: Verify user ownership
|
|
if ! verify_user_ownership "$user" "$wp_config"; then
|
|
actual_owner=$(stat -c '%U' "$wp_config" 2>/dev/null || ls -l "$wp_config" | awk '{print $3}')
|
|
echo -e "${YELLOW}⚠${NC} User mismatch detected:"
|
|
echo " Extracted user: $user"
|
|
echo " Actual owner: $actual_owner"
|
|
echo ""
|
|
echo -n "Continue anyway? (y/n) [n]: "
|
|
read -r confirm
|
|
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
else
|
|
echo -e "${GREEN}✓${NC} User ownership verified"
|
|
fi
|
|
|
|
# Check 3: Validate wp-config.php syntax BEFORE any changes
|
|
if ! validate_wp_config_syntax "$wp_config"; then
|
|
print_error "wp-config.php has syntax errors - cannot modify"
|
|
echo " Please fix syntax issues first"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
echo -e "${GREEN}✓${NC} wp-config.php syntax is valid"
|
|
|
|
# Check 4: Check for existing DISABLE_WP_CRON
|
|
if disable_wp_cron_exists "$wp_config"; then
|
|
echo -e "${YELLOW}wp-cron is already disabled for this site${NC}"
|
|
echo ""
|
|
echo -n "Re-configure anyway? (y/n) [n]: "
|
|
read -r confirm
|
|
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
else
|
|
echo -e "${GREEN}✓${NC} wp-cron currently enabled (will be disabled)"
|
|
fi
|
|
|
|
# Check 5: Check for existing cron job
|
|
if cron_job_exists "$user" "$site_path"; then
|
|
echo -e "${YELLOW}⚠${NC} System cron job already exists for this site"
|
|
echo ""
|
|
echo -n "Update existing cron job? (y/n) [n]: "
|
|
read -r confirm
|
|
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
else
|
|
echo -e "${GREEN}✓${NC} No existing system cron job"
|
|
fi
|
|
|
|
echo ""
|
|
echo "All validation checks passed. Ready to make changes..."
|
|
echo ""
|
|
|
|
# CREATE BACKUP WITH TIMESTAMP
|
|
echo -e "${BOLD}BACKUP CREATION:${NC}"
|
|
backup_file=$(create_timestamped_backup "$wp_config")
|
|
if [ $? -ne 0 ] || [ -z "$backup_file" ]; then
|
|
print_error "Failed to create backup of wp-config.php"
|
|
echo " Cannot proceed without backup"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}✓${NC} Backup created successfully"
|
|
echo -e "${CYAN}Location:${NC} $backup_file"
|
|
echo -e "${CYAN}Timestamp:${NC} $(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo ""
|
|
|
|
# User confirmation to proceed with modification
|
|
echo -e "${YELLOW}IMPORTANT:${NC} This will modify your wp-config.php file"
|
|
echo ""
|
|
echo -n "Proceed with modification? (y/n) [y]: "
|
|
read -r confirm
|
|
if [ "$confirm" = "n" ] || [ "$confirm" = "N" ]; then
|
|
echo "Operation cancelled. Backup preserved at: $backup_file"
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
|
|
echo ""
|
|
echo "Modifying wp-config.php..."
|
|
echo ""
|
|
|
|
# Safely disable wp-cron in wp-config.php
|
|
if disable_wpcron_in_config "$wp_config"; then
|
|
echo -e "${GREEN}✓${NC} Set DISABLE_WP_CRON to true in wp-config.php"
|
|
|
|
# CRITICAL: Verify syntax after modification
|
|
if ! validate_wp_config_syntax "$wp_config"; then
|
|
print_error "CRITICAL: wp-config.php syntax became invalid after modification!"
|
|
echo " Restoring backup..."
|
|
if cp "$backup_file" "$wp_config"; then
|
|
echo -e "${GREEN}✓${NC} Restored from backup: $backup_file"
|
|
echo ""
|
|
echo "Your original wp-config.php has been restored."
|
|
echo "Backup (with attempted modification) kept at: ${backup_file}.failed"
|
|
cp "$backup_file" "${backup_file}.failed"
|
|
else
|
|
print_error "CRITICAL: Could not restore from backup!"
|
|
echo "Original backup location: $backup_file"
|
|
fi
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
echo -e "${GREEN}✓${NC} wp-config.php syntax verified after modification"
|
|
else
|
|
print_error "Failed to modify wp-config.php"
|
|
echo " Please check file permissions and syntax"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
# Add cron job with staggered timing
|
|
if [ -z "$site_path" ]; then
|
|
echo -e "${RED}✗${NC} Could not determine site path"
|
|
exit 1
|
|
fi
|
|
cron_cmd=$(build_cron_command "$site_path")
|
|
|
|
# Check if cron job already exists (for duplicate prevention)
|
|
if cron_job_exists "$user" "$site_path"; then
|
|
# Remove old one first to avoid duplicates
|
|
safe_remove_cron_jobs "$user" "$site_path.*wp-cron.php"
|
|
echo -e "${YELLOW}⚠${NC} Removed existing cron job (updating)"
|
|
fi
|
|
|
|
# Generate staggered cron time and add to crontab
|
|
cron_time=$(generate_staggered_cron)
|
|
if safe_add_cron_job "$user" "$cron_time" "$cron_cmd"; then
|
|
echo -e "${GREEN}✓${NC} Added cron job ($cron_time)"
|
|
else
|
|
echo -e "${YELLOW}⚠${NC} Failed to add cron job"
|
|
fi
|
|
|
|
echo ""
|
|
print_success "WordPress cron converted to system cron for $domain"
|
|
echo ""
|
|
echo "Changes made:"
|
|
echo " • DISABLE_WP_CRON set to true in wp-config.php"
|
|
echo " • System cron job added (runs once per hour)"
|
|
echo " • Backup saved: ${wp_config}.backup-*"
|
|
;;
|
|
|
|
3)
|
|
# Disable wp-cron for specific user
|
|
echo ""
|
|
echo -n "Enter cPanel username (or 0 to cancel): "
|
|
read -r target_user
|
|
|
|
if [ -z "$target_user" ] || [ "$target_user" = "0" ]; then
|
|
echo "Operation cancelled."
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
|
|
# INPUT SANITIZATION: Validate username format
|
|
if ! is_valid_username_format "$target_user"; then
|
|
print_error "Invalid username format. Use only lowercase letters, numbers, hyphens, and underscores."
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -d "/home/$target_user" ]; then
|
|
print_error "User $target_user does not exist"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "Searching for WordPress installations for user: $target_user"
|
|
echo ""
|
|
|
|
wp_configs=$(find "/home/$target_user" -name "wp-config.php" -type f 2>/dev/null)
|
|
|
|
if [ -z "$wp_configs" ]; then
|
|
print_error "No WordPress installations found for $target_user"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
count=0
|
|
converted=0
|
|
failed=0
|
|
while IFS= read -r wp_config; do
|
|
count=$((count + 1))
|
|
site_path=$(dirname "$wp_config")
|
|
|
|
# Validate site path
|
|
if [ -z "$site_path" ] || [ ! -d "$site_path" ]; then
|
|
echo -e "${YELLOW}Warning: Invalid site path${NC}"
|
|
failed=$((failed + 1))
|
|
continue
|
|
fi
|
|
|
|
# Extract user from site path (per-site, not using $target_user assumption)
|
|
user=$(extract_user_from_path "$site_path")
|
|
if [ -z "$user" ]; then
|
|
echo -e "${YELLOW}Warning: Could not extract username from $site_path${NC}"
|
|
failed=$((failed + 1))
|
|
continue
|
|
fi
|
|
|
|
echo -e "${BOLD}Site $count:${NC} $site_path"
|
|
|
|
# Create timestamped backup
|
|
backup_file=$(create_timestamped_backup "$wp_config")
|
|
if [ -z "$backup_file" ]; then
|
|
echo " • ${YELLOW}Warning: Backup failed, skipping site${NC}"
|
|
failed=$((failed + 1))
|
|
echo ""
|
|
continue
|
|
fi
|
|
echo " • Backed up wp-config.php"
|
|
|
|
# Safely disable wp-cron
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo " [DRY-RUN] Would modify wp-config.php"
|
|
else
|
|
if ! disable_wpcron_in_config "$wp_config"; then
|
|
echo " • ${RED}Error: Could not modify wp-config.php${NC}"
|
|
[ -f "$backup_file" ] && cp "$backup_file" "$wp_config" 2>/dev/null
|
|
failed=$((failed + 1))
|
|
echo ""
|
|
continue
|
|
fi
|
|
fi
|
|
echo " • Set DISABLE_WP_CRON to true"
|
|
|
|
# Validate syntax after modification
|
|
if [ "$DRY_RUN" != "true" ] && ! validate_wp_config_syntax "$wp_config"; then
|
|
echo " • ${RED}Error: wp-config.php syntax invalid after modification${NC}"
|
|
cp "$backup_file" "$wp_config" 2>/dev/null
|
|
[ -f "$backup_file" ] && cp "$backup_file" "${backup_file}.failed" 2>/dev/null
|
|
failed=$((failed + 1))
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
# Add cron job with staggered timing
|
|
cron_cmd=$(build_cron_command "$site_path")
|
|
|
|
# Check if PHP binary is available
|
|
if [ ! -x "$PHP_BIN" ]; then
|
|
echo " • ${RED}Error: PHP binary not found at $PHP_BIN${NC}"
|
|
failed=$((failed + 1))
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
if ! cron_job_exists "$user" "$site_path"; then
|
|
cron_time=$(generate_staggered_cron)
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo " [DRY-RUN] Would add cron job ($cron_time)"
|
|
else
|
|
if safe_add_cron_job "$user" "$cron_time" "$cron_cmd"; then
|
|
echo " • Added cron job ($cron_time)"
|
|
converted=$((converted + 1))
|
|
else
|
|
echo " • ${RED}Error: Failed to add cron job${NC}"
|
|
failed=$((failed + 1))
|
|
fi
|
|
fi
|
|
else
|
|
echo " • Cron job already exists"
|
|
fi
|
|
|
|
echo ""
|
|
done <<< "$wp_configs"
|
|
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo -e "${CYAN}[DRY-RUN] Would have converted $count site(s)${NC}"
|
|
else
|
|
print_success "$converted WordPress sites for $target_user converted to system cron"
|
|
if [ $failed -gt 0 ]; then
|
|
print_warning "$failed site(s) failed or were skipped"
|
|
fi
|
|
fi
|
|
;;
|
|
|
|
4)
|
|
# Server-wide conversion
|
|
echo ""
|
|
echo -e "${RED}${BOLD}WARNING: Server-Wide wp-cron Conversion${NC}"
|
|
echo ""
|
|
echo "This will:"
|
|
echo " • Find ALL WordPress installations on the server"
|
|
echo " • Disable wp-cron in each wp-config.php"
|
|
echo " • Add system cron jobs for each user"
|
|
echo ""
|
|
echo -n "Are you sure? Type 'yes' to confirm: "
|
|
read -r confirm
|
|
|
|
if [ "$confirm" != "yes" ]; then
|
|
echo "Cancelled"
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
|
|
echo ""
|
|
echo "Processing WordPress installations from cache..."
|
|
echo ""
|
|
|
|
total=0
|
|
converted=0
|
|
failed=0
|
|
|
|
# PERFORMANCE: Use cached paths (scanned once at startup, ~10-50x faster)
|
|
wp_configs=$(get_wp_sites_cached)
|
|
|
|
if [ -z "$wp_configs" ]; then
|
|
echo -e "${YELLOW}No WordPress installations found${NC}"
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
|
|
while IFS= read -r wp_config; do
|
|
total=$((total + 1))
|
|
site_path=$(dirname "$wp_config")
|
|
if [ -z "$site_path" ]; then
|
|
echo -e "${RED}✗ Could not determine site path${NC}"
|
|
failed=$((failed + 1))
|
|
continue
|
|
fi
|
|
user=$(extract_user_from_path "$site_path")
|
|
|
|
echo -e "${BOLD}Processing:${NC} $site_path (user: $user)"
|
|
|
|
# Create timestamped backup
|
|
backup_file=$(create_timestamped_backup "$wp_config")
|
|
if [ -z "$backup_file" ]; then
|
|
echo " ${RED}✗ Backup failed, skipping${NC}"
|
|
failed=$((failed + 1))
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
# Safely disable wp-cron
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo " [DRY-RUN] Would modify wp-config.php"
|
|
else
|
|
if ! disable_wpcron_in_config "$wp_config"; then
|
|
echo -e "${RED}✗ Failed to modify wp-config.php${NC}"
|
|
[ -f "$backup_file" ] && cp "$backup_file" "$wp_config" 2>/dev/null
|
|
failed=$((failed + 1))
|
|
echo ""
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
# Validate syntax after modification
|
|
if [ "$DRY_RUN" != "true" ] && ! validate_wp_config_syntax "$wp_config"; then
|
|
echo " ${RED}✗ wp-config.php syntax invalid after modification${NC}"
|
|
cp "$backup_file" "$wp_config" 2>/dev/null
|
|
[ -f "$backup_file" ] && cp "$backup_file" "${backup_file}.failed" 2>/dev/null
|
|
failed=$((failed + 1))
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
# Check PHP binary
|
|
if [ ! -x "$PHP_BIN" ]; then
|
|
echo " ${RED}✗ PHP binary not found at $PHP_BIN${NC}"
|
|
failed=$((failed + 1))
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
# Add cron job with staggered timing
|
|
cron_cmd=$(build_cron_command "$site_path")
|
|
|
|
if ! cron_job_exists "$user" "$site_path"; then
|
|
cron_time=$(generate_staggered_cron)
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo " [DRY-RUN] Would add cron job ($cron_time)"
|
|
else
|
|
if safe_add_cron_job "$user" "$cron_time" "$cron_cmd"; then
|
|
echo " Cron: $cron_time"
|
|
converted=$((converted + 1))
|
|
else
|
|
echo " ${RED}✗ Failed to add cron job${NC}"
|
|
failed=$((failed + 1))
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [ "$DRY_RUN" != "true" ]; then
|
|
echo -e "${GREEN}✓${NC} Converted"
|
|
fi
|
|
echo ""
|
|
done <<< "$wp_configs"
|
|
|
|
echo ""
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo -e "${CYAN}[DRY-RUN] Would convert up to $total site(s)${NC}"
|
|
else
|
|
print_success "Server-wide conversion complete"
|
|
echo ""
|
|
echo "Summary:"
|
|
echo " • Total WordPress sites found: $total"
|
|
echo " • Successfully converted: $converted"
|
|
if [ $failed -gt 0 ]; then
|
|
echo -e " • ${RED}Failed or skipped: $failed${NC}"
|
|
fi
|
|
fi
|
|
;;
|
|
|
|
5)
|
|
# Check status
|
|
echo ""
|
|
echo "Check wp-cron status for:"
|
|
echo -e " ${CYAN}1)${NC} Specific domain"
|
|
echo -e " ${CYAN}2)${NC} Specific user"
|
|
echo -e " ${RED}0)${NC} Cancel"
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
# Validate check_choice input
|
|
while true; do
|
|
echo -n "Select [1]: "
|
|
read -r check_choice
|
|
check_choice="${check_choice:-1}"
|
|
|
|
if ! [[ "$check_choice" =~ ^[0-2]$ ]]; then
|
|
echo ""
|
|
print_error "Invalid choice. Please enter 0, 1, or 2"
|
|
echo ""
|
|
continue
|
|
fi
|
|
break
|
|
done
|
|
|
|
if [ "$check_choice" = "0" ]; then
|
|
echo "Operation cancelled."
|
|
press_enter
|
|
exit 0
|
|
elif [ "$check_choice" = "1" ]; then
|
|
echo ""
|
|
echo -n "Enter domain name (or 0 to cancel): "
|
|
read -r domain
|
|
|
|
if [ -z "$domain" ] || [ "$domain" = "0" ]; then
|
|
echo "Operation cancelled."
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
|
|
# INPUT SANITIZATION: Validate domain format
|
|
if ! is_valid_domain_format "$domain"; then
|
|
print_error "Invalid domain format. Use only letters, numbers, hyphens, and dots."
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
# Find WordPress for domain
|
|
wp_config=""
|
|
|
|
# Method 1: Check main_domain in main files
|
|
userdata_base="${SYS_CPANEL_USERDATA_DIR:-/var/cpanel/userdata}"
|
|
for userdata_file in "$userdata_base"/*/main; do
|
|
if grep -q "^main_domain: $domain" "$userdata_file" 2>/dev/null; then
|
|
user=$(basename "$(dirname "$userdata_file")")
|
|
potential_config="/home/$user/public_html/wp-config.php"
|
|
if [ -f "$potential_config" ]; then
|
|
wp_config="$potential_config"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Method 2: Search domain-specific files for servername
|
|
if [ -z "$wp_config" ]; then
|
|
for userdata_file in "$userdata_base"/*/*; do
|
|
[[ "$userdata_file" == *.cache ]] && continue
|
|
[[ "$userdata_file" == */main ]] && continue
|
|
[[ "$userdata_file" == */cache ]] && continue
|
|
[[ "$userdata_file" == */cache.json ]] && continue
|
|
|
|
if grep -q "^servername: $domain" "$userdata_file" 2>/dev/null; then
|
|
user=$(basename "$(dirname "$userdata_file")")
|
|
potential_config="/home/$user/public_html/wp-config.php"
|
|
if [ -f "$potential_config" ]; then
|
|
wp_config="$potential_config"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [ -z "$wp_config" ]; then
|
|
print_error "WordPress not found for $domain"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${BOLD}WordPress Cron Status for $domain${NC}"
|
|
echo ""
|
|
echo "Config file: $wp_config"
|
|
echo ""
|
|
|
|
if grep -q "define.*DISABLE_WP_CRON.*true" "$wp_config" 2>/dev/null; then
|
|
echo -e "wp-cron: ${GREEN}DISABLED${NC} (using system cron)"
|
|
|
|
# Check for cron job
|
|
site_path=$(dirname "$wp_config")
|
|
user=$(extract_user_from_path "$site_path")
|
|
|
|
if crontab -u "$user" -l 2>/dev/null | grep -q "$WP_CRON_FILENAME"; then
|
|
echo -e "System cron: ${GREEN}CONFIGURED${NC}"
|
|
echo ""
|
|
echo "Cron jobs:"
|
|
crontab -u "$user" -l 2>/dev/null | grep "$WP_CRON_FILENAME"
|
|
else
|
|
echo -e "System cron: ${RED}NOT CONFIGURED${NC}"
|
|
fi
|
|
else
|
|
echo -e "wp-cron: ${YELLOW}ENABLED${NC} (default WordPress cron)"
|
|
echo ""
|
|
echo "Recommendation: Disable wp-cron and use system cron for better performance"
|
|
fi
|
|
|
|
else
|
|
echo ""
|
|
echo -n "Enter cPanel username (or 0 to cancel): "
|
|
read -r check_user
|
|
|
|
if [ -z "$check_user" ] || [ "$check_user" = "0" ]; then
|
|
echo "Operation cancelled."
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
|
|
# INPUT SANITIZATION: Validate username format
|
|
if ! is_valid_username_format "$check_user"; then
|
|
print_error "Invalid username format. Use only lowercase letters, numbers, hyphens, and underscores."
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -d "/home/$check_user" ]; then
|
|
print_error "User $check_user does not exist"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${BOLD}WordPress Cron Status for user: $check_user${NC}"
|
|
echo ""
|
|
|
|
wp_configs=$(find "/home/$check_user" -name "wp-config.php" -type f 2>/dev/null)
|
|
|
|
if [ -z "$wp_configs" ]; then
|
|
echo "No WordPress installations found"
|
|
else
|
|
count=0
|
|
while IFS= read -r wp_config; do
|
|
count=$((count + 1))
|
|
site_path=$(dirname "$wp_config")
|
|
|
|
echo -e "${count}. ${BOLD}$site_path${NC}"
|
|
|
|
if grep -q "define.*DISABLE_WP_CRON.*true" "$wp_config" 2>/dev/null; then
|
|
echo " wp-cron: ${GREEN}DISABLED${NC}"
|
|
else
|
|
echo " wp-cron: ${YELLOW}ENABLED${NC}"
|
|
fi
|
|
echo ""
|
|
done <<< "$wp_configs"
|
|
|
|
# Show cron jobs
|
|
echo -e "${BOLD}Cron Jobs:${NC}"
|
|
if crontab -u "$check_user" -l 2>/dev/null | grep -q "$WP_CRON_FILENAME"; then
|
|
crontab -u "$check_user" -l 2>/dev/null | grep "$WP_CRON_FILENAME"
|
|
else
|
|
echo " No wp-cron jobs found"
|
|
fi
|
|
fi
|
|
fi
|
|
;;
|
|
|
|
6)
|
|
# Re-enable wp-cron for specific domain
|
|
echo ""
|
|
echo -n "Enter domain name (or 0 to cancel): "
|
|
read -r domain
|
|
|
|
if [ -z "$domain" ] || [ "$domain" = "0" ]; then
|
|
echo "Operation cancelled."
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
|
|
# INPUT SANITIZATION: Validate domain format
|
|
if ! is_valid_domain_format "$domain"; then
|
|
print_error "Invalid domain format. Use only letters, numbers, hyphens, and dots."
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
# Find WordPress installation
|
|
wp_config=""
|
|
|
|
# Method 1: Check main_domain in main files
|
|
for userdata_file in /var/cpanel/userdata/*/main; do
|
|
if grep -q "^main_domain: $domain" "$userdata_file" 2>/dev/null; then
|
|
user=$(basename "$(dirname "$userdata_file")")
|
|
potential_config="/home/$user/public_html/wp-config.php"
|
|
if [ -f "$potential_config" ]; then
|
|
wp_config="$potential_config"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Method 2: Search domain-specific files for servername
|
|
if [ -z "$wp_config" ]; then
|
|
for userdata_file in /var/cpanel/userdata/*/*; do
|
|
[[ "$userdata_file" == *.cache ]] && continue
|
|
[[ "$userdata_file" == */main ]] && continue
|
|
[[ "$userdata_file" == */cache ]] && continue
|
|
[[ "$userdata_file" == */cache.json ]] && continue
|
|
|
|
if grep -q "^servername: $domain" "$userdata_file" 2>/dev/null; then
|
|
user=$(basename "$(dirname "$userdata_file")")
|
|
potential_config="/home/$user/public_html/wp-config.php"
|
|
if [ -f "$potential_config" ]; then
|
|
wp_config="$potential_config"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
if [ -z "$wp_config" ]; then
|
|
print_error "WordPress installation not found for $domain"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
echo -e "${GREEN}Found WordPress:${NC} $wp_config"
|
|
echo ""
|
|
|
|
# Create timestamped backup
|
|
backup_file=$(create_timestamped_backup "$wp_config")
|
|
if [ -z "$backup_file" ]; then
|
|
print_error "Backup failed, aborting"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
echo -e "${GREEN}✓${NC} Backed up wp-config.php"
|
|
|
|
# Re-enable wp-cron
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo "[DRY-RUN] Would remove DISABLE_WP_CRON from wp-config.php"
|
|
else
|
|
if ! enable_wpcron_in_config "$wp_config"; then
|
|
echo -e "${YELLOW}⚠${NC} DISABLE_WP_CRON not found or already enabled"
|
|
fi
|
|
|
|
# Validate syntax after modification
|
|
if ! validate_wp_config_syntax "$wp_config"; then
|
|
echo -e "${RED}✗${NC} wp-config.php syntax invalid after modification"
|
|
cp "$backup_file" "$wp_config" 2>/dev/null
|
|
[ -f "$backup_file" ] && cp "$backup_file" "${backup_file}.failed" 2>/dev/null
|
|
print_error "Reverted from backup"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
echo -e "${GREEN}✓${NC} Removed DISABLE_WP_CRON from wp-config.php"
|
|
fi
|
|
|
|
# Remove cron job - Multi-panel support
|
|
site_path=$(dirname "$wp_config")
|
|
user=$(extract_user_from_path "$site_path")
|
|
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo "[DRY-RUN] Would remove cron job from user crontab"
|
|
else
|
|
if safe_remove_cron_jobs "$user" "$site_path.*wp-cron.php"; then
|
|
echo -e "${GREEN}✓${NC} Removed cron job from user crontab"
|
|
else
|
|
echo -e "${YELLOW}⚠${NC} Failed to remove cron job"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo -e "${CYAN}[DRY-RUN] Would revert WordPress cron to default for $domain${NC}"
|
|
else
|
|
print_success "WordPress cron reverted to default for $domain"
|
|
fi
|
|
;;
|
|
|
|
7)
|
|
# Re-enable wp-cron for specific user
|
|
echo ""
|
|
echo -n "Enter cPanel username (or 0 to cancel): "
|
|
read -r target_user
|
|
|
|
if [ -z "$target_user" ] || [ "$target_user" = "0" ]; then
|
|
echo "Operation cancelled."
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
|
|
# INPUT SANITIZATION: Validate username format
|
|
if ! is_valid_username_format "$target_user"; then
|
|
print_error "Invalid username format. Use only lowercase letters, numbers, hyphens, and underscores."
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -d "/home/$target_user" ]; then
|
|
print_error "User $target_user does not exist"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
echo ""
|
|
echo "Reverting WordPress installations for user: $target_user"
|
|
echo ""
|
|
|
|
wp_configs=$(find "/home/$target_user" -name "wp-config.php" -type f 2>/dev/null)
|
|
|
|
if [ -z "$wp_configs" ]; then
|
|
print_error "No WordPress installations found for $target_user"
|
|
press_enter
|
|
exit 1
|
|
fi
|
|
|
|
count=0
|
|
converted=0
|
|
failed=0
|
|
while IFS= read -r wp_config; do
|
|
count=$((count + 1))
|
|
site_path=$(dirname "$wp_config")
|
|
|
|
echo -e "${BOLD}Site $count:${NC} $site_path"
|
|
|
|
# Create timestamped backup
|
|
backup_file=$(create_timestamped_backup "$wp_config")
|
|
if [ -z "$backup_file" ]; then
|
|
echo " • ${RED}Backup failed, skipping${NC}"
|
|
failed=$((failed + 1))
|
|
echo ""
|
|
continue
|
|
fi
|
|
echo " • Backed up wp-config.php"
|
|
|
|
# Re-enable wp-cron
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo " [DRY-RUN] Would remove DISABLE_WP_CRON"
|
|
else
|
|
if ! enable_wpcron_in_config "$wp_config"; then
|
|
echo " • Already using default wp-cron"
|
|
fi
|
|
|
|
# Validate syntax after modification
|
|
if ! validate_wp_config_syntax "$wp_config"; then
|
|
echo " • ${RED}Syntax error after modification${NC}"
|
|
cp "$backup_file" "$wp_config" 2>/dev/null
|
|
[ -f "$backup_file" ] && cp "$backup_file" "${backup_file}.failed" 2>/dev/null
|
|
failed=$((failed + 1))
|
|
echo ""
|
|
continue
|
|
fi
|
|
echo " • Removed DISABLE_WP_CRON"
|
|
converted=$((converted + 1))
|
|
fi
|
|
|
|
echo ""
|
|
done <<< "$wp_configs"
|
|
|
|
# Remove all wp-cron jobs for this user
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo "[DRY-RUN] Would remove all wp-cron jobs for user $target_user"
|
|
else
|
|
if safe_remove_cron_jobs "$target_user" "$WP_CRON_FILENAME"; then
|
|
echo -e "${GREEN}✓${NC} Removed all wp-cron jobs from user crontab"
|
|
fi
|
|
fi
|
|
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo -e "${CYAN}[DRY-RUN] Would revert $count site(s) for $target_user${NC}"
|
|
else
|
|
print_success "All WordPress sites for $target_user reverted to default wp-cron"
|
|
if [ $failed -gt 0 ]; then
|
|
print_warning "$failed site(s) failed or were skipped"
|
|
fi
|
|
fi
|
|
;;
|
|
|
|
8)
|
|
# Server-wide revert
|
|
echo ""
|
|
echo -e "${RED}${BOLD}WARNING: Server-Wide Revert${NC}"
|
|
echo ""
|
|
echo "This will:"
|
|
echo " • Find ALL WordPress installations on the server"
|
|
echo " • Remove DISABLE_WP_CRON from each wp-config.php"
|
|
echo " • Remove all wp-cron system cron jobs"
|
|
echo ""
|
|
echo -n "Are you sure? Type 'yes' to confirm: "
|
|
read -r confirm
|
|
|
|
if [ "$confirm" != "yes" ]; then
|
|
echo "Cancelled"
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
|
|
echo ""
|
|
echo "Processing WordPress installations from cache..."
|
|
echo ""
|
|
|
|
total=0
|
|
reverted=0
|
|
failed=0
|
|
|
|
# PERFORMANCE: Use cached paths (scanned once at startup, ~10-50x faster)
|
|
wp_configs=$(get_wp_sites_cached)
|
|
|
|
if [ -z "$wp_configs" ]; then
|
|
echo -e "${YELLOW}No WordPress installations found${NC}"
|
|
press_enter
|
|
exit 0
|
|
fi
|
|
|
|
while IFS= read -r wp_config; do
|
|
total=$((total + 1))
|
|
site_path=$(dirname "$wp_config")
|
|
if [ -z "$site_path" ]; then
|
|
echo -e "${RED}✗ Could not determine site path${NC}"
|
|
failed=$((failed + 1))
|
|
continue
|
|
fi
|
|
user=$(extract_user_from_path "$site_path")
|
|
|
|
echo -e "${BOLD}Processing:${NC} $site_path (user: $user)"
|
|
|
|
# Create timestamped backup
|
|
backup_file=$(create_timestamped_backup "$wp_config")
|
|
if [ -z "$backup_file" ]; then
|
|
echo " ${RED}✗ Backup failed, skipping${NC}"
|
|
failed=$((failed + 1))
|
|
echo ""
|
|
continue
|
|
fi
|
|
|
|
# Re-enable wp-cron
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo " [DRY-RUN] Would remove DISABLE_WP_CRON"
|
|
else
|
|
if ! enable_wpcron_in_config "$wp_config"; then
|
|
echo " Already using default wp-cron"
|
|
fi
|
|
|
|
# Validate syntax after modification
|
|
if ! validate_wp_config_syntax "$wp_config"; then
|
|
echo " ${RED}✗ Syntax error after modification${NC}"
|
|
cp "$backup_file" "$wp_config" 2>/dev/null
|
|
[ -f "$backup_file" ] && cp "$backup_file" "${backup_file}.failed" 2>/dev/null
|
|
failed=$((failed + 1))
|
|
echo ""
|
|
continue
|
|
fi
|
|
reverted=$((reverted + 1))
|
|
echo -e "${GREEN}✓${NC} Reverted"
|
|
fi
|
|
echo ""
|
|
done <<< "$wp_configs"
|
|
|
|
# Remove all wp-cron jobs from all users
|
|
echo ""
|
|
echo "Removing wp-cron jobs from user crontabs..."
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo "[DRY-RUN] Would remove wp-cron jobs from all users"
|
|
else
|
|
for user_home in /home/*; do
|
|
user=$(basename "$user_home")
|
|
if crontab -u "$user" -l 2>/dev/null | grep -q "$WP_CRON_FILENAME"; then
|
|
if safe_remove_cron_jobs "$user" "$WP_CRON_FILENAME"; then
|
|
echo " • Removed cron jobs for user: $user"
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
echo ""
|
|
if [ "$DRY_RUN" = "true" ]; then
|
|
echo -e "${CYAN}[DRY-RUN] Would revert up to $total site(s)${NC}"
|
|
else
|
|
print_success "Server-wide revert complete"
|
|
echo ""
|
|
echo "Summary:"
|
|
echo " • Total WordPress sites found: $total"
|
|
echo " • Successfully reverted: $reverted"
|
|
if [ $failed -gt 0 ]; then
|
|
echo -e " • ${RED}Failed or skipped: $failed${NC}"
|
|
fi
|
|
fi
|
|
;;
|
|
|
|
9)
|
|
# Run pre-flight checks
|
|
echo ""
|
|
print_banner "Pre-Flight Checks"
|
|
preflight_check "$SYS_CONTROL_PANEL"
|
|
;;
|
|
|
|
10)
|
|
# Show detailed status
|
|
echo ""
|
|
print_banner "WordPress Installation Status"
|
|
show_installation_status "$SYS_CONTROL_PANEL"
|
|
;;
|
|
|
|
0)
|
|
exit 0
|
|
;;
|
|
|
|
*)
|
|
print_error "Invalid option"
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
press_enter
|