get_var( $wpdb->prepare( "SELECT MAX(comment_date_gmt) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1' AND comment_type=''", $post_id ) ); } /** * Return the content type used to serve a Sitemap XML file. * Uses text/xml by default, possibly overridden by jetpack_sitemap_content_type filter. * * @module sitemaps * * @return string Internet media type for the sitemap XML. */ function jetpack_sitemap_content_type() { /** * Filter the content type used to serve the XML sitemap file. * * @module sitemaps * * @since 3.9.0 * * @param string $content_type By default, it's 'text/xml'. */ return apply_filters( 'jetpack_sitemap_content_type', 'text/xml' ); } /** * Write an XML tag. * * @module sitemaps * * @param array $data Information to write an XML tag. */ function jetpack_print_sitemap_item( $data ) { jetpack_print_xml_tag( array( 'url' => $data ) ); } /** * Write an opening tag and its matching closing tag. * * @module sitemaps * * @param array $array Information to write a tag, opening and closing it. */ function jetpack_print_xml_tag( $array ) { foreach ( $array as $key => $value ) { if ( is_array( $value ) ) { echo "<$key>"; jetpack_print_xml_tag( $value ); echo ""; } else { echo "<$key>" . esc_html( $value ) . ""; } } } /** * Convert an array to a SimpleXML child of the passed tree. * * @module sitemaps * * @param array $data array containing element value pairs, including other arrays, for XML contruction. * @param SimpleXMLElement $tree A SimpleXMLElement class object used to attach new children. * * @return SimpleXMLElement full tree with new children mapped from array. */ function jetpack_sitemap_array_to_simplexml( $data, &$tree ) { $doc_namespaces = $tree->getDocNamespaces(); foreach ( $data as $key => $value ) { // Allow namespaced keys by use of colon in $key, namespaces must be part of the document $namespace = null; if ( false !== strpos( $key, ':' ) && 'image' != $key ) { list( $namespace_prefix, $key ) = explode( ':', $key ); if ( isset( $doc_namespaces[ $namespace_prefix ] ) ) { $namespace = $doc_namespaces[ $namespace_prefix ]; } } if ( 'image' != $key ) { if ( is_array( $value ) ) { $child = $tree->addChild( $key, null, $namespace ); jetpack_sitemap_array_to_simplexml( $value, $child ); } else { $tree->addChild( $key, esc_html( $value ), $namespace ); } } elseif ( is_array( $value ) ) { foreach ( $value as $image ) { $child = $tree->addChild( $key, null, $namespace ); jetpack_sitemap_array_to_simplexml( $image, $child ); } } } return $tree; } /** * Define an array of attribute value pairs for use inside the root element of an XML document. * Intended for mapping namespace and namespace URI values. * Passes array through jetpack_sitemap_ns for other functions to add their own namespaces. * * @module sitemaps * * @return array array of attribute value pairs passed through the jetpack_sitemap_ns filter */ function jetpack_sitemap_namespaces() { /** * Filter the attribute value pairs used for namespace and namespace URI mappings. * * @module sitemaps * * @since 3.9.0 * * @param array $namespaces Associative array with namespaces and namespace URIs. */ return apply_filters( 'jetpack_sitemap_ns', array( 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation' => 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd', 'xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9', // Mobile namespace from http://support.google.com/webmasters/bin/answer.py?hl=en&answer=34648 'xmlns:mobile' => 'http://www.google.com/schemas/sitemap-mobile/1.0', 'xmlns:image' => 'http://www.google.com/schemas/sitemap-image/1.1', ) ); } /** * Start sitemap XML document, writing its heading and tag with namespaces. * * @module sitemaps * * @param $charset string Charset for current XML document. * * @return string */ function jetpack_sitemap_initstr( $charset ) { // URL to XSLT $xsl = get_option( 'permalink_structure' ) ? home_url( '/sitemap.xsl' ) : home_url( '/?jetpack-sitemap-xsl=true' ); $initstr = '' . "\n"; $initstr .= '' . "\n"; $initstr .= '' . "\n"; $initstr .= ' $value ) { $initstr .= ' ' . esc_html( $attribute ) . '="' . esc_attr( $value ) . '"'; } $initstr .= ' />'; return $initstr; } /** * Load XSLT for sitemap. * * @module sitemaps * * @param string $type XSLT to load. */ function jetpack_load_xsl( $type = '' ) { $transient_xsl = empty( $type ) ? 'jetpack_sitemap_xsl' : "jetpack_{$type}_sitemap_xsl"; $xsl = get_transient( $transient_xsl ); if ( $xsl ) { header( 'Content-Type: ' . jetpack_sitemap_content_type(), true ); echo $xsl; die(); } // Populate $xsl. Use $type. include_once JETPACK__PLUGIN_DIR . 'modules/sitemaps/sitemap-xsl.php'; if ( ! empty( $xsl ) ) { set_transient( $transient_xsl, $xsl, DAY_IN_SECONDS ); echo $xsl; } die(); } /** * Responds with an XSLT to stylize sitemap. * * @module sitemaps */ function jetpack_print_sitemap_xsl() { jetpack_load_xsl(); } /** * Responds with an XSLT to stylize news sitemap. * * @module sitemaps */ function jetpack_print_news_sitemap_xsl() { jetpack_load_xsl( 'news' ); } /** * Print an XML sitemap conforming to the Sitemaps.org protocol. * Outputs an XML list of up to the latest 1000 posts. * * @module sitemaps * * @link http://sitemaps.org/protocol.php Sitemaps.org protocol. */ function jetpack_print_sitemap() { global $wpdb; $xml = get_transient( 'jetpack_sitemap' ); if ( $xml ) { header( 'Content-Type: ' . jetpack_sitemap_content_type(), true ); echo $xml; die(); } // Compatibility with PHP 5.3 and older if ( ! defined( 'ENT_XML1' ) ) { define( 'ENT_XML1', 16 ); } /** * Filter the post types that will be included in sitemap. * * @module sitemaps * * @since 3.9.0 * * @param array $post_types Array of post types. */ $post_types = apply_filters( 'jetpack_sitemap_post_types', array( 'post', 'page' ) ); $post_types_in = array(); foreach ( (array) $post_types as $post_type ) { $post_types_in[] = $wpdb->prepare( '%s', $post_type ); } $post_types_in = join( ",", $post_types_in ); // use direct query instead because get_posts was acting too heavy for our needs //$posts = get_posts( array( 'numberposts'=>1000, 'post_type'=>$post_types, 'post_status'=>'published' ) ); $posts = $wpdb->get_results( "SELECT ID, post_type, post_modified_gmt, comment_count FROM $wpdb->posts WHERE post_status='publish' AND post_type IN ({$post_types_in}) ORDER BY post_modified_gmt DESC LIMIT 1000" ); if ( empty( $posts ) ) { status_header( 404 ); } header( 'Content-Type: ' . jetpack_sitemap_content_type() ); $initstr = jetpack_sitemap_initstr( get_bloginfo( 'charset' ) ); $tree = simplexml_load_string( $initstr ); // If we did not get a valid string, force UTF-8 and try again. if ( false === $tree ) { $initstr = jetpack_sitemap_initstr( 'UTF-8' ); $tree = simplexml_load_string( $initstr ); } unset( $initstr ); $latest_mod = ''; foreach ( $posts as $post ) { /** * Filter condition to allow skipping specific posts in sitemap. * * @module sitemaps * * @since 3.9.0 * * @param bool $skip Current boolean. False by default, so no post is skipped. * @param WP_POST $post Current post object. */ if ( apply_filters( 'jetpack_sitemap_skip_post', false, $post ) ) { continue; } $post_latest_mod = null; $url = array( 'loc' => esc_url( get_permalink( $post->ID ) ) ); // If this post is configured to be the site home, skip since it's added separately later if ( untrailingslashit( get_permalink( $post->ID ) ) == untrailingslashit( get_option( 'home' ) ) ) { continue; } // Mobile node specified in http://support.google.com/webmasters/bin/answer.py?hl=en&answer=34648 $url['mobile:mobile'] = ''; // Image node specified in http://support.google.com/webmasters/bin/answer.py?hl=en&answer=178636 // These attachments were produced with batch SQL earlier in the script if ( ! post_password_required( $post->ID ) ) { $media = array(); $methods = array( 'from_thumbnail' => false, 'from_slideshow' => false, 'from_gallery' => false, 'from_attachment' => false, 'from_html' => false, ); foreach ( $methods as $method => $value ) { $methods[ $method ] = true; $images_collected = Jetpack_PostImages::get_images( $post->ID, $methods ); if ( is_array( $images_collected ) ) { $media = array_merge( $media, $images_collected ); } $methods[ $method ] = false; } $images = array(); foreach ( $media as $item ) { if ( ! isset( $item['type'] ) || 'image' != $item['type'] ) { continue; } $one_image = array(); if ( isset( $item['src'] ) ) { $one_image['image:loc'] = esc_url( $item['src'] ); $one_image['image:title'] = sanitize_title_with_dashes( $name = pathinfo( $item['src'], PATHINFO_FILENAME ) ); } $images[] = $one_image; } if ( ! empty( $images ) ) { $url['image:image'] = $images; } } if ( $post->post_modified_gmt && $post->post_modified_gmt != '0000-00-00 00:00:00' ) { $post_latest_mod = $post->post_modified_gmt; } if ( $post->comment_count > 0 ) { // last modified based on last comment $latest_comment_datetime = jetpack_get_approved_comments_max_datetime( $post->ID ); if ( ! empty( $latest_comment_datetime ) ) { if ( is_null( $post_latest_mod ) || $latest_comment_datetime > $post_latest_mod ) { $post_latest_mod = $latest_comment_datetime; } } unset( $latest_comment_datetime ); } if ( ! empty( $post_latest_mod ) ) { $latest_mod = max( $latest_mod, $post_latest_mod ); $url['lastmod'] = jetpack_w3cdate_from_mysql( $post_latest_mod ); } unset( $post_latest_mod ); if ( $post->post_type == 'page' ) { $url['changefreq'] = 'weekly'; $url['priority'] = '0.6'; // set page priority above default priority of 0.5 } else { $url['changefreq'] = 'monthly'; } /** * Filter associative array with data to build node and its descendants for current post. * * @module sitemaps * * @since 3.9.0 * * @param array $url Data to build parent and children nodes for current post. * @param int $post_id Current post ID. */ $url_node = apply_filters( 'jetpack_sitemap_url', $url, $post->ID ); jetpack_sitemap_array_to_simplexml( array( 'url' => $url_node ), $tree ); unset( $url ); } $blog_home = array( 'loc' => esc_url( get_option( 'home' ) ), 'changefreq' => 'daily', 'priority' => '1.0' ); if ( ! empty( $latest_mod ) ) { $blog_home['lastmod'] = jetpack_w3cdate_from_mysql( $latest_mod ); header( 'Last-Modified:' . mysql2date( 'D, d M Y H:i:s', $latest_mod, 0 ) . ' GMT' ); } /** * Filter associative array with data to build node and its descendants for site home. * * @module sitemaps * * @since 3.9.0 * * @param array $blog_home Data to build parent and children nodes for site home. */ $url_node = apply_filters( 'jetpack_sitemap_url_home', $blog_home ); jetpack_sitemap_array_to_simplexml( array( 'url' => $url_node ), $tree ); unset( $blog_home ); /** * Filter data before rendering it as XML. * * @module sitemaps * * @since 3.9.0 * * @param SimpleXMLElement $tree Data tree for sitemap. * @param string $latest_mod Date of last modification. */ $tree = apply_filters( 'jetpack_print_sitemap', $tree, $latest_mod ); $xml = $tree->asXML(); unset( $tree ); if ( ! empty( $xml ) ) { set_transient( 'jetpack_sitemap', $xml, DAY_IN_SECONDS ); echo $xml; } die(); } /** * Prints the news XML sitemap conforming to the Sitemaps.org protocol. * Outputs an XML list of up to 1000 posts published in the last 2 days. * * @module sitemaps * * @link http://sitemaps.org/protocol.php Sitemaps.org protocol. */ function jetpack_print_news_sitemap() { $xml = get_transient( 'jetpack_news_sitemap' ); if ( $xml ) { header( 'Content-Type: application/xml' ); echo $xml; die(); } global $wpdb; /** * Filter post types to be included in news sitemap. * * @module sitemaps * * @since 3.9.0 * * @param array $post_types Array with post types to include in news sitemap. */ $post_types = apply_filters( 'jetpack_sitemap_news_sitemap_post_types', array( 'post' ) ); if ( empty( $post_types ) ) { return; } $post_types_in = array(); foreach ( $post_types as $post_type ) { $post_types_in[] = $wpdb->prepare( '%s', $post_type ); } $post_types_in_string = implode( ', ', $post_types_in ); /** * Filter limit of entries to include in news sitemap. * * @module sitemaps * * @since 3.9.0 * * @param int $count Number of entries to include in news sitemap. */ $limit = apply_filters( 'jetpack_sitemap_news_sitemap_count', 1000 ); $cur_datetime = current_time( 'mysql', true ); $query = $wpdb->prepare( " SELECT p.ID, p.post_title, p.post_type, p.post_date, p.post_name, p.post_date_gmt, GROUP_CONCAT(t.name SEPARATOR ', ') AS keywords FROM $wpdb->posts AS p LEFT JOIN $wpdb->term_relationships AS r ON p.ID = r.object_id LEFT JOIN $wpdb->term_taxonomy AS tt ON r.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = 'post_tag' LEFT JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE post_status='publish' AND post_type IN ( {$post_types_in_string} ) AND post_date_gmt > (%s - INTERVAL 2 DAY) GROUP BY p.ID ORDER BY p.post_date_gmt DESC LIMIT %d", $cur_datetime, $limit ); // URL to XSLT $xsl = get_option( 'permalink_structure' ) ? home_url( 'news-sitemap.xsl' ) : home_url( '/?jetpack-news-sitemap-xsl=true' ); // Unless it's zh-cn for Simplified Chinese or zh-tw for Traditional Chinese, // trim national variety so an ISO 639 language code as required by Google. $language_code = strtolower( get_locale() ); if ( in_array( $language_code, array( 'zh_tw', 'zh_cn' ) ) ) { $language_code = str_replace( '_', '-', $language_code ); } else { $language_code = preg_replace( '/(_.*)$/i', '', $language_code ); } header( 'Content-Type: application/xml' ); ob_start(); echo '' . "\n"; echo '' . "\n"; echo '' . "\n"; ?> get_results( $query ); foreach ( $posts as $post ): /** * Filter condition to allow skipping specific posts in news sitemap. * * @module sitemaps * * @since 3.9.0 * * @param bool $skip Current boolean. False by default, so no post is skipped. * @param WP_POST $post Current post object. */ if ( apply_filters( 'jetpack_sitemap_news_skip_post', false, $post ) ) { continue; } $GLOBALS['post'] = $post; $url = array(); $url['loc'] = get_permalink( $post->ID ); $news = array(); $news['news:publication']['news:name'] = get_bloginfo_rss( 'name' ); $news['news:publication']['news:language'] = $language_code; $news['news:publication_date'] = jetpack_w3cdate_from_mysql( $post->post_date_gmt ); $news['news:title'] = get_the_title_rss(); if ( $post->keywords ) { $news['news:keywords'] = html_entity_decode( ent2ncr( $post->keywords ), ENT_HTML5 ); } $url['news:news'] = $news; // Add image to sitemap $post_thumbnail = Jetpack_PostImages::get_image( $post->ID ); if ( isset( $post_thumbnail['src'] ) ) { $url['image:image'] = array( 'image:loc' => esc_url( $post_thumbnail['src'] ) ); } /** * Filter associative array with data to build node and its descendants for current post in news sitemap. * * @module sitemaps * * @since 3.9.0 * * @param array $url Data to build parent and children nodes for current post. * @param int $post_id Current post ID. */ $url = apply_filters( 'jetpack_sitemap_news_sitemap_item', $url, $post ); if ( empty( $url ) ) { continue; } jetpack_print_sitemap_item( $url ); endforeach; ?>