WordPress.org

Make WordPress Core

Ticket #26937: 26937.5.patch

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

     
    11251125 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. 
    11261126 * @param bool         $previous       Optional. Whether to retrieve previous post. 
    11271127 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'. 
    1128  * @return mixed       Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists. 
     1128 * @param mixed        $current_post   Optional. Post object, array, or ID to find adjacent post for. 
     1129 * @return mixed       Post object if successful. Null if current post doesn't exist. Empty string if no corresponding adjacent post exists. 
    11291130 */ 
    1130 function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) { 
    1131         global $wpdb; 
     1131function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category', $current_post = false ) { 
     1132        if ( ! $current_post ) { 
     1133                $current_post = get_post(); 
     1134        } 
    11321135 
    1133         if ( ( ! $post = get_post() ) || ! taxonomy_exists( $taxonomy ) ) 
    1134                 return null; 
     1136        $adjacent = new WP_Get_Adjacent_Post( $current_post, $previous, $in_same_term, $taxonomy, $excluded_terms ); 
     1137        return $adjacent->adjacent_post; 
     1138} 
    11351139 
    1136         $current_post_date = $post->post_date; 
     1140/** 
     1141 * WordPress Adjacent Post API 
     1142 * 
     1143 * Based on the current or specified post, determines either the previous or next post based on the criteria specified. 
     1144 * Supports retrieving posts with the same taxonomy terms and posts that lack specific terms. 
     1145 */ 
     1146class WP_Get_Adjacent_Post { 
     1147        public $adjacent_post   = null; 
    11371148 
    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"; 
     1149        private $current_post   = false; 
     1150        private $adjacent       = 'previous'; 
     1151        private $taxonomy       = 'category'; 
     1152        private $in_same_term   = false; 
     1153        private $excluded_terms = ''; 
    11421154 
    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 ); 
     1155        /** 
     1156         * Validate inputs, set up class variables, and determine adjacent post 
     1157         * 
     1158         * @param mixed        $current_post   Optional. Post object, array, or ID to find adjacent post for. 
     1159         * @param bool         $previous       Optional. Whether to retrieve previous post. 
     1160         * @param bool         $in_same_term   Optional. Whether post should be in a same taxonomy term. 
     1161         * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'. 
     1162         * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs. 
     1163         * @return null 
     1164         */ 
     1165        public function __construct( $current_post = false, $previous = true, $in_same_term = false, $taxonomy = 'category', $excluded_terms = '' ) { 
     1166                // Validate specified post, otherwise default to current post 
     1167                if ( is_a( $current_post, 'WP_Post' ) ) { 
     1168                        $this->current_post = $current_post; 
     1169                } elseif ( is_array( $current_post ) && isset( $current_post['ID'] ) ) { 
     1170                        $this->current_post = get_post( $current_post['ID'] ); 
     1171                } elseif ( is_numeric( $current_post ) ) { 
     1172                        $this->current_post = get_post( (int) $current_post ); 
     1173                } else { 
     1174                        $this->current_post = get_post(); 
    11501175                } 
    11511176 
    1152                 $posts_in_ex_terms_sql = $wpdb->prepare( "AND tt.taxonomy = %s", $taxonomy ); 
     1177                // Validate terms specified for exclusion 
    11531178                if ( ! empty( $excluded_terms ) ) { 
    11541179                        if ( ! is_array( $excluded_terms ) ) { 
    1155                                 // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and " 
     1180                                // back-compat, $excluded_terms used to be IDs separated by " and " 
    11561181                                if ( false !== strpos( $excluded_terms, ' and ' ) ) { 
    11571182                                        _deprecated_argument( __FUNCTION__, '3.3', sprintf( __( 'Use commas instead of %s to separate excluded terms.' ), "'and'" ) ); 
    11581183                                        $excluded_terms = explode( ' and ', $excluded_terms ); 
     
    11611186                                } 
    11621187                        } 
    11631188 
    1164                         $excluded_terms = array_map( 'intval', $excluded_terms ); 
     1189                        $this->excluded_terms = array_map( 'intval', $excluded_terms ); 
     1190                } 
    11651191 
    1166                         if ( ! empty( $term_array ) ) { 
    1167                                 $excluded_terms = array_diff( $excluded_terms, $term_array ); 
    1168                                 $posts_in_ex_terms_sql = ''; 
     1192                // Validate remaining inputs 
     1193                $this->adjacent       = $previous ? 'previous' : 'next'; 
     1194                $this->in_same_term   = (bool) $in_same_term; 
     1195                $this->taxonomy       = taxonomy_exists( $taxonomy ) ? $taxonomy : ''; 
     1196 
     1197                // Determine adjacent post 
     1198                $this->get_post(); 
     1199        } 
     1200 
     1201        /** 
     1202         * Allow direct access to adjacent post from the class instance itself 
     1203         * 
     1204         * @param string $property 
     1205         * @return mixed String when adjacent post is found and post property exists. Null when no adjacent post is found. 
     1206         */ 
     1207        public function __get( $property ) { 
     1208                if ( is_object( $this->adjacent_post ) && property_exists( $this->adjacent_post, $property ) ) { 
     1209                        return $this->adjacent_post->{$property}; 
     1210                } else { 
     1211                        return null; 
     1212                } 
     1213        } 
     1214 
     1215        /** 
     1216         * Determine adjacent post for specified post and adjacency 
     1217         * 
     1218         * @return mixed Post object if successful. Null if current post doesn't exist. Empty string if no corresponding adjacent post exists. 
     1219         */ 
     1220        protected function get_post() { 
     1221                // Return null when either the post or taxonomy doesn't exist 
     1222                if ( ! $this->current_post || ! taxonomy_exists( $this->taxonomy ) ) { 
     1223                        return; 
     1224                } 
     1225 
     1226                // Query basics 
     1227                $args = array( 
     1228                        'posts_per_page'   => 1, 
     1229                        'post_status'      => 'publish', 
     1230                        'post_type'        => 'post', 
     1231                        'orderby'          => 'date', 
     1232                        'order'            => 'previous' === $this->adjacent ? 'DESC' : 'ASC', 
     1233                        'no_found_rows'    => true, 
     1234                        'cache_results'    => true, 
     1235                        'suppress_filters' => false, 
     1236                        'date_query'       => array(), 
     1237                ); 
     1238 
     1239                // Query pieces 
     1240                $tax_query = array(); 
     1241 
     1242                // Set up for requests limited to posts that share terms 
     1243                if ( $this->in_same_term ) { 
     1244                        $terms = get_the_terms( $this->current_post->ID, $this->taxonomy ); 
     1245 
     1246                        if ( is_array( $terms ) && ! empty( $terms ) ) { 
     1247                                $terms = wp_list_pluck( $terms, 'term_id' ); 
     1248                                $terms = array_values( $terms ); 
     1249                                $terms = array_map( 'intval', $terms ); 
     1250                        } else { 
     1251                                unset( $terms ); 
    11691252                        } 
     1253                } 
    11701254 
    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 ); 
     1255                // Handle excluded terms 
     1256                if ( ! empty( $this->excluded_terms ) ) { 
     1257                        $tax_query[] = array( 
     1258                                'taxonomy' => $this->taxonomy, 
     1259                                'slugs'    => $this->excluded_terms, 
     1260                                'compare'  => 'NOT IN', 
     1261                        ); 
     1262                } 
     1263 
     1264                // If requesting same term, ensure that excluded terms don't appear in term list list 
     1265                if ( isset( $terms ) ) { 
     1266                        if ( isset( $this->excluded_terms ) && is_array( $this->excluded_terms ) ) { 
     1267                                $terms = array_diff( $terms, $this->excluded_terms ); 
    11731268                        } 
     1269 
     1270                        if ( ! empty( $terms ) ) { 
     1271                                $tax_query[] = array( 
     1272                                        'taxonomy' => $this->taxonomy, 
     1273                                        'terms'    => $terms, 
     1274                                ); 
     1275                        } 
    11741276                } 
     1277 
     1278                // If we have a tax query, add it to our query args 
     1279                if ( ! empty( $tax_query ) ) { 
     1280                        $args['tax_query'] = $tax_query; 
     1281                } 
     1282 
     1283                // And now, the date constraint 
     1284                if ( 'previous' === $this->adjacent ) { 
     1285                        $args['date_query'][] = array( 
     1286                                'before'    => $this->current_post->post_date, 
     1287                                'inclusive' => true, 
     1288                        ); 
     1289                } else { 
     1290                        $args['date_query'][] = array( 
     1291                                'after'     => $this->current_post->post_date, 
     1292                                'inclusive' => true, 
     1293                        ); 
     1294                } 
     1295 
     1296                // Ensure the current post isn't returned, since we're using an inclusive date query 
     1297                $args['post__not_in'] = array( $this->current_post->ID ); 
     1298 
     1299                /** 
     1300                 * Let plugins modify the parameters for the adjacent post query 
     1301                 * 
     1302                 * @since 3.9.0 
     1303                 * 
     1304                 * @param array $args WP_Query arguments 
     1305                 * @param array       Arguments when `get_adjacent_post()` was called 
     1306                 */ 
     1307                $args = apply_filters( 'get_adjacent_post_args', $args, compact( 'in_same_term', 'excluded_terms', 'previous', 'taxonony' ) ); 
     1308 
     1309                // Get the posts and return either the post object or null 
     1310                add_filter( 'posts_clauses', array( $this, 'filter' ) ); 
     1311                $results = get_posts( $args ); 
     1312 
     1313                if ( is_array( $results ) && ! empty( $results ) ) { 
     1314                        $this->adjacent_post = array_shift( $results ); 
     1315                } else { 
     1316                        $this->adjacent_post = ''; 
     1317                } 
    11751318        } 
    11761319 
    1177         $adjacent = $previous ? 'previous' : 'next'; 
    1178         $op = $previous ? '<' : '>'; 
    1179         $order = $previous ? 'DESC' : 'ASC'; 
     1320        /** 
     1321         * Apply the legacy filters to WP_Query's clauses 
     1322         * 
     1323         * @param array $clauses 
     1324         * @uses $this->filter_join_and_where() 
     1325         * @uses $this->filter_sort() 
     1326         * @filter post_clauses 
     1327         * @return array 
     1328         */ 
     1329        public function filter( $clauses ) { 
     1330                // Immediately deregister these legacy filters to avoid modifying any calls to WP_Query from filter callbacks hooked to WP_Query filters 
     1331                remove_filter( 'posts_clauses', array( $this, 'filter' ) ); 
    11801332 
    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" ); 
     1333                // The `join` and `where` filters are identical in their parameters, so we can use the same approach for both 
     1334                foreach ( array( 'join', 'where', ) as $clause ) { 
     1335                        if ( has_filter( 'get_' . $this->adjacent . '_post_' . $clause ) ) { 
     1336                                $clauses[ $clause ] = $this->filter_join_and_where( $clauses[ $clause ], $clause ); 
     1337                        } 
     1338                } 
    11841339 
    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; 
     1340                // The legacy `sort` filter combined the ORDER BY and LIMIT clauses, while `WP_Query` does not, which requires special handling 
     1341                if ( has_filter( 'get_' . $this->adjacent . '_post_sort' ) ) { 
     1342                        $sort_clauses = $this->filter_sort( $clauses['orderby'], $clauses['limits'] ); 
     1343                        $clauses      = array_merge( $clauses, $sort_clauses ); 
     1344                } 
     1345 
     1346                return $clauses; 
    11921347        } 
    11931348 
    1194         $result = $wpdb->get_var( $query ); 
    1195         if ( null === $result ) 
    1196                 $result = ''; 
     1349        /** 
     1350         * Apply the legacy `join` or `where` clause filter to the clauses built by WP_Query 
     1351         * 
     1352         * @param string $value 
     1353         * @param string $clause 
     1354         * @return string 
     1355         */ 
     1356        protected function filter_join_and_where( $value, $clause ) { 
     1357                return apply_filters( 'get_' . $this->adjacent . '_post_' . $clause, $value, $this->in_same_term, $this->excluded_terms ); 
     1358        } 
    11971359 
    1198         wp_cache_set( $query_key, $result, 'counts' ); 
     1360        /** 
     1361         * Apply legacy `sort` filter, which applies to both the ORDER BY and LIMIT clauses 
     1362         * 
     1363         * @param string $orderby 
     1364         * @param string $limits 
     1365         * @return array 
     1366         */ 
     1367        protected function filter_sort( $orderby, $limits ) { 
     1368                $sort = apply_filters( 'get_' . $this->adjacent . '_post_sort', 'ORDER BY ' . $orderby . ' ' . $limits ); 
    11991369 
    1200         if ( $result ) 
    1201                 $result = get_post( $result ); 
     1370                if ( empty( $sort ) ) { 
     1371                        return compact( 'orderby', 'limits' ); 
     1372                } 
    12021373 
    1203         return $result; 
     1374                // The legacy filter could allow either clause to be removed, or their order inverted, so we need to know what we have and where. 
     1375                $has_order_by = stripos( $sort, 'order by' ); 
     1376                $has_limit    = stripos( $sort, 'limit' ); 
     1377 
     1378                // Split the string of one or two clauses into their respective array keys 
     1379                if ( false !== $has_order_by && false !== $has_limit ) { 
     1380                        // The LIMIT clause cannot appear before the ORDER BY clause in a valid query 
     1381                        // However, since the legacy filter would allow a user to invert the order, we maintain that handling so the same errors are triggered. 
     1382                        if ( $has_order_by < $has_limit ) { 
     1383                                $orderby = trim( str_ireplace( 'order by', '', substr( $sort, 0, $has_limit ) ) ); 
     1384                                $limits  = trim( substr( $sort, $has_limit ) ); 
     1385                        } else { 
     1386                                $orderby = trim( str_ireplace( 'order by', '', substr( $sort, $has_order_by ) ) ); 
     1387                                $limits  = trim( substr( $sort, 0, $has_order_by ) ); 
     1388                        } 
     1389                } elseif ( false !== $has_order_by ) { 
     1390                        $orderby = trim( str_ireplace( 'order by', '', $sort ) ); 
     1391                        $limits  = ''; 
     1392                } elseif ( false !== $has_limit ) { 
     1393                        $orderby = ''; 
     1394                        $limits  = trim( $sort ); 
     1395                } 
     1396 
     1397                return compact( 'orderby', 'limits' ); 
     1398        } 
    12041399} 
    12051400 
    12061401/** 
  • 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, get_adjacent_post( false, null, true ) ); 
     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, get_adjacent_post( false, null, false ) ); 
     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, get_adjacent_post( false, null, false ) ); 
     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, get_adjacent_post( false, null, false ) ); 
     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, get_adjacent_post( false, null, false ) ); 
     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                $sort = str_replace( 'LIMIT 0, 1', 'LIMIT 1, 2', $sort ); 
     283                return $sort; 
     284        } 
    170285} 
     286 No newline at end of file