|null> */ private static $seen = array(); /** * Archive context IDs found in this request. * * @var array */ private static $archives = array(); public static function init(): void { add_action( 'threepo_order_changed', array( self::class, 'forget' ), 0 ); add_action( 'pre_get_posts', array( self::class, 'prepare' ) ); add_filter( 'posts_clauses', array( self::class, 'clauses' ), 20, 2 ); add_filter( 'query_loop_block_query_vars', array( self::class, 'block' ), 20, 2 ); add_filter( 'terms_clauses', array( self::class, 'terms' ), 20, 3 ); add_action( 'init', array( self::class, 'rest_hooks' ), 30 ); } /** * @return array|null */ public static function public_context( int $id ): ?array { $c = self::stored( $id ); return $c && in_array( $c['scope'], array( 'block', 'archive' ), true ) ? $c : null; } /** * Any stored context, public or not, read once per request. * * @return array|null */ public static function stored( int $id ): ?array { if ( ! array_key_exists( $id, self::$seen ) ) { self::$seen[ $id ] = Store::get( $id ); } return self::$seen[ $id ]; } /** Drop per-request lookups after a confirmed change, so later queries use the new revision. */ public static function forget(): void { self::$seen = array(); self::$archives = array(); } public static function rest_hooks(): void { foreach ( get_post_types( array( 'show_in_rest' => true ) ) as $type ) { add_filter( 'rest_' . $type . '_collection_params', static function ( $params ) { $params['threepo_context'] = array( 'type' => 'integer', 'minimum' => 1, 'description' => __( 'Connected Post Order context.', '3task-post-order' ), ); return $params; } ); add_filter( 'rest_' . $type . '_query', array( self::class, 'rest' ), 20, 2 ); } } /** * @param array $args Validated values. * @return array */ public static function rest( array $args, WP_REST_Request $r ): array { if ( $r['threepo_context'] ) { $args['threepo_context'] = (int) $r['threepo_context']; } return $args; } /** * @param array $args Validated values. * @param \WP_Block $block WordPress boundary value. * @return array */ public static function block( array $args, $block ): array { $id = (int) ( $block->context['query']['threepo_context'] ?? 0 ); if ( $id ) { $args['threepo_context'] = $id; } return $args; } public static function prepare( WP_Query $q ): void { $id = (int) $q->get( 'threepo_context' ); if ( ! $id && ! is_admin() && $q->is_main_query() && ! $q->is_search() && ! $q->is_feed() && ! $q->is_singular() && ! $q->get( 'orderby' ) ) { global $wpdb; $term = $q->get_queried_object(); $tax = ''; $term_id = 0; if ( $term instanceof \WP_Term ) { $tax = $term->taxonomy; $term_id = $term->term_id; } if ( $term_id || $q->is_home() || $q->is_post_type_archive() ) { $type = ( $q->get( 'post_type' ) ? $q->get( 'post_type' ) : 'post' ); if ( is_string( $type ) ) { $key = $type . ':' . $tax . ':' . $term_id; if ( ! isset( self::$archives[ $key ] ) ) { self::$archives[ $key ] = (int) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM %i WHERE kind='post' AND object_type=%s AND taxonomy=%s AND term_id=%d AND parent_id=0 AND scope='archive' LIMIT 1", Store::table( 'contexts' ), $type, $tax, $term_id ) ); } $id = self::$archives[ $key ]; } } } if ( ! $id ) { return; } $native = is_admin() && $q->is_main_query() && $q->get( 'threepo_native' ); $c = $native ? Store::get( $id ) : self::public_context( $id ); $type = ( $q->get( 'post_type' ) ? $q->get( 'post_type' ) : 'post' ); $q->set( 'threepo_fallback', false ); if ( ! $c ) { $stored = $native ? null : self::stored( $id ); if ( $stored && 'post' === $stored['kind'] && $type === $stored['object_type'] ) { // Not connected right now (admin only, import preview): keep the order's selection, use the query's own sorting. $q->set( 'threepo_context', $id ); $q->set( 'threepo_fallback', true ); return; } // Deleted order: the query runs as written instead of returning an empty list. $q->set( 'threepo_context', 0 ); return; } // A connection never broadens statuses; for another kind or content type it is ignored. if ( 'post' !== $c['kind'] || $type !== $c['object_type'] ) { $q->set( 'threepo_context', 0 ); return; } $q->set( 'threepo_context', $id ); $q->set( 'threepo_revision', $c['revision'] ); $q->set( 'ignore_sticky_posts', true ); } /** * @param array $clauses Validated values. * @return array */ public static function clauses( array $clauses, WP_Query $q ): array { $id = (int) $q->get( 'threepo_context' ); if ( ! $id ) { return $clauses; } global $wpdb; if ( $q->get( 'threepo_fallback' ) ) { $c = self::stored( $id ); if ( $c && 'post' === $c['kind'] ) { $s = Order::source( $c ); $clauses['where'] .= ' AND EXISTS (SELECT 1 FROM ' . $s['from'] . ' WHERE ' . $s['where'] . ' AND ' . $s['id'] . '=' . $wpdb->posts . '.ID)'; } return $clauses; } $c = ( is_admin() && $q->is_main_query() && $q->get( 'threepo_native' ) ) ? Store::get( $id ) : self::public_context( $id ); if ( ! $c || 'post' !== $c['kind'] ) { return $clauses; } $s = Order::source( $c ); $clauses['join'] .= $wpdb->prepare( ' LEFT JOIN %i threepo_rank ON threepo_rank.object_id=%i.ID AND threepo_rank.context_id=%d AND %d=%d', Store::table( 'entries' ), $wpdb->posts, $id, $c['revision'], $c['revision'] ); $clauses['where'] .= ' AND EXISTS (SELECT 1 FROM ' . $s['from'] . ' WHERE ' . $s['where'] . ' AND ' . $s['id'] . '=' . $wpdb->posts . '.ID)'; $clauses['orderby'] = '(threepo_rank.rank_value IS NULL),threepo_rank.rank_value ASC,' . $wpdb->posts . '.post_date DESC,' . $wpdb->posts . '.ID DESC'; return $clauses; } /** * @param array $clauses Validated values. * @param list $taxonomies Validated values. * @param array $args Validated values. * @return array */ public static function terms( array $clauses, array $taxonomies, array $args ): array { $id = (int) ( $args['threepo_context'] ?? 0 ); if ( ! $id ) { return $clauses; } $c = is_admin() && Native::context() && Native::context()['id'] === $id ? Native::context() : self::public_context( $id ); if ( ! $c || 'term' !== $c['kind'] || array( $c['object_type'] ) !== $taxonomies ) { return $clauses; } global $wpdb; $clauses['join'] .= $wpdb->prepare( ' LEFT JOIN %i threepo_rank ON threepo_rank.object_id=t.term_id AND threepo_rank.context_id=%d AND %d=%d', Store::table( 'entries' ), $id, $c['revision'], $c['revision'] ); $clauses['where'] .= $wpdb->prepare( ' AND tt.parent=%d', $c['parent_id'] ); $clauses['orderby'] = 'ORDER BY (threepo_rank.rank_value IS NULL),threepo_rank.rank_value ASC,t.name ASC,t.term_id ASC'; $clauses['order'] = ''; return $clauses; } }