input( 'format', 'post', '' ) ); if ( ! $format ) { wp_send_json_error( array( 'message' => __( 'Invalid format.', 'ad-fuz' ) ) ); return; } $allFormats = Common::active_global_ad_format(); $formatData = $allFormats[ $format ] ?? null; if ( ! $formatData || ( $formatData['placement_type'] ?? '' ) !== 'global' ) { wp_send_json_error( array( 'message' => __( 'Invalid global ad format.', 'ad-fuz' ) ) ); return; } $status = sanitize_key( $request->input( 'status', 'post', 'active' ) ); $raw_rules = $request->input( 'rules', 'post', array() ); $max_spaces = $formatData['max_spaces'] ?? null; $option_key = 'adfuz_global_ads_' . $format; $sanitized_rules = array(); // Validate and sanitize rules based on format definition if ( is_array( $raw_rules ) ) { foreach ( $raw_rules as $index => $rule ) { $sanitized_rule = array(); // Validate against format definition $sanitized_rule = self::validate_rule( $rule, $formatData, $max_spaces ); if ( ! empty( $sanitized_rule ) ) { $sanitized_rules[] = $sanitized_rule; } } } // Prepare final data $data_to_save = array( 'status' => $status, 'updated_at' => current_time( 'mysql' ), 'rules' => $sanitized_rules, ); update_option( $option_key, $data_to_save ); wp_send_json_success( array( 'message' => sprintf( /* translators: %s: Format label */ __( '%s settings saved successfully.', 'ad-fuz' ), esc_html( $formatData['name'] ?? ucfirst( $format ) ) ), ) ); } /** * Validate a single rule against format definition. */ private static function validate_rule( array $rule, array $formatData, ?int $max_spaces ): array { $sanitized = array(); $settings_def = $formatData['settings'] ?? array(); $has_positions = ! empty( $formatData['positions'] ); // Validate title $sanitized['title'] = sanitize_text_field( $rule['title'] ?? '' ); // Validate priority $sanitized['priority'] = absint( $rule['priority'] ?? 10 ); // Validate status $sanitized['status'] = in_array( $rule['status'] ?? 'active', array( 'active', 'inactive' ), true ) ? $rule['status'] : 'active'; // Validate scope $sanitized['scope'] = in_array( $rule['scope'] ?? 'all', array( 'all', 'specific_post_types', 'specific_posts' ), true ) ? $rule['scope'] : 'all'; // Validate post_ids if ( isset( $rule['post_ids'] ) && is_array( $rule['post_ids'] ) ) { $sanitized['post_ids'] = array_map( 'absint', $rule['post_ids'] ); } else { $sanitized['post_ids'] = array(); } // Validate post_types if ( isset( $rule['post_types'] ) && is_array( $rule['post_types'] ) ) { $sanitized['post_types'] = array_map( 'sanitize_key', $rule['post_types'] ); } else { $sanitized['post_types'] = array(); } // Validate position-based formats if ( $has_positions ) { $positions = $formatData['positions'] ?? array(); $sanitized['positions'] = array(); foreach ( $positions as $posKey => $posLabel ) { $space_id = isset( $rule['positions'][ $posKey ] ) ? absint( $rule['positions'][ $posKey ] ) : 0; if ( $space_id > 0 ) { $sanitized['positions'][ $posKey ] = $space_id; } } // Validate position if it's a single position format (like corner_peel) if ( isset( $rule['position'] ) ) { $valid_positions = array_keys( $positions ); if ( in_array( $rule['position'], $valid_positions, true ) ) { $sanitized['position'] = $rule['position']; } } } else { // Validate single ad_space_id for non-position formats $sanitized['ad_space_id'] = absint( $rule['ad_space_id'] ?? 0 ); } // Validate settings based on format definition $sanitized['settings'] = array(); foreach ( $settings_def as $key => $def ) { $raw_value = $rule['settings'][ $key ] ?? $def['default'] ?? ''; $sanitizer = $def['sanitize'] ?? null; if ( is_callable( $sanitizer ) ) { $sanitized['settings'][ $key ] = $sanitizer( $raw_value ); } else { // Default sanitization based on type $type = $def['type'] ?? 'text'; switch ( $type ) { case 'number': $sanitized['settings'][ $key ] = absint( $raw_value ); break; case 'color': $sanitized['settings'][ $key ] = sanitize_hex_color( $raw_value ) ?: $def['default']; break; case 'toggle': $sanitized['settings'][ $key ] = ! empty( $raw_value ) && $raw_value != '0' ? '1' : '0'; break; case 'select': $valid_options = array_keys( $def['options'] ?? array() ); if ( in_array( $raw_value, $valid_options, true ) ) { $sanitized['settings'][ $key ] = $raw_value; } else { $sanitized['settings'][ $key ] = $def['default'] ?? ''; } break; default: $sanitized['settings'][ $key ] = sanitize_text_field( $raw_value ); break; } } } // Check if the rule has any valid assigned ad spaces. $has_assigned = false; if ( $has_positions ) { $has_assigned = ! empty( array_filter( $sanitized['positions'] ?? array() ) ); } else { $has_assigned = ! empty( $sanitized['ad_space_id'] ); } // Only return the rule if it has at least one assigned ad space. if ( ! $has_assigned ) { return array(); } return $sanitized; } /** * @return string */ protected static function getModel(): string { return ''; } /** * AJAX endpoint to search posts for Select2. */ public static function search_posts(): void { Common::checkCapability( static::getCapabilities() ); Common::verifyNonce(); $search = sanitize_text_field( $_POST['search'] ?? '' ); $page = absint( $_POST['page'] ?? 1 ); $per_page = 10; $args = array( 'post_type' => 'any', 'post_status' => 'publish', 'posts_per_page' => $per_page, 'paged' => $page, 's' => $search, 'orderby' => 'relevance', 'order' => 'DESC', ); $query = new WP_Query( $args ); $results = array(); foreach ( $query->posts as $post ) { $results[] = array( 'id' => $post->ID, 'text' => sprintf( '%s (#%d)', $post->post_title, $post->ID ), ); } wp_send_json_success( array( 'results' => $results, 'pagination' => array( 'more' => $page < $query->max_num_pages, ), ) ); } }