PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.14.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.14.0
2.14.1 2.14.0 2.13.0 2.12.0 2.11.0 2.10.0 2.9.0 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 All 56 releases
← All changes | includes/seo/class-analytics-manager.php +20 -132 2.14.1 → 2.14.0 View file →
@@ -750,17 +750,29 @@
750 750 // Fallback to old client if new one fails init (shouldn't happen if they use same creds)
751 751 $search_performance = $this->search_console_client->get_search_performance($site_url, $date_range, ['query'], 1000);
752 752 }
753 753
754 - // Position distribution over every query with an
755 - // impression, not over the 1,000-row list above (#913).
756 - $position_distribution = $this->search_analytics_client
757 - ? $this->count_position_distribution($site_url, $start_date, $end_date, $search_performance['rows'] ?? [])
758 - : self::bucket_positions(
759 - $search_performance['rows'] ?? [],
760 - count($search_performance['rows'] ?? []) < 1000
761 - );
754 + // Calculate position distribution
755 + $position_distribution = [
756 + 'top_3' => 0,
757 + '4_10' => 0,
758 + '10_50' => 0,
759 + '51_100' => 0
760 + ];
762 761
762 + foreach ($search_performance['rows'] ?? [] as $row) {
763 + $position = $row['position'] ?? 0;
764 + if ($position <= 3) {
765 + $position_distribution['top_3']++;
766 + } elseif ($position <= 10) {
767 + $position_distribution['4_10']++;
768 + } elseif ($position <= 50) {
769 + $position_distribution['10_50']++;
770 + } elseif ($position <= 100) {
771 + $position_distribution['51_100']++;
772 + }
773 + }
774 +
763 775 $dashboard_data['search_performance'] = array_merge($search_performance, [
764 776 'totals' => $totals,
765 777 'position_distribution' => $position_distribution
766 778 ]);
@@ -799,132 +811,8 @@
799 811 // long-lived GSC payload has been stored.
800 812 $dashboard_data['core_web_vitals'] = $this->get_dashboard_core_web_vitals();
801 813
802 814 return $dashboard_data;
803 - }
804 -
805 - /**
806 - * Rows per page when counting the position distribution. The most the
807 - * Search Analytics API returns in one request.
808 - *
809 - * @since 2.15.0
810 - */
811 - private const POSITION_PAGE_SIZE = 25000;
812 -
813 - /**
814 - * Pages read before the count stops: 200,000 queries. A property past
815 - * that is counted over its top 200,000 by clicks and flagged incomplete.
816 - *
817 - * @since 2.15.0
818 - */
819 - private const POSITION_MAX_PAGES = 8;
820 -
821 - /**
822 - * Count the period's queries into position buckets across the whole
823 - * property (#913).
824 - *
825 - * The dashboard's query list is capped at 1,000 rows ordered by clicks,
826 - * so counting it told any larger site it had exactly 1,000 queries and
827 - * dropped the long tail, which is where positions 51-100 live. When that
828 - * list came back short it already holds every query and is counted as
829 - * is, with no extra request. Otherwise the property is paged with
830 - * `startRow` at POSITION_PAGE_SIZE rows until a short page, counting as
831 - * rows arrive rather than keeping them.
832 - *
833 - * A page that fails (other than a 401, which is re-thrown so the token
834 - * refresh runs) leaves the count at what was read so far, flagged
835 - * `complete: false`, rather than failing the whole dashboard.
836 - *
837 - * @since 2.15.0
838 - *
839 - * @param string $site_url Search Console property.
840 - * @param string $start_date Window start (Y-m-d).
841 - * @param string $end_date Window end (Y-m-d).
842 - * @param array $first_rows The capped query list already fetched.
843 - * @return array{top_3:int,4_10:int,10_50:int,51_100:int,over_100:int,complete:bool}
844 - * @throws \Exception On a 401, so get_dashboard_data() can refresh the token.
845 - */
846 - private function count_position_distribution(string $site_url, string $start_date, string $end_date, array $first_rows): array {
847 - if (count($first_rows) < 1000) {
848 - return self::bucket_positions($first_rows, true);
849 - }
850 -
851 - $distribution = self::bucket_positions([], true);
852 - $start_row = 0;
853 -
854 - for ($page = 0; $page < self::POSITION_MAX_PAGES; $page++) {
855 - try {
856 - $rows = $this->search_analytics_client->get_search_analytics_data(
857 - $site_url,
858 - $start_date,
859 - $end_date,
860 - ['query'],
861 - self::POSITION_PAGE_SIZE,
862 - $start_row
863 - )['rows'] ?? [];
864 - } catch (\Exception $e) {
865 - if ($e->getCode() === 401) {
866 - throw $e;
867 - }
868 - // Nothing read yet: the capped list is the best there is.
869 - $partial = $start_row === 0 ? self::bucket_positions($first_rows, false) : $distribution;
870 - $partial['complete'] = false;
871 - return $partial;
872 - }
873 -
874 - $page_counts = self::bucket_positions($rows, true);
875 - foreach (['top_3', '4_10', '10_50', '51_100', 'over_100'] as $bucket) {
876 - $distribution[$bucket] += $page_counts[$bucket];
877 - }
878 -
879 - if (count($rows) < self::POSITION_PAGE_SIZE) {
880 - return $distribution;
881 - }
882 - $start_row += self::POSITION_PAGE_SIZE;
883 - }
884 -
885 - $distribution['complete'] = false;
886 - return $distribution;
887 - }
888 -
889 - /**
890 - * Bucket Search Console rows by average position.
891 - *
892 - * `10_50` is the historical key for positions 11-50. Rows past 100 are
893 - * counted in `over_100`: they still had impressions.
894 - *
895 - * @since 2.15.0
896 - *
897 - * @param array $rows Search Console rows.
898 - * @param bool $complete Whether $rows is every query in the window.
899 - * @return array{top_3:int,4_10:int,10_50:int,51_100:int,over_100:int,complete:bool}
900 - */
901 - private static function bucket_positions(array $rows, bool $complete): array {
902 - $distribution = [
903 - 'top_3' => 0,
904 - '4_10' => 0,
905 - '10_50' => 0,
906 - '51_100' => 0,
907 - 'over_100' => 0,
908 - 'complete' => $complete,
909 - ];
910 -
911 - foreach ($rows as $row) {
912 - $position = (float) ($row['position'] ?? 0);
913 - if ($position <= 3) {
914 - $distribution['top_3']++;
915 - } elseif ($position <= 10) {
916 - $distribution['4_10']++;
917 - } elseif ($position <= 50) {
918 - $distribution['10_50']++;
919 - } elseif ($position <= 100) {
920 - $distribution['51_100']++;
921 - } else {
922 - $distribution['over_100']++;
923 - }
924 - }
925 -
926 - return $distribution;
927 815 }
928 816
929 817 /**
930 818 * Get Core Web Vitals for the analytics dashboard, cached independently