the_lis()) { // Schedule cleanup of old security logs add_action("wp", [$this, "schedule_log_cleanup"]); add_filter("manage_checker_posts_columns", [ $this, "filter_cpt_columns", ]); add_action( "manage_checker_posts_custom_column", [$this, "action_custom_columns_content"], 10, 2, ); add_action("add_meta_boxes", [$this, "add_checker_metabox"]); add_action("save_post_checker", [$this, "save_checker_metabox"]); add_action("wp_ajax_load_repeater_field_card", [ $this, "load_repeater_field_card", ]); add_action("wp_ajax_load_output_setting", [ $this, "load_output_setting", ]); if (!class_exists("CHECKER_SHORTCODE")) { require "class-Shortcode.php"; } new CHECKER_SHORTCODE(); } add_action("checker_security_log_cleanup", [ $this, "cleanup_security_logs", ]); } public function create_custom_post_type() { $labels = [ "name" => "Checker", "singular_name" => "Checker", "menu_name" => "Checkers", "add_new" => "Add New", "add_new_item" => "Add New Checker", "edit" => "Edit", "edit_item" => "Edit Checker", "new_item" => "New Checker", "view" => "View", "view_item" => "View Checker", "search_items" => "Search Checkers", "not_found" => "No checkers found", "not_found_in_trash" => "No checkers found in trash", "parent" => "Parent Checker", ]; $args = [ "label" => "Checkers", "description" => "Checkers for your sheet data", "labels" => $labels, "public" => false, "menu_position" => 4, "menu_icon" => SHEET_CHECKER_PRO_URL . "assets/icons8-validation-menu-icon.png", "supports" => ["title"], "hierarchical" => true, "taxonomies" => ["category"], "has_archive" => false, "rewrite" => ["slug" => "checkers"], "show_ui" => true, "show_in_menu" => true, "show_in_rest" => false, "query_var" => true, ]; register_post_type("checker", $args); } public function enqueue_bootstrap_admin() { $screen = get_current_screen(); // Check that we are on the 'Checker' post editor screen if ($screen && $screen->id === "checker") { // Enqueue Bootstrap CSS wp_enqueue_style( "bootstrap", "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css", ); // wp_enqueue_style( 'bs-table', 'https://unpkg.com/bootstrap-table@1.22.1/dist/bootstrap-table.min.css' ); wp_enqueue_style( "bs-icon", "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css", ); wp_enqueue_style( "checker-editor", SHEET_CHECKER_PRO_URL . "assets/admin-editor.css?ver=" . SHEET_CHECKER_PRO_VERSION, ); wp_enqueue_style( "datatables", "https://cdn.datatables.net/2.2.2/css/dataTables.dataTables.css", ); // Enqueue Bootstrap JS wp_enqueue_script( "bootstrap", "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js", ["jquery"], "4.5.2", true, ); wp_enqueue_script( "handlebarjs", "https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.8/handlebars.min.js", ["jquery"], "4.7.8", true, ); // wp_enqueue_script( 'bs-table', 'https://unpkg.com/bootstrap-table@1.22.1/dist/bootstrap-table.min.js', ['jquery'], '1.22.1', true ); wp_enqueue_script( "checker-editor", SHEET_CHECKER_PRO_URL . "assets/admin-editor.js", ["jquery", "handlebarjs"], SHEET_CHECKER_PRO_VERSION, true ); // Pass nonce to admin JavaScript - MUST be after enqueue but before interactions script wp_localize_script("checker-editor", "checkerAdminSecurity", [ "nonce" => wp_create_nonce("checker_admin_ajax_nonce"), "ajaxurl" => admin_url("admin-ajax.php"), ]); wp_enqueue_script( "checker-editor-interactions", SHEET_CHECKER_PRO_URL . "assets/admin-editor-interactions.js", ["jquery", "handlebarjs", "checker-editor"], SHEET_CHECKER_PRO_VERSION, true ); wp_enqueue_script( "datatables", "https://cdn.datatables.net/2.2.2/js/dataTables.js", ["jquery"], true, ); wp_enqueue_script( "datatables", "https://cdn.datatables.net/responsive/3.0.4/js/dataTables.responsive.js", ["jquery"], true, ); wp_enqueue_script( "datatables", "https://cdn.datatables.net/responsive/3.0.4/js/responsive.dataTables.js", ["jquery"], true, ); } wp_enqueue_style( "checker-editor", SHEET_CHECKER_PRO_URL . "assets/admin.css?ver=" . SHEET_CHECKER_PRO_VERSION, ); } public function filter_cpt_columns($columns) { // this will add the column to the end of the array $columns["shortcode"] = "Shortcode"; //add more columns as needed // as with all filters, we need to return the passed content/variable return $columns; } public function action_custom_columns_content($column_id, $post_id) { //run a switch statement for all of the custom columns created switch ($column_id) { case "shortcode": echo ''; break; //add more items here as needed, just make sure to use the column_id in the filter for each new item. } } public function add_checker_metabox() { add_meta_box( "dw_checker_preview", "Preview", [$this, "preview_checker_metabox"], "checker", "normal", "high", ); add_meta_box( "dw_checker_setting", "Settings", [$this, "render_checker_metabox"], "checker", "normal", "default", ); } public function save_checker_metabox($post_id) { // Save metabox data if (isset($_POST["checker"])) { $checker = $_POST["checker"]; // Sanitize all values to prevent null deprecation warnings $checker = $this->sanitize_array_recursive($checker); update_post_meta($post_id, "checker", $checker); } } /** * Recursively sanitize array values to prevent null deprecation warnings * Converts null values to empty strings * * @param mixed $data Data to sanitize * @return mixed Sanitized data */ private function sanitize_array_recursive($data) { if (is_array($data)) { foreach ($data as $key => $value) { $data[$key] = $this->sanitize_array_recursive($value); } return $data; } // Convert null to empty string if ($data === null) { return ''; } return $data; } /** * Recursively merge two arrays, with the second array's values taking precedence * Unlike array_merge_recursive, this doesn't create arrays for scalar values * * @param array $defaults Default values * @param array $args Values to merge * @return array Merged array */ private function array_merge_recursive_distinct(array $defaults, array $args) { $merged = $defaults; foreach ($args as $key => $value) { if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) { $merged[$key] = $this->array_merge_recursive_distinct($merged[$key], $value); } else { $merged[$key] = $value; } } return $merged; } public function render_checker_metabox($post) { // Retrieve existing values from the database $checker = get_post_meta($post->ID, "checker", true); $post_id = $post->ID; // Define default values - include ALL keys that templates access $defaults = [ "link" => "", "description" => "", "card" => [ "width" => 500, "background" => "#ffffff", "title" => "#333333", "description" => "#666666", "divider" => "#cccccc", "divider_width" => 1, "title_align" => "center", "description_align" => "center", ], "field" => [ "label" => "block", "label-color" => "#333333", ], "fields" => [], "search_button" => [ "position" => "flex-start", "text" => "Search", "bg_color" => "#333333", "text_color" => "#ffffff", ], "back_button" => [ "position" => "flex-start", "text" => "Back", "bg_color" => "#333333", "text_color" => "#ffffff", ], "result" => [ "initial_display" => "hidden", "filter_mode" => "search", "max_records" => 100, "display" => "vertical-table", "header" => "#333333", "value" => "#666666", "divider" => "#cccccc", "divider_width" => 1, "card_grid" => [ "desktop" => 4, "tablet" => 2, "mobile" => 1, ], ], "url_params" => [ "enabled" => "no", ], "output" => [], ]; // Parse and merge with defaults (deep merge for nested arrays) $checker = is_array($checker) ? $this->array_merge_recursive_distinct($defaults, $checker) : $defaults; require_once SHEET_CHECKER_PRO_PATH . "templates/editor/settings.php"; } public function preview_checker_metabox($post) { // Retrieve existing values from the database $checker = get_post_meta($post->ID, "checker", true); // Define default values $defaults = [ "link" => "", "description" => "", "card" => [ "width" => 500, "background" => "#ffffff", "title" => "#333333", "description" => "#666666", "divider" => "#cccccc", "divider_width" => 1, ], ]; // Parse and merge with defaults $checker = wp_parse_args($checker, $defaults); require_once SHEET_CHECKER_PRO_PATH . "templates/editor/preview.php"; } public function load_repeater_field_card() { $nonce_ok = check_ajax_referer('checker_admin_ajax_nonce', 'security', false); if (false === $nonce_ok && !current_user_can('edit_posts')) { wp_send_json_error('invalid_nonce', 403); } $post_id = isset($_REQUEST['pid']) ? absint($_REQUEST['pid']) : 0; // Require capability for existing posts; for new posts rely on logged-in nonce if ($post_id && !current_user_can('edit_posts')) { wp_send_json_error('Unauthorized request', 403); } if (!$post_id && !is_user_logged_in()) { wp_send_json_error('Unauthorized request', 403); } $checker = get_post_meta($post_id, 'checker', true); $headers_raw = isset($_REQUEST['headers']) ? (array) $_REQUEST['headers'] : []; $headers = array_map('sanitize_text_field', $headers_raw); error_log('[REPEATER] Post ID: ' . $post_id); error_log('[REPEATER] Has fields: ' . (isset($checker['fields']) && count($checker['fields']) > 0 ? 'YES' : 'NO')); error_log('[REPEATER] Headers count: ' . (is_array($headers) ? count($headers) : '0')); $response = []; if (isset($checker['fields']) && count($checker['fields']) > 0) { foreach ($checker['fields'] as $key => $field) { $response[$key] = $field; $rowHeader = []; if (is_array($headers)) { foreach($headers as $index => $header){ $id = '_'.strtolower($header); $rowHeader[$index] = $id; } } $response[$key]['selected_kolom'] = isset($response[$key]['kolom']) ? $response[$key]['kolom'] : ''; $response[$key]['kolom'] = $headers; } } else { // No saved fields - create one default field error_log('[REPEATER] Creating default field'); $response['field_1'] = [ 'type' => 'text', 'label' => '', 'placeholder' => '', 'match' => 'match', 'kolom' => $headers, 'selected_kolom' => is_array($headers) && count($headers) > 0 ? $headers[0] : '' ]; } error_log('[REPEATER] Response keys: ' . print_r(array_keys($response), true)); wp_send_json_success(['fields' => $response]); } public function load_output_setting() { $nonce_ok = check_ajax_referer('checker_admin_ajax_nonce', 'security', false); if (false === $nonce_ok && !current_user_can('edit_posts')) { wp_send_json_error('invalid_nonce', 403); } $post_id = isset($_REQUEST['pid']) ? absint($_REQUEST['pid']) : 0; // Require capability for existing posts; for new posts rely on logged-in nonce if ($post_id && !current_user_can('edit_posts')) { wp_send_json_error('Unauthorized request', 403); } if (!$post_id && !is_user_logged_in()) { wp_send_json_error('Unauthorized request', 403); } $checker = $post_id ? get_post_meta($post_id, 'checker', true) : []; $headers_raw = isset($_REQUEST['headers']) ? (array) $_REQUEST['headers'] : []; $headers = array_map('sanitize_text_field', $headers_raw); // $header = $this->parse_header_kolom($json); if (!empty($headers)) { $output_data = []; foreach ($headers as $key) { $id = strtolower(str_replace([' ', '.'], '_', $key)); $output_data[] = [ 'key' => $key, 'id' => $id, 'hide' => isset($checker['output'][$id]['hide']) ? $checker['output'][$id]['hide'] : 'no', 'type' => isset($checker['output'][$id]['type']) ? $checker['output'][$id]['type'] : 'text', 'button_text' => isset($checker['output'][$id]['button_text']) ? $checker['output'][$id]['button_text'] : '', 'prefix' => isset($checker['output'][$id]['prefix']) ? $checker['output'][$id]['prefix'] : '', 'bg_color' => isset($checker['output'][$id]['bg_color']) ? $checker['output'][$id]['bg_color'] : '#cccccc', 'text_color' => isset($checker['output'][$id]['text_color']) ? $checker['output'][$id]['text_color'] : '#000000', 'display' => isset($checker['result']['display']) && $checker['result']['display'] == 'card' ]; } wp_send_json_success(['data' => $output_data]); } else { wp_send_json_error("No headers found"); } } public function parse_header_kolom($json) { if (!is_array($json)) { $json = json_decode($json, true); } $header = array_keys($json[0]); return $header; } public function parse_options($json, $kolom) { $options = []; if ($json) { foreach ($json as $key => $value) { foreach ($value as $name => $val) { if ($name == $kolom) { if (!in_array($val, $options)) { $options[] = $val; } } } } } return $options; } /** * Schedule cleanup of old security logs */ public function schedule_log_cleanup() { // Schedule cleanup if not already scheduled if (!wp_next_scheduled("checker_security_log_cleanup")) { wp_schedule_event(time(), "daily", "checker_security_log_cleanup"); } } /** * Cleanup old security logs */ public static function cleanup_security_logs() { if (class_exists("CHECKER_SECURITY_LOGGER")) { CHECKER_SECURITY_LOGGER::cleanup_old_logs(90); // Keep logs for 90 days } } }