class-link-columns.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <?php
  2. /**
  3. * WPSEO plugin file.
  4. *
  5. * @package WPSEO\Admin\Links
  6. */
  7. /**
  8. * Represents the link columns. This class will add and handle the link columns.
  9. */
  10. class WPSEO_Link_Columns {
  11. /**
  12. * Partial column name.
  13. *
  14. * @var string
  15. */
  16. const COLUMN_LINKED = 'linked';
  17. /**
  18. * Partial column name.
  19. *
  20. * @var string
  21. */
  22. const COLUMN_LINKS = 'links';
  23. /**
  24. * Holds the link column count instance.
  25. *
  26. * @var WPSEO_Link_Column_Count
  27. */
  28. protected $link_count;
  29. /**
  30. * Storage to use.
  31. *
  32. * @var WPSEO_Meta_Storage
  33. */
  34. protected $storage;
  35. /**
  36. * List of public post types.
  37. *
  38. * @var array
  39. */
  40. protected $public_post_types = [];
  41. /**
  42. * WPSEO_Link_Columns constructor.
  43. *
  44. * @param WPSEO_Meta_Storage $storage The storage object to use.
  45. */
  46. public function __construct( WPSEO_Meta_Storage $storage ) {
  47. $this->storage = $storage;
  48. }
  49. /**
  50. * Registers the hooks.
  51. */
  52. public function register_hooks() {
  53. global $pagenow;
  54. $is_ajax_request = wp_doing_ajax();
  55. if ( ! WPSEO_Metabox::is_post_overview( $pagenow ) && ! $is_ajax_request ) {
  56. return;
  57. }
  58. // Exit when either table is not present or accessible.
  59. if ( ! WPSEO_Link_Table_Accessible::is_accessible() || ! WPSEO_Meta_Table_Accessible::is_accessible() ) {
  60. return;
  61. }
  62. if ( $is_ajax_request ) {
  63. add_action( 'admin_init', [ $this, 'set_count_objects' ] );
  64. }
  65. // Hook into tablenav to calculate links and linked.
  66. add_action( 'manage_posts_extra_tablenav', [ $this, 'count_objects' ] );
  67. add_filter( 'posts_clauses', [ $this, 'order_by_links' ], 1, 2 );
  68. add_filter( 'posts_clauses', [ $this, 'order_by_linked' ], 1, 2 );
  69. add_filter( 'admin_init', [ $this, 'register_init_hooks' ] );
  70. }
  71. /**
  72. * Register hooks that require to be registered after `init`.
  73. */
  74. public function register_init_hooks() {
  75. $this->public_post_types = apply_filters( 'wpseo_link_count_post_types', WPSEO_Post_Type::get_accessible_post_types() );
  76. if ( is_array( $this->public_post_types ) && $this->public_post_types !== [] ) {
  77. array_walk( $this->public_post_types, [ $this, 'set_post_type_hooks' ] );
  78. }
  79. }
  80. /**
  81. * Modifies the query pieces to allow ordering column by links to post.
  82. *
  83. * @param array $pieces Array of Query pieces.
  84. * @param \WP_Query $query The Query on which to apply.
  85. *
  86. * @return array
  87. */
  88. public function order_by_links( $pieces, $query ) {
  89. if ( 'wpseo-' . self::COLUMN_LINKS !== $query->get( 'orderby' ) ) {
  90. return $pieces;
  91. }
  92. return $this->build_sort_query_pieces( $pieces, $query, 'internal_link_count' );
  93. }
  94. /**
  95. * Modifies the query pieces to allow ordering column by links to post.
  96. *
  97. * @param array $pieces Array of Query pieces.
  98. * @param \WP_Query $query The Query on which to apply.
  99. *
  100. * @return array
  101. */
  102. public function order_by_linked( $pieces, $query ) {
  103. if ( 'wpseo-' . self::COLUMN_LINKED !== $query->get( 'orderby' ) ) {
  104. return $pieces;
  105. }
  106. return $this->build_sort_query_pieces( $pieces, $query, 'incoming_link_count' );
  107. }
  108. /**
  109. * Builds the pieces for a sorting query.
  110. *
  111. * @param array $pieces Array of Query pieces.
  112. * @param \WP_Query $query The Query on which to apply.
  113. * @param string $field The field in the table to JOIN on.
  114. *
  115. * @return array Modified Query pieces.
  116. */
  117. protected function build_sort_query_pieces( $pieces, $query, $field ) {
  118. global $wpdb;
  119. // We only want our code to run in the main WP query.
  120. if ( ! $query->is_main_query() ) {
  121. return $pieces;
  122. }
  123. // Get the order query variable - ASC or DESC.
  124. $order = strtoupper( $query->get( 'order' ) );
  125. // Make sure the order setting qualifies. If not, set default as ASC.
  126. if ( ! in_array( $order, [ 'ASC', 'DESC' ], true ) ) {
  127. $order = 'ASC';
  128. }
  129. $table = $this->storage->get_table_name();
  130. $pieces['join'] .= " LEFT JOIN $table AS yst_links ON yst_links.object_id = {$wpdb->posts}.ID ";
  131. $pieces['orderby'] = "{$field} $order, FIELD( {$wpdb->posts}.post_status, 'publish' ) $order, {$pieces['orderby']}";
  132. return $pieces;
  133. }
  134. /**
  135. * Sets the hooks for each post type.
  136. *
  137. * @param string $post_type The post type.
  138. */
  139. public function set_post_type_hooks( $post_type ) {
  140. add_filter( 'manage_' . $post_type . '_posts_columns', [ $this, 'add_post_columns' ] );
  141. add_action( 'manage_' . $post_type . '_posts_custom_column', [ $this, 'column_content' ], 10, 2 );
  142. add_filter( 'manage_edit-' . $post_type . '_sortable_columns', [ $this, 'column_sort' ] );
  143. }
  144. /**
  145. * Adds the columns for the post overview.
  146. *
  147. * @param array $columns Array with columns.
  148. *
  149. * @return array The extended array with columns.
  150. */
  151. public function add_post_columns( $columns ) {
  152. if ( ! is_array( $columns ) ) {
  153. return $columns;
  154. }
  155. $columns[ 'wpseo-' . self::COLUMN_LINKS ] = sprintf(
  156. '<span class="yoast-linked-to yoast-column-header-has-tooltip" data-tooltip-text="%1$s"><span class="screen-reader-text">%2$s</span></span>',
  157. esc_attr__( 'Number of outgoing internal links in this post. See "Yoast Columns" text in the help tab for more info.', 'wordpress-seo' ),
  158. esc_html__( 'Outgoing internal links', 'wordpress-seo' )
  159. );
  160. if ( ! WPSEO_Link_Query::has_unprocessed_posts( $this->public_post_types ) ) {
  161. $columns[ 'wpseo-' . self::COLUMN_LINKED ] = sprintf(
  162. '<span class="yoast-linked-from yoast-column-header-has-tooltip" data-tooltip-text="%1$s"><span class="screen-reader-text">%2$s</span></span>',
  163. esc_attr__( 'Number of internal links linking to this post. See "Yoast Columns" text in the help tab for more info.', 'wordpress-seo' ),
  164. esc_html__( 'Received internal links', 'wordpress-seo' )
  165. );
  166. }
  167. return $columns;
  168. }
  169. /**
  170. * Makes sure we calculate all values in one query.
  171. *
  172. * @param string $target Extra table navigation location which is triggered.
  173. */
  174. public function count_objects( $target ) {
  175. if ( 'top' === $target ) {
  176. $this->set_count_objects();
  177. }
  178. }
  179. /**
  180. * Sets the objects to use for the count.
  181. */
  182. public function set_count_objects() {
  183. global $wp_query;
  184. $posts = empty( $wp_query->posts ) ? $wp_query->get_posts() : $wp_query->posts;
  185. $post_ids = [];
  186. // Post lists return a list of objects.
  187. if ( isset( $posts[0] ) && is_object( $posts[0] ) ) {
  188. $post_ids = wp_list_pluck( $posts, 'ID' );
  189. }
  190. elseif ( ! empty( $posts ) ) {
  191. // Page list returns an array of post IDs.
  192. $post_ids = array_keys( $posts );
  193. }
  194. $post_ids = WPSEO_Link_Query::filter_unprocessed_posts( $post_ids );
  195. $links = new WPSEO_Link_Column_Count();
  196. $links->set( $post_ids );
  197. $this->link_count = $links;
  198. }
  199. /**
  200. * Displays the column content for the given column.
  201. *
  202. * @param string $column_name Column to display the content for.
  203. * @param int $post_id Post to display the column content for.
  204. */
  205. public function column_content( $column_name, $post_id ) {
  206. $link_count = null;
  207. switch ( $column_name ) {
  208. case 'wpseo-' . self::COLUMN_LINKS:
  209. $link_count = $this->link_count->get( $post_id, 'internal_link_count' );
  210. break;
  211. case 'wpseo-' . self::COLUMN_LINKED:
  212. if ( get_post_status( $post_id ) === 'publish' ) {
  213. $link_count = $this->link_count->get( $post_id, 'incoming_link_count' );
  214. }
  215. break;
  216. }
  217. if ( isset( $link_count ) ) {
  218. echo (int) $link_count;
  219. }
  220. }
  221. /**
  222. * Sets the sortable columns.
  223. *
  224. * @param array $columns Array with sortable columns.
  225. *
  226. * @return array The extended array with sortable columns.
  227. */
  228. public function column_sort( array $columns ) {
  229. $columns[ 'wpseo-' . self::COLUMN_LINKS ] = 'wpseo-' . self::COLUMN_LINKS;
  230. $columns[ 'wpseo-' . self::COLUMN_LINKED ] = 'wpseo-' . self::COLUMN_LINKED;
  231. return $columns;
  232. }
  233. }