class-primary-term-admin.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. <?php
  2. /**
  3. * WPSEO plugin file.
  4. *
  5. * @package WPSEO\Admin
  6. */
  7. /**
  8. * Adds the UI to change the primary term for a post.
  9. */
  10. class WPSEO_Primary_Term_Admin implements WPSEO_WordPress_Integration {
  11. /**
  12. * Constructor.
  13. */
  14. public function register_hooks() {
  15. add_filter( 'wpseo_content_meta_section_content', [ $this, 'add_input_fields' ] );
  16. add_action( 'admin_footer', [ $this, 'wp_footer' ], 10 );
  17. add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
  18. add_action( 'save_post', [ $this, 'save_primary_terms' ] );
  19. $primary_term = new WPSEO_Frontend_Primary_Category();
  20. $primary_term->register_hooks();
  21. }
  22. /**
  23. * Gets the current post ID.
  24. *
  25. * @return integer The post ID.
  26. */
  27. protected function get_current_id() {
  28. $post_id = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_NUMBER_INT );
  29. if ( empty( $post_id ) && isset( $GLOBALS['post_ID'] ) ) {
  30. $post_id = filter_var( $GLOBALS['post_ID'], FILTER_SANITIZE_NUMBER_INT );
  31. }
  32. return $post_id;
  33. }
  34. /**
  35. * Adds hidden fields for primary taxonomies.
  36. *
  37. * @param string $content The metabox content.
  38. *
  39. * @return string The HTML content.
  40. */
  41. public function add_input_fields( $content ) {
  42. $taxonomies = $this->get_primary_term_taxonomies();
  43. foreach ( $taxonomies as $taxonomy ) {
  44. $content .= $this->primary_term_field( $taxonomy->name );
  45. $content .= wp_nonce_field( 'save-primary-term', WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy->name . '_nonce', false, false );
  46. }
  47. return $content;
  48. }
  49. /**
  50. * Generates the HTML for a hidden field for a primary taxonomy.
  51. *
  52. * @param string $taxonomy_name The taxonomy's slug.
  53. *
  54. * @return string The HTML for a hidden primary taxonomy field.
  55. */
  56. protected function primary_term_field( $taxonomy_name ) {
  57. return sprintf(
  58. '<input class="yoast-wpseo-primary-term" type="hidden" id="%1$s" name="%2$s" value="%3$s" />',
  59. esc_attr( $this->generate_field_id( $taxonomy_name ) ),
  60. esc_attr( $this->generate_field_name( $taxonomy_name ) ),
  61. esc_attr( $this->get_primary_term( $taxonomy_name ) )
  62. );
  63. }
  64. /**
  65. * Generates an id for a primary taxonomy's hidden field.
  66. *
  67. * @param string $taxonomy_name The taxonomy's slug.
  68. *
  69. * @return string The field id.
  70. */
  71. protected function generate_field_id( $taxonomy_name ) {
  72. return 'yoast-wpseo-primary-' . $taxonomy_name;
  73. }
  74. /**
  75. * Generates a name for a primary taxonomy's hidden field.
  76. *
  77. * @param string $taxonomy_name The taxonomy's slug.
  78. *
  79. * @return string The field id.
  80. */
  81. protected function generate_field_name( $taxonomy_name ) {
  82. return WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy_name . '_term';
  83. }
  84. /**
  85. * Adds primary term templates.
  86. */
  87. public function wp_footer() {
  88. $taxonomies = $this->get_primary_term_taxonomies();
  89. if ( ! empty( $taxonomies ) ) {
  90. $this->include_js_templates();
  91. }
  92. }
  93. /**
  94. * Enqueues all the assets needed for the primary term interface.
  95. *
  96. * @return void
  97. */
  98. public function enqueue_assets() {
  99. global $pagenow;
  100. if ( ! WPSEO_Metabox::is_post_edit( $pagenow ) ) {
  101. return;
  102. }
  103. $taxonomies = $this->get_primary_term_taxonomies();
  104. // Only enqueue if there are taxonomies that need a primary term.
  105. if ( empty( $taxonomies ) ) {
  106. return;
  107. }
  108. $asset_manager = new WPSEO_Admin_Asset_Manager();
  109. $asset_manager->enqueue_style( 'primary-category' );
  110. $asset_manager->enqueue_script( 'primary-category' );
  111. $mapped_taxonomies = $this->get_mapped_taxonomies_for_js( $taxonomies );
  112. $data = [
  113. 'taxonomies' => $mapped_taxonomies,
  114. ];
  115. wp_localize_script( WPSEO_Admin_Asset_Manager::PREFIX . 'primary-category', 'wpseoPrimaryCategoryL10n', $data );
  116. }
  117. /**
  118. * Saves all selected primary terms.
  119. *
  120. * @param int $post_id Post ID to save primary terms for.
  121. */
  122. public function save_primary_terms( $post_id ) {
  123. // Bail if this is a multisite installation and the site has been switched.
  124. if ( is_multisite() && ms_is_switched() ) {
  125. return;
  126. }
  127. $taxonomies = $this->get_primary_term_taxonomies( $post_id );
  128. foreach ( $taxonomies as $taxonomy ) {
  129. $this->save_primary_term( $post_id, $taxonomy );
  130. }
  131. }
  132. /**
  133. * Gets the id of the primary term.
  134. *
  135. * @param string $taxonomy_name Taxonomy name for the term.
  136. *
  137. * @return int primary term id
  138. */
  139. protected function get_primary_term( $taxonomy_name ) {
  140. $primary_term = new WPSEO_Primary_Term( $taxonomy_name, $this->get_current_id() );
  141. return $primary_term->get_primary_term();
  142. }
  143. /**
  144. * Returns all the taxonomies for which the primary term selection is enabled.
  145. *
  146. * @param int $post_id Default current post ID.
  147. * @return array
  148. */
  149. protected function get_primary_term_taxonomies( $post_id = null ) {
  150. if ( null === $post_id ) {
  151. $post_id = $this->get_current_id();
  152. }
  153. $taxonomies = wp_cache_get( 'primary_term_taxonomies_' . $post_id, 'wpseo' );
  154. if ( false !== $taxonomies ) {
  155. return $taxonomies;
  156. }
  157. $taxonomies = $this->generate_primary_term_taxonomies( $post_id );
  158. wp_cache_set( 'primary_term_taxonomies_' . $post_id, $taxonomies, 'wpseo' );
  159. return $taxonomies;
  160. }
  161. /**
  162. * Includes templates file.
  163. */
  164. protected function include_js_templates() {
  165. include_once WPSEO_PATH . 'admin/views/js-templates-primary-term.php';
  166. }
  167. /**
  168. * Saves the primary term for a specific taxonomy.
  169. *
  170. * @param int $post_id Post ID to save primary term for.
  171. * @param WP_Term $taxonomy Taxonomy to save primary term for.
  172. */
  173. protected function save_primary_term( $post_id, $taxonomy ) {
  174. $primary_term = filter_input( INPUT_POST, WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy->name . '_term', FILTER_SANITIZE_NUMBER_INT );
  175. // We accept an empty string here because we need to save that if no terms are selected.
  176. if ( null !== $primary_term && check_admin_referer( 'save-primary-term', WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy->name . '_nonce' ) ) {
  177. $primary_term_object = new WPSEO_Primary_Term( $taxonomy->name, $post_id );
  178. $primary_term_object->set_primary_term( $primary_term );
  179. }
  180. }
  181. /**
  182. * Generates the primary term taxonomies.
  183. *
  184. * @param int $post_id ID of the post.
  185. *
  186. * @return array
  187. */
  188. protected function generate_primary_term_taxonomies( $post_id ) {
  189. $post_type = get_post_type( $post_id );
  190. $all_taxonomies = get_object_taxonomies( $post_type, 'objects' );
  191. $all_taxonomies = array_filter( $all_taxonomies, [ $this, 'filter_hierarchical_taxonomies' ] );
  192. /**
  193. * Filters which taxonomies for which the user can choose the primary term.
  194. *
  195. * @api array $taxonomies An array of taxonomy objects that are primary_term enabled.
  196. *
  197. * @param string $post_type The post type for which to filter the taxonomies.
  198. * @param array $all_taxonomies All taxonomies for this post types, even ones that don't have primary term
  199. * enabled.
  200. */
  201. $taxonomies = (array) apply_filters( 'wpseo_primary_term_taxonomies', $all_taxonomies, $post_type, $all_taxonomies );
  202. return $taxonomies;
  203. }
  204. /**
  205. * Creates a map of taxonomies for localization.
  206. *
  207. * @param array $taxonomies The taxononmies that should be mapped.
  208. *
  209. * @return array The mapped taxonomies.
  210. */
  211. protected function get_mapped_taxonomies_for_js( $taxonomies ) {
  212. return array_map( [ $this, 'map_taxonomies_for_js' ], $taxonomies );
  213. }
  214. /**
  215. * Returns an array suitable for use in the javascript.
  216. *
  217. * @param stdClass $taxonomy The taxonomy to map.
  218. *
  219. * @return array The mapped taxonomy.
  220. */
  221. private function map_taxonomies_for_js( $taxonomy ) {
  222. $primary_term = $this->get_primary_term( $taxonomy->name );
  223. if ( empty( $primary_term ) ) {
  224. $primary_term = '';
  225. }
  226. $terms = get_terms( $taxonomy->name );
  227. return [
  228. 'title' => $taxonomy->labels->singular_name,
  229. 'name' => $taxonomy->name,
  230. 'primary' => $primary_term,
  231. 'singularLabel' => $taxonomy->labels->singular_name,
  232. 'fieldId' => $this->generate_field_id( $taxonomy->name ),
  233. 'restBase' => ( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name,
  234. 'terms' => array_map( [ $this, 'map_terms_for_js' ], $terms ),
  235. ];
  236. }
  237. /**
  238. * Returns an array suitable for use in the javascript.
  239. *
  240. * @param stdClass $term The term to map.
  241. *
  242. * @return array The mapped terms.
  243. */
  244. private function map_terms_for_js( $term ) {
  245. return [
  246. 'id' => $term->term_id,
  247. 'name' => $term->name,
  248. ];
  249. }
  250. /**
  251. * Returns whether or not a taxonomy is hierarchical.
  252. *
  253. * @param stdClass $taxonomy Taxonomy object.
  254. *
  255. * @return bool
  256. */
  257. private function filter_hierarchical_taxonomies( $taxonomy ) {
  258. return (bool) $taxonomy->hierarchical;
  259. }
  260. }