.htaccess
1 year ago
Abilities.php
1 day ago
Blog.php
1 year ago
Helper.php
5 months ago
Init.php
5 months ago
Installer.php
7 months ago
MySQL.php
1 year ago
UI.php
4 months ago
Update.php
1 year ago
Wordpress.php
1 day ago
index.html
1 year ago
web.config
1 year ago
Helper.php
357 lines
| 1 | <?php |
| 2 | |
| 3 | namespace JetBackup\Wordpress; |
| 4 | |
| 5 | use Exception; |
| 6 | use JetBackup\Alert\Alert; |
| 7 | use JetBackup\Encryption\Crypt; |
| 8 | use JetBackup\Entities\Util; |
| 9 | use JetBackup\Exception\IOException; |
| 10 | use JetBackup\Factory; |
| 11 | use JetBackup\JetBackup; |
| 12 | use WP_User; |
| 13 | |
| 14 | if (!defined( '__JETBACKUP__')) die('Direct access is not allowed'); |
| 15 | |
| 16 | class Helper { |
| 17 | |
| 18 | private string $_homedir=''; |
| 19 | |
| 20 | private const SUPPORT_USER_TTL = 30 * 24 * 60 * 60; // 30 days in seconds |
| 21 | |
| 22 | const MYSQL_AUTH_PATTERNS = [ |
| 23 | 'db_name' => "/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*['\"]((?:\\\\.|[^'\"])+)['\"]\s*\)\s*;/", |
| 24 | 'db_user' => "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*['\"]((?:\\\\.|[^'\"])+)['\"]\s*\)\s*;/", |
| 25 | 'db_password' => "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*['\"]((?:\\\\.|[^'\"])+)['\"]\s*\)\s*;/", |
| 26 | 'db_host' => "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*['\"]((?:\\\\.|[^'\"])+)['\"]\s*\)\s*;/", |
| 27 | 'table_prefix' => "/^\s*\\\$table_prefix\s*=\s*['\"]((?:\\\\.|[^'\"])+)['\"]\s*;/", |
| 28 | ]; |
| 29 | |
| 30 | public static function isMainSite(): bool { |
| 31 | return (function_exists('is_main_site') && is_main_site()); |
| 32 | } |
| 33 | |
| 34 | public static function getMainSiteId(): int { |
| 35 | $main_site_id = 1; |
| 36 | if (function_exists('get_main_site_id')) return get_main_site_id(); |
| 37 | if (function_exists('get_site_option')) $main_site_id = (int) get_site_option('main_site_id'); |
| 38 | if ($main_site_id > 0) return $main_site_id; |
| 39 | return $main_site_id; |
| 40 | } |
| 41 | |
| 42 | public static function isMultisite(): bool { |
| 43 | if (function_exists('is_multisite') && is_multisite()) return true; |
| 44 | if (defined( 'MULTISITE')) return MULTISITE; |
| 45 | if (defined( 'SUBDOMAIN_INSTALL') || defined( 'VHOST') || defined( 'SUNRISE')) return true; |
| 46 | return false; |
| 47 | } |
| 48 | |
| 49 | public static function isNetworkAdminUser(): bool { |
| 50 | return function_exists('current_user_can') && current_user_can('manage_network') && |
| 51 | function_exists('is_super_admin') && is_super_admin(); |
| 52 | } |
| 53 | |
| 54 | /** |
| 55 | * @throws Exception |
| 56 | */ |
| 57 | public static function getWordpressUser($username) : ?array { |
| 58 | |
| 59 | if(!function_exists('get_user_by')) throw new Exception("Function get_user_by() not defined"); |
| 60 | if(!function_exists('wp_set_password')) throw new Exception("Function wp_set_password() not defined"); |
| 61 | |
| 62 | $user = get_user_by('login', $username); |
| 63 | if(!$user) return null; |
| 64 | |
| 65 | |
| 66 | $password = Util::generatePassword(); |
| 67 | wp_set_password($password, $user->ID); |
| 68 | |
| 69 | return [ |
| 70 | 'username' => $user->user_login, |
| 71 | 'email' => $user->user_email, |
| 72 | 'password' => $password, |
| 73 | 'wordpress_admin_url' => Wordpress::getAdminURL(), |
| 74 | |
| 75 | ]; |
| 76 | |
| 77 | } |
| 78 | |
| 79 | /** |
| 80 | * @throws Exception |
| 81 | */ |
| 82 | public static function clearSupportUser() : void { |
| 83 | |
| 84 | $config = Factory::getConfig(); |
| 85 | if(!$username = $config->getSupportUsername()) return; |
| 86 | if(!$user = get_user_by('login', $username)) return; |
| 87 | $user_created_date = $user->data->user_registered ?? null; |
| 88 | if(!$user_created_date) return; |
| 89 | if(!$user_created_timestamp = strtotime($user_created_date)) return; |
| 90 | |
| 91 | if ((time() - $user_created_timestamp) > self::SUPPORT_USER_TTL) { |
| 92 | |
| 93 | if (self::isMultisite()) { |
| 94 | if (!function_exists('wpmu_delete_user')) require_once WP_ROOT . JetBackup::SEP . 'wp-admin' . JetBackup::SEP . 'includes' . JetBackup::SEP . 'ms.php'; |
| 95 | revoke_super_admin($user->ID); // super admin user is protected by wp_delete user, so we need to revoke first |
| 96 | wpmu_delete_user($user->ID); |
| 97 | } else { |
| 98 | if (!function_exists('wp_delete_user')) require_once WP_ROOT . JetBackup::SEP . 'wp-admin' . JetBackup::SEP . 'includes' . JetBackup::SEP . 'user.php'; |
| 99 | wp_delete_user($user->ID); |
| 100 | } |
| 101 | $config->setSupportUsername(''); |
| 102 | $config->save(); |
| 103 | Alert::add('System Cleanup', "Temporary support user '{$user->data->user_login}' created 30 days ago removed", Alert::LEVEL_INFORMATION); |
| 104 | } |
| 105 | |
| 106 | } |
| 107 | |
| 108 | /** |
| 109 | * @throws IOException |
| 110 | * @throws Exception |
| 111 | */ |
| 112 | public static function createSupportUser() : array { |
| 113 | |
| 114 | $config = Factory::getConfig(); |
| 115 | $username = $config->getSupportUsername(); |
| 116 | if ($username && $output = self::getWordpressUser($username)) return $output; |
| 117 | |
| 118 | if(!function_exists('wp_create_user')) throw new Exception("Function wp_create_user() not defined"); |
| 119 | if(!function_exists('grant_super_admin')) throw new Exception("Function grant_super_admin() not defined"); |
| 120 | |
| 121 | // Generate new user details |
| 122 | $email = "support+" . Util::generateRandomString(5) . "@jetbackup.com"; |
| 123 | $password = Util::generatePassword(); |
| 124 | $username = "jetbackup_" . Util::generateRandomString(5); |
| 125 | |
| 126 | // Create new user |
| 127 | $user_id = wp_create_user($username, $password, $email); |
| 128 | if (is_wp_error($user_id)) throw new Exception($user_id->get_error_message()); |
| 129 | |
| 130 | $user = new WP_User($user_id); |
| 131 | $user->set_role('administrator'); |
| 132 | if (self::isMultisite()) grant_super_admin($user_id); // Grant network admin privileges |
| 133 | |
| 134 | $config->setSupportUsername($username); |
| 135 | $config->save(); |
| 136 | |
| 137 | return [ |
| 138 | 'username' => $user->user_login, |
| 139 | 'email' => $email, |
| 140 | 'password' => $password, |
| 141 | 'wordpress_admin_url' => Wordpress::getAdminURL(), |
| 142 | ]; |
| 143 | |
| 144 | } |
| 145 | |
| 146 | public static function isAdminUser(): bool { |
| 147 | return function_exists('current_user_can') && current_user_can('manage_options') && |
| 148 | function_exists('is_super_admin') && is_super_admin(); |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * Returns true/false if I am in the network admin GUI interface (not if I am network admin USER) |
| 153 | * @return bool |
| 154 | */ |
| 155 | public static function isNetworkAdminInterface(): bool { |
| 156 | if (function_exists('is_network_admin') && is_network_admin()) return true; |
| 157 | if (isset($GLOBALS['current_screen'])) return $GLOBALS['current_screen']->in_admin( 'network' ); |
| 158 | elseif (defined('WP_NETWORK_ADMIN')) return WP_NETWORK_ADMIN; |
| 159 | return false; |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | * @param bool $public |
| 164 | * Example output: /home/user/public_html/ |
| 165 | * public flag: public_html (no ending /) |
| 166 | * @return string |
| 167 | */ |
| 168 | public function getWordPressHomedir(bool $public = false): string { |
| 169 | |
| 170 | if (!$this->_homedir) { |
| 171 | $homedir = defined('WP_ROOT') ? WP_ROOT : Wordpress::getAbsPath(); |
| 172 | $this->_homedir = rtrim($homedir, JetBackup::SEP) . JetBackup::SEP; |
| 173 | } |
| 174 | |
| 175 | if ($public) return basename(rtrim($this->_homedir, JetBackup::SEP)); |
| 176 | return $this->_homedir; |
| 177 | } |
| 178 | |
| 179 | /** |
| 180 | * @return string |
| 181 | * Returns public restore file location |
| 182 | * Example - |
| 183 | * Alternate path enabled: /home/user/public_html/wp-content/plugins/backup/public/cron |
| 184 | * Alternate path disabled: /home/user/public_html |
| 185 | */ |
| 186 | public function getRestoreFileLocation() : string { |
| 187 | if (Factory::getSettingsRestore()->isRestoreAlternatePathEnabled()) return rtrim(JetBackup::CRON_PATH, JetBackup::SEP); |
| 188 | return rtrim($this->getWordPressHomedir(), JetBackup::SEP); |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * @return string |
| 193 | * Return's WordPress public_dir relative to homedir, needed for nested sites (sites inside subfolders) |
| 194 | * Example |
| 195 | * - getWordPressHomedir: /home/user/sites/www.mydomain.com/subfolder |
| 196 | * - getUserHomedir: /home/user |
| 197 | * - getWordPressRelativePublicDir: /sites/wp2.jetbackup.com/subfolder |
| 198 | */ |
| 199 | public function getWordPressRelativePublicDir() : string { |
| 200 | |
| 201 | $public_dir = trim($this->getWordPressHomedir(), JetBackup::SEP); |
| 202 | $getUserHomedir = trim($this->getUserHomedir(), JetBackup::SEP); |
| 203 | $relative_path = $getUserHomedir; |
| 204 | if ($public_dir != $getUserHomedir && str_starts_with($public_dir, $getUserHomedir)) { |
| 205 | $relative_path = trim(substr($public_dir, strlen($getUserHomedir)), JetBackup::SEP); |
| 206 | } |
| 207 | |
| 208 | return JetBackup::SEP . $relative_path; |
| 209 | } |
| 210 | |
| 211 | /** |
| 212 | * @return string |
| 213 | * Example output: /home/user |
| 214 | */ |
| 215 | |
| 216 | public static function getUserHomedir(): ?string { |
| 217 | |
| 218 | $user_details = Util::getpwuid(Util::geteuid()); |
| 219 | return $user_details['dir'] ?? null; |
| 220 | |
| 221 | } |
| 222 | |
| 223 | /** |
| 224 | * @param string|null $decryption_key Optional key to decrypt runtime credentials (queue unique ID) |
| 225 | * @throws Exception |
| 226 | */ |
| 227 | public static function parseWpConfig(?string $decryption_key = null): \stdClass { |
| 228 | |
| 229 | // Check if runtime credentials are available (from restore file) |
| 230 | // This handles cloud environments (WordPress.com, WP Cloud, Porkbun) where |
| 231 | // wp-config.php doesn't contain literal credentials |
| 232 | if (defined('JB_RUNTIME_CREDENTIALS') && $decryption_key) { |
| 233 | $decrypted = Crypt::decrypt(JB_RUNTIME_CREDENTIALS, $decryption_key); |
| 234 | $creds = json_decode($decrypted); |
| 235 | |
| 236 | if ($creds && !empty($creds->db_name)) { |
| 237 | $output = new \stdClass(); |
| 238 | $output->db_name = $creds->db_name; |
| 239 | $output->db_user = $creds->db_user; |
| 240 | $output->db_password = $creds->db_password; |
| 241 | $output->db_host = $creds->db_host; |
| 242 | $output->table_prefix = $creds->table_prefix ?? 'wp_'; |
| 243 | $output->db_port = Factory::getSettingsGeneral()->getMySQLDefaultPort(); |
| 244 | |
| 245 | // Handle host:port notation |
| 246 | if (strpos($output->db_host, ':') !== false) { |
| 247 | list($output->db_host, $output->db_port) = explode(':', $output->db_host, 2); |
| 248 | } |
| 249 | |
| 250 | return $output; |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | // Fall back to parsing wp-config.php file |
| 255 | $config_file = Factory::getSettingsGeneral()->getAlternateWpConfigLocation(); |
| 256 | if(!file_exists($config_file)) throw new Exception("The wp-config.php file does not exist at the specified path. ($config_file)"); |
| 257 | |
| 258 | $output = new \stdClass(); |
| 259 | |
| 260 | if(!($f = fopen($config_file, 'r'))) throw new Exception("Unable to open the wp-config.php file."); |
| 261 | |
| 262 | $patterns = self::MYSQL_AUTH_PATTERNS; |
| 263 | |
| 264 | while (($line = fgets($f)) !== false) { |
| 265 | $line = trim($line); |
| 266 | // Skip commented lines |
| 267 | if (strpos($line, '//') === 0 || strpos($line, '#') === 0) continue; |
| 268 | |
| 269 | foreach ($patterns as $key => $pattern) { |
| 270 | if (!preg_match($pattern, $line, $matches)) continue; |
| 271 | $value = stripcslashes($matches[1]); |
| 272 | |
| 273 | if ($key === 'db_host' && strpos($value, ':') !== false) { |
| 274 | list($output->db_host, $output->db_port) = explode(':', $value, 2); |
| 275 | } else { |
| 276 | $output->{$key} = $value; |
| 277 | } |
| 278 | |
| 279 | unset($patterns[$key]); |
| 280 | break; // Break the foreach loop if a pattern matches |
| 281 | } |
| 282 | |
| 283 | // Break the while loop if all patterns are found |
| 284 | if (empty($patterns)) break; |
| 285 | } |
| 286 | |
| 287 | fclose($f); |
| 288 | |
| 289 | // Set default port if not found |
| 290 | if (!isset($output->db_port)) $output->db_port = Factory::getSettingsGeneral()->getMySQLDefaultPort(); // Default MySQL port |
| 291 | if (!isset($output->db_name)) $output->db_name = defined('DB_NAME') ? DB_NAME : ''; |
| 292 | if (!isset($output->db_user)) $output->db_user = defined('DB_USER') ? DB_USER : ''; |
| 293 | if (!isset($output->db_password)) $output->db_password = defined('DB_PASSWORD') ? DB_PASSWORD : ''; |
| 294 | if (!isset($output->db_host)) $output->db_host = defined('DB_HOST') ? DB_HOST : ''; |
| 295 | |
| 296 | return $output; |
| 297 | |
| 298 | } |
| 299 | |
| 300 | public function getUploadDir(): string { |
| 301 | $_uploads = Wordpress::getUploadDir()['basedir'] ?? 'uploads'; |
| 302 | return Wordpress::WP_CONTENT . JetBackup::SEP . basename($_uploads); |
| 303 | } |
| 304 | |
| 305 | public static function validateEmail($email): bool { |
| 306 | // Ensure $email is an array |
| 307 | $emails = is_array($email) ? $email : [$email]; |
| 308 | |
| 309 | foreach ($emails as $singleEmail) { |
| 310 | $sanitizedEmail = Wordpress::sanitizeEmail($singleEmail); |
| 311 | if (!Wordpress::isEmail($sanitizedEmail)) return false; // Invalid email found |
| 312 | } |
| 313 | |
| 314 | return true; |
| 315 | } |
| 316 | |
| 317 | static function getCurrentScreen():?string { |
| 318 | if($screen = Wordpress::getCurrentScreen()) return $screen->id; |
| 319 | return null; |
| 320 | } |
| 321 | |
| 322 | public static function getUserId():?int { |
| 323 | if($user = Wordpress::getCurrentUser()) return $user->ID; |
| 324 | return null; |
| 325 | } |
| 326 | |
| 327 | public static function getUserEmail():?string { |
| 328 | if($user = Wordpress::getCurrentUser()) return $user->user_email; |
| 329 | return null; |
| 330 | } |
| 331 | |
| 332 | public static function getUserIP(): ?string { |
| 333 | $ip = $_SERVER['HTTP_CLIENT_IP'] ?? $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? null; |
| 334 | if ($ip === null) return null; |
| 335 | //If forwarded IPs are present, take the first one |
| 336 | if ( Wordpress::strContains( $ip, ',' ) ) {$ip = trim(explode(',', $ip)[0]);} |
| 337 | $ip = Wordpress::sanitizeTextField($ip); |
| 338 | if (filter_var($ip, FILTER_VALIDATE_IP)) return $ip; |
| 339 | return null; |
| 340 | } |
| 341 | |
| 342 | |
| 343 | |
| 344 | public static function isWPCli(): bool { |
| 345 | return (defined('WP_CLI') && WP_CLI); |
| 346 | } |
| 347 | |
| 348 | |
| 349 | public static function isCLI():bool { |
| 350 | |
| 351 | if (isset($_SERVER['HTTP_TE']) || isset($_SERVER['HTTP_COOKIE']) || isset($_SERVER['HTTP_ACCEPT'])) return false; |
| 352 | if(self::isWPCli() || in_array( PHP_SAPI, ['cli', 'cli-server']) || defined('STDIN')) return true; |
| 353 | return isset($_SERVER['argv']) && sizeof($_SERVER['argv']); |
| 354 | |
| 355 | } |
| 356 | } |
| 357 |