custom_params['orderby_meta_key'] ?? ''; $secondary = $this->custom_params['secondary_orderby'] ?? array(); $has_secondary = is_array( $secondary ) && ! empty( $secondary['order_by'] ); // The REST path (block editor preview) sends the primary sort under the // lowercase 'orderby' key; the frontend block attribute is 'orderBy'. $primary_property = $this->custom_params['orderBy'] ?? $this->custom_params['orderby'] ?? null; $rules = array(); if ( ! empty( $primary_property ) ) { $rules[] = array( 'property' => $primary_property, 'direction' => $this->custom_params['order'] ?? null, 'meta_key' => $primary_meta_key, 'name' => 'aql_orderby_primary', ); } if ( $has_secondary ) { $rules[] = array( 'property' => $secondary['order_by'], 'direction' => $secondary['order'] ?? null, 'meta_key' => $secondary['meta_key'] ?? '', 'name' => 'aql_orderby_secondary', ); } // Nothing usable to order by. if ( empty( $rules ) ) { return; } $orderby = array(); $ordering_clauses = array(); $has_primary_rule = ! empty( $primary_property ); foreach ( $rules as $rule ) { $is_meta = in_array( $rule['property'], array( 'meta_value', 'meta_value_num' ), true ) && ! empty( $rule['meta_key'] ); if ( $is_meta ) { // The referenced clause: its JOIN is restricted to the key, so // its meta_value is the real value or NULL. See the docblock. $referenced_clause = array( 'key' => $rule['meta_key'], 'compare' => 'NOT EXISTS', ); if ( 'meta_value_num' === $rule['property'] ) { $referenced_clause['type'] = 'NUMERIC'; } $ordering_clauses[ $rule['name'] ] = $referenced_clause; $ordering_clauses[ $rule['name'] . '_exists' ] = array( 'key' => $rule['meta_key'], 'compare' => 'EXISTS', ); $key = $rule['name']; } else { $key = $this->normalize_orderby_property( $rule['property'] ); } // A secondary rule that normalizes to the primary's key would // overwrite the primary's direction — drop the duplicate instead. if ( isset( $orderby[ $key ] ) ) { continue; } $orderby[ $key ] = $this->normalize_order_direction( $rule['direction'] ); } if ( ! empty( $ordering_clauses ) ) { $this->merge_ordering_meta_clauses( $ordering_clauses ); } // Preserve the string path when there's a single plain PRIMARY rule. // A lone secondary rule keeps the array form so its own direction is // not lost to the block's primary `order` value. $this->custom_args['orderby'] = ( $has_primary_rule && 1 === count( $orderby ) && empty( $ordering_clauses ) ) ? array_key_first( $orderby ) : $orderby; } /** * Merge ordering clause pairs into custom_args['meta_query'], * preserving any user-built meta query under an outer AND. * * @param array $ordering_clauses Named EXISTS/NOT EXISTS clauses. */ private function merge_ordering_meta_clauses( array $ordering_clauses ): void { $ordering_group = array_merge( array( 'relation' => 'OR' ), $ordering_clauses ); if ( empty( $this->custom_args['meta_query'] ) ) { $this->custom_args['meta_query'] = $ordering_group; return; } $this->custom_args['meta_query'] = array( 'relation' => 'AND', $this->custom_args['meta_query'], $ordering_group, ); } /** * Normalize a single orderby property for WP_Query. * * @param mixed $property Raw property value. * @return mixed Normalized property ('id' → 'ID'). */ private function normalize_orderby_property( $property ) { return ( 'id' === $property ) ? 'ID' : $property; } /** * Normalize an order direction to WP_Query's uppercase form. * * @param mixed $direction Raw direction value. * @return string 'ASC' or 'DESC' (default). */ private function normalize_order_direction( $direction ): string { return ( is_string( $direction ) && 'asc' === strtolower( $direction ) ) ? 'ASC' : 'DESC'; } }