├── .gitignore ├── caching-functions.php ├── core-fix-comment-counts-caching.php ├── core-fix-disable-adjacent-posts.php ├── core-fix-disable-include-children-query.php ├── core-fix-latest-post-modified.php ├── core-fix-media-query-caching.php ├── core-fix-update-term-count.php ├── performance-tweaks.php └── vip-performance.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sqlite 27 | 28 | # OS generated files # 29 | ###################### 30 | .DS_Store 31 | .DS_Store? 32 | ._* 33 | .Spotlight-V100 34 | .Trashes 35 | ehthumbs.db 36 | Thumbs.db 37 | 38 | .idea -------------------------------------------------------------------------------- /caching-functions.php: -------------------------------------------------------------------------------- 1 | term_id, 'get_term_by' ); 44 | else 45 | wp_cache_set( $cache_key, 0, 'get_term_by' ); // If we get an invalid value, cache it anyway 46 | } else { 47 | $term = get_term( $term_id, $taxonomy, $output, $filter ); 48 | } 49 | 50 | if ( is_wp_error( $term ) ) 51 | $term = false; 52 | 53 | return $term; 54 | } 55 | } 56 | 57 | /** 58 | * Properly clear wpcom_vip_get_term_by() cache when a term is updated 59 | */ 60 | add_action( 'edit_terms', 'wp_flush_get_term_by_cache', 10, 2 ); 61 | function wp_flush_get_term_by_cache( $term_id, $taxonomy ){ 62 | $term = get_term_by( 'id', $term_id, $taxonomy ); 63 | if ( ! $term ) { 64 | return; 65 | } 66 | foreach( array( 'name', 'slug' ) as $field ) { 67 | $cache_key = $field . '|' . $taxonomy . '|' . md5( $term->$field ); 68 | $cache_group = 'get_term_by'; 69 | wp_cache_delete( $cache_key, $cache_group ); 70 | } 71 | } 72 | 73 | /** 74 | * Cached version of term_exists() 75 | * 76 | * Term exists calls can pile up on a single pageload. 77 | * This function adds a layer of caching to prevent lots of queries. 78 | * 79 | * @param int|string $term The term to check can be id, slug or name. 80 | * @param string $taxonomy The taxonomy name to use 81 | * @param int $parent Optional. ID of parent term under which to confine the exists search. 82 | * @return mixed Returns null if the term does not exist. Returns the term ID 83 | * if no taxonomy is specified and the term ID exists. Returns 84 | * an array of the term ID and the term taxonomy ID the taxonomy 85 | * is specified and the pairing exists. 86 | */ 87 | if ( ! function_exists( 'wpcom_vip_term_exists' ) ) { 88 | function wpcom_vip_term_exists( $term, $taxonomy = '', $parent = null ) { 89 | // If $parent is not null, skip the cache 90 | if ( null !== $parent ){ 91 | return term_exists( $term, $taxonomy, $parent ); 92 | } 93 | 94 | if ( ! empty( $taxonomy ) ){ 95 | $cache_key = $term . '|' . $taxonomy; 96 | }else{ 97 | $cache_key = $term; 98 | } 99 | 100 | $cache_value = wp_cache_get( $cache_key, 'term_exists' ); 101 | 102 | // term_exists frequently returns null, but (happily) never false 103 | if ( false === $cache_value ) { 104 | $term_exists = term_exists( $term, $taxonomy ); 105 | wp_cache_set( $cache_key, $term_exists, 'term_exists' ); 106 | }else{ 107 | $term_exists = $cache_value; 108 | } 109 | 110 | if ( is_wp_error( $term_exists ) ) 111 | $term_exists = null; 112 | 113 | return $term_exists; 114 | } 115 | } 116 | 117 | /** 118 | * Properly clear wpcom_vip_term_exists() cache when a term is updated 119 | */ 120 | add_action( 'delete_term', 'wp_flush_term_exists', 10, 4 ); 121 | function wp_flush_term_exists( $term, $tt_id, $taxonomy, $deleted_term ){ 122 | foreach( array( 'term_id', 'name', 'slug' ) as $field ) { 123 | $cache_key = $deleted_term->$field . '|' . $taxonomy ; 124 | $cache_group = 'term_exists'; 125 | wp_cache_delete( $cache_key, $cache_group ); 126 | } 127 | } 128 | 129 | /** 130 | * Optimized version of get_term_link that adds caching for slug-based lookups. 131 | * 132 | * Returns permalink for a taxonomy term archive, or a WP_Error object if the term does not exist. 133 | * 134 | * @param int|string|object $term The term object / term ID / term slug whose link will be retrieved. 135 | * @param string $taxonomy The taxonomy slug. NOT required if you pass the term object in the first parameter 136 | * 137 | * @return string|WP_Error HTML link to taxonomy term archive on success, WP_Error if term does not exist. 138 | */ 139 | if ( ! function_exists( 'wpcom_vip_get_term_link' ) ) { 140 | function wpcom_vip_get_term_link( $term, $taxonomy = null ) { 141 | // ID- or object-based lookups already result in cached lookups, so we can ignore those 142 | if ( is_numeric( $term ) || is_object( $term ) ) { 143 | return get_term_link( $term, $taxonomy ); 144 | } 145 | 146 | $term_object = wpcom_vip_get_term_by( 'slug', $term, $taxonomy ); 147 | return get_term_link( $term_object ); 148 | } 149 | } 150 | 151 | /** 152 | * Cached version of get_page_by_title so that we're not making unnecessary SQL all the time 153 | * 154 | * @param string $page_title Page title 155 | * @param string $output Optional. Output type; OBJECT*, ARRAY_N, or ARRAY_A. 156 | * @param string $post_type Optional. Post type; default is 'page'. 157 | * @return WP_Post|null WP_Post on success or null on failure 158 | * @link http://vip.wordpress.com/documentation/uncached-functions/ Uncached Functions 159 | */ 160 | if ( ! function_exists( 'wpcom_vip_get_page_by_title' ) ) { 161 | function wpcom_vip_get_page_by_title( $title, $output = OBJECT, $post_type = 'page' ) { 162 | $cache_key = $post_type . '_' . sanitize_key( $title ); 163 | $page_id = wp_cache_get( $cache_key, 'get_page_by_title' ); 164 | 165 | if ( $page_id === false ) { 166 | $page = get_page_by_title( $title, OBJECT, $post_type ); 167 | $page_id = $page ? $page->ID : 0; 168 | wp_cache_set( $cache_key, $page_id, 'get_page_by_title' ); // We only store the ID to keep our footprint small 169 | } 170 | 171 | if ( $page_id ) 172 | return get_page( $page_id, $output ); 173 | 174 | return null; 175 | } 176 | } 177 | 178 | /** 179 | * Cached version of get_page_by_path so that we're not making unnecessary SQL all the time 180 | * 181 | * @param string $page_path Page path 182 | * @param string $output Optional. Output type; OBJECT*, ARRAY_N, or ARRAY_A. 183 | * @param string $post_type Optional. Post type; default is 'page'. 184 | * @return WP_Post|null WP_Post on success or null on failure 185 | * @link http://vip.wordpress.com/documentation/uncached-functions/ Uncached Functions 186 | */ 187 | if ( ! function_exists( 'wpcom_vip_get_page_by_path' ) ) { 188 | function wpcom_vip_get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) { 189 | if ( is_array( $post_type ) ) 190 | $cache_key = sanitize_key( $page_path ) . '_' . md5( serialize( $post_type ) ); 191 | else 192 | $cache_key = $post_type . '_' . sanitize_key( $page_path ); 193 | 194 | $page_id = wp_cache_get( $cache_key, 'get_page_by_path' ); 195 | 196 | if ( $page_id === false ) { 197 | $page = get_page_by_path( $page_path, $output, $post_type ); 198 | $page_id = $page ? $page->ID : 0; 199 | if ( $page_id ===0 ){ 200 | wp_cache_set( $cache_key, $page_id, 'get_page_by_path', ( 1 * HOUR_IN_SECONDS + mt_rand(0, HOUR_IN_SECONDS) ) ); // We only store the ID to keep our footprint small 201 | }else{ 202 | wp_cache_set( $cache_key, $page_id, 'get_page_by_path', ( 12 * HOUR_IN_SECONDS + mt_rand(0, HOUR_IN_SECONDS) )); // We only store the ID to keep our footprint small 203 | } 204 | } 205 | 206 | if ( $page_id ) 207 | return get_page( $page_id, $output ); 208 | 209 | return null; 210 | } 211 | } 212 | 213 | /** 214 | * Flush the cache for published pages so we don't end up with stale data 215 | * 216 | * @param string $new_status The post's new status 217 | * @param string $old_status The post's previous status 218 | * @param WP_Post $post The post 219 | * @link http://vip.wordpress.com/documentation/uncached-functions/ Uncached Functions 220 | */ 221 | if ( ! function_exists( 'wpcom_vip_flush_get_page_by_title_cache' ) ) { 222 | function wpcom_vip_flush_get_page_by_title_cache( $new_status, $old_status, $post ) { 223 | if ( 'publish' == $new_status || 'publish' == $old_status ) 224 | wp_cache_delete( $post->post_type . '_' . sanitize_key( $post->post_title ), 'get_page_by_title' ); 225 | } 226 | } 227 | add_action( 'transition_post_status', 'wpcom_vip_flush_get_page_by_title_cache', 10, 3 ); 228 | 229 | /** 230 | * Flush the cache for published pages so we don't end up with stale data 231 | * 232 | * @param string $new_status The post's new status 233 | * @param string $old_status The post's previous status 234 | * @param WP_Post $post The post 235 | * 236 | * @link http://vip.wordpress.com/documentation/uncached-functions/ Uncached Functions 237 | */ 238 | if ( ! function_exists( 'wpcom_vip_flush_get_page_by_path_cache' ) ) { 239 | function wpcom_vip_flush_get_page_by_path_cache( $new_status, $old_status, $post ) { 240 | if ( 'publish' === $new_status || 'publish' === $old_status ) { 241 | $page_path = get_page_uri( $post->ID ); 242 | wp_cache_delete( $post->post_type . '_' . sanitize_key( $page_path ), 'get_page_by_path' ); 243 | } 244 | } 245 | } 246 | add_action( 'transition_post_status', 'wpcom_vip_flush_get_page_by_path_cache', 10, 3 ); 247 | 248 | /** 249 | * Cached version of url_to_postid, which can be expensive. 250 | * 251 | * Examine a url and try to determine the post ID it represents. 252 | * 253 | * @param string $url Permalink to check. 254 | * @return int Post ID, or 0 on failure. 255 | */ 256 | if ( ! function_exists( 'wpcom_vip_url_to_postid' ) ) { 257 | function wpcom_vip_url_to_postid( $url ) { 258 | /* 259 | * Can only run after init, since home_url() has not been filtered to the mapped domain prior to that, 260 | * which will cause url_to_postid to fail 261 | * @see https://vip.wordpress.com/documentation/vip-development-tips-tricks/home_url-vs-site_url/ 262 | */ 263 | if ( ! did_action( 'init' ) ) { 264 | _doing_it_wrong( 'wpcom_vip_url_to_postid', 'wpcom_vip_url_to_postid must be called after the init action, as home_url() has not yet been filtered', '' ); 265 | 266 | return 0; 267 | } 268 | 269 | // Sanity check; no URLs not from this site 270 | if ( parse_url( $url, PHP_URL_HOST ) != wpcom_vip_get_home_host() ) 271 | return 0; 272 | 273 | $cache_key = md5( $url ); 274 | $post_id = wp_cache_get( $cache_key, 'url_to_postid' ); 275 | 276 | if ( false === $post_id ) { 277 | $post_id = url_to_postid( $url ); // Returns 0 on failure, so need to catch the false condition 278 | wp_cache_set( $cache_key, $post_id, 'url_to_postid', 3 * HOUR_IN_SECONDS ); 279 | } 280 | 281 | return $post_id; 282 | } 283 | } 284 | add_action( 'transition_post_status', function( $new_status, $old_status, $post ) { 285 | if ( 'publish' != $new_status && 'publish' != $old_status ) 286 | return; 287 | 288 | $url = get_permalink( $post->ID ); 289 | wp_cache_delete( md5( $url ), 'url_to_postid' ); 290 | }, 10, 3 ); 291 | 292 | /** 293 | * Cached version of wp_old_slug_redirect. 294 | * 295 | * Cache the results of the _wp_old_slug meta query, which can be expensive. 296 | * @deprecated use wpcom_vip_wp_old_slug_redirect instead 297 | */ 298 | if ( ! function_exists( 'wpcom_vip_old_slug_redirect' ) ) { 299 | function wpcom_vip_old_slug_redirect() { 300 | global $wp_query; 301 | if ( is_404() && '' != $wp_query->query_vars['name'] ) : 302 | global $wpdb; 303 | 304 | // Guess the current post_type based on the query vars. 305 | if ( get_query_var('post_type') ) 306 | $post_type = get_query_var('post_type'); 307 | elseif ( !empty($wp_query->query_vars['pagename']) ) 308 | $post_type = 'page'; 309 | else 310 | $post_type = 'post'; 311 | 312 | if ( is_array( $post_type ) ) { 313 | if ( count( $post_type ) > 1 ) 314 | return; 315 | $post_type = array_shift( $post_type ); 316 | } 317 | 318 | // Do not attempt redirect for hierarchical post types 319 | if ( is_post_type_hierarchical( $post_type ) ) 320 | return; 321 | 322 | $query = $wpdb->prepare("SELECT post_id FROM $wpdb->postmeta, $wpdb->posts WHERE ID = post_id AND post_type = %s AND meta_key = '_wp_old_slug' AND meta_value = %s", $post_type, $wp_query->query_vars['name']); 323 | 324 | // If year, monthnum, or day have been specified, make our query more precise 325 | // Just in case there are multiple identical _wp_old_slug values 326 | if ( '' != $wp_query->query_vars['year'] ) 327 | $query .= $wpdb->prepare(" AND YEAR(post_date) = %d", $wp_query->query_vars['year']); 328 | if ( '' != $wp_query->query_vars['monthnum'] ) 329 | $query .= $wpdb->prepare(" AND MONTH(post_date) = %d", $wp_query->query_vars['monthnum']); 330 | if ( '' != $wp_query->query_vars['day'] ) 331 | $query .= $wpdb->prepare(" AND DAYOFMONTH(post_date) = %d", $wp_query->query_vars['day']); 332 | 333 | $cache_key = md5( serialize( $query ) ); 334 | 335 | if ( false === $id = wp_cache_get( $cache_key, 'wp_old_slug_redirect' ) ) { 336 | $id = (int) $wpdb->get_var($query); 337 | 338 | wp_cache_set( $cache_key, $id, 'wp_old_slug_redirect', 5 * MINUTE_IN_SECONDS ); 339 | } 340 | 341 | if ( ! $id ) 342 | return; 343 | 344 | $link = get_permalink($id); 345 | 346 | if ( !$link ) 347 | return; 348 | 349 | wp_redirect( $link, 301 ); // Permanent redirect 350 | exit; 351 | endif; 352 | } 353 | } 354 | 355 | /** 356 | * Cached version of count_user_posts, which is uncached but doesn't always need to hit the db 357 | * 358 | * count_user_posts is generally fast, but it can be easy to end up with many redundant queries 359 | * if it's called several times per request. This allows bypassing the db queries in favor of 360 | * the cache 361 | */ 362 | if ( ! function_exists( 'wpcom_vip_count_user_posts' ) ) { 363 | function wpcom_vip_count_user_posts( $user_id ) { 364 | if ( ! is_numeric( $user_id ) ) { 365 | return 0; 366 | } 367 | 368 | $cache_key = 'vip_' . (int) $user_id; 369 | $cache_group = 'user_posts_count'; 370 | 371 | if ( false === ( $count = wp_cache_get( $cache_key, $cache_group ) ) ) { 372 | $count = count_user_posts( $user_id ); 373 | 374 | wp_cache_set( $cache_key, $count, $cache_group, 5 * MINUTE_IN_SECONDS ); 375 | } 376 | 377 | return $count; 378 | } 379 | } 380 | 381 | /* 382 | * Cached version of wp_get_nav_menu_object 383 | * 384 | * Many calls to get_term_by (with name or slug lookup as used inside the wp_get_nav_menu_object) across on a single pageload can easily add up the query count. 385 | * This function helps prevent that by taking advantage of wpcom_vip_get_term_by function which adds a layer of caching. 386 | * 387 | * @param string $menu Menu ID, slug, or name. 388 | * @uses wpcom_vip_get_term_by 389 | * @return mixed false if $menu param isn't supplied or term does not exist, menu object if successful. 390 | */ 391 | if ( ! function_exists( 'wpcom_vip_get_nav_menu_object' ) ) { 392 | function wpcom_vip_get_nav_menu_object( $menu ) { 393 | if ( ! $menu ) 394 | return false; 395 | 396 | $menu_obj = get_term( $menu, 'nav_menu' ); 397 | 398 | if ( ! $menu_obj ) { 399 | $menu_obj = wpcom_vip_get_term_by( 'slug', $menu, 'nav_menu' ); 400 | } 401 | 402 | if ( ! $menu_obj ) { 403 | $menu_obj = wpcom_vip_get_term_by( 'name', $menu, 'nav_menu' ); 404 | } 405 | 406 | if ( ! $menu_obj ) { 407 | $menu_obj = false; 408 | } 409 | 410 | return $menu_obj; 411 | } 412 | } 413 | 414 | /** 415 | * Require the Stampedeless_Cache class for use in our helper functions below. 416 | * 417 | * The Stampedeless_Cache helps prevent cache stampedes by internally varying the cache 418 | * expiration slightly when creating a cache entry in an effort to avoid multiple keys 419 | * expiring simultaneously and allowing a single request to regenerate the cache shortly 420 | * before it's expiration. 421 | */ 422 | if( function_exists( 'require_lib' ) && defined( 'WPCOM_IS_VIP_ENV' ) && WPCOM_IS_VIP_ENV ) 423 | require_lib( 'class.stampedeless-cache' ); 424 | 425 | /** 426 | * Drop in replacement for wp_cache_set(). 427 | * 428 | * Wrapper for the WPCOM Stampedeless_Cache class. 429 | * 430 | * @param string $key Cache key. 431 | * @param string|int|array|object $value Data to store in the cache. 432 | * @param string $group Optional. Cache group. 433 | * @param int $expiration Optional. Cache TTL in seconds. 434 | * @return bool This function always returns true. 435 | */ 436 | if ( ! function_exists( 'wpcom_vip_cache_set' ) ) { 437 | function wpcom_vip_cache_set( $key, $value, $group = '', $expiration = 0 ) { 438 | if( ! class_exists( 'Stampedeless_Cache' ) ) 439 | return wp_cache_set( $key, $value, $group, $expiration ); 440 | 441 | $sc = new Stampedeless_Cache( $key, $group ); 442 | $sc->set( $value, $expiration ); 443 | 444 | return true; 445 | } 446 | } 447 | 448 | /** 449 | * Drop in replacement for wp_cache_get(). 450 | * 451 | * Wrapper for the WPCOM Stampedeless_Cache class. 452 | * 453 | * @param string $key Cache key. 454 | * @param string $group Optional. Cache group. 455 | * @return mixed Returns false if failing to retrieve cache entry or the cached data otherwise. 456 | */ 457 | if ( ! function_exists( 'wpcom_vip_cache_get' ) ) { 458 | function wpcom_vip_cache_get( $key, $group = '' ) { 459 | if( ! class_exists( 'Stampedeless_Cache' ) ) 460 | return wp_cache_get( $key, $group ); 461 | 462 | $sc = new Stampedeless_Cache( $key, $group ); 463 | 464 | return $sc->get(); 465 | } 466 | } 467 | 468 | /** 469 | * Drop in replacement for wp_cache_delete(). 470 | * 471 | * Wrapper for WPCOM Stampedeless_Cache class. 472 | * 473 | * @param string $key Cache key. 474 | * @param string $group Optional. Cache group. 475 | * @return bool True on successful removal, false on failure. 476 | */ 477 | if ( ! function_exists( 'wpcom_vip_cache_delete' ) ) { 478 | function wpcom_vip_cache_delete( $key, $group = '' ) { 479 | // Delete cache itself 480 | $deleted = wp_cache_delete( $key, $group ); 481 | 482 | if ( class_exists( 'Stampedeless_Cache' ) ) { 483 | // Delete lock 484 | $lock_key = $key . '_lock'; 485 | wp_cache_delete( $lock_key, $group ); 486 | } 487 | 488 | return $deleted; 489 | } 490 | } 491 | 492 | /** 493 | * Retrieve adjacent post. 494 | * 495 | * Can either be next or previous post. The logic for excluding terms is handled within PHP, for performance benefits. 496 | * Props to Elliott Stocks 497 | * 498 | * @global wpdb $wpdb 499 | * 500 | * @param bool $in_same_term Optional. Whether post should be in a same taxonomy term. Note - only the first term will be used from wp_get_object_terms(). 501 | * @param int $excluded_term Optional. The term to exclude. 502 | * @param bool $previous Optional. Whether to retrieve previous post. 503 | * @param string $taxonomy Optional. Taxonomy, if $in_same_term is true. Default 'category'. 504 | * 505 | * @return null|string|WP_Post Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists. 506 | */ 507 | if ( ! function_exists( 'wpcom_vip_get_adjacent_post' ) ) { 508 | function wpcom_vip_get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category', $adjacent = '' ) { 509 | global $wpdb; 510 | if ( ( ! $post = get_post() ) || ! taxonomy_exists( $taxonomy ) ) { 511 | return null; 512 | } 513 | $join = ''; 514 | $where = ''; 515 | $current_post_date = $post->post_date; 516 | 517 | if ( $in_same_term ) { 518 | if ( is_object_in_taxonomy( $post->post_type, $taxonomy ) ) { 519 | $term_array = get_the_terms( $post->ID, $taxonomy ); 520 | if ( ! empty( $term_array ) && ! is_wp_error( $term_array ) ) { 521 | $term_array_ids = wp_list_pluck( $term_array, 'term_id' ); 522 | // Remove any exclusions from the term array to include. 523 | $excluded_terms = explode( ',', $excluded_terms ); 524 | if ( ! empty( $excluded_terms ) ){ 525 | $term_array_ids = array_diff( $term_array_ids, (array) $excluded_terms ); 526 | } 527 | if ( ! empty ( $term_array_ids ) ){ 528 | $term_array_ids = array_map( 'intval', $term_array_ids ); 529 | $term_id_to_search = array_pop( $term_array_ids ); // Only allow for a single term to be used. picked pseudo randomly 530 | }else{ 531 | $term_id_to_search = false; 532 | } 533 | 534 | $term_id_to_search = apply_filters( 'wpcom_vip_limit_adjacent_post_term_id', $term_id_to_search, $term_array_ids, $excluded_terms, $taxonomy, $previous ); 535 | 536 | if ( ! empty( $term_id_to_search ) ){ // Allow filters to short circuit by returning a empty like value 537 | $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"; //Only join if we are sure there is a term 538 | $where = $wpdb->prepare( "AND tt.taxonomy = %s AND tt.term_id IN (%d) ", $taxonomy,$term_id_to_search ); // 539 | } 540 | } 541 | } 542 | } 543 | 544 | $op = $previous ? '<' : '>'; 545 | $order = $previous ? 'DESC' : 'ASC'; 546 | $limit = 1; 547 | // We need 5 posts so we can filter the excluded term later on 548 | if ( ! empty ( $excluded_term ) ) { 549 | $limit = 5; 550 | } 551 | $sort = "ORDER BY p.post_date $order LIMIT $limit"; 552 | $where = $wpdb->prepare( "WHERE p.post_date $op %s AND p.post_type = %s AND p.post_status = 'publish' $where", $current_post_date, $post->post_type ); 553 | $query = "SELECT p.ID FROM $wpdb->posts AS p $join $where $sort"; 554 | 555 | $found_post = ''; // Blank instead of false so not found is cached 556 | $query_key = 'wpcom_vip_adjacent_post_' . md5( $query ); 557 | $cached_result = wp_cache_get( $query_key ); 558 | 559 | if( "not found" === $cached_result){ 560 | return false; 561 | } else if ( false !== $cached_result ) { 562 | return get_post( $cached_result ); 563 | } 564 | 565 | if ( empty ( $excluded_term ) ) { 566 | $result = $wpdb->get_var( $query ); 567 | } else { 568 | $result = $wpdb->get_results( $query ); 569 | } 570 | 571 | // Find the first post which doesn't have an excluded term 572 | if ( ! empty ( $excluded_term ) ) { 573 | foreach ( $result as $result_post ) { 574 | $post_terms = get_the_terms( $result_post, $taxonomy ); 575 | $terms_array = wp_list_pluck( $post_terms, 'term_id' ); 576 | if ( ! in_array( $excluded_term, $terms_array ) ) { 577 | $found_post = $result_post->ID; 578 | break; 579 | } 580 | } 581 | } else { 582 | $found_post = $result; 583 | } 584 | 585 | /** 586 | * If the post isn't found, lets cache a value we'll check against 587 | * Adds some variation in the caching so if a site is being crawled all the caches don't get created all the time 588 | */ 589 | if ( empty( $found_post ) ){ 590 | wp_cache_set( $query_key, "not found", 'default', 15 * MINUTE_IN_SECONDS + rand( 0, 15 * MINUTE_IN_SECONDS ) ); 591 | return false; 592 | } 593 | 594 | wp_cache_set( $query_key, $found_post, 'default', 6 * HOUR_IN_SECONDS + rand( 0, 2 * HOUR_IN_SECONDS ) ); 595 | $found_post = get_post( $found_post ); 596 | 597 | return $found_post; 598 | } 599 | } 600 | 601 | if ( ! function_exists( 'wpcom_vip_attachment_url_to_postid' ) ) { 602 | function wpcom_vip_attachment_url_to_postid( $url ){ 603 | 604 | $id = wp_cache_get( "wpcom_vip_attachment_url_post_id_". md5( $url ) ); 605 | if ( false === $id ){ 606 | $id = attachment_url_to_postid( $url ); 607 | if ( empty( $id ) ){ 608 | wp_cache_set( "wpcom_vip_attachment_url_post_id_". md5( $url ) , 'not_found', 'default', 12 * HOUR_IN_SECONDS + mt_rand(0, 4 * HOUR_IN_SECONDS ) ); 609 | }else { 610 | wp_cache_set( "wpcom_vip_attachment_url_post_id_". md5( $url ) , $id, 'default', 24 * HOUR_IN_SECONDS + mt_rand(0, 12 * HOUR_IN_SECONDS ) ); 611 | } 612 | } else if( 'not_found' === $id ){ 613 | return false; 614 | } 615 | return $id; 616 | } 617 | } 618 | 619 | if ( ! function_exists( 'wpcom_vip_enable_old_slug_redirect_caching' ) ) { 620 | function wpcom_vip_enable_old_slug_redirect_caching(){ 621 | add_action('template_redirect', 'wpcom_vip_wp_old_slug_redirect', 8 ); 622 | } 623 | } 624 | 625 | /** 626 | * This works by first looking in the cache to see if there is a value saved based on the name query var. 627 | * If one is found, redirect immediately. If nothing is found, including that there is no already cached "not_found" value we then add a hook to old_slug_redirect_url so that when the 'real' wp_old_slug_redirect is run it will store the value in the cache @see wpcom_vip_set_old_slug_redirect_cache(). 628 | * If we found a not_found we remove the template_redirect so the slow query is not run. 629 | */ 630 | if ( ! function_exists( 'wpcom_vip_enable_old_slug_redirect_caching' ) ) { 631 | function wpcom_vip_wp_old_slug_redirect(){ 632 | global $wp_query; 633 | if ( is_404() && '' !== $wp_query->query_vars['name'] ) { 634 | 635 | $redirect = wp_cache_get('old_slug'. $wp_query->query_vars['name'] ); 636 | 637 | if ( false === $redirect ){ 638 | add_filter('old_slug_redirect_url', 'wpcom_vip_set_old_slug_redirect_cache'); 639 | // If an old slug is not found the function returns early and does not apply the old_slug_redirect_url filter. so we will set the cache for not found and if it is found it will be overwritten later in wpcom_vip_set_old_slug_redirect_cache() 640 | wp_cache_set( 'old_slug'. $wp_query->query_vars['name'], 'not_found', 'default', 12 * HOUR_IN_SECONDS + mt_rand(0, 12 * HOUR_IN_SECONDS ) ); 641 | } elseif ( 'not_found' === $redirect ){ 642 | // wpcom_vip_set_old_slug_redirect_cache() will cache 'not_found' when a url is not found so we don't keep hammering the database 643 | remove_action( 'template_redirect', 'wp_old_slug_redirect' ); 644 | return; 645 | } else { 646 | wp_redirect( $redirect, 301 ); // This is kept to not safe_redirect to match the functionality of wp_old_slug_redirect 647 | exit; 648 | } 649 | } 650 | } 651 | } 652 | if ( ! function_exists( 'wpcom_vip_set_old_slug_redirect_cache' ) ) { 653 | function wpcom_vip_set_old_slug_redirect_cache( $link ){ 654 | global $wp_query; 655 | if ( ! empty( $link ) ){ 656 | wp_cache_set( 'old_slug'. $wp_query->query_vars['name'], $link, 'default', 7 * DAY_IN_SECONDS ); 657 | } 658 | return $link; 659 | } 660 | } 661 | 662 | /** 663 | * Prints the title of the most popular blog post 664 | * 665 | * @author nickmomrik 666 | * @param int $days Optional. Number of recent days to find the most popular posts from. Minimum of 2. 667 | */ 668 | if ( ! function_exists( 'wpcom_vip_top_post_title' ) ) { 669 | function wpcom_vip_top_post_title( $days = 2 ) { 670 | global $wpdb; 671 | 672 | // Compat for .org 673 | if ( ! function_exists( 'stats_get_daily_history' ) ) 674 | return array(); // TODO: Return dummy data 675 | 676 | $title = wp_cache_get("wpcom_vip_top_post_title_$days", 'output'); 677 | if ( empty($title) ) { 678 | if ( $days < 2 || !is_int($days) ) $days = 2; // Minimum is 2 because of how stats rollover for a new day 679 | 680 | $topposts = array_shift(stats_get_daily_history(false, $wpdb->blogid, 'postviews', 'post_id', false, $days, '', 11, true)); 681 | if ( $topposts ) { 682 | get_posts(array('include' => join(', ', array_keys($topposts)))); 683 | $posts = 0; 684 | foreach ( $topposts as $id => $views ) { 685 | $post = get_post($id); 686 | if ( empty( $post ) ) 687 | $post = get_page($id); 688 | if ( empty( $post ) ) 689 | continue; 690 | $title .= $post->post_title; 691 | break; 692 | } 693 | } else { 694 | $title .= ''; 695 | } 696 | wp_cache_add("wpcom_vip_top_post_title_$days", $title, 'output', 1200); 697 | } 698 | echo $title; 699 | } 700 | } 701 | 702 | /** 703 | * Fetch a remote URL and cache the result for a certain period of time. 704 | * 705 | * This function originally used file_get_contents(), hence the function name. 706 | * While it no longer does, it still operates the same as the basic PHP function. 707 | * 708 | * We strongly recommend not using a $timeout value of more than 3 seconds as this 709 | * function makes blocking requests (stops page generation and waits for the response). 710 | * 711 | * The $extra_args are: 712 | * * obey_cache_control_header: uses the "cache-control" "max-age" value if greater than $cache_time. 713 | * * http_api_args: see http://codex.wordpress.org/Function_API/wp_remote_get 714 | * 715 | * @link http://lobby.vip.wordpress.com/best-practices/fetching-remote-data/ Fetching Remote Data 716 | * @param string $url URL to fetch 717 | * @param int $timeout Optional. The timeout limit in seconds; valid values are 1-10. Defaults to 3. 718 | * @param int $cache_time Optional. The minimum cache time in seconds. Valid values are >= 60. Defaults to 900. 719 | * @param array $extra_args Optional. Advanced arguments: "obey_cache_control_header" and "http_api_args". 720 | * @return string The remote file's contents (cached) 721 | */ 722 | if ( ! function_exists( 'wpcom_vip_file_get_contents' ) ) { 723 | function wpcom_vip_file_get_contents( $url, $timeout = 3, $cache_time = 900, $extra_args = array() ) { 724 | global $blog_id; 725 | 726 | $extra_args_defaults = array( 727 | 'obey_cache_control_header' => true, // Uses the "cache-control" "max-age" value if greater than $cache_time 728 | 'http_api_args' => array(), // See http://codex.wordpress.org/Function_API/wp_remote_get 729 | ); 730 | 731 | $extra_args = wp_parse_args( $extra_args, $extra_args_defaults ); 732 | 733 | $cache_key = md5( serialize( array_merge( $extra_args, array( 'url' => $url ) ) ) ); 734 | $backup_key = $cache_key . '_backup'; 735 | $disable_get_key = $cache_key . '_disable'; 736 | $cache_group = 'wpcom_vip_file_get_contents'; 737 | 738 | // Temporary legacy keys to prevent mass cache misses during our key switch 739 | $old_cache_key = md5( $url ); 740 | $old_backup_key = 'backup:' . $old_cache_key; 741 | $old_disable_get_key = 'disable:' . $old_cache_key; 742 | 743 | // Let's see if we have an existing cache already 744 | // Empty strings are okay, false means no cache 745 | if ( false !== $cache = wp_cache_get( $cache_key, $cache_group) ) 746 | return $cache; 747 | 748 | // Legacy 749 | if ( false !== $cache = wp_cache_get( $old_cache_key, $cache_group) ) 750 | return $cache; 751 | 752 | // The timeout can be 1 to 10 seconds, we strongly recommend no more than 3 seconds 753 | $timeout = min( 10, max( 1, (int) $timeout ) ); 754 | 755 | if ( $timeout > 3 && ! is_admin() ) 756 | _doing_it_wrong( __FUNCTION__, 'Using a timeout value of over 3 seconds is strongly discouraged because users have to wait for the remote request to finish before the rest of their page loads.', null ); 757 | 758 | $server_up = true; 759 | $response = false; 760 | $content = false; 761 | 762 | // Check to see if previous attempts have failed 763 | if ( false !== wp_cache_get( $disable_get_key, $cache_group ) ) { 764 | $server_up = false; 765 | } 766 | // Legacy 767 | elseif ( false !== wp_cache_get( $old_disable_get_key, $cache_group ) ) { 768 | $server_up = false; 769 | } 770 | // Otherwise make the remote request 771 | else { 772 | $http_api_args = (array) $extra_args['http_api_args']; 773 | $http_api_args['timeout'] = $timeout; 774 | $response = wp_remote_get( $url, $http_api_args ); 775 | } 776 | 777 | // Was the request successful? 778 | if ( $server_up && ! is_wp_error( $response ) && 200 == wp_remote_retrieve_response_code( $response ) ) { 779 | $content = wp_remote_retrieve_body( $response ); 780 | 781 | $cache_header = wp_remote_retrieve_header( $response, 'cache-control' ); 782 | if ( is_array( $cache_header ) ) 783 | $cache_header = array_shift( $cache_header ); 784 | 785 | // Obey the cache time header unless an arg is passed saying not to 786 | if ( $extra_args['obey_cache_control_header'] && $cache_header ) { 787 | $cache_header = trim( $cache_header ); 788 | // When multiple cache-control directives are returned, they are comma separated 789 | foreach ( explode( ',', $cache_header ) as $cache_control ) { 790 | // In this scenario, only look for the max-age directive 791 | if( 'max-age' == substr( trim( $cache_control ), 0, 7 ) ) 792 | // Note the array_pad() call prevents 'undefined offset' notices when explode() returns less than 2 results 793 | list( $cache_header_type, $cache_header_time ) = array_pad( explode( '=', trim( $cache_control ), 2 ), 2, null ); 794 | } 795 | // If the max-age directive was found and had a value set that is greater than our cache time 796 | if ( isset( $cache_header_type ) && isset( $cache_header_time ) && $cache_header_time > $cache_time ) 797 | $cache_time = (int) $cache_header_time; // Casting to an int will strip "must-revalidate", etc. 798 | } 799 | 800 | /** 801 | * The cache time shouldn't be less than a minute 802 | * Please try and keep this as high as possible though 803 | * It'll make your site faster if you do 804 | */ 805 | $cache_time = (int) $cache_time; 806 | if ( $cache_time < 60 ) 807 | $cache_time = 60; 808 | 809 | // Cache the result 810 | wp_cache_add( $cache_key, $content, $cache_group, $cache_time ); 811 | 812 | // Additionally cache the result with no expiry as a backup content source 813 | wp_cache_add( $backup_key, $content, $cache_group ); 814 | 815 | // So we can hook in other places and do stuff 816 | do_action( 'wpcom_vip_remote_request_success', $url, $response ); 817 | } 818 | // Okay, it wasn't successful. Perhaps we have a backup result from earlier. 819 | elseif ( $content = wp_cache_get( $backup_key, $cache_group ) ) { 820 | // If a remote request failed, log why it did 821 | if ( ! defined( 'WPCOM_VIP_DISABLE_REMOTE_REQUEST_ERROR_REPORTING' ) || ! WPCOM_VIP_DISABLE_REMOTE_REQUEST_ERROR_REPORTING ) { 822 | if ( $response && ! is_wp_error( $response ) ) { 823 | error_log( "wpcom_vip_file_get_contents: Blog ID {$blog_id}: Failure for $url and the result was: " . maybe_serialize( $response['headers'] ) . ' ' . maybe_serialize( $response['response'] ) ); 824 | } elseif ( $response ) { // is WP_Error object 825 | error_log( "wpcom_vip_file_get_contents: Blog ID {$blog_id}: Failure for $url and the result was: " . maybe_serialize( $response ) ); 826 | } 827 | } 828 | } 829 | // Legacy 830 | elseif ( $content = wp_cache_get( $old_backup_key, $cache_group ) ) { 831 | // If a remote request failed, log why it did 832 | if ( ! defined( 'WPCOM_VIP_DISABLE_REMOTE_REQUEST_ERROR_REPORTING' ) || ! WPCOM_VIP_DISABLE_REMOTE_REQUEST_ERROR_REPORTING ) { 833 | if ( $response && ! is_wp_error( $response ) ) { 834 | error_log( "wpcom_vip_file_get_contents: Blog ID {$blog_id}: Failure for $url and the result was: " . maybe_serialize( $response['headers'] ) . ' ' . maybe_serialize( $response['response'] ) ); 835 | } elseif ( $response ) { // is WP_Error object 836 | error_log( "wpcom_vip_file_get_contents: Blog ID {$blog_id}: Failure for $url and the result was: " . maybe_serialize( $response ) ); 837 | } 838 | } 839 | } 840 | // We were unable to fetch any content, so don't try again for another 60 seconds 841 | elseif ( $response ) { 842 | wp_cache_add( $disable_get_key, 1, $cache_group, 60 ); 843 | 844 | // If a remote request failed, log why it did 845 | if ( ! defined( 'WPCOM_VIP_DISABLE_REMOTE_REQUEST_ERROR_REPORTING' ) || ! WPCOM_VIP_DISABLE_REMOTE_REQUEST_ERROR_REPORTING ) { 846 | if ( $response && ! is_wp_error( $response ) ) { 847 | error_log( "wpcom_vip_file_get_contents: Blog ID {$blog_id}: Failure for $url and the result was: " . maybe_serialize( $response['headers'] ) . ' ' . maybe_serialize( $response['response'] ) ); 848 | } elseif ( $response ) { // is WP_Error object 849 | error_log( "wpcom_vip_file_get_contents: Blog ID {$blog_id}: Failure for $url and the result was: " . maybe_serialize( $response ) ); 850 | } 851 | } 852 | // So we can hook in other places and do stuff 853 | do_action( 'wpcom_vip_remote_request_error', $url, $response ); 854 | } 855 | return $content; 856 | } 857 | } 858 | -------------------------------------------------------------------------------- /core-fix-comment-counts-caching.php: -------------------------------------------------------------------------------- 1 | post_type, get_post_types( array( 'public' => true ) ) ) ) 66 | return; 67 | wpcom_invalidate_post_data( $post->ID ); 68 | } 69 | 70 | 71 | -------------------------------------------------------------------------------- /core-fix-media-query-caching.php: -------------------------------------------------------------------------------- 1 | get_var( " 15 | SELECT ID 16 | FROM $wpdb->posts 17 | WHERE post_type = 'attachment' 18 | AND post_mime_type LIKE 'audio%' 19 | LIMIT 1 20 | " ); 21 | if ( empty( $has_audio ) ) { // Check into return value 22 | set_transient( 'wpcom_media_has_audio', 'not_found' ); 23 | } else { 24 | set_transient( 'wpcom_media_has_audio', $has_audio ); 25 | } 26 | } elseif ( 'not_found' === $has_audio ) { // Setting to 0 instead of false so that it doesn't query again after the filter is run and we still get the false value used later 27 | $has_audio = 0; 28 | } 29 | return $has_audio; 30 | } 31 | 32 | /** 33 | * Adds caching to media_has_video 34 | */ 35 | function wpcom_media_has_video_cache( $current_value ) { 36 | global $wpdb; 37 | 38 | $has_video = get_transient( 'wpcom_media_has_video' ); 39 | if ( false === $has_video ) { 40 | $has_video = $wpdb->get_var( " 41 | SELECT ID 42 | FROM $wpdb->posts 43 | WHERE post_type = 'attachment' 44 | AND post_mime_type LIKE 'video%' 45 | LIMIT 1 46 | " ); 47 | if ( empty( $has_video ) ) { // Check into return value 48 | set_transient( 'wpcom_media_has_video', 'not_found' ); 49 | } else { 50 | set_transient( 'wpcom_media_has_video', $has_video ); 51 | } 52 | } elseif ( 'not_found' === $has_video ) { // Setting to 0 instead of false so that it doesn't query again after the filter is run and we still get the false value used later 53 | $has_video = 0; 54 | } 55 | return $has_video; 56 | } 57 | 58 | /** 59 | * Adds caching to media_months_array 60 | */ 61 | function wpcom_media_months_array_cache( $current_value ) { 62 | global $wpdb; 63 | 64 | $months = get_transient( 'media_months_array' ); 65 | if ( false === $months ){ 66 | $months = $wpdb->get_results( $wpdb->prepare( " 67 | SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month 68 | FROM $wpdb->posts 69 | WHERE post_type = %s 70 | ORDER BY post_date DESC 71 | LIMIT 1 72 | ", 'attachment' ) ); 73 | if ( empty( $months ) ) { // Check into return value 74 | set_transient( 'wpcom_media_months_array', 'not_found' ); 75 | } else { 76 | set_transient( 'wpcom_media_months_array', $months ); 77 | } 78 | } elseif ( 'not_found' === $months ) { // Setting to empty instead of false so that it doesn't query again after the filter is run and we still get the false value used later 79 | $months = []; 80 | } 81 | return $months; 82 | } 83 | 84 | /** 85 | * Filter clears out transients from wpcom_media_has_audio and wpcom_media_has_video 86 | * Function wpcom_vip_bust_media_months_cache clears out transients from wpcom_media_months_array 87 | */ 88 | add_filter( 'add_attachment', 'wpcom_vip_media_clear_caches' ); 89 | function wpcom_vip_media_clear_caches( $id ) { 90 | $upload_post = get_post( $id ); 91 | if ( strpos( $upload_post->post_mime_type, "video" ) === 0 ) { // Check if it starts with video 92 | delete_transient( 'wpcom_media_has_video' );//@todo: only delete if cache is false? 93 | } else if ( strpos( $upload_post->post_mime_type, "audio" ) === 0 ) { // Check if it starts with audio 94 | delete_transient( 'wpcom_media_has_audio' );//@todo: only delete if cache is false? 95 | } 96 | } 97 | add_action( 'add_attachment', 'wpcom_vip_bust_media_months_cache' ); 98 | function wpcom_vip_bust_media_months_cache( $post_id ) { 99 | global $wpdb; 100 | 101 | // Determines what month/year the most recent attachment is 102 | $months = $wpdb->get_results( $wpdb->prepare( " 103 | SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month 104 | FROM $wpdb->posts 105 | WHERE post_type = %s 106 | ORDER BY post_date DESC 107 | LIMIT 1 108 | ", 'attachment' ) ); //This query will run faster than the core one since it orders and returns the first one. @todo: See if we can add the post_status to better use the type_status_date index. 109 | 110 | // Simplifies by assigning the object to $months 111 | $months = array_shift( array_values( $months ) ); 112 | 113 | // Compares the dates of the new and the most recent attachment 114 | if ( 115 | ! $months->year == get_the_time( 'Y', $post_id ) && 116 | ! $months->month == get_the_time( 'm', $post_id ) 117 | ) { 118 | // The new attachment is not in the same month/year as the most recent attachment, so the transient is refreshed 119 | delete_transient( 'wpcom_media_months_array' ); 120 | } 121 | } -------------------------------------------------------------------------------- /core-fix-update-term-count.php: -------------------------------------------------------------------------------- 1 | blogid == $tc_blog_id ) $tc_before = microtime(true); 19 | if ( $wpdb->blogid == $tc_blog_id ) { 20 | $tc_get_times = array(); 21 | $tc_update_times = array(); 22 | $tc_coap_times = array(); 23 | } 24 | // Update counts for the post's terms. 25 | foreach ( (array) get_object_taxonomies( $post->post_type ) as $taxonomy ) { 26 | if ( $wpdb->blogid == $tc_blog_id ) $tc_before_get = microtime(true); 27 | $tt_ids = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'tt_ids' ) ); 28 | if ( $wpdb->blogid == $tc_blog_id ) $tc_after_get = microtime(true); 29 | if ( $wpdb->blogid == $tc_blog_id ) $tc_get_times[] = ($tc_after_get - $tc_before_get); 30 | if ( $wpdb->blogid == $tc_blog_id ) $tc_before_update = microtime(true); 31 | wp_update_term_count( $tt_ids, $taxonomy ); 32 | if ( $wpdb->blogid == $tc_blog_id ) $tc_after_update = microtime(true); 33 | if ( $wpdb->blogid == $tc_blog_id ) $tc_update_times[] = ($tc_after_update - $tc_before_update); 34 | global $tc_coap_time; 35 | if ( $wpdb->blogid == $tc_blog_id ) $tc_coap_times[] = (int)$tc_coap_time; 36 | } 37 | if ( $wpdb->blogid == $tc_blog_id ) $tc_after = microtime(true); 38 | if ( $wpdb->blogid == $tc_blog_id && 'post' == $post->post_type ) { 39 | $tc_total = ($tc_after - $tc_before); 40 | $tc_update = array_sum($tc_update_times); 41 | $tc_updates = implode(", ", $tc_update_times); 42 | $tc_update_ratio = $tc_update / $tc_total; 43 | $tc_get = array_sum($tc_get_times); 44 | $tc_gets = implode(", ", $tc_get_times); 45 | $tc_get_ratio = $tc_get / $tc_total; 46 | $tc_coap = array_sum($tc_coap_times); 47 | $tc_coaps = implode(", ", $tc_coap_times); 48 | $tc_coap_ratio = $tc_coap / $tc_total; 49 | $tc_report = "total: $tc_total\n\nget: $tc_get ($tc_get_ratio)\n$tc_gets\n\nupdate: $tc_update ($tc_update_ratio)\n$tc_updates\n\ncoap: $tc_coap ($tc_coap_ratio)\n$tc_coaps"; 50 | $tc_post_id = $post->ID; 51 | wp_mail( 'nikolay@automattic.com', "TechCrunch updated $tc_post_id", $tc_report ); 52 | } 53 | } 54 | 55 | /** 56 | * Update the custom taxonomies' term counts when a post's status is changed && when posts are greater than 2000. 57 | * 58 | * For example, default posts term counts (for custom taxonomies) don't include 59 | * private / draft posts. 60 | * 61 | * @since 3.3.0 62 | * @access private 63 | * 64 | * @param string $new_status New post status. 65 | * @param string $old_status Old post status. 66 | * @param WP_Post $post Post object. 67 | */ 68 | if ( count( $posts ) > 2000 ) { 69 | _update_term_count_on_transition_post_status( $new_status, $old_status, $post ); 70 | } -------------------------------------------------------------------------------- /performance-tweaks.php: -------------------------------------------------------------------------------- 1 |