PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.9.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.9.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 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 All 50 releases
← All changes | includes/seo/class-author-archives-manager.php +70 -17 2.0.0 → 2.9.0 View file →
@@ -101,14 +101,19 @@
101 101 if ($description === '') {
102 102 return;
103 103 }
104 104
105 - // Ensure description is within optimal length (150-160 characters)
106 - if (strlen($description) > 160) {
107 - $description = wp_trim_words($description, 25, '...');
108 - }
105 + // Keep the rendered description within the ~160 characters search
106 + // engines display. Measure and cut in CHARACTERS: strlen() counts
107 + // BYTES, so a Cyrillic/CJK description tripped a limit it was nowhere
108 + // near, and wp_trim_words() cuts by WORD COUNT, so a long-worded
109 + // description sailed past the cap entirely. Three units, three
110 + // different answers.
111 + $description = $this->trim_to_length($description, self::DESCRIPTION_MAX_LENGTH);
109 112
110 - echo "<!-- Search Engine Optimization by ThinkRank - https://thinkrank.ai/ -->\n";
113 + // Opens the block through SEO_Manager so its closing comment, printed
114 + // on wp_head at priority 99, knows an opener was emitted.
115 + \ThinkRank\Frontend\SEO_Manager::note_opening_comment();
111 116 echo "<!-- ThinkRank SEO Meta Description -->\n";
112 117 echo '<meta name="description" content="' . esc_attr($description) . '" />' . "\n";
113 118 echo "<!-- /ThinkRank SEO Meta Description -->\n";
114 119 }
@@ -113,8 +118,36 @@
113 118 echo "<!-- /ThinkRank SEO Meta Description -->\n";
114 119 }
115 120
116 121 /**
122 + * Characters search engines display for a meta description.
123 + *
124 + * @since 2.2.0
125 + * @var int
126 + */
127 + private const DESCRIPTION_MAX_LENGTH = 160;
128 +
129 + /**
130 + * Trim a description to a character budget, multibyte-safe.
131 + *
132 + * Cuts on a word boundary when one is available inside the budget, so the
133 + * result does not end mid-word; falls back to a hard character cut for
134 + * scripts that do not use spaces (CJK), where a word-boundary search would
135 + * find nothing and return the string untouched.
136 + *
137 + * @since 2.2.0
138 + * @param string $description Description text.
139 + * @param int $limit Maximum length in characters, ellipsis included.
140 + * @return string
141 + */
142 + private function trim_to_length(string $description, int $limit): string {
143 + // The implementation moved to Seo_Text so the four other
144 + // description paths could stop carrying the broken version of it
145 + // (#687). This stays as the local name the author-archive code reads.
146 + return \ThinkRank\Core\Seo_Text::trim_to_length($description, $limit);
147 + }
148 +
149 + /**
117 150 * Modify document title for author archives
118 151 *
119 152 * @since 1.0.0
120 153 * @param string $title Original title
@@ -127,9 +160,9 @@
127 160 // A per-author SEO title (e.g. imported from another SEO plugin)
128 161 // overrides the global template entirely.
129 162 $custom_title = (string) get_user_meta($author_id, '_thinkrank_seo_title', true);
130 163 if ($custom_title !== '') {
131 - return $custom_title;
164 + return $this->with_page_suffix($custom_title);
132 165 }
133 166
134 167 $settings = Settings::instance();
135 168 $template = $settings->get('author_archives_title', Settings::DEFAULT_AUTHOR_ARCHIVES_TITLE);
@@ -147,31 +180,46 @@
147 180 if (class_exists('ThinkRank\SEO\Site_Identity_Manager')) {
148 181 $separator = \ThinkRank\SEO\Site_Identity_Manager::get_active_separator_symbol();
149 182 }
150 183
151 - // Page number
152 - $page_str = '';
153 - $paged = get_query_var('paged') ? (int) get_query_var('paged') : 1;
154 - if ($paged > 1) {
155 - // translators: %d is the page number for paginated author archives.
156 - $page_str = sprintf(__('Page %d', 'thinkrank'), $paged);
157 - }
158 -
184 + // %page% resolves to nothing here. This filter runs at priority 15,
185 + // after SEO_Manager's at priority 1, so its own page indicator was
186 + // the one that reached the page — and it rendered without the
187 + // separator the rest of the site uses ("admin | tr Page 2" against
188 + // "Uncategorized | tr | Page 2" everywhere else). The token stays
189 + // recognised so a template that already contains it does not leak
190 + // the literal string; the indicator itself comes from the one
191 + // helper every other context uses (#397 review).
159 192 $replacements = [
160 193 '%author_name%' => $author_name,
161 194 '%site_title%' => $site_title,
162 195 '%separator%' => $separator,
163 - '%page%' => $page_str
196 + '%page%' => ''
164 197 ];
165 198
166 199 $rendered = str_replace(array_keys($replacements), array_values($replacements), $template);
167 200
168 - return $this->tidy_whitespace($rendered);
201 + return $this->with_page_suffix($this->tidy_whitespace($rendered));
169 202 }
170 203 return $title;
171 204 }
172 205
173 206 /**
207 + * Append the shared page indicator, when SEO_Manager is available.
208 + *
209 + * @since 2.0.1
210 + * @param string $title Rendered author-archive title.
211 + * @return string
212 + */
213 + private function with_page_suffix(string $title): string {
214 + if (!class_exists('\ThinkRank\Frontend\SEO_Manager')) {
215 + return $title;
216 + }
217 +
218 + return \ThinkRank\Frontend\SEO_Manager::with_page_suffix($title);
219 + }
220 +
221 + /**
174 222 * Collapse the gaps left by variables that resolved to nothing.
175 223 *
176 224 * %page% is empty on an unpaginated archive, so the stock template ended
177 225 * every author <title> with a stray trailing space; two empty variables in
@@ -244,9 +292,14 @@
244 292 // Default to true (enabled)
245 293 $enabled = $settings->get('author_archives_enabled', true);
246 294
247 295 if (!$enabled) {
248 - wp_safe_redirect(home_url(), 301);
296 + // 302, not 301. This redirect lasts exactly as long as the
297 + // setting stays off, but a 301 is cached by browsers and CDNs
298 + // indefinitely — so turning author archives back on could not
299 + // undo it for anyone who had already been redirected, and there
300 + // was no server-side way to fix that.
301 + wp_safe_redirect(home_url(), 302);
249 302 exit;
250 303 }
251 304 }
252 305 }