class-gsc-table.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. <?php
  2. /**
  3. * WPSEO plugin file.
  4. *
  5. * @package WPSEO\Admin\Google_Search_Console
  6. */
  7. if ( ! class_exists( 'WP_List_Table' ) ) {
  8. require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
  9. }
  10. /**
  11. * Class WPSEO_GSC_Table
  12. */
  13. class WPSEO_GSC_Table extends WP_List_Table {
  14. const FREE_MODAL_HEIGHT = 140;
  15. /**
  16. * @var string
  17. */
  18. private $search_string;
  19. /**
  20. * @var array
  21. */
  22. protected $_column_headers;
  23. /**
  24. * The category that is displayed
  25. *
  26. * @var mixed|string
  27. */
  28. private $current_view;
  29. /**
  30. * @var integer
  31. */
  32. private $per_page = 50;
  33. /**
  34. * @var integer
  35. */
  36. private $current_page = 1;
  37. /**
  38. * Search Console table class constructor (subclasses list table).
  39. *
  40. * @param string $platform Platform (desktop, mobile, feature phone).
  41. * @param string $category Type of the issues.
  42. * @param array $items Set of the issues to display.
  43. */
  44. public function __construct( $platform, $category, array $items ) {
  45. parent::__construct();
  46. // Adding the thickbox.
  47. add_thickbox();
  48. // Set search string.
  49. $search_string = filter_input( INPUT_GET, 's' );
  50. if ( $search_string !== '' ) {
  51. $this->search_string = $search_string;
  52. }
  53. $this->current_view = $category;
  54. // Set the crawl issue source.
  55. $this->show_fields( $platform );
  56. $this->items = $items;
  57. }
  58. /**
  59. * Getting the screen id from this table
  60. *
  61. * @return string
  62. */
  63. public function get_screen_id() {
  64. return $this->screen->id;
  65. }
  66. /**
  67. * Setup the table variables, fetch the items from the database, search, sort and format the items.
  68. */
  69. public function prepare_items() {
  70. // Get variables needed for pagination.
  71. $this->per_page = $this->get_items_per_page( 'errors_per_page', $this->per_page );
  72. $paged = filter_input( INPUT_GET, 'paged' );
  73. $this->current_page = intval( ( ! empty( $paged ) ) ? $paged : 1 );
  74. $this->setup_columns();
  75. $this->views();
  76. $this->parse_items();
  77. }
  78. /**
  79. * Set the table columns
  80. *
  81. * @return array
  82. */
  83. public function get_columns() {
  84. $columns = array(
  85. 'cb' => '<input type="checkbox" />',
  86. 'url' => __( 'URL', 'wordpress-seo' ),
  87. 'last_crawled' => __( 'Last crawled', 'wordpress-seo' ),
  88. 'first_detected' => __( 'First detected', 'wordpress-seo' ),
  89. 'response_code' => __( 'Response code', 'wordpress-seo' ),
  90. );
  91. return $columns;
  92. }
  93. /**
  94. * Return the columns that are sortable
  95. *
  96. * @return array
  97. */
  98. protected function get_sortable_columns() {
  99. $sortable_columns = array(
  100. 'url' => array( 'url', false ),
  101. 'last_crawled' => array( 'last_crawled', false ),
  102. 'first_detected' => array( 'first_detected', false ),
  103. 'response_code' => array( 'response_code', false ),
  104. );
  105. return $sortable_columns;
  106. }
  107. /**
  108. * Return available bulk actions
  109. *
  110. * @return array
  111. */
  112. protected function get_bulk_actions() {
  113. return array(
  114. 'mark_as_fixed' => __( 'Mark as fixed', 'wordpress-seo' ),
  115. );
  116. }
  117. /**
  118. * Default method to display a column
  119. *
  120. * @param array $item Data array.
  121. * @param string $column_name Column name key.
  122. *
  123. * @return mixed
  124. */
  125. protected function column_default( $item, $column_name ) {
  126. return $item[ $column_name ];
  127. }
  128. /**
  129. * Checkbox column
  130. *
  131. * @param array $item Item data array.
  132. *
  133. * @return string
  134. */
  135. protected function column_cb( $item ) {
  136. return sprintf(
  137. '<input type="checkbox" name="wpseo_crawl_issues[]" id="cb-%1$s" value="%2$s" /><label for="cb-%1$s" class="screen-reader-text">%3$s</label>',
  138. md5( $item['url'] ),
  139. $item['url'],
  140. __( 'Select redirect', 'wordpress-seo' )
  141. );
  142. }
  143. /**
  144. * Formatting the output of the column last crawled into a dateformat
  145. *
  146. * @param array $item Item data array.
  147. *
  148. * @return string
  149. */
  150. protected function column_last_crawled( $item ) {
  151. return date_i18n( get_option( 'date_format' ), strtotime( $item['last_crawled'] ) );
  152. }
  153. /**
  154. * Formatting the output of the column first detected into a dateformat
  155. *
  156. * @param array $item Item data array.
  157. *
  158. * @return string
  159. */
  160. protected function column_first_detected( $item ) {
  161. return date_i18n( get_option( 'date_format' ), strtotime( $item['first_detected'] ) );
  162. }
  163. /**
  164. * URL column
  165. *
  166. * @param array $item Item data array.
  167. *
  168. * @return string
  169. */
  170. protected function column_url( $item ) {
  171. $actions = array();
  172. if ( $this->can_create_redirect() ) {
  173. /** Gets the modal box */
  174. $modal = $this->get_modal_box( $item['url'] );
  175. $modal->load_view( md5( $item['url'] ) );
  176. $actions['create_redirect'] = '<a href="#TB_inline?width=600&height=' . $modal->get_height() . '&inlineId=redirect-' . md5( $item['url'] ) . '" class="thickbox wpseo-open-gsc-redirect-modal aria-button-if-js">' . __( 'Create redirect', 'wordpress-seo' ) . '</a>';
  177. }
  178. $actions['view'] = '<a href="' . home_url( $item['url'] ) . '" target="_blank">' . __( 'View', 'wordpress-seo' ) . '</a>';
  179. $actions['markasfixed'] = '<a href="javascript:wpseoMarkAsFixed(\'' . urlencode( $item['url'] ) . '\');">' . __( 'Mark as fixed', 'wordpress-seo' ) . '</a>';
  180. return sprintf(
  181. '<span class="value">%1$s</span> %2$s',
  182. $item['url'],
  183. $this->row_actions( $actions )
  184. );
  185. }
  186. /**
  187. * Running the setup of the columns
  188. */
  189. private function setup_columns() {
  190. $this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() );
  191. }
  192. /**
  193. * Check if the current category allow creating redirects
  194. *
  195. * @return bool
  196. */
  197. private function can_create_redirect() {
  198. return in_array( $this->current_view, array( 'soft_404', 'not_found', 'access_denied' ), true );
  199. }
  200. /**
  201. * Setting the table navigation
  202. *
  203. * @param int $total_items Total number of items.
  204. * @param int $posts_per_page Number of items per page.
  205. */
  206. private function set_pagination( $total_items, $posts_per_page ) {
  207. $this->set_pagination_args( array(
  208. 'total_items' => $total_items,
  209. 'total_pages' => ceil( ( $total_items / $posts_per_page ) ),
  210. 'per_page' => $posts_per_page,
  211. ) );
  212. }
  213. /**
  214. * Setting the items
  215. */
  216. private function parse_items() {
  217. if ( is_array( $this->items ) && count( $this->items ) > 0 ) {
  218. if ( ! empty( $this->search_string ) ) {
  219. $this->do_search();
  220. }
  221. $this->set_pagination( count( $this->items ), $this->per_page );
  222. $this->sort_items();
  223. $this->paginate_items();
  224. }
  225. }
  226. /**
  227. * Search through the items
  228. */
  229. private function do_search() {
  230. $results = array();
  231. foreach ( $this->items as $item ) {
  232. foreach ( $item as $value ) {
  233. if ( stristr( $value, $this->search_string ) !== false ) {
  234. $results[] = $item;
  235. continue;
  236. }
  237. }
  238. }
  239. $this->items = $results;
  240. }
  241. /**
  242. * Running the pagination
  243. */
  244. private function paginate_items() {
  245. // Setting the starting point. If starting point is below 1, overwrite it with value 0, otherwise it will be sliced of at the back.
  246. $slice_start = ( $this->current_page - 1 );
  247. if ( $slice_start < 0 ) {
  248. $slice_start = 0;
  249. }
  250. // Apply 'pagination'.
  251. $this->items = array_slice( $this->items, ( $slice_start * $this->per_page ), $this->per_page );
  252. }
  253. /**
  254. * Sort the items by callback
  255. */
  256. private function sort_items() {
  257. // Sort the results.
  258. usort( $this->items, array( $this, 'do_reorder' ) );
  259. }
  260. /**
  261. * Doing the sorting of the issues
  262. *
  263. * @param array $a First data set for comparison.
  264. * @param array $b Second data set for comparison.
  265. *
  266. * @return int
  267. */
  268. private function do_reorder( $a, $b ) {
  269. $orderby = filter_input( INPUT_GET, 'orderby' );
  270. $order = filter_input( INPUT_GET, 'order' );
  271. // If no sort, default to title.
  272. $orderby = ( ! empty( $orderby ) ) ? $orderby : 'url';
  273. // If no order, default to asc.
  274. $order = ( ! empty( $order ) ) ? $order : 'asc';
  275. // When there is a raw field of it, sort by this field.
  276. if ( array_key_exists( $orderby . '_raw', $a ) && array_key_exists( $orderby . '_raw', $b ) ) {
  277. $orderby = $orderby . '_raw';
  278. }
  279. // Determine sort order.
  280. $result = strcmp( $a[ $orderby ], $b[ $orderby ] );
  281. // Send final sort direction to usort.
  282. return ( $order === 'asc' ) ? $result : ( - $result );
  283. }
  284. /**
  285. * Checks if premium is loaded, if not the nopremium modal will be shown. Otherwise it will load the premium one.
  286. *
  287. * @param string $url URL string.
  288. *
  289. * @return WPSEO_GSC_Modal Instance of the GSC modal.
  290. */
  291. private function get_modal_box( $url ) {
  292. if ( defined( 'WPSEO_PREMIUM_FILE' ) && class_exists( 'WPSEO_Premium_GSC_Modal' ) ) {
  293. static $premium_modal;
  294. if ( ! $premium_modal ) {
  295. $premium_modal = new WPSEO_Premium_GSC_Modal();
  296. }
  297. return $premium_modal->show( $url );
  298. }
  299. return new WPSEO_GSC_Modal(
  300. dirname( __FILE__ ) . '/views/gsc-redirect-nopremium.php',
  301. self::FREE_MODAL_HEIGHT,
  302. array( 'url' => $url )
  303. );
  304. }
  305. /**
  306. * Showing the hidden fields used by the AJAX requests
  307. *
  308. * @param string $platform Platform (desktop, mobile, feature phone).
  309. */
  310. private function show_fields( $platform ) {
  311. echo '<input type="hidden" name="wpseo_gsc_nonce" value="' . esc_attr( wp_create_nonce( 'wpseo_gsc_nonce' ) ) . '" />';
  312. echo '<input id="field_platform" type="hidden" name="platform" value="' . esc_attr( $platform ) . '" />';
  313. echo '<input id="field_category" type="hidden" name="category" value="' . esc_attr( $this->current_view ) . '" />';
  314. }
  315. }