course)) { $this->course = new CourseHelper(); } return $this->course; } public function cpt(): CptHelper|null { if (is_null($this->cpt)) { $this->cpt = new CptHelper(); } return $this->cpt; } public function email(): EmailHelper|null { if (is_null($this->email)) { $this->email = new EmailHelper(); } return $this->email; } public function queryLogger(): QueryLogger|null { if (is_null($this->queryLogger)) { $this->queryLogger = new QueryLogger(); } return $this->queryLogger; } public function ai(): AiHelper|null { if (is_null($this->ai)) { $this->ai = new AiHelper(); } return $this->ai; } public function acadlix_modify_video_shortcode($content) { if (empty($content)) { return $content; // If content is empty, return it as-is } if (has_shortcode($content, 'video')) { // Use regex to find all [video] shortcodes in the content $pattern = get_shortcode_regex(['video']); $content = preg_replace_callback("/$pattern/", function ($matches) { // Check if the matched shortcode is [video] if ($matches[2] === 'video') { // Parse shortcode attributes $attrs = shortcode_parse_atts($matches[3]); // Remove width and height if they exist unset($attrs['width'], $attrs['height']); // Rebuild the shortcode with the modified attributes $attr_string = ''; if ($attrs) { foreach ($attrs as $key => $value) { $attr_string .= sprintf(' %s="%s"', $key, esc_attr($value)); } } // Return the modified shortcode return sprintf('[%s%s]%s[/%s]', $matches[2], $attr_string, $matches[5], $matches[2]); } return $matches[0]; // Return original shortcode if not [video] }, $content); } return $content; } public function renderShortCode($content) { // Prevent null warnings — always ensure it's a string $content = (string) ($content ?? ''); $content = $this->acadlix_modify_video_shortcode($content); $content = apply_filters('the_content', $content); // phpcs:ignore return $content; } public function upload_base64_image_to_wordpress($content) { $pattern = '/]+src="data:image\/([^;]+);base64,([^"]+)"[^>]*>/i'; // Callback function to handle each match $callback = function ($matches) { require_once(ABSPATH . 'wp-admin/includes/file.php'); // Initialize the filesystem global $wp_filesystem; if (empty($wp_filesystem)) { WP_Filesystem(); // This initializes the $wp_filesystem global } // Extract mime type and base64 data from matches $mime_type = $matches[1]; $base64_data = $matches[2]; // Decode base64 data $image_data = base64_decode($base64_data); // Create a unique filename $filename = uniqid() . '.' . $mime_type; // Path to the uploads directory $upload_dir = wp_upload_dir(); $file_path = $upload_dir['path'] . '/' . $filename; // Save the image data to a file if (!$wp_filesystem->put_contents($file_path, $image_data, FS_CHMOD_FILE)) { /* translators: %s is the filepath */ return new WP_Error('failed', sprintf(__('Failed to write to file: %s', 'acadlix'), esc_html($file_path)), array('status' => 400)); } else { // Prepare file data for WP attachment $wp_file_type = wp_check_filetype($filename, null); $attachment = array( 'guid' => $upload_dir['url'] . '/' . $filename, 'post_mime_type' => $wp_file_type['type'], 'post_title' => sanitize_file_name($filename), 'post_content' => '', 'post_status' => 'inherit' ); // // Insert the attachment into the media library $attach_id = wp_insert_attachment($attachment, $file_path); // // Generate attachment metadata require_once(ABSPATH . 'wp-admin/includes/image.php'); $attach_data = wp_generate_attachment_metadata($attach_id, $file_path); wp_update_attachment_metadata($attach_id, $attach_data); // Return the URL of the uploaded image return "" . $filename . ""; } }; // Perform the replacement $new_content = preg_replace_callback($pattern, $callback, $content); return $new_content; } public function acadlix_upload_file_to_wordpress( $file, $subdir = '/', $allowed_extensions = [], $max_size_mb = null, $options = [] ) { require_once ABSPATH . 'wp-admin/includes/file.php'; // 1. Validate raw file payload if (!is_array($file) || empty($file['tmp_name']) || empty($file['name'])) { return new WP_Error('invalid_file', __('Invalid file upload data provided.', 'acadlix')); } if (isset($file['error']) && $file['error'] !== UPLOAD_ERR_OK) { return new WP_Error('upload_error', __('File upload error encountered.', 'acadlix'), ['status' => 400]); } // 2. Extension validation $original_filename = sanitize_file_name($file['name']); $extension = strtolower(pathinfo($original_filename, PATHINFO_EXTENSION)); $clean_allowed_extensions = array_filter(array_map('strtolower', (array) $allowed_extensions)); if (!empty($clean_allowed_extensions) && !in_array($extension, $clean_allowed_extensions, true)) { return new WP_Error( 'invalid_extension', sprintf( /* translators: %s is the file extension */ __('File extension ".%s" is not allowed.', 'acadlix'), $extension ), ['status' => 400] ); } // 3. File size validation if ($max_size_mb !== null && floatval($max_size_mb) > 0) { $max_size_bytes = floatval($max_size_mb) * 1024 * 1024; if (!empty($file['size']) && $file['size'] > $max_size_bytes) { return new WP_Error( 'file_too_large', /* translators: %1$s is the file name, %2$s is the maximum allowed size in MB */ sprintf(__('File "%1$s" exceeds the maximum allowed size of %2$s MB.', 'acadlix'), $original_filename, $max_size_mb), ['status' => 400] ); } } // 4. Ensure target directory exists $upload_dir = wp_upload_dir(); $custom_dir_path = $upload_dir['basedir'] . $subdir; if (!file_exists($custom_dir_path)) { if (!wp_mkdir_p($custom_dir_path)) { return new WP_Error('mkdir_failed', __('Failed to create destination upload directory.', 'acadlix'), ['status' => 500]); } } // 5. Generate unique secure filename $secure_token = wp_generate_password(16, false, false); $filename_wo_ext = pathinfo($original_filename, PATHINFO_FILENAME); $unique_filename = "{$filename_wo_ext}_{$secure_token}_" . time() . ".{$extension}"; $file['name'] = $unique_filename; // 6. Map MIME types for wp_handle_upload safety $constructed_mimes = []; if (!empty($clean_allowed_extensions)) { $wp_mimes = wp_get_mime_types(); foreach ($wp_mimes as $ext_pattern => $mime) { foreach (explode('|', $ext_pattern) as $ext) { if (in_array(strtolower($ext), $clean_allowed_extensions, true)) { $constructed_mimes[$ext_pattern] = $mime; } } } } // 7. Dynamic Upload Directory Filter Scope $upload_dir_callback = function ($dirs) use ($subdir) { $dirs['subdir'] = $subdir; $dirs['path'] = $dirs['basedir'] . $subdir; $dirs['url'] = $dirs['baseurl'] . $subdir; return $dirs; }; add_filter('upload_dir', $upload_dir_callback); $overrides = array_merge( ['test_form' => false], $options['upload_overrides'] ?? [], ['mimes' => !empty($constructed_mimes) ? $constructed_mimes : null] ); $upload = wp_handle_upload($file, $overrides); // Clean up filter callback remove_filter('upload_dir', $upload_dir_callback); if (isset($upload['error'])) { return new WP_Error('upload_error', $upload['error'], ['status' => 400]); } // 8. Construct Output File Payload $new_file = [ 'file_name' => $file['name'], 'file_size' => $file['size'], 'file_extension' => $extension, 'file_url' => $upload['url'], 'file_path' => $upload['file'], 'file_type' => $upload['type'], ]; // 9. Stateless Cryptographic Delete Token (Option 1) if (!empty($options['generate_delete_token'])) { $current_user_id = get_current_user_id(); $token_payload = [ 'file_name' => $file['name'], 'user_id' => $current_user_id, 'created' => time(), ]; $token_json = wp_json_encode($token_payload); $hmac_sig = wp_hash($token_json, 'nonce'); $new_file['delete_token'] = base64_encode($token_json . '|' . $hmac_sig); } return $new_file; } public function acadlix_delete_file_from_wordpress($file_path_or_url, $delete_token = null, $options = []) { $defaults = [ 'verify_token' => true, 'enforce_user' => true, 'allowed_subdir' => '', ]; $options = wp_parse_args($options, $defaults); // 1. Path Resolution First (Need real path to extract target filename) $upload_dir = wp_upload_dir(); $base_dir = realpath($upload_dir['basedir']); if (!$base_dir) { return new WP_Error('upload_dir_error', __('Uploads directory cannot be resolved.', 'acadlix'), ['status' => 500]); } $file_path = $file_path_or_url; // Convert URL to absolute path if needed if (filter_var($file_path_or_url, FILTER_VALIDATE_URL)) { if (str_starts_with($file_path_or_url, $upload_dir['baseurl'])) { $relative_path = str_replace($upload_dir['baseurl'], '', $file_path_or_url); $file_path = $upload_dir['basedir'] . $relative_path; } else { return new WP_Error( 'invalid_file_url', __('The provided file URL does not belong to the WordPress uploads directory.', 'acadlix'), ['status' => 400] ); } } // Resolve symlinks and relative path sequences ('..') $real_file_path = realpath($file_path); if (!$real_file_path || !file_exists($real_file_path)) { return new WP_Error( 'file_not_found', __('The target file does not exist on the server.', 'acadlix'), ['status' => 404] ); } // Security: Prevent Directory Traversal Attack if (!str_starts_with($real_file_path, $base_dir)) { return new WP_Error( 'directory_traversal', __('Access denied: File path is outside the allowed uploads directory.', 'acadlix'), ['status' => 403] ); } // Enforce subdirectory restriction if option is set if (!empty($options['allowed_subdir'])) { $allowed_subdir_path = realpath($upload_dir['basedir'] . '/' . ltrim($options['allowed_subdir'], '/')); if ($allowed_subdir_path && !str_starts_with($real_file_path, $allowed_subdir_path)) { return new WP_Error( 'restricted_subdir', __('Access denied: File resides outside the permitted subdirectory.', 'acadlix'), ['status' => 403] ); } } // Prevent deleting core directory or index files if (is_dir($real_file_path) || basename($real_file_path) === 'index.php') { return new WP_Error('invalid_target', __('Cannot delete system directories or index files.', 'acadlix'), ['status' => 400]); } // 2. Token & Payload Verification if ($options['verify_token']) { if (empty($delete_token)) { return new WP_Error( 'missing_token', __('Delete token is required for this operation.', 'acadlix'), ['status' => 400] ); } $decoded = base64_decode($delete_token, true); if ($decoded === false || !str_contains($decoded, '|')) { return new WP_Error( 'invalid_token_format', __('Invalid delete token structure.', 'acadlix'), ['status' => 400] ); } list($token_json, $token_hmac) = explode('|', $decoded, 2); // Verify HMAC signature to prevent payload tampering $expected_hmac = wp_hash($token_json, 'nonce'); if (!hash_equals($expected_hmac, $token_hmac)) { return new WP_Error( 'token_tampered', __('Delete token verification failed. Token signature is invalid.', 'acadlix'), ['status' => 403] ); } $token_data = json_decode($token_json, true); if (!is_array($token_data) || empty($token_data['file_name'])) { return new WP_Error( 'token_corrupt', __('Delete token payload is corrupt or missing file name.', 'acadlix'), ['status' => 400] ); } // *** CRITICAL CHECK: Verify token's file_name matches the target file being deleted *** $target_filename = basename($real_file_path); if (!hash_equals($token_data['file_name'], $target_filename)) { return new WP_Error( 'token_filename_mismatch', __('Delete token does not match the specified file.', 'acadlix'), ['status' => 403] ); } // Validate user ownership if enabled if ($options['enforce_user']) { $current_user_id = get_current_user_id(); if (!empty($token_data['user_id']) && intval($token_data['user_id']) !== $current_user_id) { // Allow site admins to bypass ownership check if (!current_user_can('manage_options')) { return new WP_Error( 'unauthorized_user', __('You do not have permission to delete this file.', 'acadlix'), ['status' => 403] ); } } } } // 3. Perform Deletion if (!wp_delete_file($real_file_path)) { return new WP_Error( 'delete_failed', __('Failed to delete file from the filesystem.', 'acadlix'), ['status' => 500] ); } return true; } public function acadlix_get_system_languages() { if (!function_exists('wp_get_available_translations')) { return ['en_US' => 'English']; } $translations = wp_get_available_translations(); $language_map = []; foreach ($translations as $locale => $details) { $language_map[$locale] = $details['native_name']; } $language_map['en_US'] ??= 'English'; return $language_map; } public function acadlix_currencies() { $currencies = array( 'AFN' => __('Afghan afghani', 'acadlix'), 'ALL' => __('Albanian lek', 'acadlix'), 'AED' => __('United Arab Emirates dirham', 'acadlix'), 'AOA' => __('Angolan kwanza', 'acadlix'), 'ARS' => __('Argentine peso', 'acadlix'), 'AMD' => __('Armenian dram', 'acadlix'), 'ANG' => __('Netherlands Antilles guilder', 'acadlix'), 'AWG' => __('Aruban florin', 'acadlix'), 'AUD' => __('Australian dollar', 'acadlix'), 'AZN' => __('Azerbaijani manat', 'acadlix'), 'BSD' => __('Bahamian dollar', 'acadlix'), 'BHD' => __('Bahraini dinar', 'acadlix'), 'BDT' => __('Bangladeshi taka', 'acadlix'), 'BBD' => __('Barbadian dollar', 'acadlix'), 'BYR' => __('Belarusian ruble', 'acadlix'), 'BZD' => __('Belizean dollar', 'acadlix'), 'BMD' => __('Bermudian dollar', 'acadlix'), 'BTN' => __('Bhutanese ngultrum', 'acadlix'), 'BOB' => __('Bolivian boliviano', 'acadlix'), 'BAM' => __('Bosnia and Herzegovina convertible mark', 'acadlix'), 'BWP' => __('Botswana pula', 'acadlix'), 'BRL' => __('Brazilian real', 'acadlix'), 'BND' => __('Brunei dollar', 'acadlix'), 'BGN' => __('Bulgarian lev', 'acadlix'), 'BIF' => __('Burundian franc', 'acadlix'), 'CAD' => __('Canadian dollar', 'acadlix'), 'CVE' => __('Cape Verdean escudo', 'acadlix'), 'CLP' => __('Chilean peso', 'acadlix'), 'CNY' => __('Chinese renminbi', 'acadlix'), 'COP' => __('Colombian peso', 'acadlix'), 'CDF' => __('Congolese franc', 'acadlix'), 'CHF' => __('Swiss franc', 'acadlix'), 'CRC' => __('Costa Rican colón', 'acadlix'), 'CUC' => __('Cuban peso', 'acadlix'), 'CZK' => __('Czech koruna', 'acadlix'), 'DKK' => __('Danish krone', 'acadlix'), 'DJF' => __('Djiboutian franc', 'acadlix'), 'DOP' => __('Dominican peso', 'acadlix'), 'DZD' => __('Algerian dinar', 'acadlix'), 'EGP' => __('Egyptian pound', 'acadlix'), 'ERN' => __('Eritrean nakfa', 'acadlix'), 'EUR' => __('Euro', 'acadlix'), 'ETB' => __('Ethiopian birr', 'acadlix'), 'FKP' => __('Falkland Islands pound', 'acadlix'), 'FJD' => __('Fijian dollar', 'acadlix'), 'GMD' => __('Gambian dalasi', 'acadlix'), 'GEL' => __('Georgian lari', 'acadlix'), 'GHS' => __('Ghanian cedi', 'acadlix'), 'GIP' => __('Gibraltar pound', 'acadlix'), 'GTQ' => __('Guatemalan quetzal', 'acadlix'), 'GBP' => __('British pound', 'acadlix'), 'GNF' => __('Guinean franc', 'acadlix'), 'GYD' => __('Guyanese dollar', 'acadlix'), 'HTG' => __('Haitian gourde', 'acadlix'), 'HNL' => __('Honduran lempira', 'acadlix'), 'HKD' => __('Hong Kong dollar', 'acadlix'), 'HRK' => __('Croatian kuna', 'acadlix'), 'HUF' => __('Hungarian forint', 'acadlix'), 'ISK' => __('Icelandic króna', 'acadlix'), 'INR' => __('Indian rupee', 'acadlix'), 'IDR' => __('Indonesian rupiah', 'acadlix'), 'IRR' => __('Iranian rial', 'acadlix'), 'IQD' => __('Iraqi dinar', 'acadlix'), 'ILS' => __('Israeli new sheqel', 'acadlix'), 'JMD' => __('Jamaican dollar', 'acadlix'), 'JPY' => __('Japanese yen ', 'acadlix'), 'JEP' => __('Jersey pound', 'acadlix'), 'JOD' => __('Jordanian dinar', 'acadlix'), 'KZT' => __('Kazakhstani tenge', 'acadlix'), 'KES' => __('Kenyan shilling', 'acadlix'), 'KPW' => __('North Korean won', 'acadlix'), 'KHR' => __('Cambodian riel', 'acadlix'), 'KWD' => __('Kuwaiti dinar', 'acadlix'), 'KGS' => __('Kyrgyzstani som', 'acadlix'), 'KYD' => __('Cayman Islands dollar', 'acadlix'), 'KMF' => __('Comorian franc', 'acadlix'), 'KRW' => __('South Korean won', 'acadlix'), 'LAK' => __('Lao kip', 'acadlix'), 'LVL' => __('Latvian lats', 'acadlix'), 'LBP' => __('Lebanese pound', 'acadlix'), 'LSL' => __('Lesotho loti', 'acadlix'), 'LRD' => __('Liberian dollar', 'acadlix'), 'LD' => __('Libyan dinar', 'acadlix'), 'LYD' => __('Libyan dinar', 'acadlix'), 'LKR' => __('Sri Lankan rupee', 'acadlix'), 'LTL' => __('Lithuanian litas', 'acadlix'), 'MOP' => __('Macanese pataca', 'acadlix'), 'MKD' => __('Macedonian denar', 'acadlix'), 'MGA' => __('Malagasy ariary', 'acadlix'), 'MWK' => __('Malawian kwacha', 'acadlix'), 'MYR' => __('Malaysian ringgit', 'acadlix'), 'MVR' => __('Maldivian rufiyaa', 'acadlix'), 'MRO' => __('Mauritanian ouguiya', 'acadlix'), 'MUR' => __('Mauritian rupee', 'acadlix'), 'MXN' => __('Mexican peso', 'acadlix'), 'MDL' => __('Moldovan leu', 'acadlix'), 'MNT' => __('Mongolian tugrik', 'acadlix'), 'MAD' => __('Moroccan dirham', 'acadlix'), 'MZN' => __('Mozambican metical', 'acadlix'), 'MMK' => __('Burmese kyat', 'acadlix'), 'NAD' => __('Namibian dollar', 'acadlix'), 'NPR' => __('Nepalese rupee', 'acadlix'), 'NIO' => __('Nicaraguan córdoba', 'acadlix'), 'NGN' => __('Nigerian naira', 'acadlix'), 'NOK' => __('Norwegian krone', 'acadlix'), 'NZD' => __('New Zealand dollar', 'acadlix'), 'OMR' => __('Omani rial', 'acadlix'), 'PKR' => __('Pakistani rupee', 'acadlix'), 'PAB' => __('Panamanian balboa', 'acadlix'), 'PGK' => __('Papua New Guinea kina', 'acadlix'), 'PYG' => __('Paraguayan guarani', 'acadlix'), 'PEN' => __('Peruvian nuevo sol', 'acadlix'), 'PHP' => __('Philippine peso', 'acadlix'), 'PLN' => __('Polish zloty', 'acadlix'), 'QAR' => __('Qatari riyal', 'acadlix'), 'RON' => __('Romanian leu', 'acadlix'), 'RUB' => __('Russian ruble', 'acadlix'), 'RSD' => __('Serbian dinar', 'acadlix'), 'RWF' => __('Rwandan franc', 'acadlix'), 'SVC' => __('Salvadoran colón', 'acadlix'), 'STD' => __('São Tomé and Príncipe dobra', 'acadlix'), 'SAR' => __('Saudi riyal', 'acadlix'), 'SCR' => __('Seychellois rupee', 'acadlix'), 'SLL' => __('Sierra Leonean leone', 'acadlix'), 'SGD' => __('Singapore dollar', 'acadlix'), 'SBD' => __('Solomon Islands dollar', 'acadlix'), 'SOS' => __('Somali shilling', 'acadlix'), 'SHP' => __('St. Helena pound', 'acadlix'), 'SDG' => __('Sudanese pound', 'acadlix'), 'SRD' => __('Surinamese dollar', 'acadlix'), 'SZL' => __('Swazi lilangeni', 'acadlix'), 'SEK' => __('Swedish krona', 'acadlix'), 'SYP' => __('Syrian pound', 'acadlix'), 'TWD' => __('New Taiwan dollar', 'acadlix'), 'TJS' => __('Tajikistani somoni', 'acadlix'), 'TZS' => __('Tanzanian shilling', 'acadlix'), 'THB' => __('Thai baht ', 'acadlix'), 'TOP' => __('Tongan pa’anga', 'acadlix'), 'TTD' => __('Trinidad and Tobago dollar', 'acadlix'), 'TND' => __('Tunisian dinar', 'acadlix'), 'TRY' => __('Turkish lira', 'acadlix'), 'TMT' => __('Turkmenistani manat', 'acadlix'), 'UGX' => __('Ugandan shilling', 'acadlix'), 'UAH' => __('Ukrainian hryvnia', 'acadlix'), 'USD' => __('US dollar', 'acadlix'), 'UYU' => __('Uruguayan peso', 'acadlix'), 'UZS' => __('Uzbekistani som', 'acadlix'), 'VUV' => __('Vanuatu vatu', 'acadlix'), 'VEF' => __('Venezuelan bolivar', 'acadlix'), 'VND' => __('Vietnamese dong', 'acadlix'), 'WST' => __('Samoan tālā', 'acadlix'), 'XCD' => __('East Caribbean dollar', 'acadlix'), 'XOF' => __('West African CFA franc', 'acadlix'), 'XAF' => __('Central African CFA franc', 'acadlix'), 'XPF' => __('CFP franc', 'acadlix'), 'YER' => __('Yemeni rial', 'acadlix'), 'ZAR' => __('South African rand', 'acadlix'), 'ZMK' => __('Zambian kwacha', 'acadlix'), 'ZWL' => __('Zimbabwean dollar', 'acadlix'), ); asort($currencies); return $currencies; } public function acadlix_currency_symbols() { $symbols = array( 'AED' => 'د.إ', 'AFN' => '؋', 'ALL' => 'L', 'AMD' => 'AMD', 'ANG' => 'ƒ', 'AOA' => 'Kz', 'ARS' => '$', 'AUD' => '$', 'AWG' => 'Afl.', 'AZN' => 'AZN', 'BAM' => 'KM', 'BBD' => '$', 'BDT' => '৳ ', 'BGN' => 'лв.', 'BHD' => '.د.ب', 'BIF' => 'Fr', 'BMD' => '$', 'BND' => '$', 'BOB' => 'Bs.', 'BRL' => 'R$', 'BSD' => '$', 'BTC' => '฿', 'BTN' => 'Nu.', 'BWP' => 'P', 'BYR' => 'Br', 'BYN' => 'Br', 'BZD' => '$', 'CAD' => '$', 'CDF' => 'Fr', 'CHF' => 'CHF', 'CLP' => '$', 'CNY' => '¥', 'COP' => '$', 'CRC' => '₡', 'CUC' => '$', 'CUP' => '$', 'CVE' => '$', 'CZK' => 'Kč', 'DJF' => 'Fr', 'DKK' => 'DKK', 'DOP' => 'RD$', 'DZD' => 'د.ج', 'EGP' => 'EGP', 'ERN' => 'Nfk', 'ETB' => 'Br', 'EUR' => '€', 'FJD' => '$', 'FKP' => '£', 'GBP' => '£', 'GEL' => '₾', 'GGP' => '£', 'GHS' => '₵', 'GIP' => '£', 'GMD' => 'D', 'GNF' => 'Fr', 'GTQ' => 'Q', 'GYD' => '$', 'HKD' => '$', 'HNL' => 'L', 'HRK' => 'kn', 'HTG' => 'G', 'HUF' => 'Ft', 'IDR' => 'Rp', 'ILS' => '₪', 'IMP' => '£', 'INR' => '₹', 'IQD' => 'ع.د', 'IRR' => '﷼', 'IRT' => 'تومان', 'ISK' => 'kr.', 'JEP' => '£', 'JMD' => '$', 'JOD' => 'د.ا', 'JPY' => '¥', 'KES' => 'KSh', 'KGS' => 'сом', 'KHR' => '៛', 'KMF' => 'Fr', 'KPW' => '₩', 'KRW' => '₩', 'KWD' => 'د.ك', 'KYD' => '$', 'KZT' => '₸', 'LAK' => '₭', 'LBP' => 'ل.ل', 'LKR' => 'රු', 'LRD' => '$', 'LSL' => 'L', 'LYD' => 'ل.د', 'MAD' => 'د.م.', 'MDL' => 'MDL', 'MGA' => 'Ar', 'MKD' => 'ден', 'MMK' => 'Ks', 'MNT' => '₮', 'MOP' => 'P', 'MRU' => 'UM', 'MUR' => '₨', 'MVR' => '.ރ', 'MWK' => 'MK', 'MXN' => '$', 'MYR' => 'RM', 'MZN' => 'MT', 'NAD' => 'N$', 'NGN' => '₦', 'NIO' => 'C$', 'NOK' => 'kr', 'NPR' => '₨', 'NZD' => '$', 'OMR' => 'ر.ع.', 'PAB' => 'B/.', 'PEN' => 'S/', 'PGK' => 'K', 'PHP' => '₱', 'PKR' => '₨', 'PLN' => 'zł', 'PRB' => 'р.', 'PYG' => '₲', 'QAR' => 'ر.ق', 'RMB' => '¥', 'RON' => 'lei', 'RSD' => 'рсд', 'RUB' => '₽', 'RWF' => 'Fr', 'SAR' => 'ر.س', 'SBD' => '$', 'SCR' => '₨', 'SDG' => 'ج.س.', 'SEK' => 'kr', 'SGD' => '$', 'SHP' => '£', 'SLL' => 'Le', 'SOS' => 'Sh', 'SRD' => '$', 'SSP' => '£', 'STN' => 'Db', 'SYP' => 'ل.س', 'SZL' => 'L', 'THB' => '฿', 'TJS' => 'ЅМ', 'TMT' => 'm', 'TND' => 'د.ت', 'TOP' => 'T$', 'TRY' => '₺', 'TTD' => '$', 'TWD' => 'NT$', 'TZS' => 'Sh', 'UAH' => '₴', 'UGX' => 'UGX', 'USD' => '$', 'UYU' => '$', 'UZS' => 'UZS', 'VEF' => 'Bs F', 'VES' => 'Bs.S', 'VND' => '₫', 'VUV' => 'Vt', 'WST' => 'T', 'XAF' => 'CFA', 'XCD' => '$', 'XOF' => 'CFA', 'XPF' => 'Fr', 'YER' => '﷼', 'ZAR' => 'R', 'ZMW' => 'ZK', ); return $symbols; } public function acadlix_get_currency_with_symbols() { $currency_symbol = []; $currencies = $this->acadlix_currencies(); $symbol = $this->acadlix_currency_symbols(); $i = 0; foreach ($currencies as $key => $currency) { $currency_symbol[$i]['short_name'] = $key; $currency_symbol[$i]['name'] = $currency; $currency_symbol[$i]['symbol'] = $symbol[$key] ?? ''; $i++; } return $currency_symbol; } public function acadlix_options() { $options = [ // Page Setup 'acadlix_dashboard_page_id' => null, // 'acadlix_cart_page_id' => null, 'acadlix_checkout_page_id' => null, 'acadlix_thankyou_page_id' => null, 'acadlix_one_click_checkout' => 'no', // Course Option 'acadlix_no_of_courses_per_page' => 12, 'acadlix_column_per_row' => 3, 'acadlix_disable_wishlist' => 'no', 'acadlix_enable_rating_and_reviews' => 'no', 'acadlix_require_admin_approval_for_reviews' => 'no', 'acadlix_review_pagination_count' => 10, 'acadlix_enable_course_filters' => 'no', 'acadlix_disable_student_enrolled' => 'no', // Currency Option 'acadlix_currency' => 'USD', 'acadlix_currency_position' => 'Left ( $99.99 )', 'acadlix_thousand_separator' => ',', 'acadlix_decimal_separator' => '.', 'acadlix_number_of_decimals' => 2, 'acadlix_default_payment_gateway' => '', // Admin Option 'acadlix_default_rows_per_page' => 20, 'acadlix_admin_auto_registration_to_courses' => 'no', 'acadlix_admin_can_assign_courses_to_student' => 'no', 'acadlix_admin_can_remove_student_from_course' => 'no', 'acadlix_enable_gutenberg_editor_for_lesson' => 'no', // Front end Option 'acadlix_disable_admin_toolbar' => 'no', 'acadlix_enable_content_protection' => 'no', // Student Dashboard Option 'acadlix_logout_redirect_url' => '', 'acadlix_enable_dashboard_fullwidth' => 'no', 'acadlix_enable_site_logo_in_header' => 'no', 'acadlix_custom_logo' => [], 'acadlix_show_dark_mode_toggle' => 'no', 'acadlix_show_font_resizer' => 'no', 'acadlix_enable_course_content_prefetch' => 'no', // Checkout Option 'acadlix_enable_coupon_code' => 'no', // Data management 'acadlix_delete_data_on_plugin_uninstall' => 'no', // Notification option 'acadlix_notify_course_purchase_to_student' => 'no', 'acadlix_notify_course_purchase_to_admin' => 'no', 'acadlix_notify_course_completion_to_student' => 'no', 'acadlix_notify_course_completion_to_admin' => 'no', 'acadlix_notify_failed_transaction_to_student' => 'no', 'acadlix_notify_failed_transaction_to_admin' => 'no', 'acadlix_notify_offline_purchase_to_student' => 'no', 'acadlix_notify_offline_purchase_to_admin' => 'no', // Permalink option 'acadlix_course_base' => 'courses', 'acadlix_course_category_base' => 'course-category', 'acadlix_course_tag_base' => 'course-tag', // Student Dashboard option 'acadlix_enable_student_info' => 'yes', 'acadlix_enable_quick_performance' => 'yes', 'acadlix_enable_advertising_banner' => 'yes', 'acadlix_enable_continue_learning' => 'yes', 'acadlix_enable_course_category' => 'yes', 'acadlix_enable_recently_joined_courses' => 'yes', 'acadlix_enable_popular_courses' => 'yes', 'acadlix_enable_new_courses' => 'yes', 'acadlix_enable_subject_performance' => 'yes', 'acadlix_enable_recently_attempted_quizzes' => 'yes', 'acadlix_course_link_type_desktop' => 'public', 'acadlix_course_link_type_mobile' => 'public', //Advertisement banner options 'acadlix_advertising_banners' => [], // DB option 'acadlix_db_version' => '', // Certificate option 'acadlix_certificate_authorised_name' => '', 'acadlix_certificate_authorised_company' => '', 'acadlix_certificate_show_instructor_name_on_certificate' => 'no', 'acadlix_certificate_show_course_completion_date_on_certificate' => 'no', 'acadlix_certificate_page_id' => null, 'acadlix_certificate_show_certificate_link_in_email' => 'no', 'acadlix_certificate_signature' => [], 'acadlix_certificate_template' => 'classic-landscape', // Authentication option 'acadlix_default_auth_screen' => 'login', 'acadlix_registration_fields' => $this->acadlix_registration_fields(), 'acadlix_enable_fraud_protection' => 'no', 'acadlix_v3_site_key' => '', // Caching option 'acadlix_student_dashboard_offline_cache' => 'no', 'acadlix_student_dashboard_cache_ttl_days' => 7, 'acadlix_student_dashboard_cache_buster' => 'acadlix-v1', 'acadlix_student_dashboard_cache_debug' => 'no', ]; // Filter options $options = apply_filters('acadlix_options', $options); return $options; } public function acadlix_advance_options() { $options = [ 'acadlix_razorpay_active' => 'no', 'acadlix_razorpay_client_id' => '', 'acadlix_razorpay_secret_key' => '', 'acadlix_razorpay_webhook_secret' => '', 'acadlix_razorpay_webhook_url' => $this->acadlix_get_webhook_url('razorpay'), 'acadlix_paypal_active' => 'no', 'acadlix_paypal_client_id' => '', 'acadlix_paypal_secret_key' => '', 'acadlix_paypal_sandbox' => 'no', 'acadlix_paypal_webhook_id' => '', 'acadlix_paypal_webhook_url' => $this->acadlix_get_webhook_url('paypal'), 'acadlix_payu_active' => 'no', 'acadlix_payu_merchant_key' => '', 'acadlix_payu_salt' => '', 'acadlix_payu_sandbox' => 'no', 'acadlix_payu_webhook_url' => $this->acadlix_get_webhook_url('payu'), 'acadlix_offline_payment' => 'no', 'acadlix_stripe_active' => 'no', 'acadlix_stripe_public_key' => '', 'acadlix_stripe_secret_key' => '', 'acadlix_stripe_sandbox' => 'no', 'acadlix_stripe_webhook_signature_key' => '', 'acadlix_stripe_webhook_url' => $this->acadlix_get_webhook_url('stripe'), 'acadlix_v3_secret_key' => '', 'acadlix_offline_active' => 'no', 'acadlix_offline_instructions' => '', 'acadlix_offline_enable_file_upload' => 'no', 'acadlix_offline_max_upload_file_size' => 2, 'acadlix_offline_allowed_mime_types' => [], 'acadlix_knit_pay_active' => 'no', 'acadlix_knit_pay_title' => 'Online Payment', 'acadlix_knit_pay_configuration' => '', 'acadlix_knit_pay_description' => '', 'acadlix_openai_api_key' => null, ]; return $options; } public function acadlix_registration_fields() { return [ [ 'id' => 'first_name', 'name' => 'First Name', 'label' => 'First Name', 'placeholder' => 'First Name', 'type' => 'text', 'meta_key' => 'first_name', 'is_meta' => true, 'default' => true, 'enabled' => false, 'required' => false, 'value' => '', 'settings' => [ "width" => "50%", ], 'controls' => [ 'can_disable' => true, 'can_require_toggle' => true, 'can_delete' => false, 'can_edit_name' => false, 'can_edit_type' => false, 'can_edit_meta_key' => false, 'can_edit_label' => true, 'can_edit_placeholder' => true, ], ], [ 'id' => 'last_name', 'name' => 'Last Name', 'label' => 'Last Name', 'placeholder' => 'Last Name', 'type' => 'text', 'meta_key' => 'last_name', 'is_meta' => true, 'default' => true, 'enabled' => false, 'required' => false, 'value' => '', 'settings' => [ "width" => "50%", ], 'controls' => [ 'can_disable' => true, 'can_require_toggle' => true, 'can_delete' => false, 'can_edit_name' => false, 'can_edit_type' => false, 'can_edit_meta_key' => false, 'can_edit_label' => true, 'can_edit_placeholder' => true, ], ], [ 'id' => 'username', 'name' => 'Username', 'label' => 'Username', 'placeholder' => 'Username', 'type' => 'text', 'meta_key' => 'user_login', 'is_meta' => false, 'default' => true, 'enabled' => true, 'required' => true, 'value' => '', 'controls' => [ 'can_disable' => true, 'can_require_toggle' => true, 'can_delete' => false, 'can_edit_name' => false, 'can_edit_type' => false, 'can_edit_meta_key' => false, 'can_edit_label' => true, 'can_edit_placeholder' => true, ], ], [ 'id' => 'email', 'name' => 'Email', 'label' => 'Email', 'placeholder' => 'Email', 'type' => 'email', 'meta_key' => 'user_email', 'is_meta' => false, 'default' => true, 'enabled' => true, 'required' => true, 'value' => '', 'controls' => [ 'can_disable' => false, 'can_require_toggle' => false, 'can_delete' => false, 'can_edit_name' => false, 'can_edit_type' => false, 'can_edit_meta_key' => false, 'can_edit_label' => true, 'can_edit_placeholder' => true, ], ], [ 'id' => 'password', 'name' => 'Password', 'label' => 'Password', 'placeholder' => 'Password', 'type' => 'password', 'meta_key' => 'user_pass', 'is_meta' => false, 'default' => true, 'enabled' => true, 'required' => true, 'value' => '', 'controls' => [ 'can_disable' => false, 'can_require_toggle' => false, 'can_delete' => false, 'can_edit_name' => false, 'can_edit_type' => false, 'can_edit_meta_key' => false, 'can_edit_label' => true, 'can_edit_placeholder' => true, ], ], [ 'id' => 'confirm_password', 'name' => 'Confirm Password', 'label' => 'Confirm Password', 'placeholder' => 'Confirm Password', 'type' => 'password', 'meta_key' => 'user_pass', 'is_meta' => false, 'default' => true, 'enabled' => true, 'required' => true, 'value' => '', 'controls' => [ 'can_disable' => true, 'can_require_toggle' => true, 'can_delete' => false, 'can_edit_name' => false, 'can_edit_type' => false, 'can_edit_meta_key' => false, 'can_edit_label' => true, 'can_edit_placeholder' => true, ], ], [ 'id' => 'phone_number', 'name' => 'Phone Number', 'label' => 'Phone Number', 'placeholder' => 'Phone Number', 'type' => 'tel', 'meta_key' => get_option("acadlix_phone_user_meta_key", "_acadlix_profile_phone_number"), 'is_meta' => true, 'default' => true, 'enabled' => get_option("acadlix_registration_options", [])['phone']['enabled'] ?? false, 'required' => get_option("acadlix_registration_options", [])['phone']['required'] ?? false, 'value' => '', 'settings' => [ 'phonecode' => [ 'enabled' => true, 'placeholder' => 'Code', 'default' => get_option("acadlix_registration_options", [])['phone']['default_phonecode'] ?? '', 'meta_key' => get_option("acadlix_phonecode_user_meta_key", "_acadlix_profile_phonecode"), 'value' => get_option("acadlix_registration_options", [])['phone']['default_phonecode'] ?? '', ], 'isocode' => [ 'default' => get_option("acadlix_registration_options", [])['phone']['default_isocode'] ?? '', 'meta_key' => get_option("acadlix_isocode_user_meta_key", "_acadlix_profile_isocode"), 'value' => get_option("acadlix_registration_options", [])['phone']['default_isocode'] ?? '', ] ], 'controls' => [ 'can_disable' => true, 'can_require_toggle' => true, 'can_delete' => false, 'can_edit_name' => false, 'can_edit_type' => false, 'can_edit_meta_key' => false, 'can_edit_label' => true, 'can_edit_placeholder' => true, ], ], [ 'id' => 'user_url', 'name' => 'Website', 'label' => 'Website', 'placeholder' => 'https://example.com', 'type' => 'url', 'meta_key' => 'user_url', 'is_meta' => false, 'default' => true, 'enabled' => false, 'required' => false, 'value' => '', 'controls' => [ 'can_disable' => true, 'can_require_toggle' => true, 'can_delete' => false, 'can_edit_name' => false, 'can_edit_type' => false, 'can_edit_meta_key' => false, 'can_edit_label' => true, 'can_edit_placeholder' => true, ], ], [ 'id' => 'address', 'name' => 'Address', 'label' => 'Address', 'placeholder' => 'Address', 'type' => 'text', 'meta_key' => '_acadlix_profile_address', 'is_meta' => true, 'default' => true, 'enabled' => false, 'required' => false, 'value' => '', 'controls' => [ 'can_disable' => true, 'can_require_toggle' => true, 'can_delete' => false, 'can_edit_name' => false, 'can_edit_type' => false, 'can_edit_meta_key' => false, 'can_edit_label' => true, 'can_edit_placeholder' => true, ], ], [ 'id' => 'country', 'name' => 'Country', 'label' => 'Country', 'placeholder' => 'Country', 'type' => 'country', 'meta_key' => '_acadlix_profile_country', 'is_meta' => true, 'default' => true, 'enabled' => false, 'required' => false, 'value' => '', 'controls' => [ 'can_disable' => true, 'can_require_toggle' => true, 'can_delete' => false, 'can_edit_name' => false, 'can_edit_type' => false, 'can_edit_meta_key' => false, 'can_edit_label' => true, 'can_edit_placeholder' => true, ], ], [ 'id' => 'city', 'name' => 'City', 'label' => 'City', 'placeholder' => 'City', 'type' => 'text', 'meta_key' => '_acadlix_profile_city', 'is_meta' => true, 'default' => true, 'enabled' => false, 'required' => false, 'value' => '', 'controls' => [ 'can_disable' => true, 'can_require_toggle' => true, 'can_delete' => false, 'can_edit_name' => false, 'can_edit_type' => false, 'can_edit_meta_key' => false, 'can_edit_label' => true, 'can_edit_placeholder' => true, ], ], [ 'id' => 'zip_code', 'name' => 'Zip Code', 'label' => 'Zip Code', 'placeholder' => 'Zip Code', 'type' => 'text', 'meta_key' => '_acadlix_profile_zip_code', 'is_meta' => true, 'default' => true, 'enabled' => false, 'required' => false, 'value' => '', 'controls' => [ 'can_disable' => true, 'can_require_toggle' => true, 'can_delete' => false, 'can_edit_name' => false, 'can_edit_type' => false, 'can_edit_meta_key' => false, 'can_edit_label' => true, 'can_edit_placeholder' => true, ], ], [ 'id' => 'bio', 'name' => 'Bio', 'label' => 'Bio', 'placeholder' => 'Bio', 'type' => 'textarea', 'meta_key' => 'description', 'is_meta' => true, 'default' => true, 'enabled' => false, 'required' => false, 'value' => '', 'controls' => [ 'can_disable' => true, 'can_require_toggle' => true, 'can_delete' => false, 'can_edit_name' => false, 'can_edit_type' => false, 'can_edit_meta_key' => false, 'can_edit_label' => true, 'can_edit_placeholder' => true, ], ], ]; } public function acadlix_get_webhook_url($payment_method = 'paypal') { // Ensure method is safe for URLs $payment_method = sanitize_key($payment_method); // Base REST route $rest_route = sprintf( 'acadlix/v1/front-checkout/handle-webhook' ); // Build REST URL $url = add_query_arg( array('payment_method' => $payment_method), rest_url($rest_route) ); return esc_url_raw($url); } public function acadlix_get_all_options($options = []) { if (empty($options)) { $options = $this->acadlix_options(); } $new_options = []; if (count($options) > 0) { foreach ($options as $key => $option) { $new_options[$key] = $this->acadlix_get_option($key, $option); } } return $new_options; } public function acadlix_delete_all_options() { $options = $this->acadlix_options(); $advance_options = $this->acadlix_advance_options(); $options = array_merge($options, $advance_options); if (count($options) > 0) { foreach ($options as $key => $option) { $this->acadlix_delete_option($key); } } } public function acadlix_get_option($key = '', $default = false) { $options = $this->acadlix_options(); if (!empty($key)) { return get_option($key, $options[$key] ?? $default); } return $default; } public function acadlix_update_option($key = '', $value = '') { if (!empty($key)) { update_option($key, $value); } } public function acadlix_delete_option($key = '') { if (!empty($key)) { delete_option($key); } } /** * Formats a date string according to the WordPress date format setting. * * @param string $dateStr The date string to format. * * @return string The formatted date string. */ public function formatDate(string $dateStr): string { $dateFormat = $this->acadlix_get_option('date_format'); $dateObj = strtotime($dateStr); $formattedDate = date($dateFormat, $dateObj); // phpcs:ignore return $formattedDate; } public function acadlix_get_time_zone_string() { $timezone = $this->acadlix_get_option('timezone_string'); if ($timezone) { return $timezone; } // get UTC offset, if it isn't set then return UTC $utcOffset = $this->acadlix_get_option('gmt_offset', 0); if ($utcOffset === 0) { return 'UTC'; } // Adjust UTC offset from hours to seconds $utcOffset *= 3600; // Attempt to guess the timezone string from the UTC offset $timezone = timezone_name_from_abbr('', $utcOffset, 0); if ($timezone) { return $timezone; } return 'UTC'; } public function acadlix_get_date_time_format() { return $this->acadlix_get_option('date_format') . ' ' . $this->acadlix_get_option('time_format'); } public function acadlix_ddd($data) { foreach (func_get_args() as $arg) { echo '
';
        print_r($arg);  // phpcs:ignore
        echo '
'; } } public function acadlix_dd($data) { foreach (func_get_args() as $arg) { echo '
';
        print_r($arg);  // phpcs:ignore
        echo '
'; } die; } public function acadlix_max_execution_time() { return ini_get('max_execution_time') * 1000; } public function acadlix_get_email_template($template_name, $type = 'student', $vars = []) { $file_path = ACADLIX_TEMPLATE_PATH . 'email/' . $type . '/' . $template_name; // if (file_exists($file_path)) { // return file_get_contents($file_path); // } // return ''; if (!file_exists($file_path)) { return ''; } extract($vars, EXTR_SKIP); // Start output buffering ob_start(); // Execute the PHP template file include $file_path; // Get the final rendered HTML and clear the buffer $content = ob_get_clean(); return $content; } public function acadlix_preload_scripts($script = '') { $manifest_path = ACADLIX_BUILD_PATH . acadlix()->versionPath . '/manifest.php'; if (file_exists($manifest_path)) { $manifest = include $manifest_path; // Preload dependencies if (!empty($script) && is_array($manifest[$script]['imports'])) { foreach ($manifest[$script]['imports'] as $name => $file) { if (strpos($name, 'vendor-') === 0) { echo '' . "\n"; } } } } } public function acadlix_table_prefix($table_name) { global $wpdb; return "{$wpdb->prefix}acadlix_{$table_name}"; } public function acadlix_wp_prefix($table_name) { global $wpdb; return "{$wpdb->prefix}{$table_name}"; } public function acadlix_fk_prefix($table_name, $key) { global $wpdb; // Base string: wp_tablename_key_fk $full_name = "{$wpdb->prefix}{$table_name}_{$key}_fk"; // If it's over 30 characters, shorten it deterministically if (strlen($full_name) > 30) { // Use an md5 hash snippet of the table + key to keep it unique but short $short_hash = substr(md5($table_name . $key), 0, 6); return "{$wpdb->prefix}fk_{$short_hash}"; } return $full_name; } public function acadlix_index_prefix($table_name, $key) { global $wpdb; // Base string: wp_tablename_key_ix $full_name = "{$wpdb->prefix}{$table_name}_{$key}_ix"; // If it's over 30 characters, shorten it deterministically if (strlen($full_name) > 30) { // Use an md5 hash snippet of the table + key to keep it unique but short $short_hash = substr(md5($table_name . $key), 0, 6); return "{$wpdb->prefix}ix_{$short_hash}"; } return $full_name; } public function acadlix_old_fk_prefix($table_name, $key) { return "acadlix_{$table_name}_{$key}_foreign"; } public function acadlix_old_index_prefix($table_name, $key) { return "acadlix_{$table_name}_{$key}_index"; } public function acadlix_update_fk($table, $oldConstraint, $column, $referencedTable, $newConstraint) { global $wpdb; $exists = $wpdb->get_var( // phpcs:ignore $wpdb->prepare( 'SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND CONSTRAINT_NAME = %s', DB_NAME, $table, $oldConstraint ) ); if ($exists) { try { Manager::schema()->table($table, function ($table) use ($oldConstraint) { $table->dropForeign($oldConstraint); }); } catch (\Exception $e) { // Fail silently or log if needed } } // Step 2: Check if new constraint already exists $newExists = $wpdb->get_var( // phpcs:ignore $wpdb->prepare( 'SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND CONSTRAINT_NAME = %s', DB_NAME, $table, $newConstraint ) ); if (!$newExists) { Manager::schema()->table($table, function ($table) use ($column, $referencedTable, $newConstraint) { $table ->foreign($column, $newConstraint) ->references('id') ->on($referencedTable) ->onDelete('cascade'); }); } } public function acadlix_update_index($table, $oldIndex, $columns, $newIndex) { global $wpdb; // Step 1: Drop old index if it exists $oldExists = $wpdb->get_var( // phpcs:ignore $wpdb->prepare( 'SELECT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND INDEX_NAME = %s', DB_NAME, $table, $oldIndex ) ); if ($oldExists) { try { Manager::schema()->table($table, function ($table) use ($oldIndex) { $table->dropIndex($oldIndex); // Drop old index }); } catch (\Throwable $e) { // Optional: log or silently ignore } } $newExists = $wpdb->get_var( // phpcs:ignore $wpdb->prepare( 'SELECT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND INDEX_NAME = %s', DB_NAME, $table, $newIndex ) ); if (!$newExists) { Manager::schema()->table($table, function ($table) use ($columns, $newIndex) { $table->index($columns, $newIndex); }); } } public function acadlix_get_all_addons(): array { return [ [ 'name' => __('Knit Pay Integration', 'acadlix'), 'description' => __('Knit pay integration for seamless payment processing.', 'acadlix'), 'pro' => false, 'internal' => true, 'installed' => true, 'active' => $this->acadlix_get_option('acadlix_addon_knit_pay_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_knit_pay_enabled', 'icon' => 'KnitPayIcon', 'icon_color' => '#efefef', 'disabled' => acadlix()->integrations()->knit_pay()->is_active() ? false : true, ], [ 'name' => __('Bunny.net Integration', 'acadlix'), 'description' => __('Bunny.net integration for efficient video content management.', 'acadlix'), 'pro' => false, 'internal' => true, 'installed' => true, 'active' => $this->acadlix_get_option('acadlix_addon_bunny_net_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_bunny_net_enabled', 'icon' => 'BunnyNetIcon', 'icon_color' => '#efefef', 'disabled' => false, ], [ 'name' => __('Acadlix Bulk Question Upload', 'acadlix'), 'description' => __('Upload hundreds of questions in seconds via MS Word.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => true, 'active' => $this->acadlix_get_option('acadlix_addon_bulk_question_upload_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_bulk_question_upload_enabled', 'icon' => 'FaCloudUploadAlt', 'icon_color' => 'red', 'disabled' => false, ], [ 'name' => __('Acadlix Assignments', 'acadlix'), 'description' => __('Create, assign, and evaluate student assignments seamlessly.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => true, 'active' => $this->acadlix_get_option('acadlix_addon_assignments_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_assignments_enabled', 'icon' => 'MdAssignment', 'icon_color' => '#ffa65a', 'disabled' => false, ], [ 'name' => __('Acadlix Zoom Integration', 'acadlix'), 'description' => __('Schedule and manage live classes via Zoom from your dashboard.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => true, 'active' => $this->acadlix_get_option('acadlix_addon_zoom_integration_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_zoom_integration_enabled', 'icon' => 'BiLogoZoom', 'icon_color' => '#2d8cff', 'disabled' => false, ], [ 'name' => __('Acadlix Advanced Report', 'acadlix'), 'description' => __('Generate detailed reports for your students and courses.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => true, 'active' => $this->acadlix_get_option('acadlix_addon_advanced_report_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_advanced_report_enabled', 'icon' => 'HiDocumentReport', 'icon_color' => '#4A90E2', 'disabled' => false, ], [ 'name' => __('Acadlix Subscriptions', 'acadlix'), 'description' => __('Manage subscriptions for your students, courses and course bundles.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => true, 'active' => $this->acadlix_get_option('acadlix_addon_subscriptions_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_subscriptions_enabled', 'icon' => 'MdSubscriptions', 'icon_color' => '#22C55E', 'disabled' => false, ], [ 'name' => __('Acadlix Data Exporter', 'acadlix'), 'description' => __('Export results, answer sheets in PDF or Excel.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => true, 'active' => $this->acadlix_get_option('acadlix_addon_data_exporter_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_data_exporter_enabled', 'icon' => 'FaFileExport', 'icon_color' => '#3B82F6', 'disabled' => false, ], [ 'name' => __('Question Error Reporting', 'acadlix'), 'description' => __('This addon lets students flag and report incorrect or unclear questions, helping admins maintain accuracy and improve question quality.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => true, 'active' => $this->acadlix_get_option('acadlix_addon_question_error_reporting_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_question_error_reporting_enabled', 'icon' => 'TbAlertTriangleFilled', 'icon_color' => '#FFAB00', 'disabled' => false, ], [ 'name' => __('Social Login', 'acadlix'), 'description' => __('Allow users to log in using their social media accounts.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => true, 'active' => $this->acadlix_get_option('acadlix_addon_social_login_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_social_login_enabled', 'icon' => 'SocialLogin', 'icon_color' => '#3B82F6', 'disabled' => false, ], [ 'name' => __('Course Bundle', 'acadlix'), 'description' => __('Allow users to purchase multiple courses as a bundle.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => true, 'active' => $this->acadlix_get_option('acadlix_addon_course_bundle_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_course_bundle_enabled', 'icon' => 'FaLayerGroup', 'icon_color' => '#7C3AED', 'disabled' => false, ], [ 'name' => __('Google Meet', 'acadlix'), 'description' => __('Schedule and manage live classes via Google Meet from your dashboard.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => false, 'active' => $this->acadlix_get_option('acadlix_addon_google_meet_integration_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_google_meet_integration_enabled', 'icon' => 'GoogleMeet', 'icon_color' => '#e9ecea', 'disabled' => false, ], [ 'name' => __('Microsoft Teams', 'acadlix'), 'description' => __('Schedule and manage live classes via Microsoft Teams from your dashboard.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => false, 'active' => $this->acadlix_get_option('acadlix_addon_microsoft_teams_integration_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_microsoft_teams_integration_enabled', 'icon' => 'BsMicrosoftTeams', 'icon_color' => '#5553c1', 'disabled' => false, ], [ 'name' => __('Drip Content', 'acadlix'), 'description' => __('Release course lessons, sections, or quizzes automatically based on schedule or completion progress. Keep learners engaged with a structured learning path and prevent content overload.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => false, 'active' => $this->acadlix_get_option('acadlix_addon_drip_content_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_drip_content_enabled', 'icon' => 'MdAccountTree', 'icon_color' => '#13647a', 'disabled' => false, ], [ 'name' => __('PWA', 'acadlix'), 'description' => __('Progressive Web App (PWA) support for offline access and improved performance.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => false, 'active' => $this->acadlix_get_option('acadlix_addon_pwa_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_pwa_enabled', 'icon' => 'MdInstallMobile', 'icon_color' => '#3B0764', 'disabled' => false, ], [ 'name' => __('Youtube live streaming', 'acadlix'), 'description' => __('Add YouTube Live Streams directly to your Acadlix courses, allowing students to join live classes from within the course.', 'acadlix'), 'pro' => true, 'internal' => true, 'installed' => false, 'active' => $this->acadlix_get_option('acadlix_addon_youtube_live_streaming_enabled', false) == 'yes', 'url' => '', 'option_name' => 'acadlix_addon_youtube_live_streaming_enabled', 'icon' => 'FaYoutube', 'icon_color' => '#efefef', 'disabled' => false, ], ]; } public function is_youtube_live_streaming_addon_active() { $value = get_option('acadlix_addon_youtube_live_streaming_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function is_pwa_addon_active() { $value = get_option('acadlix_addon_pwa_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function is_knit_pay_integration_addon_active() { $value = get_option('acadlix_addon_knit_pay_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->integrations()->knit_pay()->is_active()) { return false; } return true; } public function is_bunny_net_integration_addon_active() { $value = get_option('acadlix_addon_bunny_net_enabled', false); if ($value != 'yes') { return false; } return true; } public function is_drip_content_addon_active() { $value = get_option('acadlix_addon_drip_content_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function is_microsoft_teams_integration_addon_active() { $value = get_option('acadlix_addon_microsoft_teams_integration_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function is_google_meet_integration_addon_active() { $value = get_option('acadlix_addon_google_meet_integration_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function is_social_login_addon_active() { $value = get_option('acadlix_addon_social_login_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function is_bulk_question_addon_active() { $value = get_option('acadlix_addon_bulk_question_upload_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function is_assignment_addon_active() { $value = get_option('acadlix_addon_assignments_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function is_zoom_integration_addon_active() { $value = get_option('acadlix_addon_zoom_integration_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function is_advanced_report_addon_active() { $value = get_option('acadlix_addon_advanced_report_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function is_subscriptions_addon_active() { $value = get_option('acadlix_addon_subscriptions_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function is_data_exporter_addon_active() { $value = get_option('acadlix_addon_data_exporter_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function is_question_error_reporting_addon_active() { $value = get_option('acadlix_addon_question_error_reporting_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function is_course_bundle_addon_active() { $value = get_option('acadlix_addon_course_bundle_enabled', false); if ($value != 'yes') { return false; } if (!acadlix()->pro) { return false; } else { if (!acadlix()->license()->isActive) { return false; } } return true; } public function acadlix_delete_addon_options() { $options = $this->acadlix_get_all_addons(); foreach ($options as $option) { $this->acadlix_delete_option($option['option_name']); } } public function acadlix_extra_options() { return [ 'acadlix_theme_settings', 'acadlix_free_course_enrollment_update', 'acadlix_wizard', 'acadlix_deactivated_plugin_notice', 'acadlix_activation_pending', 'acadlix_phone_user_meta_key', 'acadlix_phonecode_user_meta_key', 'acadlix_flush_rewrite', 'acadlix_registration_options' ]; } public function acadlix_delete_extra_options() { $options = $this->acadlix_extra_options(); foreach ($options as $option) { $this->acadlix_delete_option($option); } } private function acadlix_allowed_html() { return [ 'main' => ['id' => true, 'class' => true, 'role' => true, 'style' => true], 'section' => ['id' => true, 'class' => true, 'style' => true], 'header' => ['id' => true, 'class' => true, 'style' => true], 'footer' => ['id' => true, 'class' => true, 'style' => true], 'nav' => ['id' => true, 'class' => true, 'role' => true, 'style' => true, 'aria-*' => true], 'div' => ['id' => true, 'class' => true, 'style' => true, 'role' => true, 'data-*' => true, 'aria-*' => true], 'span' => ['id' => true, 'class' => true, 'style' => true, 'data-*' => true, 'aria-*' => true], 'p' => ['id' => true, 'class' => true, 'style' => true], 'a' => [ 'href' => true, 'class' => true, 'target' => true, 'rel' => true, 'title' => true, 'id' => true, 'style' => true, 'aria-*' => true, 'data-*' => true, ], 'ul' => ['id' => true, 'class' => true, 'style' => true, 'data-*' => true], 'ol' => ['id' => true, 'class' => true, 'style' => true, 'data-*' => true], 'li' => ['id' => true, 'class' => true, 'style' => true, 'data-*' => true], 'strong' => [], 'em' => [], 'br' => [], 'small' => ['class' => true, 'style' => true], 'h1' => ['id' => true, 'class' => true, 'style' => true], 'h2' => ['id' => true, 'class' => true, 'style' => true], 'h3' => ['id' => true, 'class' => true, 'style' => true], 'h4' => ['id' => true, 'class' => true, 'style' => true], 'h5' => ['id' => true, 'class' => true, 'style' => true], 'h6' => ['id' => true, 'class' => true, 'style' => true], 'img' => [ 'src' => true, 'alt' => true, 'class' => true, 'width' => true, 'height' => true, 'loading' => true, 'srcset' => true, 'sizes' => true, 'style' => true, 'data-*' => true, ], 'i' => ['class' => true, 'aria-hidden' => true, 'style' => true], 'svg' => ['class' => true, 'viewBox' => true, 'xmlns' => true, 'width' => true, 'height' => true, 'aria-hidden' => true, 'focusable' => true, 'role' => true], 'path' => ['d' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true], 'button' => ['type' => true, 'class' => true, 'id' => true, 'data-*' => true, 'aria-*' => true, 'style' => true], 'input' => ['type' => true, 'name' => true, 'value' => true, 'placeholder' => true, 'class' => true, 'id' => true, 'checked' => true, 'disabled' => true, 'aria-*' => true, 'data-*' => true, 'style' => true], 'label' => ['for' => true, 'class' => true, 'id' => true, 'style' => true], 'select' => ['name' => true, 'class' => true, 'id' => true, 'style' => true], 'option' => ['value' => true, 'selected' => true, 'label' => true], 'textarea' => ['name' => true, 'class' => true, 'id' => true, 'rows' => true, 'cols' => true, 'placeholder' => true, 'style' => true], 'form' => ['action' => true, 'method' => true, 'class' => true, 'id' => true, 'style' => true], 'time' => ['datetime' => true, 'class' => true], 'figure' => ['class' => true], 'figcaption' => ['class' => true], 'style' => true, ]; } public function acadlix_render_component($node) { // Allowed HTML for dynamic rendering $allowed_html = $this->acadlix_allowed_html(); // Allow plain string or callable directly if (is_string($node)) { echo wp_kses($node, $allowed_html); return; } if (is_callable($node)) { $output = call_user_func($node); echo wp_kses($output, $allowed_html); return; } if (!is_array($node)) { return; } // Skip if not visible if (isset($node['visible']) && $node['visible'] === false) { return; } $tag = !empty($node['component']) ? strtolower($node['component']) : 'div'; // Special case: "php" pseudo-component if ($tag === 'php' && isset($node['value'])) { $output = is_callable($node['value']) ? call_user_func($node['value']) : $node['value']; echo wp_kses($output, $allowed_html); return; } /** * ---------------------------------------- * Build attributes safely * ---------------------------------------- */ $attributes = ''; if (!empty($node['props']) && is_array($node['props'])) { foreach ($node['props'] as $key => $value) { if (is_bool($value)) { if ($value) { $attributes .= ' ' . esc_attr($key); } continue; } if ($value !== null) { // Special handling for style if ($key === 'style') { $safe_style = safecss_filter_attr($value); if (!empty($safe_style)) { $attributes .= sprintf( ' %s="%s"', esc_attr($key), esc_attr($safe_style) ); } continue; } $attributes .= sprintf( ' %s="%s"', esc_attr($key), esc_attr($value) ); } } } // Self-closing tags $self_closing = in_array( $tag, ['input', 'img', 'br', 'hr', 'meta', 'link'], true ); if ($self_closing) { printf( '<%1$s%2$s />', esc_html($tag), $attributes // phpcs:ignore ); return; } // Output opening tag printf( '<%1$s %2$s>', esc_html($tag), $attributes // phpcs:ignore ); if (array_key_exists('value', $node) && $node['value'] !== null) { echo wp_kses($node['value'], $allowed_html); } /** * ---------------------------------------- * Children * ---------------------------------------- */ if (!empty($node['children']) && is_array($node['children'])) { foreach ($node['children'] as $child) { $this->acadlix_render_component($child); } } /** * ---------------------------------------- * Closing tag * ---------------------------------------- */ printf( '', esc_html($tag) ); } public function acadlix_render_tree($tree) { // $tree = apply_filters('acadlix_render_tree', $tree); if (is_array($tree)) { foreach ($tree as $node) { $this->acadlix_render_component($node); } } elseif (is_string($tree) || is_callable($tree)) { $this->acadlix_render_component($tree); } } public function acadlix_add_to_date($date = '', $format = 'Y-m-d H:i:s', $number = 1, $type = 'day') { // Convert given date to timestamp (WP timezone aware) if (empty($date)) { $date = current_time('timestamp'); } $timestamp = is_numeric($date) ? (int) $date : strtotime($date); if (!$timestamp) { return false; } switch (strtolower($type)) { case 'day': case 'days': $timestamp += $number * DAY_IN_SECONDS; break; case 'week': case 'weeks': $timestamp += $number * WEEK_IN_SECONDS; break; case 'month': case 'months': $timestamp = strtotime("+{$number} month", $timestamp); break; case 'year': case 'years': $timestamp = strtotime("+{$number} year", $timestamp); break; default: return false; } $tz_string = $this->acadlix_get_option('timezone_string') ?: 'UTC'; $timezone = new \DateTimeZone($tz_string); return wp_date($format, $timestamp, $timezone); } public function acadlix_convert_to_unit_price($amount = 0) { // Convert null/empty to string if ($amount === null) { $amount = '0'; } // Get decimal places (default 2) $decimal_places = $this->acadlix_get_option('acadlix_number_of_decimals', 2); $multiplier = pow(10, $decimal_places); // Get thousand separator (default empty string) $thousand_separator = $this->acadlix_get_option('acadlix_thousand_separator', ''); // Remove thousand separator from string if present if ($thousand_separator !== '') { $amount = str_replace($thousand_separator, '', strval($amount)); } // Convert to number and multiply $amount = floatval($amount); return round($amount * $multiplier); } public function acadlix_format_price_for_storage($price) { $decimal_places = $this->acadlix_get_option('acadlix_number_of_decimals', 2); return round((float) $price, (int) $decimal_places); } public function acadlix_format_price_for_display($price) { $decimal_places = $this->acadlix_get_option('acadlix_number_of_decimals', 2); $decimal_separator = $this->acadlix_get_option('acadlix_decimal_separator', '.'); $thousand_separator = $this->acadlix_get_option('acadlix_thousand_separator', ''); return number_format((float) $price, (int) $decimal_places, $decimal_separator, $thousand_separator); } public function acadlix_get_price_with_currency(float|string $price, $currency = '') { $price = (float) preg_replace('/[^\d.-]/', '', (string) $price); if (empty($currency)) { $currency = $this->acadlix_get_option('acadlix_currency'); } $currency_position_option = $this->acadlix_get_option('acadlix_currency_position'); $currency_symbols = $this->acadlix_currency_symbols(); if (!array_key_exists($currency, $currency_symbols)) { return new \WP_Error('currency_symbol_not_found', __('Currency symbol not found for the selected currency.', 'acadlix')); } $currency_symbol = $currency_symbols[$currency]; $price = $this->acadlix_format_price_for_display($price); return match ($currency_position_option) { 'Left ( $99.99 )' => "$currency_symbol$price", 'Right ( 99.99$ )' => "$price$currency_symbol", 'Left with space ( $ 99.99 )' => "$currency_symbol $price", 'Right with space ( 99.99 $ )' => "$price $currency_symbol", default => "$currency_symbol$price", }; } public function acadlix_is_json($string) { if (!is_string($string)) { return false; } json_decode($string); return json_last_error() === JSON_ERROR_NONE; } public function acadlix_format_e164($phone, $country_code) { if (empty($phone) || empty($country_code)) { return ''; } // If already E.164 → trust but verify if (strpos($phone, '+') === 0) { return preg_replace('/[^\+\d]/', '', $phone); } $phone = preg_replace('/\D/', '', $phone); $country_code = preg_replace('/\D/', '', $country_code); // Remove leading zero if (substr($phone, 0, 1) === '0') { $phone = substr($phone, 1); } return '+' . $country_code . $phone; } public function acadlix_css_color($value, $default) { if (!is_string($value)) { return $default; } $value = trim($value); // Hex colors (#fff, #ffffff) if (sanitize_hex_color($value)) { return $value; } // hsl(0–360, 0–100%, 0–100%) if ( preg_match( '/^hsl\(\s*(?:[0-9]|[1-9][0-9]|[12][0-9]{2}|3[0-5][0-9]|360)\s*,\s*(?:100|[0-9]{1,2})%\s*,\s*(?:100|[0-9]{1,2})%\s*\)$/', $value ) ) { return $value; } // rgb(0–255, 0–255, 0–255) if ( preg_match( '/^rgb\(\s*(?:25[0-5]|2[0-4]\d|1?\d{1,2})\s*,\s*(?:25[0-5]|2[0-4]\d|1?\d{1,2})\s*,\s*(?:25[0-5]|2[0-4]\d|1?\d{1,2})\s*\)$/', $value ) ) { return $value; } // rgba(0–255, 0–255, 0–255, 0–1) if ( preg_match( '/^rgba\(\s*(?:25[0-5]|2[0-4]\d|1?\d{1,2})\s*,\s*(?:25[0-5]|2[0-4]\d|1?\d{1,2})\s*,\s*(?:25[0-5]|2[0-4]\d|1?\d{1,2})\s*,\s*(?:0|1|0?\.\d+)\s*\)$/', $value ) ) { return $value; } return $default; } public function acadlix_css_size($value, $default) { if (preg_match('/^\d+(\.\d+)?(px|rem|em|%)$/', (string) $value)) { return $value; } return $default; } public function acadlix_css_number($value, $default) { return is_numeric($value) ? (string) $value : (string) $default; } public function acadlix_css_line_height($value, $default) { if (is_numeric($value) || preg_match('/^\d+(\.\d+)?$/', (string) $value)) { return (string) $value; } return $default; } public function acadlix_generate_certificate_id() { do { $id = 'CRT-' . wp_date('Y') . '-' . strtoupper(substr(bin2hex(random_bytes(4)), 0, 8)); } while ( acadlix()->model()->userActivityMeta()->where('reference_id', $id)->exists() ); return $id; } public function acadlix_httpsify($url) { if (strpos($url, 'localhost') === false) { return str_replace('http://', 'https://', $url); } else { return $url; } } public function acadlix_get_wp_rest_nonce() { return wp_create_nonce('wp_rest'); } public function acadlix_get_api_url() { return esc_url_raw(rest_url('acadlix/v1')); } public function acadlix_sync_user_billing_data($user_id, array $billing_info) { if (!$user_id || empty($billing_info)) { return new WP_Error('invalid_data', __('User ID and billing information are required.', 'acadlix')); } // 1. Define required keys and sanitize input $required_fields = array('first_name', 'email'); foreach ($required_fields as $field) { if (empty($billing_info[$field])) { /* translators: %s is the field name */ return new WP_Error('missing_field', sprintf(__('The %s field is required in billing info.', 'acadlix'), $field)); } } $user = get_userdata($user_id); if (!$user) { return new WP_Error('user_not_found', __('User does not exist.', 'acadlix')); } // 2. Sync standard WordPress User Fields (if currently empty) $user_updates = array('ID' => $user_id); if (empty($user->first_name) && !empty($billing_info['first_name'])) { $user_updates['first_name'] = sanitize_text_field($billing_info['first_name']); } if (empty($user->last_name) && !empty($billing_info['last_name'])) { $user_updates['last_name'] = sanitize_text_field($billing_info['last_name']); } // Update core user record only if needed if (count($user_updates) > 1) { wp_update_user($user_updates); } // 3. Sync User Meta Fields (only update if meta does not exist or is empty) $meta_mappings = array( '_acadlix_profile_phonecode' => 'phonecode', '_acadlix_profile_isocode' => 'isocode', '_acadlix_profile_phone_number' => 'phone_number', '_acadlix_profile_address' => 'address', '_acadlix_profile_country' => 'country', '_acadlix_profile_city' => 'city', '_acadlix_profile_zip_code' => 'zip_code', ); foreach ($meta_mappings as $meta_key => $input_key) { if (!empty($billing_info[$input_key])) { $existing_val = get_user_meta($user_id, $meta_key, true); if (empty($existing_val)) { update_user_meta($user_id, $meta_key, sanitize_text_field($billing_info[$input_key])); } } } return true; } public static function instance() { if (is_null(self::$_instance)) { self::$_instance = new self(); } return self::$_instance; } } }