array( 'type' => array(), 'name' => array(), 'id' => array(), 'class' => array(), 'value' => array(), 'placeholder' => array(), 'min' => array(), 'max' => array(), 'step' => array(), 'checked' => array(), 'required' => array(), 'data-*' => true, ), 'select' => array( 'name' => array(), 'id' => array(), 'class' => array(), 'multiple' => array(), 'data-*' => true, ), 'option' => array( 'value' => array(), 'selected' => array(), ), 'optiongroup' => array( 'label' => array(), ), 'span' => array( 'class' => array(), 'id' => array(), 'data-*' => true, ), 'div' => array( 'class' => array(), 'id' => array(), 'data-*' => true, ), 'label' => array( 'for' => array(), 'class' => array(), ), 'p' => array( 'class' => array(), ), ); // Register custom post type add_action('init', array($this, 'register_post_type')); // Add the post type under WooCommerce menu add_action('admin_menu', array($this, 'add_to_woocommerce_menu')); // Remove original menu item add_action('admin_menu', array($this, 'remove_default_menu_item'), 999); // Add meta box for conditions add_action('add_meta_boxes', array($this, 'add_conditions_meta_box')); // Save conditions when post is saved add_action('save_post_pisol_anfw_notice', array($this, 'save_conditions'), 10, 3); // Enqueue scripts and styles add_action('admin_enqueue_scripts', array($this, 'enqueue_scripts')); // Render trigger dropdown add_action('edit_form_after_title', array($this, 'render_trigger_dropdown')); } /** * Get allowed HTML for condition fields * * @return array Allowed HTML tags and attributes */ public static function get_allowed_html() { return self::$allowed_html; } /** * Register the custom post type */ public function register_post_type() { $labels = array( 'name' => _x('Notifications', 'post type general name', 'advance-notification-for-woocommerce'), 'singular_name' => _x('Notification', 'post type singular name', 'advance-notification-for-woocommerce'), 'menu_name' => _x('Notifications', 'admin menu', 'advance-notification-for-woocommerce'), 'name_admin_bar' => _x('Notification', 'add new on admin bar', 'advance-notification-for-woocommerce'), 'add_new' => _x('Add New', 'notification', 'advance-notification-for-woocommerce'), 'add_new_item' => __('Add New Notification', 'advance-notification-for-woocommerce'), 'new_item' => __('New Notification', 'advance-notification-for-woocommerce'), 'edit_item' => __('Edit Notification', 'advance-notification-for-woocommerce'), 'view_item' => __('View Notification', 'advance-notification-for-woocommerce'), 'all_items' => __('All Notifications', 'advance-notification-for-woocommerce'), 'search_items' => __('Search Notifications', 'advance-notification-for-woocommerce'), 'parent_item_colon' => __('Parent Notifications:', 'advance-notification-for-woocommerce'), 'not_found' => __('No notifications found.', 'advance-notification-for-woocommerce'), 'not_found_in_trash' => __('No notifications found in Trash.', 'advance-notification-for-woocommerce'), ); $args = array( 'labels' => $labels, 'description' => __('Conditional notifications for WooCommerce orders', 'advance-notification-for-woocommerce'), 'public' => false, 'publicly_queryable' => false, 'show_ui' => true, 'show_in_menu' => true, // We'll move this under WooCommerce menu later 'query_var' => false, 'rewrite' => false, 'capability_type' => 'post', 'has_archive' => false, 'hierarchical' => false, 'menu_position' => null, 'supports' => array('title', 'excerpt'), // Added 'excerpt' for short description 'menu_icon' => 'dashicons-tag', ); register_post_type('pisol_anfw_notice', $args); } /** * Add the post type as a submenu under WooCommerce */ public function add_to_woocommerce_menu() { add_submenu_page( 'advance-notification-for-woocommerce', __('Create Notifications', 'advance-notification-for-woocommerce'), __('Create Notifications', 'advance-notification-for-woocommerce'), 'manage_woocommerce', 'edit.php?post_type=pisol_anfw_notice' ); } /** * Remove the default menu item since we're adding it under WooCommerce */ public function remove_default_menu_item() { remove_menu_page('edit.php?post_type=pisol_anfw_notice'); } /** * Add meta box for notification conditions */ public function add_conditions_meta_box() { add_meta_box( 'pisol_anfw_notification_conditions', __('Notification Conditions', 'advance-notification-for-woocommerce'), array($this, 'render_conditions_meta_box'), 'pisol_anfw_notice', 'normal', 'high' ); } /** * Render the conditions meta box content * * @param \WP_Post $post The post object. */ public function render_conditions_meta_box($post) { // Add nonce for security wp_nonce_field('pisol_anfw_save_conditions', 'pisol_anfw_conditions_nonce'); $selected_trigger = get_post_meta($post->ID, '_pisol_anfw_trigger', true); if (empty($selected_trigger)) { echo '

' . esc_html__('Please select and save a Notification Trigger before adding conditions. The conditions metabox will be available after you save.', 'advance-notification-for-woocommerce') . '

'; return; } $conditions = get_post_meta($post->ID, '_pisol_anfw_conditions', true); $conditions = $conditions ? $conditions : array(); $condition_types = $this->get_condition_types($selected_trigger); $logic_operators = array('AND', 'OR'); // Include the template for conditions form include plugin_dir_path(__FILE__) . 'templates/conditions-form.php'; } /** * Get all available condition types from condition classes * * @return array Array of condition types */ private function get_condition_types($selected_trigger = '') { $condition_types = array(); // Apply filter to allow condition classes to register themselves $flat_condition_types = apply_filters('pisol_anfw_condition_types', array(), $selected_trigger); // Get the condition objects to organize by group $condition_objects = apply_filters('pisol_anfw_condition_objects', array(), $selected_trigger); // Create grouped structure if we have condition objects if (!empty($condition_objects)) { $grouped_conditions = array(); // Group conditions by their group property foreach ($condition_objects as $condition) { if (method_exists($condition, 'get_id') && method_exists($condition, 'get_name') && method_exists($condition, 'get_group')) { $group = $condition->get_group(); $id = $condition->get_id(); $name = $condition->get_name(); if (!isset($grouped_conditions[$group])) { $grouped_conditions[$group] = array(); } $grouped_conditions[$group][$id] = $name; } } // Sort groups alphabetically ksort($grouped_conditions); // Within each group, sort conditions alphabetically foreach ($grouped_conditions as $group => $conditions) { asort($conditions); $grouped_conditions[$group] = $conditions; } return $grouped_conditions; } // Fallback to flat structure if no condition objects are available return $flat_condition_types; } /** * Save conditions when the post is saved * * @param int $post_id The post ID. * @param \WP_Post $post The post object. * @param bool $update Whether this is an existing post being updated. */ public function save_conditions($post_id, $post, $update) { // Check if nonce is valid if (!isset($_POST['pisol_anfw_conditions_nonce']) || !wp_verify_nonce( sanitize_text_field(wp_unslash($_POST['pisol_anfw_conditions_nonce'])), 'pisol_anfw_save_conditions')) { return; } // Check if user has permission if (!current_user_can('edit_post', $post_id)) { return; } // Don't save during autosave if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } // Check if correct post type if ('pisol_anfw_notice' !== $post->post_type) { return; } // Sanitize and save conditions if (isset($_POST['pisol_anfw_conditions']) && is_array($_POST['pisol_anfw_conditions'])) { $raw_conditions = map_deep(wp_unslash($_POST['pisol_anfw_conditions']), 'sanitize_text_field'); $conditions = $this->sanitize_conditions($raw_conditions); update_post_meta($post_id, '_pisol_anfw_conditions', $conditions); } else { delete_post_meta($post_id, '_pisol_anfw_conditions'); } // Sanitize and save trigger if (isset($_POST['pisol_anfw_trigger']) && !empty($_POST['pisol_anfw_trigger'])) { update_post_meta($post_id, '_pisol_anfw_trigger', sanitize_text_field(wp_unslash($_POST['pisol_anfw_trigger']))); } } /** * Sanitize the conditions array * * @param array $conditions The raw conditions array. * @return array Sanitized conditions. */ private function sanitize_conditions($conditions) { $sanitized = array(); if (!is_array($conditions)) { return $sanitized; } foreach ($conditions as $group_id => $group) { // Skip empty groups (no conditions or invalid data) if (empty($group['conditions']) || !is_array($group['conditions'])) { continue; } // Initialize this group in the sanitized array $sanitized[$group_id] = array( 'match_type' => isset($group['match_type']) && in_array($group['match_type'], array('all', 'any')) ? sanitize_text_field($group['match_type']) : 'all', 'conditions' => array(), ); // Add logic operator for group (except first group) if (isset($group['logic'])) { $sanitized[$group_id]['logic'] = in_array($group['logic'], array('AND', 'OR')) ? $group['logic'] : 'AND'; } // Process each condition in this group foreach ($group['conditions'] as $condition_id => $condition) { // Skip empty conditions if (empty($condition['type'])) { continue; } $sanitized[$group_id]['conditions'][$condition_id] = array( 'type' => sanitize_text_field($condition['type']), 'operator' => isset($condition['operator']) ? sanitize_text_field($condition['operator']) : '', ); // We no longer need logic for individual conditions // The group's match_type handles this logic now // Handle different value types (array for multi-select or string for regular fields) if (isset($condition['value'])) { if (is_array($condition['value'])) { // For multi-select fields, sanitize each value and join with comma $sanitized_values = array_map('sanitize_text_field', $condition['value']); $sanitized[$group_id]['conditions'][$condition_id]['value'] = $sanitized_values; } else { $sanitized[$group_id]['conditions'][$condition_id]['value'] = sanitize_text_field($condition['value']); } } else { $sanitized[$group_id]['conditions'][$condition_id]['value'] = ''; } } // If no valid conditions in this group after sanitizing, remove the group if (empty($sanitized[$group_id]['conditions'])) { unset($sanitized[$group_id]); } } return $sanitized; } /** * Enqueue scripts and styles for the admin * * @param string $hook Current admin page. */ public function enqueue_scripts($hook) { global $post_type; global $post; // Only enqueue on our post type edit screens if ('pisol_anfw_notice' !== $post_type) { return; } $selected_trigger = get_post_meta($post->ID, '_pisol_anfw_trigger', true); // First check if WooCommerce is active before using WC() if (!function_exists('WC')) { return; } // Enqueue WooCommerce admin styles with correct handle wp_enqueue_style('woocommerce_admin_styles'); // Enqueue SelectWoo script without making it a dependency wp_enqueue_script('selectWoo'); // Enqueue our admin styles with no Select2 dependency wp_enqueue_style( 'pisol-anfw-admin', plugin_dir_url(__FILE__) . 'assets/css/admin.css', array(), '1.0.0' ); wp_enqueue_script( 'pisol-anfw-conditions', plugin_dir_url(__FILE__) . 'assets/js/conditions.js', array('jquery', 'jquery-ui-sortable', 'wp-util', 'selectWoo'), '1.0.0', true ); // Localize script with data wp_localize_script( 'pisol-anfw-conditions', 'pisol_anfw_conditions', array( 'nonce' => wp_create_nonce('pisol_anfw_ajax_nonce'), 'condition_types' => $this->get_condition_types($selected_trigger), 'trigger_type'=> $selected_trigger , 'texts' => array( 'add_group' => __('Add Condition Group', 'advance-notification-for-woocommerce'), 'add_rule' => __('Add Condition', 'advance-notification-for-woocommerce'), 'remove' => __('Remove', 'advance-notification-for-woocommerce'), 'choose_condition' => __('Select Condition', 'advance-notification-for-woocommerce'), 'confirm_remove_group' => __('Are you sure you want to remove this condition group?', 'advance-notification-for-woocommerce'), ) ) ); } /** * Render the trigger dropdown below the post title * * @param WP_Post $post */ public function render_trigger_dropdown($post) { if ($post->post_type !== 'pisol_anfw_notice') { return; } $triggers = apply_filters('pisol_anfw_triggers', array()); $selected_trigger = get_post_meta($post->ID, '_pisol_anfw_trigger', true); $is_saved = !empty($post->ID) && $selected_trigger; echo '
'; echo ' '; echo ''; if ($selected_trigger && isset($triggers[$selected_trigger]['description'])) { echo '

' . esc_html($triggers[$selected_trigger]['description']) . '

'; } if ($is_saved) { echo '

' . esc_html__('The trigger type is locked and cannot be changed for this notification.', 'advance-notification-for-woocommerce') . '

'; } echo '
'; } }