/modules/theme-tools/featured-content.php

  1. <?php 
  2.  
  3. if ( ! class_exists( 'Featured_Content' ) && isset( $GLOBALS['pagenow'] ) && 'plugins.php' !== $GLOBALS['pagenow'] ) { 
  4.  
  5. /** 
  6. * Featured Content. 
  7. * 
  8. * This module will allow users to define a subset of posts to be displayed in a 
  9. * theme-designated featured content area. 
  10. * 
  11. * This feature will only be activated for themes that declare that they support 
  12. * it. This can be done by adding code similar to the following during the 
  13. * "after_setup_theme" action: 
  14. * 
  15. * add_theme_support( 'featured-content', array( 
  16. * 'filter' => 'mytheme_get_featured_content',  
  17. * 'max_posts' => 20,  
  18. * 'post_types' => array( 'post', 'page' ),  
  19. * ) ); 
  20. * 
  21. * For maximum compatibility with different methods of posting users will 
  22. * designate a featured post tag to associate posts with. Since this tag now has 
  23. * special meaning beyond that of a normal tags, users will have the ability to 
  24. * hide it from the front-end of their site. 
  25. */ 
  26. class Featured_Content { 
  27.  
  28. /** 
  29. * The maximum number of posts that a Featured Content area can contain. We 
  30. * define a default value here but themes can override this by defining a 
  31. * "max_posts" entry in the second parameter passed in the call to 
  32. * add_theme_support( 'featured-content' ). 
  33. * 
  34. * @see Featured_Content::init() 
  35. */ 
  36. public static $max_posts = 15; 
  37.  
  38. /** 
  39. * The registered post types supported by Featured Content. Themes can add 
  40. * Featured Content support for registered post types by defining a 
  41. * 'post_types' argument (string|array) in the call to 
  42. * add_theme_support( 'featured-content' ). 
  43. * 
  44. * @see Featured_Content::init() 
  45. */ 
  46. public static $post_types = array( 'post' ); 
  47.  
  48. /** 
  49. * Instantiate. 
  50. * 
  51. * All custom functionality will be hooked into the "init" action. 
  52. */ 
  53. public static function setup() { 
  54. add_action( 'init', array( __CLASS__, 'init' ), 30 ); 
  55.  
  56. /** 
  57. * Conditionally hook into WordPress. 
  58. * 
  59. * Themes must declare that they support this module by adding 
  60. * add_theme_support( 'featured-content' ); during after_setup_theme. 
  61. * 
  62. * If no theme support is found there is no need to hook into WordPress. We'll 
  63. * just return early instead. 
  64. * 
  65. * @uses Featured_Content::$max_posts 
  66. */ 
  67. public static function init() { 
  68. $theme_support = get_theme_support( 'featured-content' ); 
  69.  
  70. // Return early if theme does not support featured content. 
  71. if ( ! $theme_support ) { 
  72. return; 
  73.  
  74. /** 
  75. * An array of named arguments must be passed as the second parameter 
  76. * of add_theme_support(). 
  77. */ 
  78. if ( ! isset( $theme_support[0] ) ) { 
  79. return; 
  80.  
  81. if ( isset( $theme_support[0]['featured_content_filter'] ) ) { 
  82. $theme_support[0]['filter'] = $theme_support[0]['featured_content_filter']; 
  83. unset( $theme_support[0]['featured_content_filter'] ); 
  84.  
  85. // Return early if "filter" has not been defined. 
  86. if ( ! isset( $theme_support[0]['filter'] ) ) { 
  87. return; 
  88.  
  89. // Theme can override the number of max posts. 
  90. if ( isset( $theme_support[0]['max_posts'] ) ) { 
  91. self::$max_posts = absint( $theme_support[0]['max_posts'] ); 
  92.  
  93. add_filter( $theme_support[0]['filter'], array( __CLASS__, 'get_featured_posts' ) ); 
  94. add_action( 'customize_register', array( __CLASS__, 'customize_register' ), 9 ); 
  95. add_action( 'admin_init', array( __CLASS__, 'register_setting' ) ); 
  96. add_action( 'save_post', array( __CLASS__, 'delete_transient' ) ); 
  97. add_action( 'delete_post_tag', array( __CLASS__, 'delete_post_tag' ) ); 
  98. add_action( 'customize_controls_enqueue_scripts', array( __CLASS__, 'enqueue_scripts' ) ); 
  99. add_action( 'pre_get_posts', array( __CLASS__, 'pre_get_posts' ) ); 
  100. add_action( 'switch_theme', array( __CLASS__, 'switch_theme' ) ); 
  101. add_action( 'switch_theme', array( __CLASS__, 'delete_transient' ) ); 
  102. add_action( 'wp_loaded', array( __CLASS__, 'wp_loaded' ) ); 
  103. add_action( 'split_shared_term', array( __CLASS__, 'jetpack_update_featured_content_for_split_terms', 10, 4 ) ); 
  104.  
  105.  
  106. if ( isset( $theme_support[0]['additional_post_types'] ) ) { 
  107. $theme_support[0]['post_types'] = array_merge( array( 'post' ), (array) $theme_support[0]['additional_post_types'] ); 
  108. unset( $theme_support[0]['additional_post_types'] ); 
  109.  
  110. // Themes can allow Featured Content pages 
  111. if ( isset( $theme_support[0]['post_types'] ) ) { 
  112. self::$post_types = array_merge( self::$post_types, (array) $theme_support[0]['post_types'] ); 
  113.  
  114. // register post_tag support for each post type 
  115. foreach ( self::$post_types as $post_type ) { 
  116. register_taxonomy_for_object_type( 'post_tag', $post_type ); 
  117.  
  118. /** 
  119. * Hide "featured" tag from the front-end. 
  120. * 
  121. * Has to run on wp_loaded so that the preview filters of the customizer 
  122. * have a chance to alter the value. 
  123. */ 
  124. public static function wp_loaded() { 
  125. if ( self::get_setting( 'hide-tag' ) ) { 
  126. add_filter( 'get_terms', array( __CLASS__, 'hide_featured_term' ), 10, 3 ); 
  127. add_filter( 'get_the_terms', array( __CLASS__, 'hide_the_featured_term' ), 10, 3 ); 
  128.  
  129. /** 
  130. * Get featured posts 
  131. * 
  132. * This method is not intended to be called directly. Theme developers should 
  133. * place a filter directly in their theme and then pass its name as a value of 
  134. * the "filter" key in the array passed as the $args parameter during the call 
  135. * to: add_theme_support( 'featured-content', $args ). 
  136. * 
  137. * @uses Featured_Content::get_featured_post_ids() 
  138. * 
  139. * @return array 
  140. */ 
  141. public static function get_featured_posts() { 
  142. $post_ids = self::get_featured_post_ids(); 
  143.  
  144. // No need to query if there is are no featured posts. 
  145. if ( empty( $post_ids ) ) { 
  146. return array(); 
  147.  
  148. $featured_posts = get_posts( array( 
  149. 'include' => $post_ids,  
  150. 'posts_per_page' => count( $post_ids ),  
  151. 'post_type' => self::$post_types,  
  152. ) ); 
  153.  
  154. return $featured_posts; 
  155.  
  156. /** 
  157. * Get featured post IDs 
  158. * 
  159. * This function will return the an array containing the post IDs of all 
  160. * featured posts. 
  161. * 
  162. * Sets the "featured_content_ids" transient. 
  163. * 
  164. * @return array Array of post IDs. 
  165. */ 
  166. public static function get_featured_post_ids() { 
  167. // Return array of cached results if they exist. 
  168. $featured_ids = get_transient( 'featured_content_ids' ); 
  169. if ( ! empty( $featured_ids ) ) { 
  170. return array_map( 
  171. 'absint',  
  172. /** 
  173. * Filter the list of Featured Posts IDs. 
  174. * 
  175. * @module theme-tools 
  176. * 
  177. * @since 2.7.0 
  178. * 
  179. * @param array $featured_ids Array of post IDs. 
  180. */ 
  181. apply_filters( 'featured_content_post_ids', (array) $featured_ids ) 
  182. ); 
  183.  
  184. $settings = self::get_setting(); 
  185.  
  186. // Return empty array if no tag name is set. 
  187. $term = get_term_by( 'name', $settings['tag-name'], 'post_tag' ); 
  188. if ( ! $term ) { 
  189. $term = get_term_by( 'id', $settings['tag-id'], 'post_tag' ); 
  190. if ( $term ) { 
  191. $tag = $term->term_id; 
  192. } else { 
  193. /** This action is documented in modules/theme-tools/featured-content.php */ 
  194. return apply_filters( 'featured_content_post_ids', array() ); 
  195.  
  196. // Back compat for installs that have the quantity option still set. 
  197. $quantity = isset( $settings['quantity'] ) ? $settings['quantity'] : self::$max_posts; 
  198.  
  199. // Query for featured posts. 
  200. $featured = get_posts( array( 
  201. 'numberposts' => $quantity,  
  202. 'post_type' => self::$post_types,  
  203. 'tax_query' => array( 
  204. array( 
  205. 'field' => 'term_id',  
  206. 'taxonomy' => 'post_tag',  
  207. 'terms' => $tag,  
  208. ),  
  209. ),  
  210. ) ); 
  211.  
  212. // Return empty array if no featured content exists. 
  213. if ( ! $featured ) { 
  214. /** This action is documented in modules/theme-tools/featured-content.php */ 
  215. return apply_filters( 'featured_content_post_ids', array() ); 
  216.  
  217. // Ensure correct format before save/return. 
  218. $featured_ids = wp_list_pluck( (array) $featured, 'ID' ); 
  219. $featured_ids = array_map( 'absint', $featured_ids ); 
  220.  
  221. set_transient( 'featured_content_ids', $featured_ids ); 
  222.  
  223. /** This action is documented in modules/theme-tools/featured-content.php */ 
  224. return apply_filters( 'featured_content_post_ids', $featured_ids ); 
  225.  
  226. /** 
  227. * Delete Transient. 
  228. * 
  229. * Hooks in the "save_post" action. 
  230. * @see Featured_Content::validate_settings(). 
  231. */ 
  232. public static function delete_transient() { 
  233. delete_transient( 'featured_content_ids' ); 
  234.  
  235. /** 
  236. * Exclude featured posts from the blog query when the blog is the front-page,  
  237. * and user has not checked the "Display tag content in all listings" checkbox. 
  238. * 
  239. * Filter the home page posts, and remove any featured post ID's from it. 
  240. * Hooked onto the 'pre_get_posts' action, this changes the parameters of the 
  241. * query before it gets any posts. 
  242. * 
  243. * @uses Featured_Content::get_featured_post_ids(); 
  244. * @uses Featured_Content::get_setting(); 
  245. * @param WP_Query $query 
  246. * @return WP_Query Possibly modified WP_Query 
  247. */ 
  248. public static function pre_get_posts( $query ) { 
  249.  
  250. // Bail if not home or not main query. 
  251. if ( ! $query->is_home() || ! $query->is_main_query() ) { 
  252. return; 
  253.  
  254. // Bail if the blog page is not the front page. 
  255. if ( 'posts' !== get_option( 'show_on_front' ) ) { 
  256. return; 
  257.  
  258. $featured = self::get_featured_post_ids(); 
  259.  
  260. // Bail if no featured posts. 
  261. if ( ! $featured ) { 
  262. return; 
  263.  
  264. $settings = self::get_setting(); 
  265.  
  266. // Bail if the user wants featured posts always displayed. 
  267. if ( true == $settings['show-all'] ) { 
  268. return; 
  269.  
  270. // We need to respect post ids already in the blacklist. 
  271. $post__not_in = $query->get( 'post__not_in' ); 
  272.  
  273. if ( ! empty( $post__not_in ) ) { 
  274. $featured = array_merge( (array) $post__not_in, $featured ); 
  275. $featured = array_unique( $featured ); 
  276.  
  277. $query->set( 'post__not_in', $featured ); 
  278.  
  279. /** 
  280. * Reset tag option when the saved tag is deleted. 
  281. * 
  282. * It's important to mention that the transient needs to be deleted, too. 
  283. * While it may not be obvious by looking at the function alone, the transient 
  284. * is deleted by Featured_Content::validate_settings(). 
  285. * 
  286. * Hooks in the "delete_post_tag" action. 
  287. * @see Featured_Content::validate_settings(). 
  288. * 
  289. * @param int $tag_id The term_id of the tag that has been deleted. 
  290. * @return void 
  291. */ 
  292. public static function delete_post_tag( $tag_id ) { 
  293. $settings = self::get_setting(); 
  294.  
  295. if ( empty( $settings['tag-id'] ) || $tag_id != $settings['tag-id'] ) { 
  296. return; 
  297.  
  298. $settings['tag-id'] = 0; 
  299. $settings = self::validate_settings( $settings ); 
  300. update_option( 'featured-content', $settings ); 
  301.  
  302. /** 
  303. * Hide featured tag from displaying when global terms are queried from 
  304. * the front-end. 
  305. * 
  306. * Hooks into the "get_terms" filter. 
  307. * 
  308. * @uses Featured_Content::get_setting() 
  309. * 
  310. * @param array $terms A list of term objects. This is the return value of get_terms(). 
  311. * @param array $taxonomies An array of taxonomy slugs. 
  312. * @return array $terms 
  313. */ 
  314. public static function hide_featured_term( $terms, $taxonomies, $args ) { 
  315.  
  316. // This filter is only appropriate on the front-end. 
  317. if ( is_admin() ) { 
  318. return $terms; 
  319.  
  320. // We only want to hide the featured tag. 
  321. if ( ! in_array( 'post_tag', $taxonomies ) ) { 
  322. return $terms; 
  323.  
  324. // Bail if no terms were returned. 
  325. if ( empty( $terms ) ) { 
  326. return $terms; 
  327.  
  328. // Bail if term objects are unavailable. 
  329. if ( 'all' != $args['fields'] ) { 
  330. return $terms; 
  331.  
  332. $settings = self::get_setting(); 
  333. $tag = get_term_by( 'name', $settings['tag-name'], 'post_tag' ); 
  334.  
  335. if ( false !== $tag ) { 
  336. foreach ( $terms as $order => $term ) { 
  337. if ( is_object( $term ) && ( $settings['tag-id'] === $term->term_id || $settings['tag-name'] === $term->name ) ) { 
  338. unset( $terms[ $order ] ); 
  339.  
  340. return $terms; 
  341.  
  342. /** 
  343. * Hide featured tag from displaying when terms associated with a post object 
  344. * are queried from the front-end. 
  345. * 
  346. * Hooks into the "get_the_terms" filter. 
  347. * 
  348. * @uses Featured_Content::get_setting() 
  349. * 
  350. * @param array $terms A list of term objects. This is the return value of get_the_terms(). 
  351. * @param int $id The ID field for the post object that terms are associated with. 
  352. * @param array $taxonomy An array of taxonomy slugs. 
  353. * @return array $terms 
  354. */ 
  355. public static function hide_the_featured_term( $terms, $id, $taxonomy ) { 
  356.  
  357. // This filter is only appropriate on the front-end. 
  358. if ( is_admin() ) { 
  359. return $terms; 
  360.  
  361. // Make sure we are in the correct taxonomy. 
  362. if ( 'post_tag' != $taxonomy ) { 
  363. return $terms; 
  364.  
  365. // No terms? Return early! 
  366. if ( empty( $terms ) ) { 
  367. return $terms; 
  368.  
  369. $settings = self::get_setting(); 
  370. $tag = get_term_by( 'name', $settings['tag-name'], 'post_tag' ); 
  371.  
  372. if ( false !== $tag ) { 
  373. foreach ( $terms as $order => $term ) { 
  374. if ( $settings['tag-id'] === $term->term_id || $settings['tag-name'] === $term->name ) { 
  375. unset( $terms[ $order ] ); 
  376.  
  377. return $terms; 
  378.  
  379. /** 
  380. * Register custom setting on the Settings -> Reading screen. 
  381. * 
  382. * @uses Featured_Content::render_form() 
  383. * @uses Featured_Content::validate_settings() 
  384. * 
  385. * @return void 
  386. */ 
  387. public static function register_setting() { 
  388. add_settings_field( 'featured-content', __( 'Featured Content', 'jetpack' ), array( __class__, 'render_form' ), 'reading' ); 
  389.  
  390. // Register sanitization callback for the Customizer. 
  391. register_setting( 'featured-content', 'featured-content', array( __class__, 'validate_settings' ) ); 
  392.  
  393. /** 
  394. * Add settings to the Customizer. 
  395. * 
  396. * @param WP_Customize_Manager $wp_customize Theme Customizer object. 
  397. */ 
  398. public static function customize_register( $wp_customize ) { 
  399. $wp_customize->add_section( 'featured_content', array( 
  400. 'title' => __( 'Featured Content', 'jetpack' ),  
  401. 'description' => sprintf( __( 'Easily feature all posts with the <a href="%1$s">"featured" tag</a> or a tag of your choice. Your theme supports up to %2$s posts in its featured content area.', 'jetpack' ), admin_url( '/edit.php?tag=featured' ), absint( self::$max_posts ) ),  
  402. 'priority' => 130,  
  403. 'theme_supports' => 'featured-content',  
  404. ) ); 
  405.  
  406. /** Add Featured Content settings. 
  407. * 
  408. * Sanitization callback registered in Featured_Content::validate_settings(). 
  409. * See http://themeshaper.com/2013/04/29/validation-sanitization-in-customizer/comment-page-1/#comment-12374 
  410. */ 
  411. $wp_customize->add_setting( 'featured-content[tag-name]', array( 
  412. 'type' => 'option',  
  413. 'sanitize_js_callback' => array( __CLASS__, 'delete_transient' ),  
  414. ) ); 
  415. $wp_customize->add_setting( 'featured-content[hide-tag]', array( 
  416. 'default' => true,  
  417. 'type' => 'option',  
  418. 'sanitize_js_callback' => array( __CLASS__, 'delete_transient' ),  
  419. ) ); 
  420. $wp_customize->add_setting( 'featured-content[show-all]', array( 
  421. 'default' => false,  
  422. 'type' => 'option',  
  423. 'sanitize_js_callback' => array( __CLASS__, 'delete_transient' ),  
  424. ) ); 
  425.  
  426. // Add Featured Content controls. 
  427. $wp_customize->add_control( 'featured-content[tag-name]', array( 
  428. 'label' => __( 'Tag name', 'jetpack' ),  
  429. 'section' => 'featured_content',  
  430. 'theme_supports' => 'featured-content',  
  431. 'priority' => 20,  
  432. ) ); 
  433. $wp_customize->add_control( 'featured-content[hide-tag]', array( 
  434. 'label' => __( 'Hide tag from displaying in post meta and tag clouds.', 'jetpack' ),  
  435. 'section' => 'featured_content',  
  436. 'theme_supports' => 'featured-content',  
  437. 'type' => 'checkbox',  
  438. 'priority' => 30,  
  439. ) ); 
  440. $wp_customize->add_control( 'featured-content[show-all]', array( 
  441. 'label' => __( 'Display tag content in all listings.', 'jetpack' ),  
  442. 'section' => 'featured_content',  
  443. 'theme_supports' => 'featured-content',  
  444. 'type' => 'checkbox',  
  445. 'priority' => 40,  
  446. ) ); 
  447.  
  448. /** 
  449. * Enqueue the tag suggestion script. 
  450. */ 
  451. public static function enqueue_scripts() { 
  452. wp_enqueue_script( 'featured-content-suggest', plugins_url( 'js/suggest.js', __FILE__ ), array( 'suggest' ), '20131022', true ); 
  453.  
  454. /** 
  455. * Renders all form fields on the Settings -> Reading screen. 
  456. */ 
  457. public static function render_form() { 
  458. printf( __( 'The settings for Featured Content have <a href="%s">moved to Appearance → Customize</a>.', 'jetpack' ), admin_url( 'customize.php?#accordion-section-featured_content' ) ); 
  459.  
  460. /** 
  461. * Get settings 
  462. * 
  463. * Get all settings recognized by this module. This function will return all 
  464. * settings whether or not they have been stored in the database yet. This 
  465. * ensures that all keys are available at all times. 
  466. * 
  467. * In the event that you only require one setting, you may pass its name as the 
  468. * first parameter to the function and only that value will be returned. 
  469. * 
  470. * @param string $key The key of a recognized setting. 
  471. * @return mixed Array of all settings by default. A single value if passed as first parameter. 
  472. */ 
  473. public static function get_setting( $key = 'all' ) { 
  474. $saved = (array) get_option( 'featured-content' ); 
  475.  
  476. /** 
  477. * Filter Featured Content's default settings. 
  478. * 
  479. * @module theme-tools 
  480. * 
  481. * @since 2.7.0 
  482. * 
  483. * @param array $args { 
  484. * Array of Featured Content Settings 
  485. * 
  486. * @type int hide-tag Default is 1. 
  487. * @type int tag-id Default is 0. 
  488. * @type string tag-name Default is empty. 
  489. * @type int show-all Default is 0. 
  490. * } 
  491. */ 
  492. $defaults = apply_filters( 'featured_content_default_settings', array( 
  493. 'hide-tag' => 1,  
  494. 'tag-id' => 0,  
  495. 'tag-name' => '',  
  496. 'show-all' => 0,  
  497. ) ); 
  498.  
  499. $options = wp_parse_args( $saved, $defaults ); 
  500. $options = array_intersect_key( $options, $defaults ); 
  501.  
  502. if ( 'all' != $key ) { 
  503. return isset( $options[ $key ] ) ? $options[ $key ] : false; 
  504.  
  505. return $options; 
  506.  
  507. /** 
  508. * Validate settings 
  509. * 
  510. * Make sure that all user supplied content is in an expected format before 
  511. * saving to the database. This function will also delete the transient set in 
  512. * Featured_Content::get_featured_content(). 
  513. * 
  514. * @uses Featured_Content::delete_transient() 
  515. * 
  516. * @param array $input 
  517. * @return array $output 
  518. */ 
  519. public static function validate_settings( $input ) { 
  520. $output = array(); 
  521.  
  522. if ( empty( $input['tag-name'] ) ) { 
  523. $output['tag-id'] = 0; 
  524. } else { 
  525. $term = get_term_by( 'name', $input['tag-name'], 'post_tag' ); 
  526.  
  527. if ( $term ) { 
  528. $output['tag-id'] = $term->term_id; 
  529. } else { 
  530. $new_tag = wp_create_tag( $input['tag-name'] ); 
  531.  
  532. if ( ! is_wp_error( $new_tag ) && isset( $new_tag['term_id'] ) ) { 
  533. $output['tag-id'] = $new_tag['term_id']; 
  534.  
  535. $output['tag-name'] = $input['tag-name']; 
  536.  
  537. $output['hide-tag'] = isset( $input['hide-tag'] ) && $input['hide-tag'] ? 1 : 0; 
  538.  
  539. $output['show-all'] = isset( $input['show-all'] ) && $input['show-all'] ? 1 : 0; 
  540.  
  541. self::delete_transient(); 
  542.  
  543. return $output; 
  544.  
  545. /** 
  546. * Removes the quantity setting from the options array. 
  547. * 
  548. * @return void 
  549. */ 
  550. public static function switch_theme() { 
  551. $option = (array) get_option( 'featured-content' ); 
  552.  
  553. if ( isset( $option['quantity'] ) ) { 
  554. unset( $option['quantity'] ); 
  555. update_option( 'featured-content', $option ); 
  556.  
  557. public static function jetpack_update_featured_content_for_split_terms( $old_term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) { 
  558. $featured_content_settings = get_option( 'featured-content', array() ); 
  559.  
  560. // Check to see whether the stored tag ID is the one that's just been split. 
  561. if ( isset( $featured_content_settings['tag-id'] ) && $old_term_id == $featured_content_settings['tag-id'] && 'post_tag' == $taxonomy ) { 
  562. // We have a match, so we swap out the old tag ID for the new one and resave the option. 
  563. $featured_content_settings['tag-id'] = $new_term_id; 
  564. update_option( 'featured-content', $featured_content_settings ); 
  565.  
  566. Featured_Content::setup(); 
  567.  
  568. } // end if ( ! class_exists( 'Featured_Content' ) && isset( $GLOBALS['pagenow'] ) && 'plugins.php' !== $GLOBALS['pagenow'] ) { 
.