get_supported_post_types(); foreach ($post_types as $post_type) { // Add column header add_filter("manage_{$post_type}_posts_columns", [$this, 'add_column_header'], 10); // Add column content add_action("manage_{$post_type}_posts_custom_column", [$this, 'render_column_content'], 10, 2); // Make column sortable add_filter("manage_edit-{$post_type}_sortable_columns", [$this, 'make_column_sortable'], 10); } }); // Handle sorting add_action('pre_get_posts', [$this, 'handle_column_sorting']); // Enqueue admin styles for column add_action('admin_enqueue_scripts', [$this, 'enqueue_column_styles']); } /** * Get supported post types * * @return array Array of post type names */ private function get_supported_post_types(): array { $post_types = get_post_types(['public' => true], 'names'); // Remove unnecessary post types unset($post_types['attachment']); unset($post_types['elementor_library']); unset($post_types['oceanwp_library']); unset($post_types['ae_global_templates']); /** * Filter supported post types for ThinkRank SEO Overview column * * @param array $post_types Array of post type names */ return apply_filters('thinkrank_seo_overview_post_types', $post_types); } /** * Add column header * * @param array $columns Existing columns * @return array Modified columns */ public function add_column_header(array $columns): array { // Insert column before date column $new_columns = []; foreach ($columns as $key => $value) { if ($key === 'date') { $new_columns[self::COLUMN_ID] = __('ThinkRank SEO Overview', 'thinkrank'); } $new_columns[$key] = $value; } // If date column doesn't exist, add at the end if (!isset($columns['date'])) { $new_columns[self::COLUMN_ID] = __('ThinkRank SEO Overview', 'thinkrank'); } return $new_columns; } /** * Render column content * * @param string $column_name Column name * @param int $post_id Post ID * @return void */ public function render_column_content(string $column_name, int $post_id): void { if ($column_name !== self::COLUMN_ID) { return; } // Get SEO data $seo_data = $this->get_seo_data($post_id); // Render the column content $this->render_seo_overview($seo_data, $post_id); } /** * Get SEO data for a post * * @param int $post_id Post ID * @return array SEO data */ private function get_seo_data(int $post_id): array { // Get SEO score and metrics from database table $seo_metrics = $this->get_seo_metrics_from_database($post_id); // Get focus keyword $focus_keyword = get_post_meta($post_id, '_thinkrank_focus_keyword', true); // Get meta title $meta_title = get_post_meta($post_id, '_thinkrank_seo_title', true); // Get meta description $meta_description = get_post_meta($post_id, '_thinkrank_meta_description', true); // Get content quality and readability from database columns $content_quality = $seo_metrics['content_quality'] ?? 'Not Analyzed'; $readability = $seo_metrics['readability_score'] ?? 'N/A'; return [ 'seo_score' => $seo_metrics['seo_score'], 'seo_grade' => $seo_metrics['seo_grade'], 'content_quality' => $content_quality, 'readability' => $readability, 'focus_keyword' => !empty($focus_keyword), 'focus_keyword_value' => $focus_keyword, 'meta_title' => !empty($meta_title), 'meta_description' => !empty($meta_description), 'pillar_content' => $this->is_link_suggestions_enabled(get_post_type($post_id)) && get_post_meta($post_id, '_thinkrank_pillar_content', true) === '1', ]; } /** * Check if link suggestions are enabled for a post type * * @param string $post_type Post type * @return bool True if enabled */ private function is_link_suggestions_enabled(string $post_type): bool { $settings = get_option('thinkrank_global_seo_settings', []); if (isset($settings[$post_type]['link_suggestions'])) { return (bool) $settings[$post_type]['link_suggestions']; } return true; } /** * Get SEO metrics from database table * * @param int $post_id Post ID * @return array SEO metrics (seo_score, content_quality, readability_score) */ private function get_seo_metrics_from_database(int $post_id): array { global $wpdb; $table_name = $wpdb->prefix . 'thinkrank_seo_scores'; // Default values $default_metrics = [ 'seo_score' => null, 'seo_grade' => null, 'content_quality' => null, 'readability_score' => null, ]; // Check if table exists // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- SEO score retrieval requires direct database access $table_exists = $wpdb->get_var($wpdb->prepare( "SHOW TABLES LIKE %s", $table_name )); if (!$table_exists) { return $default_metrics; } // Get the most recent metrics from the database // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- SEO score retrieval requires direct database access $result = $wpdb->get_row($wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is properly escaped "SELECT overall_score, grade, content_quality, readability_score FROM `{$table_name}` WHERE post_id = %d ORDER BY created_at DESC LIMIT 1", $post_id ), ARRAY_A); if (!$result) { return $default_metrics; } return [ 'seo_score' => $result['overall_score'] !== null ? intval($result['overall_score']) : null, 'seo_grade' => $result['grade'] !== null ? $result['grade'] : null, 'content_quality' => $result['content_quality'], 'readability_score' => $result['readability_score'], ]; } /** * Render SEO overview content * * @param array $seo_data SEO data * @param int $post_id Post ID * @return void */ private function render_seo_overview(array $seo_data, int $post_id): void { ?>
%
= 90) { return 'great'; } elseif ($score >= 70) { return 'good'; } elseif ($score >= 50) { return 'medium'; } else { return 'poor'; } } /** * Make column sortable * * @param array $columns Sortable columns * @return array Modified sortable columns */ public function make_column_sortable(array $columns): array { $columns[self::COLUMN_ID] = 'thinkrank_seo_score'; return $columns; } /** * Handle column sorting * * @param \WP_Query $query WordPress query object * @return void */ public function handle_column_sorting(\WP_Query $query): void { if (!is_admin() || !$query->is_main_query()) { return; } $orderby = $query->get('orderby'); if ($orderby === 'thinkrank_seo_score') { $query->set('meta_key', '_thinkrank_seo_score'); $query->set('orderby', 'meta_value_num'); } } /** * Enqueue column styles * * @param string $hook_suffix Current admin page hook * @return void */ public function enqueue_column_styles(string $hook_suffix): void { // Only load on post list pages (edit.php for all post types) $screen = get_current_screen(); if (!$screen || $screen->base !== 'edit') { return; } // Add inline styles to common admin styles wp_add_inline_style('common', $this->get_column_styles()); // Enqueue inline editing script wp_enqueue_script( 'thinkrank-focus-keyword-inline-edit', THINKRANK_PLUGIN_URL . 'assets/focus-keyword-inline-edit.js', ['jquery'], THINKRANK_VERSION, true ); // Localize script with AJAX data wp_localize_script( 'thinkrank-focus-keyword-inline-edit', 'thinkrankFocusKeyword', [ 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce(\ThinkRank\Admin\Focus_Keyword_Ajax::get_nonce_action()), 'action' => \ThinkRank\Admin\Focus_Keyword_Ajax::get_ajax_action(), 'i18n' => [ 'notSet' => __('Not Set', 'thinkrank'), 'clickToEdit' => __('Click to edit', 'thinkrank'), 'pressEnter' => __('Press Enter to save, Esc to cancel', 'thinkrank'), 'saving' => __('Saving...', 'thinkrank'), 'saved' => __('Saved!', 'thinkrank'), 'error' => __('Error updating focus keyword. Please try again.', 'thinkrank'), ] ] ); } /** * Get column CSS styles * * @return string CSS styles */ private function get_column_styles(): string { return ' .column-thinkrank_seo_overview { width: 240px; } .thinkrank-seo-overview { font-size: 13px; line-height: 1.4; display: flex; flex-direction: column; gap: 6px; } .thinkrank-seo-row { display: flex; align-items: center; flex-wrap: wrap; } .thinkrank-seo-separator { margin: 0 6px; color: #c3c4c7; font-size: 10px; } /* Top Row: Badges */ .thinkrank-seo-badge { border-radius: 12px; padding: 2px 8px; font-weight: 600; font-size: 12px; display: inline-flex; align-items: center; white-space: nowrap; position: relative; } .thinkrank-seo-badge::after, .thinkrank-seo-metric::after { content: attr(title); position: absolute; bottom: calc(100% + 11px); left: 50%; transform: translateX(-50%); background: #00043D; border-radius: 8px; padding: 4px 8px; color: #fff; font-weight: 400; box-shadow: 0 1px 2px rgba(17, 17, 17, 0.1); visibility: hidden; white-space: nowrap; z-index: 20; } .thinkrank-seo-badge::before, .thinkrank-seo-metric::before { content: ""; position: absolute; bottom: calc(100% + 7px); left: calc(50% - 5px); transform: rotate(45deg); background: #00043D; width: 10px; height: 10px; border-radius: 2px; box-shadow: 0 1px 2px rgba(17, 17, 17, 0.1); visibility: hidden; z-index: 19; } .thinkrank-seo-badge:hover::before, .thinkrank-seo-badge:hover::after { visibility: visible; } /* Apply Z-Index for metric too */ .thinkrank-seo-metric:hover::before, .thinkrank-seo-metric:hover::after { visibility: visible; } .thinkrank-score-great { background-color: #d1fae5; color: #065f46; } .thinkrank-score-good { background-color: #dbeafe; color: #1e40af; } .thinkrank-score-medium { background-color: #fef3c7; color: #92400e; } .thinkrank-score-poor { background-color: #fee2e2; color: #b91c1c; } .thinkrank-status-good { background-color: #E5FFF6; color: #117953; } .thinkrank-status-ok { background-color: #EBF0FF; color: #1B42C2; } .thinkrank-status-not-analyzed, .thinkrank-seo-badge.thinkrank-not-set { background-color: #E5EBF0; color: #58677D; border-radius: 12px; padding: 2px 8px; } .thinkrank-status-needs-improvement { background-color: #FFF7EB; color: #865A13; } .thinkrank-status-no-content { background-color: #FFEBEE; color: #EC113A; } .thinkrank-pillar-icon { background-color: #F0F0F1; color: #2271b1; padding: 4px; line-height: 20px; } /* Middle Row: Focus Keyword */ .thinkrank-focus-keyword-item { width: 100%; position: relative; display: flex; align-items: center; background-color: #ECEEEE; border-radius: 4px; border: 1px solid #E4E6EC; padding: 0 8px; min-height: 24px; } .thinkrank-focus-keyword-display { cursor: pointer; flex-grow: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #1d2327; font-weight: 500; } .thinkrank-focus-keyword-display.thinkrank-not-set { color: #646970; font-style: italic; } .thinkrank-edit-icon { font-size: 16px; width: 16px; height: 16px; pointer-events: none; position: absolute; right: 8px; path { stroke: #A5AAAC; } } .thinkrank-focus-keyword-item .thinkrank-success-icon { position: absolute; right: 8px; visibility: hidden; } .thinkrank-focus-keyword-item:hover .thinkrank-edit-icon path { stroke: #4451FF; } .thinkrank-focus-keyword-input[type="text"] { width: 100%; padding: 0 8px; border: none; border-radius: 4px; font-size: 13px; margin: 0; min-height: 24px; line-height: normal; } .thinkrank-focus-keyword-item.is-editing { padding: 0; background: transparent; border-color: #5C91FE; } .thinkrank-focus-keyword-item.is-editing .thinkrank-focus-keyword-display, .thinkrank-focus-keyword-item.is-editing .thinkrank-edit-icon { display: none; } .thinkrank-focus-keyword-item.is-editing .thinkrank-success-icon { visibility: visible; } .thinkrank-focus-keyword-item.is-editing.is-saving .thinkrank-success-icon { visibility: hidden; } /* Bottom Row: Metrics */ .thinkrank-seo-metric { display: flex; align-items: center; gap: 4px; color: #646970; font-size: 12px; line-height: 20px; position: relative; } .thinkrank-seo-metric:hover::before, .thinkrank-seo-metric:hover::after { visibility: visible; } .thinkrank-metric-value { white-space: nowrap; color: #505272; } /* Animations */ .thinkrank-focus-keyword-display.thinkrank-feedback-success { animation: thinkrank-success-flash 0.5s ease-in-out; } .thinkrank-focus-keyword-display.thinkrank-feedback-error { animation: thinkrank-error-flash 0.5s ease-in-out; } @keyframes thinkrank-success-flash { 0%, 100% { background-color: transparent; } 50% { background-color: #d1fae5; } } @keyframes thinkrank-error-flash { 0%, 100% { background-color: transparent; } 50% { background-color: #fee2e2; } } .thinkrank-inline-error { position: absolute; top: 100%; left: 0; z-index: 10; color: #fff; font-size: 11px; margin-top: 4px; padding: 4px 8px; background-color: #d63638; border-radius: 4px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); } .thinkrank-inline-error::after { content: ""; position: absolute; bottom: 100%; left: 10px; border-width: 5px; border-style: solid; border-color: transparent transparent #d63638 transparent; } '; } }