WordPress.org

Make WordPress Core

Ticket #26937: 26937.2.patch

File 26937.2.patch, 14.5 KB (added by ethitter, 4 years ago)
  • src/wp-includes/link-template.php

     
    11281128 * @return mixed       Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists.
    11291129 */
    11301130function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
    1131         global $wpdb;
    1132 
    1133         if ( ( ! $post = get_post() ) || ! taxonomy_exists( $taxonomy ) )
     1131        if ( ( ! $post = get_post() ) || ! taxonomy_exists( $taxonomy ) ) {
    11341132                return null;
     1133        }
    11351134
    1136         $current_post_date = $post->post_date;
     1135        $current_post_date = get_post_field( 'post_date', $post );
    11371136
    1138         $join = '';
    1139         $posts_in_ex_terms_sql = '';
    1140         if ( $in_same_term || ! empty( $excluded_terms ) ) {
    1141                 $join = " INNER JOIN $wpdb->term_relationships AS tr ON p.ID = tr.object_id INNER JOIN $wpdb->term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
     1137        // Query basics
     1138        $args = array(
     1139                'posts_per_page'   => 1,
     1140                'post_status'      => 'publish',
     1141                'post_type'        => 'post',
     1142                'orderby'          => 'date',
     1143                'order'            => $previous ? 'DESC' : 'ASC',
     1144                'no_found_rows'    => true,
     1145                'cache_results'    => true,
     1146                'suppress_filters' => false,
     1147                'date_query'       => array(),
     1148        );
    11421149
    1143                 if ( $in_same_term ) {
    1144                         if ( ! is_object_in_taxonomy( $post->post_type, $taxonomy ) )
    1145                                 return '';
    1146                         $term_array = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) );
    1147                         if ( ! $term_array || is_wp_error( $term_array ) )
    1148                                 return '';
    1149                         $join .= $wpdb->prepare( " AND tt.taxonomy = %s AND tt.term_id IN (" . implode( ',', array_map( 'intval', $term_array ) ) . ")", $taxonomy );
     1150        // Query pieces
     1151        $tax_query = array();
     1152
     1153        // Set up for requests limited to posts that share terms
     1154        if ( $in_same_term ) {
     1155                $terms = get_the_terms( get_the_ID(), $taxonomy );
     1156
     1157                if ( is_array( $terms ) && ! empty( $terms ) ) {
     1158                        $terms = wp_list_pluck( $terms, 'term_id' );
     1159                        $terms = array_values( $terms );
     1160                        $terms = array_map( 'intval', $terms );
     1161                } else {
     1162                        unset( $terms );
    11501163                }
     1164        }
    11511165
    1152                 $posts_in_ex_terms_sql = $wpdb->prepare( "AND tt.taxonomy = %s", $taxonomy );
    1153                 if ( ! empty( $excluded_terms ) ) {
    1154                         if ( ! is_array( $excluded_terms ) ) {
    1155                                 // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
    1156                                 if ( false !== strpos( $excluded_terms, ' and ' ) ) {
    1157                                         _deprecated_argument( __FUNCTION__, '3.3', sprintf( __( 'Use commas instead of %s to separate excluded terms.' ), "'and'" ) );
    1158                                         $excluded_terms = explode( ' and ', $excluded_terms );
    1159                                 } else {
    1160                                         $excluded_terms = explode( ',', $excluded_terms );
    1161                                 }
     1166        // Handle excluded terms
     1167        if ( ! empty( $excluded_terms ) ) {
     1168                if ( ! is_array( $excluded_terms ) ) {
     1169                        // back-compat, $excluded_terms used to be IDs separated by " and "
     1170                        if ( false !== strpos( $excluded_terms, ' and ' ) ) {
     1171                                _deprecated_argument( __FUNCTION__, '3.3', sprintf( __( 'Use commas instead of %s to separate excluded terms.' ), "'and'" ) );
     1172                                $excluded_terms = explode( ' and ', $excluded_terms );
     1173                        } else {
     1174                                $excluded_terms = explode( ',', $excluded_terms );
    11621175                        }
     1176                }
    11631177
    1164                         $excluded_terms = array_map( 'intval', $excluded_terms );
     1178                $excluded_terms = array_map( 'intval', $excluded_terms );
    11651179
    1166                         if ( ! empty( $term_array ) ) {
    1167                                 $excluded_terms = array_diff( $excluded_terms, $term_array );
    1168                                 $posts_in_ex_terms_sql = '';
    1169                         }
     1180                $tax_query[] = array(
     1181                        'taxonomy' => $tax_query,
     1182                        'slugs'    => $excluded_terms,
     1183                        'compare'  => 'NOT IN',
     1184                );
     1185        }
    11701186
    1171                         if ( ! empty( $excluded_terms ) ) {
    1172                                 $posts_in_ex_terms_sql = $wpdb->prepare( " AND tt.taxonomy = %s AND tt.term_id NOT IN (" . implode( $excluded_terms, ',' ) . ')', $taxonomy );
    1173                         }
     1187        // If requesting same term, ensure that excluded terms don't appear in term list list
     1188        if ( isset( $terms ) ) {
     1189                if ( isset( $excluded_terms ) && is_array( $excluded_terms ) ) {
     1190                        $terms = array_diff( $terms, $excluded_terms );
    11741191                }
     1192
     1193                if ( ! empty( $terms ) ) {
     1194                        $tax_query[] = array(
     1195                                'taxonomy' => $taxonomy,
     1196                                'terms'    => $terms,
     1197                        );
     1198                }
    11751199        }
    11761200
    1177         $adjacent = $previous ? 'previous' : 'next';
    1178         $op = $previous ? '<' : '>';
    1179         $order = $previous ? 'DESC' : 'ASC';
     1201        // If we have a tax query, add it to our query args
     1202        if ( ! empty( $tax_query ) ) {
     1203                $args['tax_query'] = $tax_query;
     1204        }
    11801205
    1181         $join  = apply_filters( "get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms );
    1182         $where = apply_filters( "get_{$adjacent}_post_where", $wpdb->prepare( "WHERE p.post_date $op %s AND p.post_type = %s AND p.post_status = 'publish' $posts_in_ex_terms_sql", $current_post_date, $post->post_type), $in_same_term, $excluded_terms );
    1183         $sort  = apply_filters( "get_{$adjacent}_post_sort", "ORDER BY p.post_date $order LIMIT 1" );
     1206        // And now, the date constraint
     1207        if ( $previous ) {
     1208                $args['date_query'][] = array(
     1209                        'before'    => $current_post_date,
     1210                        'inclusive' => true,
     1211                );
     1212        } else {
     1213                $args['date_query'][] = array(
     1214                        'after'     => $current_post_date,
     1215                        'inclusive' => true,
     1216                );
     1217        }
    11841218
    1185         $query = "SELECT p.ID FROM $wpdb->posts AS p $join $where $sort";
    1186         $query_key = 'adjacent_post_' . md5( $query );
    1187         $result = wp_cache_get( $query_key, 'counts' );
    1188         if ( false !== $result ) {
    1189                 if ( $result )
    1190                         $result = get_post( $result );
    1191                 return $result;
     1219        // Ensure the current post isn't returned, since we're using an inclusive date query
     1220        $args['post__not_in'] = array( get_the_ID() );
     1221
     1222        /**
     1223         * Let plugins modify the parameters for the adjacent post query
     1224         *
     1225         * @since 3.9.0
     1226         *
     1227         * @param array $args WP_Query arguments
     1228         * @param array       Arguments when `get_adjacent_post()` was called
     1229         */
     1230        $args = apply_filters( 'get_adjacent_post_args', $args, compact( 'in_same_term', 'excluded_terms', 'previous', 'taxonony' ) );
     1231
     1232        // Get the posts and return either the post object or null
     1233        WP_Legacy_Adjacent_Post_Filters::add( $previous, $in_same_term, $excluded_terms );
     1234        $results = get_posts( $args );
     1235        WP_Legacy_Adjacent_Post_Filters::remove();
     1236
     1237        if ( is_array( $results ) && ! empty( $results ) ) {
     1238                return array_shift( $results );
     1239        } else {
     1240                return '';
    11921241        }
     1242}
    11931243
    1194         $result = $wpdb->get_var( $query );
    1195         if ( null === $result )
    1196                 $result = '';
     1244/**
     1245 * BACKWARDS COMPATIBILITY FOR FILTERS PREVIOUSLY IN `get_adjacent_post()`
     1246 *
     1247 * Before `get_adjacent_post()` was updated to use `WP_Query`, the SQL it produced was passed through various filters.
     1248 * The following class applies those filters to the relevant clauses produced by `WP_Query` via the latter's filters.
     1249 * The class also makes the data available that the previous filters provided.
     1250 */
     1251class WP_Legacy_Adjacent_Post_Filters {
     1252        private static $adjacent;
     1253        private static $in_same_term;
     1254        private static $excluded_terms;
    11971255
    1198         wp_cache_set( $query_key, $result, 'counts' );
     1256        /**
     1257         * Set up variables and filter the clauses for WP_Query
     1258         *
     1259         * @param bool $previous
     1260         * @param bool $in_same_term
     1261         * @param array $excluded_terms
     1262         * @return null
     1263         */
     1264        public static function add( $previous, $in_same_term, $excluded_terms ) {
     1265                self::$adjacent       = $previous ? 'previous' : 'next';
     1266                self::$in_same_term   = $in_same_term;
     1267                self::$excluded_terms = $excluded_terms;
    11991268
    1200         if ( $result )
    1201                 $result = get_post( $result );
     1269                add_filter( 'posts_clauses', array( __CLASS__, 'filter' ) );
     1270        }
    12021271
    1203         return $result;
     1272        /**
     1273         * Clean up after ourselves
     1274         *
     1275         * @return null
     1276         */
     1277        public static function remove() {
     1278                remove_filter( 'posts_clauses', array( __CLASS__, 'filter' ) );
     1279        }
     1280
     1281        /**
     1282         * Apply the legacy filters to WP_Query's clauses
     1283         *
     1284         * @param array $clauses
     1285         * @filter post_clauses
     1286         * @return array
     1287         */
     1288        public static function filter( $clauses ) {
     1289                // The `join` and `where` filters are identical in their parameters, so we can use the same approach for both
     1290                foreach ( array( 'join', 'where', ) as $clause ) {
     1291                        if ( has_filter( 'get_' . self::$adjacent . '_post_' . $clause ) ) {
     1292                                $clauses[ $clause ] = apply_filters( 'get_' . self::$adjacent . '_post_' . $clause, $clauses[ $clause ], self::$in_same_term, self::$excluded_terms );
     1293                        }
     1294                }
     1295
     1296                // The legacy `sort` filter combined the ORDER BY and LIMIT clauses, while `WP_Query` does not, which makes this a bit complicated
     1297                if ( has_filter( 'get_' . self::$adjacent . '_post_sort' ) ) {
     1298                        $sort = apply_filters( 'get_' . self::$adjacent . '_post_sort', 'ORDER BY ' . $clauses['orderby'] . ' ' . $clauses['limits'] );
     1299
     1300                        if ( ! empty( $sort ) ) {
     1301                                // The legacy filter could allow either clause to be removed, or their order inverted, so we need to know what we have and where.
     1302                                $has_order_by = stripos( $sort, 'order by' );
     1303                                $has_limit    = stripos( $sort, 'limit' );
     1304
     1305                                // Split the string of one or two clauses into their respective array keys
     1306                                if ( false !== $has_order_by && false !== $has_limit ) {
     1307                                        // The LIMIT clause cannot appear before the ORDER BY clause in a valid query
     1308                                        // However, since the legacy filter would allow a user to invert the order, we maintain that handling so the same errors are triggered.
     1309                                        if ( $has_order_by < $has_limit ) {
     1310                                                $clauses['orderby'] = trim( str_ireplace( 'order by', '', substr( $sort, 0, $has_limit ) ) );
     1311                                                $clauses['limits']  = trim( substr( $sort, $has_limit ) );
     1312                                        } else {
     1313                                                $clauses['orderby'] = trim( str_ireplace( 'order by', '', substr( $sort, $has_order_by ) ) );
     1314                                                $clauses['limits']  = trim( substr( $sort, 0, $has_order_by ) );
     1315                                        }
     1316                                } elseif ( false !== $has_order_by ) {
     1317                                        $clauses['orderby'] = trim( str_ireplace( 'order by', '', $sort ) );
     1318                                        $clauses['limits']  = '';
     1319                                } elseif ( false !== $has_limit ) {
     1320                                        $clauses['orderby'] = '';
     1321                                        $clauses['limits']  = trim( $sort );
     1322                                }
     1323                        }
     1324                }
     1325
     1326                return $clauses;
     1327        }
    12041328}
    12051329
    12061330/**
  • tests/phpunit/tests/link.php

     
    167167                $this->assertEquals( array( $post_two ), get_boundary_post( true, '', true, 'post_tag' ) );
    168168                $this->assertEquals( array( $post_four ), get_boundary_post( true, '', false, 'post_tag' ) );
    169169        }
     170
     171        /**
     172         * @ticket 26937
     173         */
     174        function test_legacy_get_adjacent_post_filters() {
     175                // Need some sample posts to test adjacency
     176                $post_one = $this->factory->post->create_and_get( array(
     177                        'post_title' => 'First',
     178                        'post_date' => '2012-01-01 12:00:00'
     179                ) );
     180
     181                $post_two = $this->factory->post->create_and_get( array(
     182                        'post_title' => 'Second',
     183                        'post_date' => '2012-02-01 12:00:00'
     184                ) );
     185
     186                $post_three = $this->factory->post->create_and_get( array(
     187                        'post_title' => 'Third',
     188                        'post_date' => '2012-03-01 12:00:00'
     189                ) );
     190
     191                $post_four = $this->factory->post->create_and_get( array(
     192                        'post_title' => 'Fourth',
     193                        'post_date' => '2012-04-01 12:00:00'
     194                ) );
     195
     196                // Add some meta so we can join the postmeta table and query
     197                add_post_meta( $post_three->ID, 'unit_test_meta', 'waffle' );
     198
     199                // Test "where" filter for a previous post
     200                add_filter( 'get_previous_post_where', array( $this, 'filter_previous_post_where' ) );
     201                $this->go_to( get_permalink( $post_three->ID ) );
     202                $this->assertEquals( $post_one->post_title, get_adjacent_post( false, null, true )->post_title );
     203                remove_filter( 'get_previous_post_where', array( $this, 'filter_previous_post_where' ) );
     204
     205                // Test "where" filter for a next post
     206                add_filter( 'get_next_post_where', array( $this, 'filter_next_post_where' ) );
     207                $this->go_to( get_permalink( $post_two->ID ) );
     208                $this->assertEquals( $post_four->post_title, get_adjacent_post( false, null, false )->post_title );
     209                remove_filter( 'get_next_post_where', array( $this, 'filter_next_post_where' ) );
     210
     211                // Test "join" filter by joining the postmeta table and restricting by meta key
     212                add_filter( 'get_next_post_join', array( $this, 'filter_next_post_join' ) );
     213                add_filter( 'get_next_post_where', array( $this, 'filter_next_post_where_with_join' ) );
     214                $this->go_to( get_permalink( $post_one->ID ) );
     215                $this->assertEquals( $post_three->post_title, get_adjacent_post( false, null, false )->post_title );
     216                remove_filter( 'get_next_post_join', array( $this, 'filter_next_post_join' ) );
     217                remove_filter( 'get_next_post_where', array( $this, 'filter_next_post_where_with_join' ) );
     218
     219                // Test "sort" filter when modifying ORDER BY clause
     220                add_filter( 'get_next_post_sort', array( $this, 'filter_next_post_sort' ) );
     221                $this->go_to( get_permalink( $post_one->ID ) );
     222                $this->assertEquals( $post_four->post_title, get_adjacent_post( false, null, false )->post_title );
     223                remove_filter( 'get_next_post_sort', array( $this, 'filter_next_post_sort' ) );
     224
     225                // Test "sort" filter when modifying LIMIT clause
     226                add_filter( 'get_next_post_sort', array( $this, 'filter_next_post_sort_limit' ) );
     227                $this->go_to( get_permalink( $post_one->ID ) );
     228                $this->assertEquals( $post_three->post_title, get_adjacent_post( false, null, false )->post_title );
     229                remove_filter( 'get_next_post_sort', array( $this, 'filter_next_post_sort_limit' ) );
     230        }
     231
     232        /**
     233         * Filter callback for `test_legacy_get_adjacent_post_filters()`
     234         */
     235        function filter_previous_post_where( $where ) {
     236                $where .= " AND post_title !='Second'";
     237                return $where;
     238        }
     239
     240        /**
     241         * Filter callback for `test_legacy_get_adjacent_post_filters()`
     242         */
     243        function filter_next_post_where( $where ) {
     244                $where .= " AND post_title !='Third'";
     245                return $where;
     246        }
     247
     248        /**
     249         * Filter callback for `test_legacy_get_adjacent_post_filters()`
     250         */
     251        function filter_next_post_join( $join ) {
     252                global $wpdb;
     253
     254                $join .= " INNER JOIN {$wpdb->postmeta} ON {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id";
     255                return $join;
     256        }
     257
     258        /**
     259         * Filter callback for `test_legacy_get_adjacent_post_filters()`
     260         */
     261        function filter_next_post_where_with_join( $where ) {
     262                global $wpdb;
     263
     264                $where .= " AND {$wpdb->postmeta}.meta_key = 'unit_test_meta'";
     265                return $where;
     266        }
     267
     268        /**
     269         * Filter callback for `test_legacy_get_adjacent_post_filters()`
     270         */
     271        function filter_next_post_sort( $sort ) {
     272                global $wpdb;
     273
     274                $sort = str_replace( $wpdb->posts . '.post_date', $wpdb->posts . '.post_title', $sort );
     275                return $sort;
     276        }
     277
     278        /**
     279         * Filter callback for `test_legacy_get_adjacent_post_filters()`
     280         */
     281        function filter_next_post_sort_limit( $sort ) {
     282                global $wpdb;
     283
     284                $sort = str_replace( 'LIMIT 0, 1', 'LIMIT 1, 2', $sort );
     285                return $sort;
     286        }
    170287}
     288 No newline at end of file