base ) ) { return false; } if ( 'edit' !== $screen->base ) { return false; } $pt = ! empty( $screen->post_type ) ? $screen->post_type : false; if ( $query->get( 'post_type' ) !== $pt ) { return false; } $meta_queries = $query->get( 'meta_query', array() ); // Set SEO analysis threshold meta query. if ( isset( $data['wds_analysis_threshold'] ) ) { $raw = ! empty( $data['wds_analysis_threshold'] ) && is_numeric( $data['wds_analysis_threshold'] ) ? (int) $data['wds_analysis_threshold'] . '' : '69'; if ( empty( $raw ) ) { return false; } $rx = ''; foreach ( str_split( $raw ) as $char ) { $rx .= ! empty( $char ) ? '[0-' . (int) $char . ']?' : '0?'; } $rx = substr( $rx, 0, strlen( $rx ) - 1 ); // Strip last question mark. $meta_queries[] = array( 'key' => Smartcrawl_Model_Analysis::META_KEY_ANALYSIS, 'value' => '[[:punct:]]percentage[[:punct:]];i:' . $rx . ';', 'compare' => 'REGEXP', ); } // Set readability meta query. if ( isset( $data['wds_readability_threshold'] ) ) { // Filter by just readable/not readable. $readable = ! empty( $data['wds_readability_threshold'] ) ? 1 : 0; $meta_queries[] = array( 'key' => Smartcrawl_Model_Analysis::META_KEY_READABILITY, 'value' => '[[:punct:]]is_readable[[:punct:]];b:' . (int) $readable . ';', 'compare' => 'REGEXP', ); } if ( ! empty( $meta_queries ) ) { $query->set( 'meta_query', $meta_queries ); } return true; } /** * Enqueues admin scripts * * @param string $hook Page hook. * * @return bool Status */ public function inject_script_dependencies( $hook ) { if ( 'edit.php' !== $hook ) { return false; } wp_enqueue_script( Smartcrawl_Controller_Assets::WP_POST_LIST_TABLE_JS ); wp_enqueue_style( Smartcrawl_Controller_Assets::WP_POST_LIST_TABLE_CSS ); return true; } /** * Sets up column filtering actions * * @return void */ public function set_up_post_columns() { // Set up column filtering. foreach ( smartcrawl_frontend_post_types() as $type ) { add_filter( "manage_{$type}_posts_columns", array( $this, 'add_analysis_columns' ) ); add_action( "manage_{$type}_posts_custom_column", array( $this, 'add_analysis_column_data' ), 10, 2 ); } add_action( 'quick_edit_custom_box', array( $this, 'add_quick_edit_focus_keyword_field' ), 10, 1 ); } public function add_quick_edit_focus_keyword_field( $column ) { if ( $column === 'seo' ) { Smartcrawl_Simple_Renderer::render( 'post-list/quick-edit-seo-analysis', array() ); } } /** * Injects custom columns for analysis * * @param array $columns Columns hash. * * @return array */ public function add_analysis_columns( $columns ) { if ( Smartcrawl_Settings::get_setting( 'analysis-seo' ) ) { $columns['seo'] = __( 'SEO', 'wds' ); } if ( Smartcrawl_Settings::get_setting( 'analysis-readability' ) ) { $columns['readability'] = __( 'Readability', 'wds' ); } return $columns; } /** * Adds custom columns analysis data * * @param string $cid Column ID. * @param int $post_id Post ID. * * @return bool */ public function add_analysis_column_data( $cid, $post_id ) { if ( ! in_array( $cid, array( 'seo', 'readability' ), true ) ) { return false; } $result = $this->get_post_analysis_result_markup( $post_id ); if ( 'seo' === $cid ) { echo wp_kses_post( $result['seo'] ); } if ( 'readability' === $cid ) { echo wp_kses_post( $result['readability'] ); } return true; } /** * Gets post analysis results markup for posts column * * @param int $post_id ID of the post. * * @return array List of column markups */ public function get_post_analysis_result_markup( $post_id ) { $model = new Smartcrawl_Model_Analysis( $post_id ); $result = array( 'seo' => '', 'readability' => '', ); $na = '
' . esc_html( __( 'N/A', 'wds' ) ) . '
'; if ( ! $model->has_post_data( Smartcrawl_Model_Analysis::DATA_ANALYSIS ) ) { $result['seo'] = $na; } else { $data = $model->get_post_data( Smartcrawl_Model_Analysis::DATA_ANALYSIS ); $focus_keywords = smartcrawl_get_value( 'focus-keywords', $post_id ); $focus_keywords_available = ! empty( $focus_keywords ); if ( ! $focus_keywords_available ) { $result['seo'] = Smartcrawl_Simple_Renderer::load( 'post-list/post-seo-analysis-errors', array( 'focus_missing' => true, 'status_class' => 'wds-status-invalid', 'errors' => array( 'focus-keyword-missing' => esc_html__( 'You need to add focus keywords to see recommendations for this article.', 'wds' ), ), ) ); } elseif ( empty( $data['errors'] ) ) { $result['seo'] = Smartcrawl_Simple_Renderer::load( 'post-list/post-seo-analysis-good' ); } else { $result['seo'] = Smartcrawl_Simple_Renderer::load( 'post-list/post-seo-analysis-errors', array( 'errors' => $data['errors'], ) ); } } if ( ! $model->has_post_data( Smartcrawl_Model_Analysis::DATA_READABILITY ) ) { $result['readability'] = $na; } else { $data = $model->get_post_data( Smartcrawl_Model_Analysis::DATA_READABILITY ); $readability_score = intval( ceil( smartcrawl_get_array_value( $data, 'score' ) ) ); $readability_ignored = Smartcrawl_Checks::is_readability_ignored( $post_id ); $readability_state = $model->get_kincaid_readability_state( $readability_score, $readability_ignored ); $readability_class = sprintf( 'wds-status-%s', $readability_state ); $readability_level = $model->get_readability_level( false ); $tag = smartcrawl_get_array_value( $model->get_readability_levels_map(), array( $readability_level, 'tag' ) ); $tag = empty( $tag ) ? esc_html__( 'N/A', 'wds' ) : $tag; $result['readability'] = '
' . esc_html( $tag ) . '
'; if ( empty( $readability_score ) ) { $result['readability'] .= '
' . $readability_level . '
'; } } return $result; } /** * Handles check ignoring front-end requests * * @return void */ public function json_set_ignore_check() { $data = $this->get_request_data(); if ( empty( $data['post_id'] ) || ! is_numeric( $data['post_id'] ) ) { wp_send_json_error(); return; } if ( empty( $data['check_id'] ) ) { wp_send_json_error(); return; } Smartcrawl_Checks::add_ignored_check( (int) $data['post_id'], sanitize_text_field( $data['check_id'] ) ); $model = new Smartcrawl_Model_Analysis( (int) $data['post_id'] ); $model->clear_cached_data(); wp_send_json_success(); } /** * Handles check de-ignoring front-end requests * * @return void */ public function json_unset_ignore_check() { $data = $this->get_request_data(); if ( empty( $data['post_id'] ) || ! is_numeric( $data['post_id'] ) ) { wp_send_json_error(); return; } if ( empty( $data['check_id'] ) ) { wp_send_json_error(); return; } Smartcrawl_Checks::remove_ignored_check( (int) $data['post_id'], sanitize_text_field( $data['check_id'] ) ); $model = new Smartcrawl_Model_Analysis( (int) $data['post_id'] ); $model->clear_cached_data(); wp_send_json_success(); } /** * Sends postbox editor JSON response with detailed post analysis * * @return void */ public function json_get_post_editor_analysis() { $data = $this->get_request_data(); if ( empty( $data['post_id'] ) || ! is_numeric( $data['post_id'] ) ) { wp_send_json_error(); return; } $is_dirty = (boolean) smartcrawl_get_array_value( $data, 'is_dirty' ); $post_id = (int) smartcrawl_get_array_value( $data, 'post_id' ); $post = get_post( $post_id ); /** * If there is_dirty flag is set i.e. are unsaved changes in the editor then we * will fetch the latest post revision and analyze that. */ $post_to_analyze = $is_dirty ? smartcrawl_get_latest_post_version( $post_id ) : $post; $this->analyze_post( $post_to_analyze->ID ); $out = array(); ob_start(); $this->add_seo_analysis_metabox_content( $post ); $out['seo'] = ob_get_clean(); ob_start(); $this->add_readability_analysis_metabox_content( $post ); $out['readability'] = ob_get_clean(); ob_start(); $this->add_postbox_fields( $post ); $out['postbox_fields'] = ob_get_clean(); wp_send_json_success( $out ); } /** * Injects SEO analysis metabox content * * @param WP_Post $post Post instance. * * @return bool */ public function add_seo_analysis_metabox_content( $post ) { if ( ! Smartcrawl_Settings::get_setting( 'analysis-seo' ) ) { return false; } // If no analysis data is available, run analysis first $this->maybe_analyze_post( $post->ID ); $model = new Smartcrawl_Model_Analysis( $post->ID ); $seo_data = $model->get_post_data( Smartcrawl_Model_Analysis::DATA_ANALYSIS ); $errors = smartcrawl_get_array_value( $seo_data, 'errors' ); $checks = smartcrawl_get_array_value( $seo_data, 'checks' ); if ( empty( $checks ) ) { // For older versions that didn't cache checks $result = Smartcrawl_Checks::apply( $post->ID ); $checks = $result->get_applied_checks(); } $focus_keywords = Smartcrawl_Meta_Value_Helper::get()->get_focus_keywords( $post ); $focus_keywords_available = ! empty( $focus_keywords ); Smartcrawl_Simple_Renderer::render( 'metabox/analysis-seo-analysis', array( 'checks' => $checks, 'error_count' => count( $errors ), 'focus_keywords_available' => $focus_keywords_available, ) ); return true; } /** * Injects readability analysis metabox content * * @param WP_Post $post Post instance. * * @return bool */ public function add_readability_analysis_metabox_content( $post ) { if ( ! Smartcrawl_Settings::get_setting( 'analysis-readability' ) ) { return false; } // If no analysis data is available, run analysis first $this->maybe_analyze_post( $post->ID ); $model = new Smartcrawl_Model_Analysis( $post->ID ); $readability_data = $model->get_post_data( Smartcrawl_Model_Analysis::DATA_READABILITY ); $readability_ignored = Smartcrawl_Checks::is_readability_ignored( $post->ID ); Smartcrawl_Simple_Renderer::render( 'metabox/analysis-readability', array( 'model' => $model, 'readability_data' => $readability_data, 'readability_ignored' => $readability_ignored, ) ); return true; } private function post_type_requires_analysis( $post_id ) { $post_type = get_post_type_object( get_post_type( $post_id ) ); return 'revision' === $post_type->name || $post_type->public; } /** * Update post analysis data * * Forcefully updates the post analysis data, * no questions asked * * @param int $post_id Post ID. * * @return bool Status */ public function analyze_post( $post_id ) { if ( empty( $post_id ) || ! is_numeric( $post_id ) || ! $this->post_type_requires_analysis( $post_id ) ) { return false; } if ( ! current_user_can( 'edit_post', $post_id ) ) { return false; } $model = new Smartcrawl_Model_Analysis( $post_id ); $model->update_analysis_data(); $model->update_readability_data(); return true; } /** * Update post analysis data only if there's no such data * * @param int $post_id Post ID. * * @return bool Whether we updated the analysis data or not */ public function maybe_analyze_post( $post_id ) { $analyzed = false; if ( empty( $post_id ) || ! is_numeric( $post_id ) ) { return $analyzed; } $model = new Smartcrawl_Model_Analysis( $post_id ); if ( ! $model->has_post_data( Smartcrawl_Model_Analysis::DATA_ANALYSIS ) ) { if ( current_user_can( 'edit_post', $post_id ) ) { $model->update_analysis_data(); } $analyzed = true; } if ( ! $model->has_post_data( Smartcrawl_Model_Analysis::DATA_READABILITY ) ) { if ( current_user_can( 'edit_post', $post_id ) ) { $model->update_readability_data(); } $analyzed = true; } return $analyzed; } /** * Injects postbox publish editor content * * @param WP_Post $post Post instance. * * @return void */ public function add_postbox_fields( $post ) { $model = new Smartcrawl_Model_Analysis( $post->ID ); $focus_keywords = Smartcrawl_Meta_Value_Helper::get()->get_focus_keywords( $post ); $focus_keywords_available = ! empty( $focus_keywords ); if ( in_array( get_post_status( $post ), array( 'draft', 'auto-draft' ), true ) ) { $result = Smartcrawl_Checks::apply( $post->ID ); $checks = $result->get_applied_checks(); $has_errors = false; foreach ( $checks as $title => $chk ) { if ( empty( $chk['status'] ) && empty( $chk['ignored'] ) ) { $has_errors = true; break; } } } else { $seo_data = $model->get_post_data( Smartcrawl_Model_Analysis::DATA_ANALYSIS ); $has_errors = ! empty( $seo_data['errors'] ); } if ( ! $focus_keywords_available ) { $seo_class = 'wds-status-invalid'; $seo_text = __( 'No Focus Keyword', 'wds' ); } elseif ( $has_errors ) { $seo_class = 'wds-status-warning'; $seo_text = __( 'Needs Improvement', 'wds' ); } else { $seo_class = 'wds-status-success'; $seo_text = __( 'Good', 'wds' ); } $readability_data = $model->get_post_data( Smartcrawl_Model_Analysis::DATA_READABILITY ); $readability_score = smartcrawl_get_array_value( $readability_data, 'score' ); $readability_score = intval( ceil( $readability_score ) ); $readability_ignored = Smartcrawl_Checks::is_readability_ignored( $post->ID ); $readability_state = $model->get_kincaid_readability_state( $readability_score, $readability_ignored ); $readability_class = sprintf( 'wds-status-%s', $readability_state ); $readability_text = $model->get_readability_level(); ?>
get_request_data(); if ( empty( $data['post_id'] ) || ! is_numeric( $data['post_id'] ) ) { wp_send_json_error(); return; } $this->maybe_analyze_post( (int) $data['post_id'] ); $model = new Smartcrawl_Model_Analysis( (int) $data['post_id'] ); wp_send_json_success( array( 'analysis' => $model->get_post_data( Smartcrawl_Model_Analysis::DATA_ANALYSIS ), 'readability' => $model->get_post_data( Smartcrawl_Model_Analysis::DATA_READABILITY ), 'readability_threshold' => $model->get_readability_threshold(), 'readable' => $model->is_readable(), ) ); } /** * Sends JSON response with post analysis markup * * As a side-effect, updates the analysis if needed * * @return void */ public function json_get_post_analysis_markup() { $data = $this->get_request_data(); if ( empty( $data['post_id'] ) || ! is_numeric( $data['post_id'] ) ) { wp_send_json_error(); return; } $this->maybe_analyze_post( (int) $data['post_id'] ); $result = $this->get_post_analysis_result_markup( (int) $data['post_id'] ); wp_send_json_success( $result ); } /** * Force analysis recheck and respond with column markup data * * @return void */ public function json_get_post_analysis_recheck() { $data = $this->get_request_data(); if ( empty( $data['post_id'] ) || ! is_numeric( $data['post_id'] ) ) { wp_send_json_error(); return; } $this->analyze_post( (int) $data['post_id'] ); $result = $this->get_post_analysis_result_markup( (int) $data['post_id'] ); wp_send_json_success( $result ); } private function get_request_data() { return isset( $_POST['_wds_nonce'] ) && wp_verify_nonce( $_POST['_wds_nonce'], 'wds-metabox-nonce' ) ? stripslashes_deep( $_POST ) : array(); } }