class.jetpack-twitter-cards.php 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. <?php
  2. /* Twitter Cards
  3. *
  4. * Hooks onto the Open Graph protocol and extends it by adding only the tags
  5. * we need for twitter cards.
  6. *
  7. * @see /wp-content/blog-plugins/open-graph.php
  8. * @see https://dev.twitter.com/cards/overview
  9. */
  10. class Jetpack_Twitter_Cards {
  11. static function twitter_cards_tags( $og_tags ) {
  12. global $post;
  13. if ( post_password_required() ) {
  14. return $og_tags;
  15. }
  16. /** This action is documented in class.jetpack.php */
  17. if ( apply_filters( 'jetpack_disable_twitter_cards', false ) ) {
  18. return $og_tags;
  19. }
  20. /*
  21. * These tags apply to any page (home, archives, etc)
  22. */
  23. // If we have information on the author/creator, then include that as well
  24. if ( ! empty( $post ) && ! empty( $post->post_author ) ) {
  25. /** This action is documented in modules/sharedaddy/sharing-sources.php */
  26. $handle = apply_filters( 'jetpack_sharing_twitter_via', '', $post->ID );
  27. if ( ! empty( $handle ) && ! self::is_default_site_tag( $handle ) ) {
  28. $og_tags['twitter:creator'] = self::sanitize_twitter_user( $handle );
  29. }
  30. }
  31. $site_tag = self::site_tag();
  32. /** This action is documented in modules/sharedaddy/sharing-sources.php */
  33. $site_tag = apply_filters( 'jetpack_sharing_twitter_via', $site_tag, ( is_singular() ? $post->ID : null ) );
  34. /** This action is documented in modules/sharedaddy/sharing-sources.php */
  35. $site_tag = apply_filters( 'jetpack_twitter_cards_site_tag', $site_tag, $og_tags );
  36. if ( ! empty( $site_tag ) ) {
  37. $og_tags['twitter:site'] = self::sanitize_twitter_user( $site_tag );
  38. }
  39. if ( ! is_singular() || ! empty( $og_tags['twitter:card'] ) ) {
  40. /**
  41. * Filter the default Twitter card image, used when no image can be found in a post.
  42. *
  43. * @module sharedaddy, publicize
  44. *
  45. * @since 5.9.0
  46. *
  47. * @param string $str Default image URL.
  48. */
  49. $image = apply_filters( 'jetpack_twitter_cards_image_default', '' );
  50. if ( ! empty( $image ) ) {
  51. $og_tags['twitter:image'] = $image;
  52. }
  53. return $og_tags;
  54. }
  55. $the_title = get_the_title();
  56. if ( ! $the_title ) {
  57. $the_title = get_bloginfo( 'name' );
  58. }
  59. $og_tags['twitter:text:title'] = $the_title;
  60. /*
  61. * The following tags only apply to single pages.
  62. */
  63. $card_type = 'summary';
  64. // Try to give priority to featured images
  65. if ( class_exists('Jetpack_PostImages') ) {
  66. $featured = Jetpack_PostImages::from_thumbnail( $post->ID, 240, 240 );
  67. if ( !empty( $featured ) && count( $featured ) > 0 ) {
  68. if ( (int) $featured[0]['src_width'] >= 280 && (int) $featured[0]['src_height'] >= 150 ) {
  69. $card_type = 'summary_large_image';
  70. $og_tags['twitter:image'] = esc_url( add_query_arg( 'w', 640, $featured[0]['src'] ) );
  71. } else {
  72. $og_tags['twitter:image'] = esc_url( add_query_arg( 'w', 240, $featured[0]['src'] ) );
  73. }
  74. }
  75. }
  76. // Only proceed with media analysis if a featured image has not superseded it already.
  77. if ( empty( $og_tags['twitter:image'] ) && empty( $og_tags['twitter:image:src'] ) ) {
  78. if ( ! class_exists( 'Jetpack_Media_Summary' ) && defined('IS_WPCOM') && IS_WPCOM ) {
  79. include( WP_CONTENT_DIR . '/lib/class.wpcom-media-summary.php' );
  80. }
  81. if ( ! class_exists( 'Jetpack_Media_Summary' ) ) {
  82. jetpack_require_lib( 'class.media-summary' );
  83. }
  84. // Test again, class should already be auto-loaded in Jetpack.
  85. // If not, skip extra media analysis and stick with a summary card
  86. if ( class_exists( 'Jetpack_Media_Summary' ) ) {
  87. $extract = Jetpack_Media_Summary::get( $post->ID );
  88. if ( 'gallery' == $extract['type'] ) {
  89. list( $og_tags, $card_type ) = self::twitter_cards_define_type_based_on_image_count( $og_tags, $extract );
  90. } elseif ( 'video' == $extract['type'] ) {
  91. // Leave as summary, but with large pict of poster frame (we know those comply to Twitter's size requirements)
  92. $card_type = 'summary_large_image';
  93. $og_tags['twitter:image'] = esc_url( add_query_arg( 'w', 640, $extract['image'] ) );
  94. } else {
  95. list( $og_tags, $card_type ) = self::twitter_cards_define_type_based_on_image_count( $og_tags, $extract );
  96. }
  97. }
  98. }
  99. $og_tags['twitter:card'] = $card_type;
  100. // Make sure we have a description for Twitter, their validator isn't happy without some content (single space not valid).
  101. if ( ! isset( $og_tags['og:description'] ) || '' == trim( $og_tags['og:description'] ) || __('Visit the post for more.', 'jetpack') == $og_tags['og:description'] ) { // empty( trim( $og_tags['og:description'] ) ) isn't valid php
  102. $has_creator = ( ! empty($og_tags['twitter:creator']) && '@wordpressdotcom' != $og_tags['twitter:creator'] ) ? true : false;
  103. if ( ! empty( $extract ) && 'video' == $extract['type'] ) { // use $extract['type'] since $card_type is 'summary' for video posts
  104. /* translators: %s is the post author */
  105. $og_tags['twitter:description'] = ( $has_creator ) ? sprintf( __( 'Video post by %s.', 'jetpack' ), $og_tags['twitter:creator'] ) : __( 'Video post.', 'jetpack' );
  106. } else {
  107. /* translators: %s is the post author */
  108. $og_tags['twitter:description'] = ( $has_creator ) ? sprintf( __( 'Post by %s.', 'jetpack' ), $og_tags['twitter:creator'] ) : __( 'Visit the post for more.', 'jetpack');
  109. }
  110. }
  111. if ( empty( $og_tags['twitter:image'] ) && empty( $og_tags['twitter:image:src'] ) ) {
  112. /** This action is documented in class.jetpack-twitter-cards.php */
  113. $image = apply_filters( 'jetpack_twitter_cards_image_default', '' );
  114. if ( ! empty( $image ) ) {
  115. $og_tags['twitter:image'] = $image;
  116. }
  117. }
  118. return $og_tags;
  119. }
  120. static function sanitize_twitter_user( $str ) {
  121. return '@' . preg_replace( '/^@/', '', $str );
  122. }
  123. static function is_default_site_tag( $site_tag ) {
  124. return in_array( $site_tag, array( '@wordpressdotcom', '@jetpack', 'wordpressdotcom', 'jetpack' ) );
  125. }
  126. static function prioritize_creator_over_default_site( $site_tag, $og_tags = array() ) {
  127. if ( ! empty( $og_tags['twitter:creator'] ) && self::is_default_site_tag( $site_tag ) ) {
  128. return $og_tags['twitter:creator'];
  129. }
  130. return $site_tag;
  131. }
  132. static function twitter_cards_define_type_based_on_image_count( $og_tags, $extract ) {
  133. $card_type = 'summary';
  134. $img_count = $extract['count']['image'];
  135. if ( empty( $img_count ) ) {
  136. // No images, use Blavatar as a thumbnail for the summary type.
  137. if ( function_exists('blavatar_domain') ) {
  138. $blavatar_domain = blavatar_domain( site_url() );
  139. if ( blavatar_exists( $blavatar_domain ) ) {
  140. $og_tags['twitter:image'] = blavatar_url( $blavatar_domain, 'img', 240 );
  141. }
  142. }
  143. // Second fall back, Site Logo
  144. if ( empty( $og_tags['twitter:image'] ) && ( function_exists( 'jetpack_has_site_logo' ) && jetpack_has_site_logo() ) ) {
  145. $og_tags['twitter:image'] = jetpack_get_site_logo( 'url' );
  146. }
  147. // Third fall back, Site Icon
  148. if ( empty( $og_tags['twitter:image'] ) && ( function_exists( 'has_site_icon' ) && has_site_icon() ) ) {
  149. $og_tags['twitter:image'] = get_site_icon_url( '240' );
  150. }
  151. // Not falling back on Gravatar, because there's no way to know if we end up with an auto-generated one.
  152. } elseif ( $img_count && ( 'image' == $extract['type'] || 'gallery' == $extract['type'] ) ) {
  153. // Test for $extract['type'] to limit to image and gallery, so we don't send a potential fallback image like a Gravatar as a photo post.
  154. $card_type = 'summary_large_image';
  155. $og_tags['twitter:image'] = esc_url( add_query_arg( 'w', 1400, ( empty( $extract['images'] ) ) ? $extract['image'] : $extract['images'][0]['url'] ) );
  156. }
  157. return array( $og_tags, $card_type );
  158. }
  159. static function twitter_cards_output( $og_tag ) {
  160. return ( false !== strpos( $og_tag, 'twitter:' ) ) ? preg_replace( '/property="([^"]+)"/', 'name="\1"', $og_tag ) : $og_tag;
  161. }
  162. static function settings_init() {
  163. add_settings_section( 'jetpack-twitter-cards-settings', 'Twitter Cards', '__return_false', 'sharing' );
  164. add_settings_field(
  165. 'jetpack-twitter-cards-site-tag',
  166. __( 'Twitter Site Tag', 'jetpack' ),
  167. array( __CLASS__, 'settings_field' ),
  168. 'sharing',
  169. 'jetpack-twitter-cards-settings',
  170. array(
  171. 'label_for' => 'jetpack-twitter-cards-site-tag',
  172. )
  173. );
  174. }
  175. static function sharing_global_options() {
  176. do_settings_fields( 'sharing', 'jetpack-twitter-cards-settings' );
  177. }
  178. static function site_tag() {
  179. $site_tag = ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ?
  180. trim( get_option( 'twitter_via' ) ) :
  181. Jetpack_Options::get_option_and_ensure_autoload( 'jetpack-twitter-cards-site-tag', '' );
  182. if ( empty( $site_tag ) ) {
  183. if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
  184. return 'wordpressdotcom';
  185. } else {
  186. return;
  187. }
  188. }
  189. return $site_tag;
  190. }
  191. static function settings_field() {
  192. wp_nonce_field( 'jetpack-twitter-cards-settings', 'jetpack_twitter_cards_nonce', false );
  193. ?>
  194. <input type="text" id="jetpack-twitter-cards-site-tag" class="regular-text" name="jetpack-twitter-cards-site-tag" value="<?php echo esc_attr( get_option( 'jetpack-twitter-cards-site-tag' ) ); ?>" />
  195. <p class="description" style="width: auto;"><?php esc_html_e( 'The Twitter username of the owner of this site\'s domain.', 'jetpack' ); ?></p>
  196. <?php
  197. }
  198. static function settings_validate() {
  199. if ( wp_verify_nonce( $_POST['jetpack_twitter_cards_nonce'], 'jetpack-twitter-cards-settings' ) ) {
  200. update_option( 'jetpack-twitter-cards-site-tag', trim( ltrim( strip_tags( $_POST['jetpack-twitter-cards-site-tag'] ), '@' ) ) );
  201. }
  202. }
  203. static function init() {
  204. add_filter( 'jetpack_open_graph_tags', array( __CLASS__, 'twitter_cards_tags' ) );
  205. add_filter( 'jetpack_open_graph_output', array( __CLASS__, 'twitter_cards_output' ) );
  206. add_filter( 'jetpack_twitter_cards_site_tag', array( __CLASS__, 'site_tag' ), -99 );
  207. add_filter( 'jetpack_twitter_cards_site_tag', array( __CLASS__, 'prioritize_creator_over_default_site' ), 99, 2 );
  208. add_action( 'admin_init', array( __CLASS__, 'settings_init' ) );
  209. add_action( 'sharing_global_options', array( __CLASS__, 'sharing_global_options' ) );
  210. add_action( 'sharing_admin_update', array( __CLASS__, 'settings_validate' ) );
  211. }
  212. }
  213. Jetpack_Twitter_Cards::init();