Changeset 27285
- Timestamp:
- 02/26/2014 05:09:54 PM (11 years ago)
- Location:
- trunk
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/link-template.php
r27265 r27285 1 <?php1 <?php 2 2 /** 3 3 * WordPress Link Template Functions … … 1120 1120 * @param bool $previous Optional. Whether to retrieve previous post. 1121 1121 * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. 1122 * @return mixed Post object if successful. Null if global $post is not set. Empty string if no correspondingpost exists.1122 * @return mixed Post object if successful. Null if current post doesn't exist. Empty string if no corresponding adjacent post exists. 1123 1123 */ 1124 1124 function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) { 1125 global $wpdb; 1126 1127 if ( ( ! $post = get_post() ) || ! taxonomy_exists( $taxonomy ) ) 1128 return null; 1129 1130 $current_post_date = $post->post_date; 1131 1132 $join = ''; 1133 $posts_in_ex_terms_sql = ''; 1134 if ( $in_same_term || ! empty( $excluded_terms ) ) { 1135 $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"; 1136 1137 if ( $in_same_term ) { 1138 if ( ! is_object_in_taxonomy( $post->post_type, $taxonomy ) ) 1139 return ''; 1140 $term_array = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) ); 1141 if ( ! $term_array || is_wp_error( $term_array ) ) 1142 return ''; 1143 $join .= $wpdb->prepare( " AND tt.taxonomy = %s AND tt.term_id IN (" . implode( ',', array_map( 'intval', $term_array ) ) . ")", $taxonomy ); 1125 if ( is_string( $excluded_terms ) && false !== strpos( $excluded_terms, ' and ' ) ) { 1126 // back-compat: $excluded_terms used to be IDs separated by " and " 1127 _deprecated_argument( __FUNCTION__, '3.3', sprintf( __( 'Use commas instead of %s to separate excluded terms.' ), "'and'" ) ); 1128 $excluded_terms = explode( ' and ', $excluded_terms ); 1129 } 1130 if ( $excluded_terms ) { 1131 $excluded_terms = wp_parse_id_list( $excluded_terms ); 1132 } else { 1133 $excluded_terms = array(); 1134 } 1135 1136 $adjacent = new WP_Get_Adjacent_Post( array( 1137 'post' => get_post(), 1138 'previous' => $previous, 1139 'taxonomy' => $taxonomy, 1140 'in_same_term' => $in_same_term, 1141 'excluded_terms' => $excluded_terms, 1142 ) ); 1143 1144 return $adjacent->adjacent_post; 1145 } 1146 1147 /** 1148 * WordPress Adjacent Post API 1149 * 1150 * Based on the current or specified post, determines either the previous or 1151 * next post based on the criteria specified. Supports retrieving posts with the 1152 * same taxonomy terms and posts that lack specific terms. 1153 */ 1154 class WP_Get_Adjacent_Post { 1155 public $adjacent_post = null; 1156 1157 protected $current_post = false; 1158 protected $adjacent = 'previous'; 1159 protected $taxonomy = 'category'; 1160 protected $in_same_term = false; 1161 protected $excluded_terms = ''; 1162 1163 /** 1164 * Class constructor. 1165 * 1166 * The post is queried is run if arguments are passed to the constructor. 1167 * Otherwise, the get_post() method will need to be called. 1168 * 1169 * @param array $args Optional. See the get_post() method for $args. 1170 */ 1171 public function __construct( $args = array() ) { 1172 if ( empty( $args ) ) { 1173 return; 1144 1174 } 1145 1175 1146 $posts_in_ex_terms_sql = $wpdb->prepare( "AND tt.taxonomy = %s", $taxonomy ); 1147 if ( ! empty( $excluded_terms ) ) { 1148 if ( ! is_array( $excluded_terms ) ) { 1149 // back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and " 1150 if ( false !== strpos( $excluded_terms, ' and ' ) ) { 1151 _deprecated_argument( __FUNCTION__, '3.3', sprintf( __( 'Use commas instead of %s to separate excluded terms.' ), "'and'" ) ); 1152 $excluded_terms = explode( ' and ', $excluded_terms ); 1153 } else { 1154 $excluded_terms = explode( ',', $excluded_terms ); 1155 } 1156 } 1157 1158 $excluded_terms = array_map( 'intval', $excluded_terms ); 1159 1160 if ( ! empty( $term_array ) ) { 1161 $excluded_terms = array_diff( $excluded_terms, $term_array ); 1162 $posts_in_ex_terms_sql = ''; 1163 } 1164 1165 if ( ! empty( $excluded_terms ) ) { 1166 $posts_in_ex_terms_sql = $wpdb->prepare( " AND tt.taxonomy = %s AND tt.term_id NOT IN (" . implode( $excluded_terms, ',' ) . ')', $taxonomy ); 1176 $this->get_post( $args ); 1177 } 1178 1179 /** 1180 * Allow direct access to adjacent post from the class instance itself 1181 * 1182 * @param string $property 1183 * @return mixed String when adjacent post is found and post property exists. Null when no adjacent post is found. 1184 */ 1185 public function __get( $property ) { 1186 if ( is_object( $this->adjacent_post ) && property_exists( $this->adjacent_post, $property ) ) { 1187 return $this->adjacent_post->{$property}; 1188 } else { 1189 return null; 1190 } 1191 } 1192 1193 /** 1194 * Determine adjacent post for specified post and adjacency. 1195 * 1196 * @since 3.9.0 1197 * 1198 * @param array $args { 1199 * Arguments for querying the adjacent post. 1200 * 1201 * @type mixed $post Optional. Post object or ID to find adjacent post for. 1202 * @type bool $previous Optional. Whether to retrieve previous post. 1203 * @type string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. 1204 * @type bool $in_same_term Optional. Whether post should be in a same taxonomy term. 1205 * @type array $excluded_terms Optional. Array of excluded term IDs. 1206 * } 1207 * @return mixed Post object on success. False if no adjacent post exists. Null on failure. 1208 */ 1209 protected function get_post( $args ) { 1210 $this->current_post = get_post( $args['post'] ); 1211 $this->excluded_terms = array_map( 'intval', $args['excluded_terms'] ); 1212 $this->adjacent = $args['previous'] ? 'previous' : 'next'; 1213 $this->in_same_term = (bool) $args['in_same_term']; 1214 1215 // Return null when either the post or taxonomy doesn't exist. 1216 if ( ! $this->current_post ) { 1217 return; 1218 } 1219 if ( $this->in_same_term || $this->excluded_terms ) { 1220 if ( ! taxonomy_exists( $args['taxonomy'] ) ) { 1221 return; 1167 1222 } 1168 1223 } 1169 } 1170 1171 $adjacent = $previous ? 'previous' : 'next'; 1172 $op = $previous ? '<' : '>'; 1173 $order = $previous ? 'DESC' : 'ASC'; 1174 1175 $join = apply_filters( "get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms ); 1176 $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 ); 1177 $sort = apply_filters( "get_{$adjacent}_post_sort", "ORDER BY p.post_date $order LIMIT 1" ); 1178 1179 $query = "SELECT p.ID FROM $wpdb->posts AS p $join $where $sort"; 1180 $query_key = 'adjacent_post_' . md5( $query ); 1181 $result = wp_cache_get( $query_key, 'counts' ); 1182 if ( false !== $result ) { 1183 if ( $result ) 1184 $result = get_post( $result ); 1185 return $result; 1186 } 1187 1188 $result = $wpdb->get_var( $query ); 1189 if ( null === $result ) 1190 $result = ''; 1191 1192 wp_cache_set( $query_key, $result, 'counts' ); 1193 1194 if ( $result ) 1195 $result = get_post( $result ); 1196 1197 return $result; 1224 1225 // Build our arguments for WP_Query. 1226 $query_args = array( 1227 'posts_per_page' => 1, 1228 'post_status' => 'publish', 1229 'post_type' => 'post', 1230 'orderby' => 'date', 1231 'order' => 'previous' === $this->adjacent ? 'DESC' : 'ASC', 1232 'no_found_rows' => true, 1233 'cache_results' => true, 1234 'date_query' => array(), 1235 ); 1236 1237 $tax_query = array(); 1238 1239 // Set up for requests limited to posts that share terms. 1240 if ( $this->in_same_term ) { 1241 $terms = get_the_terms( $this->current_post->ID, $args['taxonomy'] ); 1242 1243 if ( is_array( $terms ) && ! empty( $terms ) ) { 1244 $terms = wp_list_pluck( $terms, 'term_id' ); 1245 $terms = array_values( $terms ); 1246 $terms = array_map( 'intval', $terms ); 1247 } else { 1248 unset( $terms ); 1249 } 1250 } 1251 1252 // Handle excluded terms. 1253 if ( $this->excluded_terms ) { 1254 $tax_query[] = array( 1255 'taxonomy' => $args['taxonomy'], 1256 'slugs' => $this->excluded_terms, 1257 'compare' => 'NOT IN', 1258 ); 1259 } 1260 1261 // If requesting same term, ensure excluded terms don't appear in term list. 1262 if ( isset( $terms ) ) { 1263 if ( isset( $this->excluded_terms ) && is_array( $this->excluded_terms ) ) { 1264 $terms = array_diff( $terms, $this->excluded_terms ); 1265 } 1266 1267 if ( ! empty( $terms ) ) { 1268 $tax_query[] = array( 1269 'taxonomy' => $args['taxonomy'], 1270 'terms' => $terms, 1271 ); 1272 } 1273 } 1274 1275 // If we have a tax query, add it to our query args. 1276 if ( $tax_query ) { 1277 $query_args['tax_query'] = $tax_query; 1278 } 1279 1280 // And now, the date constraint. 1281 $date_query_key = 'previous' === $this->adjacent ? 'before' : 'after'; 1282 1283 $query_args['date_query'][] = array( 1284 $date_query_key => $this->current_post->post_date, 1285 'inclusive' => true, 1286 ); 1287 1288 // Ensure the current post isn't returned, since we're using an inclusive date query. 1289 $query_args['post__not_in'] = array( $this->current_post->ID ); 1290 1291 /** 1292 * Filter the arguments passed to WP_Query when finding an adjacent post. 1293 * 1294 * @since 3.9.0 1295 * 1296 * @param array $query_args WP_Query arguments. 1297 * @param array $args Arguments passed to WP_Get_Adjacent_Post. 1298 */ 1299 $query_args = apply_filters( 'get_adjacent_post_query_args', $query_args, $args ); 1300 1301 add_filter( 'posts_clauses', array( $this, 'filter' ) ); 1302 $query = new WP_Query( $query_args ); 1303 1304 if ( $query->posts ) { 1305 $this->adjacent_post = $query->post; 1306 } else { 1307 $this->adjacent_post = false; 1308 } 1309 } 1310 1311 /** 1312 * Apply the deprecated filters to WP_Query's clauses. 1313 * 1314 * @param array $clauses 1315 * @uses $this->filter_join_and_where() 1316 * @uses $this->filter_sort() 1317 * @filter post_clauses 1318 * @return array 1319 */ 1320 public function filter( $clauses ) { 1321 // Immediately deregister these legacy filters to avoid modifying 1322 // any calls to WP_Query from filter callbacks hooked to WP_Query filters. 1323 remove_filter( 'posts_clauses', array( $this, 'filter' ) ); 1324 1325 // The `join` and `where` filters are identical in their parameters, 1326 // so we can use the same approach for both. 1327 foreach ( array( 'join', 'where' ) as $clause ) { 1328 if ( has_filter( 'get_' . $this->adjacent . '_post_' . $clause ) ) { 1329 $clauses[ $clause ] = $this->filter_join_and_where( $clauses[ $clause ], $clause ); 1330 } 1331 } 1332 1333 // The legacy `sort` filter combined the ORDER BY and LIMIT clauses, 1334 // while `WP_Query` does not, which requires special handling. 1335 if ( has_filter( 'get_' . $this->adjacent . '_post_sort' ) ) { 1336 $sort_clauses = $this->filter_sort( $clauses['orderby'], $clauses['limits'] ); 1337 $clauses = array_merge( $clauses, $sort_clauses ); 1338 } 1339 1340 return $clauses; 1341 } 1342 1343 /** 1344 * Apply the deprecated `join` or `where` clause filter to the clauses built by WP_Query. 1345 * 1346 * @param string $value 1347 * @param string $clause 1348 * @return string 1349 */ 1350 protected function filter_join_and_where( $value, $clause ) { 1351 /** 1352 * @deprecated 3.9.0 1353 */ 1354 return apply_filters( 'get_' . $this->adjacent . '_post_' . $clause, $value, $this->in_same_term, $this->excluded_terms ); 1355 } 1356 1357 /** 1358 * Apply deprecated `sort` filter, which applies to both the ORDER BY and LIMIT clauses. 1359 * 1360 * @param string $orderby 1361 * @param string $limits 1362 * @return array 1363 */ 1364 protected function filter_sort( $orderby, $limits ) { 1365 /** 1366 * @deprecated 3.9.0 1367 */ 1368 $sort = apply_filters( 'get_' . $this->adjacent . '_post_sort', 'ORDER BY ' . $orderby . ' ' . $limits ); 1369 1370 if ( empty( $sort ) ) { 1371 return compact( 'orderby', 'limits' ); 1372 } 1373 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 } 1198 1399 } 1199 1400 -
trunk/tests/phpunit/tests/link.php
r25959 r27285 168 168 $this->assertEquals( array( $post_four ), get_boundary_post( true, '', false, 'post_tag' ) ); 169 169 } 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 } 170 285 }
Note: See TracChangeset
for help on using the changeset viewer.