Changeset 3798783
- Timestamp:
- 11/12/2026 04:25:55 PM (less than one hour ago)
- Location:
- seomedic-seo-link-auditor/trunk
- Files:
-
- 7 edited
-
admin/css/dashboard-widget.css (modified) (1 diff)
-
changelog.txt (modified) (1 diff)
-
includes/class-dashboard-widget.php (modified) (2 diffs)
-
includes/class-seo-integration.php (modified) (1 diff)
-
readme.txt (modified) (3 diffs)
-
seomedic.php (modified) (3 diffs)
-
uninstall.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
seomedic-seo-link-auditor/trunk/admin/css/dashboard-widget.css
r3798411 r3798783 627 627 color: #646970; 628 628 } 629 630 /* Your Focus Keywords (3.1.7) — rows reuse .seomedic-mover-row */ 631 .seomedic-focus-list { 632 margin: 0 0 10px; 633 } 634 .seomedic-focus-list .seomedic-mover-query a { 635 color: inherit; 636 text-decoration: none; 637 } 638 .seomedic-focus-list .seomedic-mover-query a:hover { 639 color: #2271b1; 640 text-decoration: underline; 641 } 642 .seomedic-mover-pill.seomedic-mover-flat { 643 background: #f0f0f1; 644 color: #50575e; 645 } -
seomedic-seo-link-auditor/trunk/changelog.txt
r3798411 r3798783 6 6 7 7 == Changelog == 8 9 = 3.1.7 = 10 * Dashboard widget (Pro): "Your Focus Keywords", under Money Pages. `SEOMedic_GSC_Integration::get_focus_keyword_tracking()` matches each published post's effective focus keyword (Yoast, then Rank Math primary, then ours; `get_focus_keyword_posts()`, ≤ 500 posts, cached an hour in `seomedic_focus_kw_posts`, cleared by `update_focus_keyword()` and both uninstall paths) against the keyword snapshots the daily cron already records — no Google calls. Match is exact after `norm_keyword()` (case, entities, curly quotes, whitespace) or same content words in any order (stopwords mirror `utils/focusKeywordMatch.ts`); related queries and plurals never count; exact beats variant. Pairs in an older snapshot but not the latest are "dropped" and always get a row. Positions are trailing 90-day averages and the block says so; keywords with no data are counted as "no position yet" (below #30, under 10 impressions, or outside the top 500 pairs), never shown as a number. Harness tests/focus-keyword-tracking-harness.php (30). 8 11 9 12 = 3.1.6 = -
seomedic-seo-link-auditor/trunk/includes/class-dashboard-widget.php
r3798411 r3798783 427 427 <?php self::render_top_by_clicks( $top30, $top30_state ); ?> 428 428 <?php self::render_money_pages(); ?> 429 <?php self::render_focus_keywords(); ?> 429 430 <?php self::render_movers( $movers ); ?> 430 431 … … 996 997 } 997 998 999 /** 1000 * "Your Focus Keywords": each post's focus keyword with its Google position 1001 * and trend, read from the keyword snapshots the daily cron already records 1002 * (SEOMedic_GSC_Integration::get_focus_keyword_tracking()). No Google call. 1003 * 1004 * Positions are trailing 90-day averages, which the hint line says, and a 1005 * keyword missing from the data is "no position yet", never a number: the 1006 * snapshot also leaves out pairs under 10 impressions or outside the site's 1007 * top 500, so "below #30" would overclaim. 1008 * Renders nothing when no post has a focus keyword: no nagging. 1009 */ 1010 private static function render_focus_keywords() { 1011 if ( ! method_exists( 'SEOMedic_GSC_Integration', 'get_focus_keyword_tracking' ) ) { 1012 return; 1013 } 1014 $t = SEOMedic_GSC_Integration::get_focus_keyword_tracking(); 1015 if ( empty( $t['set'] ) ) { 1016 return; 1017 } 1018 $rows = isset( $t['rows'] ) && is_array( $t['rows'] ) ? $t['rows'] : array(); 1019 $tracked = array_values( array_filter( $rows, function ( $r ) { 1020 return 'tracked' === $r['status']; 1021 } ) ); 1022 $dropped = array_values( array_filter( $rows, function ( $r ) { 1023 return 'dropped' === $r['status']; 1024 } ) ); 1025 // Up to two that fell out of the data always get a row: they are the 1026 // ones worth noticing, and sorting by impressions would bury them. 1027 $show_dropped = array_slice( $dropped, 0, 2 ); 1028 $show = array_merge( array_slice( $tracked, 0, self::FOCUS_ROWS - count( $show_dropped ) ), $show_dropped ); 1029 $missing = max( 0, (int) $t['set'] - count( $tracked ) - count( $dropped ) ); 1030 ?> 1031 <h4 class="seomedic-widget-section-title"> 1032 <?php esc_html_e( 'Your Focus Keywords', 'seomedic-seo-link-auditor' ); ?> 1033 <span class="seomedic-money-count"> 1034 <?php 1035 echo esc_html( sprintf( 1036 /* translators: 1: posts with a focus keyword, 2: how many of those have position data */ 1037 __( '%1$s set · %2$s with a position', 'seomedic-seo-link-auditor' ), 1038 number_format_i18n( (int) $t['set'] ), 1039 number_format_i18n( count( $tracked ) ) 1040 ) ); 1041 ?> 1042 </span> 1043 </h4> 1044 <?php if ( '' === $t['asOf'] ) : ?> 1045 <p class="seomedic-movers-empty"> 1046 <?php esc_html_e( 'Positions appear here after the first daily sync with Google.', 'seomedic-seo-link-auditor' ); ?> 1047 </p> 1048 <?php 1049 return; 1050 endif; 1051 ?> 1052 <p class="seomedic-widget-hint"> 1053 <?php esc_html_e( 'Average Google position over the last 90 days, recorded daily, for the keyword each post targets.', 'seomedic-seo-link-auditor' ); ?> 1054 </p> 1055 <?php if ( empty( $show ) ) : ?> 1056 <p class="seomedic-movers-empty"> 1057 <?php esc_html_e( 'No position for these keywords yet. Google shows their pages for them below #30, fewer than 10 times, or not at all.', 'seomedic-seo-link-auditor' ); ?> 1058 </p> 1059 <?php else : ?> 1060 <div class="seomedic-focus-list"> 1061 <?php foreach ( $show as $r ) : ?> 1062 <?php self::render_focus_row( $r ); ?> 1063 <?php endforeach; ?> 1064 </div> 1065 <?php endif; ?> 1066 <?php if ( $missing > 0 && ! empty( $show ) ) : ?> 1067 <p class="seomedic-fresh-more"> 1068 <?php 1069 echo esc_html( sprintf( 1070 /* translators: %s: number of focus keywords */ 1071 _n( 1072 '%s other focus keyword has no position yet (below #30, fewer than 10 impressions, or not shown at all).', 1073 '%s other focus keywords have no position yet (below #30, fewer than 10 impressions, or not shown at all).', 1074 $missing, 1075 'seomedic-seo-link-auditor' 1076 ), 1077 number_format_i18n( $missing ) 1078 ) ); 1079 ?> 1080 </p> 1081 <?php endif; ?> 1082 <?php 1083 } 1084 1085 /** Rows in "Your Focus Keywords". */ 1086 const FOCUS_ROWS = 6; 1087 1088 /** 1089 * One focus-keyword row: keyword (links to the editor), sparkline, position 1090 * and change. Shares the Biggest Movers row styles. 1091 * 1092 * @param array $r One row from get_focus_keyword_tracking(). 1093 */ 1094 private static function render_focus_row( $r ) { 1095 $id = (int) $r['postId']; 1096 $title = html_entity_decode( get_the_title( $id ), ENT_QUOTES | ENT_HTML5, 'UTF-8' ); 1097 $keyword = html_entity_decode( (string) $r['keyword'], ENT_QUOTES | ENT_HTML5, 'UTF-8' ); 1098 $edit = get_edit_post_link( $id, 'raw' ); 1099 $delta = isset( $r['delta'] ) ? $r['delta'] : null; 1100 $points = isset( $r['points'] ) && is_array( $r['points'] ) ? $r['points'] : array(); 1101 $dropped = 'dropped' === $r['status']; 1102 1103 $tip = $title; 1104 if ( 'variant' === $r['match'] ) { 1105 /* translators: %s: the Google search the position belongs to */ 1106 $tip .= ' — ' . sprintf( __( 'Google search “%s” (same words, different order)', 'seomedic-seo-link-auditor' ), $r['query'] ); 1107 } 1108 if ( $dropped ) { 1109 /* translators: 1: last position, 2: date */ 1110 $tip .= ' — ' . sprintf( __( 'Dropped out of the data: now below #30 or under 10 impressions. Last seen at #%1$s on %2$s.', 'seomedic-seo-link-auditor' ), number_format_i18n( (float) $r['position'], 1 ), $r['lastSeen'] ); 1111 } elseif ( null !== $delta ) { 1112 /* translators: %s: date the history starts */ 1113 $tip .= ' — ' . sprintf( __( 'Change since %s', 'seomedic-seo-link-auditor' ), $r['since'] ); 1114 } 1115 1116 // Lower position number = higher in Google, so a negative delta is up. 1117 if ( $dropped ) { 1118 $dir = 'down'; 1119 $pill = __( 'dropped', 'seomedic-seo-link-auditor' ); 1120 $color = '#d63638'; 1121 } elseif ( null === $delta ) { 1122 $dir = 'flat'; 1123 $pill = __( 'new', 'seomedic-seo-link-auditor' ); 1124 $color = '#8c8f94'; 1125 } elseif ( abs( $delta ) < 0.5 ) { 1126 $dir = 'flat'; 1127 $pill = '='; 1128 $color = '#8c8f94'; 1129 } else { 1130 $dir = $delta < 0 ? 'up' : 'down'; 1131 $pill = ( $delta < 0 ? '▲ ' : '▼ ' ) . number_format_i18n( abs( $delta ), 1 ); 1132 $color = $delta < 0 ? '#00a32a' : '#d63638'; 1133 } 1134 ?> 1135 <div class="seomedic-mover-row" title="<?php echo esc_attr( $tip ); ?>"> 1136 <span class="seomedic-mover-query"> 1137 <?php if ( $edit ) : ?> 1138 <a href="<?php echo esc_url( $edit ); ?>"><?php echo esc_html( $keyword ); ?></a> 1139 <?php else : ?> 1140 <?php echo esc_html( $keyword ); ?> 1141 <?php endif; ?> 1142 </span> 1143 <span class="seomedic-mover-spark"><?php echo self::position_sparkline_svg( $points, $color ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_kses'd inside the helper. ?></span> 1144 <span class="seomedic-mover-pos"><?php echo esc_html( $dropped ? '—' : '#' . number_format_i18n( round( (float) $r['position'] ) ) ); ?></span> 1145 <span class="seomedic-mover-pill seomedic-mover-<?php echo esc_attr( $dir ); ?>"><?php echo esc_html( $pill ); ?></span> 1146 </div> 1147 <?php 1148 } 1149 998 1150 private static function render_movers( $movers ) { 999 1151 $gainers = isset( $movers['gainers'] ) ? $movers['gainers'] : array(); -
seomedic-seo-link-auditor/trunk/includes/class-seo-integration.php
r3791215 r3798783 215 215 // as updated whenever we made it to the meta-update line above. 216 216 $changes['updated'] = true; 217 // The dashboard widget's focus-keyword list caches post → keyword for an 218 // hour (SEOMedic_GSC_Integration::TRANSIENT_FOCUS_KEYWORDS, Pro). Named 219 // as a string so this core file never touches the Pro class. 220 delete_transient( 'seomedic_focus_kw_posts' ); 217 221 return $changes; 218 222 } -
seomedic-seo-link-auditor/trunk/readme.txt
r3798411 r3798783 5 5 Requires at least: 5.8 6 6 Tested up to: 7.1 7 Stable tag: 3.1. 67 Stable tag: 3.1.7 8 8 Requires PHP: 7.4 9 9 License: GPLv2 or later … … 206 206 with the plugin in changelog.txt. 207 207 208 = 3.1.7 = 209 * New (Pro): "Your Focus Keywords" on the WordPress dashboard widget shows each post's focus keyword with its Google position, a trend line and the change over time, using history SEOMedic already records daily (no extra Google requests). Keywords without a position yet are counted, never given a made-up number. 210 208 211 = 3.1.6 = 209 212 * Improved (Pro): the dashboard widget's Top 10 pages by clicks and Just Started Ranking now load by themselves a moment after the WordPress dashboard opens, instead of asking you to press Sync. … … 214 217 * New (Pro): the editor sidebar shows whether Google has indexed the post, its clicks and impressions, and the top searches it's found for, as soon as you open it (no Analyze needed), with a Check button. For an old post Google never indexed (or looked at and declined), it suggests redirecting it and lets you pick the post to redirect to right there. 215 218 216 = 3.1.4 =217 * Improved: the Redirects list explains what each fix changes, and flags old archive pages (like /category/news/page/4) that redirect to a single post, with a one-click option to point them at the archive instead.218 219 219 == Upgrade Notice == 220 221 = 3.1.7 = 222 Your Focus Keywords on the dashboard widget: each post's focus keyword with its Google position and trend. 220 223 221 224 = 3.1.6 = -
seomedic-seo-link-auditor/trunk/seomedic.php
r3798411 r3798783 4 4 * Plugin URI: https://getseomedic.com/ 5 5 * Description: Audit broken links, fix missing meta descriptions, detect H1 issues, repair YouTube embeds, audit Schema.org markup, and clean up internal linking — all from one dashboard. 6 * Version: 3.1. 66 * Version: 3.1.7 7 7 * Author: Andrew Murray 8 8 * Author URI: https://getseomedic.com … … 174 174 delete_transient( 'seomedic_dataforseo_balance' ); 175 175 delete_transient( 'seomedic_serp_last_raw' ); 176 delete_transient( 'seomedic_focus_kw_posts' ); 176 177 delete_option( 'seomedic_seo_tasklist' ); 177 178 delete_metadata( 'user', 0, 'seomedic_review_notice', '', true ); … … 192 193 */ 193 194 if ( ! defined( 'SEOMEDIC_VERSION' ) ) { 194 define( 'SEOMEDIC_VERSION', '3.1. 6' );195 define( 'SEOMEDIC_VERSION', '3.1.7' ); 195 196 define( 'SEOMEDIC_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 196 197 define( 'SEOMEDIC_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); -
seomedic-seo-link-auditor/trunk/uninstall.php
r3791215 r3798783 119 119 delete_transient( 'seomedic_mixed_content_audit' ); 120 120 delete_transient( 'seomedic_post_index' ); 121 delete_transient( 'seomedic_focus_kw_posts' ); 121 122 122 123 // Per-user dismissal meta for the upgrade + review-request notices.
Note:
See TracChangeset
for help on using the changeset viewer.