analysis_seo = new WPSEO_Metabox_Analysis_SEO(); $this->analysis_readability = new WPSEO_Metabox_Analysis_Readability(); } /** * Sets up up the hooks. */ public function setup_hooks() { $this->set_post_type_hooks(); if ( $this->analysis_seo->is_enabled() ) { add_action( 'restrict_manage_posts', array( $this, 'posts_filter_dropdown' ) ); } if ( $this->analysis_readability->is_enabled() ) { add_action( 'restrict_manage_posts', array( $this, 'posts_filter_dropdown_readability' ) ); } add_filter( 'request', array( $this, 'column_sort_orderby' ) ); } /** * Adds the column headings for the SEO plugin for edit posts / pages overview. * * @param array $columns Already existing columns. * * @return array Array containing the column headings. */ public function column_heading( $columns ) { if ( $this->is_metabox_hidden() === true ) { return $columns; } $added_columns = array(); if ( $this->analysis_seo->is_enabled() ) { $added_columns['wpseo-score'] = '' . __( 'SEO score', 'wordpress-seo' ) . ''; } if ( $this->analysis_readability->is_enabled() ) { $added_columns['wpseo-score-readability'] = '' . __( 'Readability score', 'wordpress-seo' ) . ''; } $added_columns['wpseo-title'] = __( 'SEO Title', 'wordpress-seo' ); $added_columns['wpseo-metadesc'] = __( 'Meta Desc.', 'wordpress-seo' ); if ( $this->analysis_seo->is_enabled() ) { $added_columns['wpseo-focuskw'] = __( 'Focus KW', 'wordpress-seo' ); } return array_merge( $columns, $added_columns ); } /** * Displays the column content for the given column. * * @param string $column_name Column to display the content for. * @param int $post_id Post to display the column content for. */ public function column_content( $column_name, $post_id ) { if ( $this->is_metabox_hidden() === true ) { return; } switch ( $column_name ) { case 'wpseo-score': echo $this->parse_column_score( $post_id ); break; case 'wpseo-score-readability': echo $this->parse_column_score_readability( $post_id ); break; case 'wpseo-title': echo esc_html( apply_filters( 'wpseo_title', wpseo_replace_vars( $this->page_title( $post_id ), get_post( $post_id, ARRAY_A ) ) ) ); break; case 'wpseo-metadesc': $metadesc_val = apply_filters( 'wpseo_metadesc', wpseo_replace_vars( WPSEO_Meta::get_value( 'metadesc', $post_id ), get_post( $post_id, ARRAY_A ) ) ); $metadesc = ( '' === $metadesc_val ) ? '' . esc_html__( 'Meta description not set.', 'wordpress-seo' ) . '' : esc_html( $metadesc_val ); echo $metadesc; break; case 'wpseo-focuskw': $focuskw_val = WPSEO_Meta::get_value( 'focuskw', $post_id ); $focuskw = ( '' === $focuskw_val ) ? '' . esc_html__( 'Focus keyword not set.', 'wordpress-seo' ) . '' : esc_html( $focuskw_val ); echo $focuskw; break; } } /** * Indicates which of the SEO columns are sortable. * * @param array $columns Appended with their orderby variable. * * @return array Array containing the sortable columns. */ public function column_sort( $columns ) { if ( $this->is_metabox_hidden() === true ) { return $columns; } $columns['wpseo-metadesc'] = 'wpseo-metadesc'; if ( $this->analysis_seo->is_enabled() ) { $columns['wpseo-focuskw'] = 'wpseo-focuskw'; } return $columns; } /** * Hides the SEO title, meta description and focus keyword columns if the user hasn't chosen which columns to hide. * * @param array|false $result The hidden columns. * @param string $option The option name used to set which columns should be hidden. * @param WP_User $user The User. * * @return array $result Array containing the columns to hide. */ public function column_hidden( $result, $option, $user ) { global $wpdb; if ( $user->has_prop( $wpdb->get_blog_prefix() . $option ) || $user->has_prop( $option ) ) { return $result; } if ( ! is_array( $result ) ) { $result = array(); } array_push( $result, 'wpseo-title', 'wpseo-metadesc' ); if ( $this->analysis_seo->is_enabled() ) { array_push( $result, 'wpseo-focuskw' ); } return $result; } /** * Adds a dropdown that allows filtering on the posts SEO Quality. */ public function posts_filter_dropdown() { if ( ! $this->can_display_filter() ) { return; } $ranks = WPSEO_Rank::get_all_ranks(); echo ''; echo ''; } /** * Adds a dropdown that allows filtering on the posts Readability Quality. * * @return void */ public function posts_filter_dropdown_readability() { if ( ! $this->can_display_filter() ) { return; } $ranks = WPSEO_Rank::get_all_readability_ranks(); echo ''; echo ''; } /** * Generates an '; } /** * Determines the SEO score filter to be later used in the meta query, based on the passed SEO filter. * * @param string $seo_filter The SEO filter to use to determine what further filter to apply. * * @return array The SEO score filter. */ protected function determine_seo_filters( $seo_filter ) { if ( $seo_filter === WPSEO_Rank::NO_FOCUS ) { return $this->create_no_focus_keyword_filter(); } if ( $seo_filter === WPSEO_Rank::NO_INDEX ) { return $this->create_no_index_filter(); } $rank = new WPSEO_Rank( $seo_filter ); return $this->create_seo_score_filter( $rank->get_starting_score(), $rank->get_end_score() ); } /** * Determines the Readabilty score filter to the meta query, based on the passed Readabilty filter. * * @param string $readability_filter The Readability filter to use to determine what further filter to apply. * * @return array The Readability score filter. */ protected function determine_readability_filters( $readability_filter ) { $rank = new WPSEO_Rank( $readability_filter ); return $this->create_readability_score_filter( $rank->get_starting_score(), $rank->get_end_score() ); } /** * Creates a keyword filter for the meta query, based on the passed Keyword filter. * * @param string $keyword_filter The keyword filter to use. * * @return array The keyword filter. */ protected function get_keyword_filter( $keyword_filter ) { return array( 'post_type' => get_query_var( 'post_type', 'post' ), 'meta_key' => WPSEO_Meta::$meta_prefix . 'focuskw', 'meta_value' => sanitize_text_field( $keyword_filter ), ); } /** * Determines whether the passed filter is considered to be valid. * * @param mixed $filter The filter to check against. * * @return bool Whether or not the filter is considered valid. */ protected function is_valid_filter( $filter ) { return ! empty( $filter ) && is_string( $filter ); } /** * Collects the filters and merges them into a single array. * * @return array Array containing all the applicable filters. */ protected function collect_filters() { $active_filters = array(); $seo_filter = $this->get_current_seo_filter(); $readability_filter = $this->get_current_readability_filter(); $current_keyword_filter = $this->get_current_keyword_filter(); if ( $this->is_valid_filter( $seo_filter ) ) { $active_filters = array_merge( $active_filters, $this->determine_seo_filters( $seo_filter ) ); } if ( $this->is_valid_filter( $readability_filter ) ) { $active_filters = array_merge( $active_filters, $this->determine_readability_filters( $readability_filter ) ); } if ( $this->is_valid_filter( $current_keyword_filter ) ) { $active_filters = array_merge( $active_filters, $this->get_keyword_filter( $current_keyword_filter ) ); } return $active_filters; } /** * Modify the query based on the filters that are being passed. * * @param array $vars Query variables that need to be modified based on the filters. * * @return array Array containing the meta query to use for filtering the posts overview. */ public function column_sort_orderby( $vars ) { $collected_filters = $this->collect_filters(); if ( isset( $vars['orderby'] ) ) { $vars = array_merge( $vars, $this->filter_order_by( $vars['orderby'] ) ); } return $this->build_filter_query( $vars, $collected_filters ); } /** * Retrieves the meta robots query values to be used within the meta query. * * @return array Array containing the query parameters regarding meta robots. */ protected function get_meta_robots_query_values() { return array( 'relation' => 'OR', array( 'key' => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex', 'compare' => 'NOT EXISTS', ), array( 'key' => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex', 'value' => '1', 'compare' => '!=', ), ); } /** * Determines the score filters to be used. If more than one is passed, it created an AND statement for the query. * * @param array $score_filters Array containing the score filters. * * @return array Array containing the score filters that need to be applied to the meta query. */ protected function determine_score_filters( $score_filters ) { if ( count( $score_filters ) > 1 ) { return array_merge( array( 'relation' => 'AND' ), $score_filters ); } return $score_filters; } /** * Retrieves the post type from the $_GET variable. * * @return string The current post type. */ public function get_current_post_type() { return filter_input( INPUT_GET, 'post_type' ); } /** * Retrieves the SEO filter from the $_GET variable. * * @return string The current post type. */ public function get_current_seo_filter() { return filter_input( INPUT_GET, 'seo_filter' ); } /** * Retrieves the Readability filter from the $_GET variable. * * @return string The current post type. */ public function get_current_readability_filter() { return filter_input( INPUT_GET, 'readability_filter' ); } /** * Retrieves the keyword filter from the $_GET variable. * * @return string The current post type. */ public function get_current_keyword_filter() { return filter_input( INPUT_GET, 'seo_kw_filter' ); } /** * Uses the vars to create a complete filter query that can later be executed to filter out posts. * * @param array $vars Array containing the variables that will be used in the meta query. * @param array $filters Array containing the filters that we need to apply in the meta query. * * @return array Array containing the complete filter query. */ protected function build_filter_query( $vars, $filters ) { // If no filters were applied, just return everything. if ( count( $filters ) === 0 ) { return $vars; } $result = array( 'meta_query' => array() ); $result['meta_query'] = array_merge( $result['meta_query'], array( $this->determine_score_filters( $filters ) ) ); $current_seo_filter = $this->get_current_seo_filter(); // This only applies for the SEO score filter because it can because the SEO score can be altered by the no-index option. if ( $this->is_valid_filter( $current_seo_filter ) && ! in_array( $current_seo_filter, array( WPSEO_Rank::NO_INDEX, WPSEO_Rank::NO_FOCUS ), true ) ) { $result['meta_query'] = array_merge( $result['meta_query'], array( $this->get_meta_robots_query_values() ) ); } return array_merge( $vars, $result ); } /** * Creates a Readability score filter. * * @param number $low The lower boundary of the score. * @param number $high The higher boundary of the score. * * @return array The Readability Score filter. */ protected function create_readability_score_filter( $low, $high ) { return array( array( 'key' => WPSEO_Meta::$meta_prefix . 'content_score', 'value' => array( $low, $high ), 'type' => 'numeric', 'compare' => 'BETWEEN', ), ); } /** * Creates an SEO score filter. * * @param number $low The lower boundary of the score. * @param number $high The higher boundary of the score. * * @return array The SEO score filter. */ protected function create_seo_score_filter( $low, $high ) { return array( array( 'key' => WPSEO_Meta::$meta_prefix . 'linkdex', 'value' => array( $low, $high ), 'type' => 'numeric', 'compare' => 'BETWEEN', ), ); } /** * Creates a filter to retrieve posts that were set to no-index. * * @return array Array containin the no-index filter. */ protected function create_no_index_filter() { return array( array( 'key' => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex', 'value' => '1', 'compare' => '=', ), ); } /** * Creates a filter to retrieve posts that have no keyword set. * * @return array Array containing the no focus keyword filter. */ protected function create_no_focus_keyword_filter() { return array( array( 'key' => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex', 'value' => 'needs-a-value-anyway', 'compare' => 'NOT EXISTS', ), array( 'key' => WPSEO_Meta::$meta_prefix . 'linkdex', 'value' => 'needs-a-value-anyway', 'compare' => 'NOT EXISTS', ), ); } /** * Returns filters when $order_by is matched in the if-statement. * * @param string $order_by The ID of the column by which to order the posts. * * @return array Array containing the order filters. */ private function filter_order_by( $order_by ) { switch ( $order_by ) { case 'wpseo-metadesc': return array( 'meta_key' => WPSEO_Meta::$meta_prefix . 'metadesc', 'orderby' => 'meta_value', ); case 'wpseo-focuskw': return array( 'meta_key' => WPSEO_Meta::$meta_prefix . 'focuskw', 'orderby' => 'meta_value', ); } return array(); } /** * Parses the score column. * * @param integer $post_id The ID of the post for which to show the score. * * @return string The HTML for the SEO score indicator. */ private function parse_column_score( $post_id ) { if ( WPSEO_Meta::get_value( 'meta-robots-noindex', $post_id ) === '1' ) { $rank = new WPSEO_Rank( WPSEO_Rank::NO_INDEX ); $title = __( 'Post is set to noindex.', 'wordpress-seo' ); WPSEO_Meta::set_value( 'linkdex', 0, $post_id ); return $this->render_score_indicator( $rank, $title ); } if ( WPSEO_Meta::get_value( 'focuskw', $post_id ) === '' ) { $rank = new WPSEO_Rank( WPSEO_Rank::NO_FOCUS ); $title = __( 'Focus keyword not set.', 'wordpress-seo' ); return $this->render_score_indicator( $rank, $title ); } $score = (int) WPSEO_Meta::get_value( 'linkdex', $post_id ); $rank = WPSEO_Rank::from_numeric_score( $score ); $title = $rank->get_label(); return $this->render_score_indicator( $rank, $title ); } /** * Parsing the readability score column. * * @param int $post_id The ID of the post for which to show the readability score. * * @return string The HTML for the readability score indicator. */ private function parse_column_score_readability( $post_id ) { $score = (int) WPSEO_Meta::get_value( 'content_score', $post_id ); $rank = WPSEO_Rank::from_numeric_score( $score ); return $this->render_score_indicator( $rank ); } /** * Sets up the hooks for the post_types. */ private function set_post_type_hooks() { $post_types = WPSEO_Post_Type::get_accessible_post_types(); if ( is_array( $post_types ) && $post_types !== array() ) { foreach ( $post_types as $post_type ) { if ( $this->is_metabox_hidden( $post_type ) === false ) { add_filter( 'manage_' . $post_type . '_posts_columns', array( $this, 'column_heading' ), 10, 1 ); add_action( 'manage_' . $post_type . '_posts_custom_column', array( $this, 'column_content', ), 10, 2 ); add_action( 'manage_edit-' . $post_type . '_sortable_columns', array( $this, 'column_sort', ), 10, 2 ); /* * Use the `get_user_option_{$option}` filter to change the output of the get_user_option * function for the `manage{$screen}columnshidden` option, which is based on the current * admin screen. The admin screen we want to target is the `edit-{$post_type}` screen. */ $filter = sprintf( 'get_user_option_%s', sprintf( 'manage%scolumnshidden', 'edit-' . $post_type ) ); add_filter( $filter, array( $this, 'column_hidden' ), 10, 3 ); } } unset( $post_type ); } } /** * Test whether the metabox should be hidden either by choice of the admin or because * the post type is not a public post type. * * @since 1.5.0 * * @param string $post_type Optional. The post type to test, defaults to the current post post_type. * * @return bool Whether or not the meta box (and associated columns etc) should be hidden. */ private function is_metabox_hidden( $post_type = null ) { if ( ! isset( $post_type ) ) { $current_post_type = $this->get_current_post_type(); if ( ! empty( $current_post_type ) ) { $post_type = sanitize_text_field( $current_post_type ); } } if ( isset( $post_type ) ) { // Don't make static as post_types may still be added during the run. $cpts = WPSEO_Post_Type::get_accessible_post_types(); $options = get_option( 'wpseo_titles' ); return ( ( isset( $options[ 'hideeditbox-' . $post_type ] ) && $options[ 'hideeditbox-' . $post_type ] === true ) || in_array( $post_type, $cpts, true ) === false ); } return false; } /** * Retrieve the page title. * * @param int $post_id Post to retrieve the title for. * * @return string */ private function page_title( $post_id ) { $fixed_title = WPSEO_Meta::get_value( 'title', $post_id ); if ( $fixed_title !== '' ) { return $fixed_title; } $post = get_post( $post_id ); $options = WPSEO_Options::get_option( 'wpseo_titles' ); if ( is_object( $post ) && ( isset( $options[ 'title-' . $post->post_type ] ) && $options[ 'title-' . $post->post_type ] !== '' ) ) { $title_template = $options[ 'title-' . $post->post_type ]; $title_template = str_replace( ' %%page%% ', ' ', $title_template ); return wpseo_replace_vars( $title_template, $post ); } return wpseo_replace_vars( '%%title%%', $post ); } /** * @param WPSEO_Rank $rank The rank this indicator should have. * @param string $title Optional. The title for this rank, defaults to the title of the rank. * * @return string The HTML for a score indicator. */ private function render_score_indicator( $rank, $title = '' ) { if ( empty( $title ) ) { $title = $rank->get_label(); } return '' . $title . ''; } /** * Hacky way to get round the limitation that you can only have AND *or* OR relationship between * meta key clauses and not a combination - which is what we need. * * @deprecated 3.5 Unnecessary with nested meta queries in core. * @codeCoverageIgnore * * @param string $where Where clause. * * @return string */ public function seo_score_posts_where( $where ) { _deprecated_function( __METHOD__, '3.5' ); global $wpdb; /* Find the two mutually exclusive noindex clauses which should be changed from AND to OR relation */ $find = '`([\s]+AND[\s]+)((?:' . $wpdb->prefix . 'postmeta|mt[0-9]|mt1)\.post_id IS NULL[\s]+)AND([\s]+\([\s]*(?:' . $wpdb->prefix . 'postmeta|mt[0-9])\.meta_key = \'' . WPSEO_Meta::$meta_prefix . 'meta-robots-noindex\' AND CAST\([^\)]+\)[^\)]+\))`'; $replace = '$1( $2OR$3 )'; $new_where = preg_replace( $find, $replace, $where ); if ( $new_where ) { return $new_where; } return $where; } /** * Determines whether or not filter dropdowns should be displayed. * * @return bool Whether or the current page can display the filter drop downs. */ public function can_display_filter() { if ( $GLOBALS['pagenow'] === 'upload.php' ) { return false; } if ( $this->is_metabox_hidden() !== false ) { return false; } $screen = get_current_screen(); if ( null === $screen ) { return false; } return in_array( $screen->post_type, WPSEO_Post_Type::get_accessible_post_types(), true ); } }