PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.7.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.7.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 1.1.0 1.10.0 All 48 releases
← All changes | includes/frontend/template-functions.php +129 -26 1.0.22.7.0 View file →
@@ -62,8 +62,60 @@
62 62 return $seo_manager->get_current_breadcrumbs();
63 63 }
64 64
65 65 /**
66 + * Display the ThinkRank hero section (Site Identity → Hero & Branding)
67 + *
68 + * Usage in theme templates:
69 + * <?php thinkrank_hero(); ?>
70 + *
71 + * Also available as the [thinkrank_hero] shortcode for use in content.
72 + *
73 + * @since 1.19.0
74 + *
75 + * @param array $args Optional. Reserved for future display arguments.
76 + * @return void
77 + */
78 +function thinkrank_hero($args = []) {
79 + /**
80 + * Fires to display the ThinkRank hero section.
81 + *
82 + * @since 1.19.0
83 + *
84 + * @param array $args Hero display arguments.
85 + */
86 + do_action('thinkrank_hero', $args);
87 +}
88 +
89 +/**
90 + * Get the ThinkRank hero section data without displaying it
91 + *
92 + * @since 1.19.0
93 + *
94 + * @return array|null Hero data (title, subtitle, cta_text, cta_url,
95 + * background_image, html) or null if not available
96 + */
97 +function thinkrank_get_hero() {
98 + // Check if ThinkRank is loaded
99 + if (!function_exists('thinkrank')) {
100 + return null;
101 + }
102 +
103 + $plugin = thinkrank();
104 + if (!$plugin) {
105 + return null;
106 + }
107 +
108 + $seo_manager = $plugin->get_component('frontend_seo');
109 +
110 + if (!$seo_manager || !method_exists($seo_manager, 'get_current_hero')) {
111 + return null;
112 + }
113 +
114 + return $seo_manager->get_current_hero();
115 +}
116 +
117 +/**
66 118 * Check if ThinkRank SEO is handling the current page
67 119 *
68 120 * @since 1.0.0
69 121 *
@@ -116,27 +168,24 @@
116 168 return $seo_manager->get_current_metadata();
117 169 }
118 170
119 171 /**
120 - * Display custom breadcrumb styles
172 + * Get breadcrumb inline CSS
121 173 *
122 174 * @since 1.0.0
123 175 *
124 - * @return void
176 + * @return string CSS styles for breadcrumbs
125 177 */
126 -function thinkrank_breadcrumb_styles() {
127 - ?>
128 - <style>
178 +function thinkrank_get_breadcrumb_css(): string {
179 + return '
129 180 .thinkrank-breadcrumbs {
130 181 margin: 1rem 0;
131 182 font-size: 0.9rem;
132 183 }
133 -
134 184 .thinkrank-breadcrumbs .breadcrumb-prefix {
135 185 font-weight: 600;
136 186 margin-right: 0.5rem;
137 187 }
138 -
139 188 .thinkrank-breadcrumbs .breadcrumb-list {
140 189 list-style: none;
141 190 margin: 0;
142 191 padding: 0;
@@ -143,9 +192,8 @@
143 192 display: flex;
144 193 flex-wrap: wrap;
145 194 align-items: center;
146 195 }
147 -
148 196 .thinkrank-breadcrumbs .breadcrumb-item {
149 197 margin: 0;
150 198 padding: 0;
151 199 display: flex;
@@ -150,33 +198,27 @@
150 198 padding: 0;
151 199 display: flex;
152 200 align-items: center;
153 201 }
154 -
155 202 .thinkrank-breadcrumbs .breadcrumb-item a {
156 203 color: #0073aa;
157 204 text-decoration: none;
158 205 }
159 -
160 206 .thinkrank-breadcrumbs .breadcrumb-item a:hover {
161 207 text-decoration: underline;
162 208 }
163 -
164 209 .thinkrank-breadcrumbs .breadcrumb-item.current span {
165 210 color: #666;
166 211 font-weight: 500;
167 212 }
168 -
169 213 .thinkrank-breadcrumbs .breadcrumb-separator {
170 214 margin: 0 0.5rem;
171 215 color: #999;
172 - }
173 - </style>
174 - <?php
216 + }';
175 217 }
176 218
177 -// Auto-include breadcrumb styles if breadcrumbs are enabled
178 -add_action('wp_head', function() {
219 +// Enqueue breadcrumb styles properly if breadcrumbs are enabled
220 +add_action('wp_enqueue_scripts', function() {
179 221 // Check if ThinkRank is loaded
180 222 if (!function_exists('thinkrank')) {
181 223 return;
182 224 }
@@ -186,17 +228,78 @@
186 228 return;
187 229 }
188 230
189 231 $seo_manager = $plugin->get_component('frontend_seo');
232 + if (!$seo_manager) {
233 + return;
234 + }
190 235
191 - if ($seo_manager) {
192 - // Check if Site Identity Manager exists and breadcrumbs are enabled
193 - if (class_exists('ThinkRank\\SEO\\Site_Identity_Manager')) {
194 - $identity_manager = new \ThinkRank\SEO\Site_Identity_Manager();
195 - $settings = $identity_manager->get_settings('site');
236 + // Both checks below read the site_identity settings category. Loading the
237 + // full Site_Identity_Manager (115KB) on every front-end page to get them
238 + // would be worse, but the two ad-hoc queries that replaced it did not share
239 + // the object cache the manager populates on `wp` — so the category was
240 + // fetched twice per request, uncached, on top of the manager's own read
241 + // (#402).
242 + //
243 + // Read that cache entry directly. Abstract_SEO_Manager caches the merged
244 + // settings under this key on every get_settings('site') call, and `wp` runs
245 + // before wp_enqueue_scripts, so on a normal front-end render this is a hit
246 + // and costs nothing. The fallback is one query for the whole category
247 + // rather than two for parts of it.
248 + $identity = wp_cache_get('seo_settings_site_identity_site_0', 'thinkrank_seo');
196 249
197 - if (!empty($settings['breadcrumbs_enabled'])) {
198 - thinkrank_breadcrumb_styles();
199 - }
250 + if (!is_array($identity)) {
251 + global $wpdb;
252 + $table = $wpdb->prefix . 'thinkrank_seo_settings';
253 +
254 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Lightweight frontend check avoids loading heavy manager class
255 + $rows = $wpdb->get_results(
256 + $wpdb->prepare(
257 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name uses WordPress prefix, safe to interpolate
258 + "SELECT setting_key, setting_value FROM `{$table}` WHERE context_type = %s AND context_id = %d AND setting_category = %s AND is_active = 1",
259 + 'site',
260 + 0,
261 + 'site_identity'
262 + ),
263 + ARRAY_A
264 + );
265 +
266 + $identity = [];
267 + foreach ((array) $rows as $row) {
268 + $identity[$row['setting_key']] = $row['setting_value'];
200 269 }
270 +
271 + // Deliberately not written back to the object cache: the manager caches
272 + // the values *merged with its defaults* under this key, and seeding it
273 + // with raw rows would hand every later reader a partial record.
201 274 }
202 -}, 100);
275 +
276 + $identity_value = static function (string $key) use ($identity): string {
277 + return isset($identity[$key]) ? trim((string) $identity[$key]) : '';
278 + };
279 +
280 + if (!empty($identity['breadcrumbs_enabled'])) {
281 + wp_enqueue_style(
282 + 'thinkrank-breadcrumbs',
283 + THINKRANK_PLUGIN_URL . 'static/css/breadcrumbs.css',
284 + [],
285 + THINKRANK_VERSION
286 + );
287 + }
288 +
289 + // Hero check — only load hero CSS when the hero will actually render.
290 + // Mirrors the render gate in SEO_Manager::generate_hero_html(): a title, a
291 + // subtitle, or a COMPLETE CTA (both text and URL). A CTA with text but no
292 + // URL renders nothing, so it must not pull in the stylesheet.
293 + $hero_renders = '' !== $identity_value('hero_title')
294 + || '' !== $identity_value('hero_subtitle')
295 + || ('' !== $identity_value('hero_cta_text') && '' !== $identity_value('hero_cta_url'));
296 +
297 + if ($hero_renders) {
298 + wp_enqueue_style(
299 + 'thinkrank-hero',
300 + THINKRANK_PLUGIN_URL . 'static/css/hero.css',
301 + [],
302 + THINKRANK_VERSION
303 + );
304 + }
305 +});