77class Feed_Shortcode {
88
99 private static $ schema_scripts = array ();
10+ private static $ schemas_processed = false ;
1011
1112 public function __construct (Feed_Deserializer $ feed_deserializer ) {
1213 $ this ->feed_deserializer = $ feed_deserializer ;
14+ // Hook early to pre-process shortcodes and extract schemas before wp_head runs
15+ add_action ('template_redirect ' , array ($ this , 'pre_process_schemas ' ), 1 );
1316 // Hook into wp_head to output schemas
1417 add_action ('wp_head ' , array ($ this , 'output_schemas_to_head ' ), 1 );
1518 }
@@ -22,6 +25,120 @@ public function register() {
2225 add_shortcode ('opio_feed ' , array ($ this , 'init ' ));
2326 }
2427
28+ /**
29+ * Pre-process shortcodes to extract schemas before wp_head runs
30+ */
31+ public function pre_process_schemas () {
32+ if (self ::$ schemas_processed || is_admin ()) {
33+ return ;
34+ }
35+
36+ global $ wp_query , $ post ;
37+
38+ $ content = '' ;
39+ $ feed_ids = array ();
40+
41+ // Check current post/page content
42+ if ($ post && isset ($ post ->post_content )) {
43+ $ content .= $ post ->post_content ;
44+ }
45+
46+ // Check all posts in the main query (for archive pages, etc.)
47+ if ($ wp_query && isset ($ wp_query ->posts ) && is_array ($ wp_query ->posts )) {
48+ foreach ($ wp_query ->posts as $ query_post ) {
49+ if (isset ($ query_post ->post_content )) {
50+ $ content .= $ query_post ->post_content ;
51+ }
52+ }
53+ }
54+
55+ // Extract all opio_feed shortcode IDs from content using regex
56+ if (preg_match_all ('/\[opio_feed[^\]]*id=[" \']?(\d+)[" \']?[^\]]*\]/i ' , $ content , $ matches )) {
57+ if (!empty ($ matches [1 ])) {
58+ $ feed_ids = array_unique (array_map ('intval ' , $ matches [1 ]));
59+ }
60+ }
61+
62+ // Also try parsing shortcode attributes for cases where format might differ
63+ if (has_shortcode ($ content , 'opio_feed ' )) {
64+ // Use WordPress shortcode parser
65+ preg_match_all ('/\[opio_feed([^\]]*)\]/i ' , $ content , $ shortcode_matches );
66+ if (!empty ($ shortcode_matches [1 ])) {
67+ foreach ($ shortcode_matches [1 ] as $ atts_string ) {
68+ $ atts = shortcode_parse_atts ($ atts_string );
69+ if (isset ($ atts ['id ' ])) {
70+ $ feed_ids [] = intval ($ atts ['id ' ]);
71+ }
72+ }
73+ $ feed_ids = array_unique ($ feed_ids );
74+ }
75+ }
76+
77+ // Process all found feed IDs
78+ foreach ($ feed_ids as $ feed_id ) {
79+ if ($ feed_id > 0 ) {
80+ $ this ->fetch_and_extract_schema ($ feed_id );
81+ }
82+ }
83+
84+ self ::$ schemas_processed = true ;
85+ }
86+
87+ /**
88+ * Fetch feed and extract schema without outputting content
89+ */
90+ private function fetch_and_extract_schema ($ feed_id ) {
91+ if (get_option ('opio_active ' ) === '0 ' ) {
92+ return ;
93+ }
94+
95+ $ feed = $ this ->feed_deserializer ->get_feed ($ feed_id );
96+
97+ if ($ feed == null ) {
98+ return ;
99+ }
100+
101+ $ feed_object = json_decode ($ feed ->post_content );
102+
103+ if (!$ feed_object ) {
104+ return ;
105+ }
106+
107+ $ biz_id = isset ($ feed_object ->biz_id ) ? $ feed_object ->biz_id : null ;
108+ $ org_id = isset ($ feed_object ->org_id ) ? $ feed_object ->org_id : null ;
109+ $ review_type = isset ($ feed_object ->review_type ) ? $ feed_object ->review_type : null ;
110+ $ review_option = isset ($ feed_object ->review_option ) ? $ feed_object ->review_option : null ;
111+
112+ if (!isset ($ biz_id ) && !isset ($ org_id )) {
113+ return ;
114+ }
115+
116+ $ valid = false ;
117+ if (isset ($ biz_id )) {
118+ $ valid = preg_match ('/^[a-zA-Z0-9]{15,20}$/ ' , $ biz_id );
119+ }
120+ if (!$ valid && isset ($ org_id )) {
121+ $ valid = preg_match ('/^[a-zA-Z0-9]{15,20}$/ ' , $ org_id );
122+ }
123+
124+ if (!$ valid ) {
125+ return ;
126+ }
127+
128+ $ option = ($ review_option == "opio " ) ? "reviewFeed " : "allReviewFeed " ;
129+ $ opio_handler = new Opio_Handler ($ biz_id , $ option , $ review_type , $ org_id );
130+ $ reviews = $ opio_handler ->get_business ();
131+
132+ if ($ reviews ) {
133+ // Extract schema scripts from the feed HTML
134+ $ schemas = $ this ->extract_schema_from_html ($ reviews );
135+ if (!empty ($ schemas )) {
136+ // Store schemas to be output in head
137+ self ::$ schema_scripts = array_merge (self ::$ schema_scripts , $ schemas );
138+ }
139+ }
140+ }
141+
25142 /**
26143 * Extract JSON-LD schema scripts from HTML content
27144 */
@@ -109,15 +226,17 @@ public function init($atts) {
109226 $ opio_handler = new Opio_Handler ($ biz_id , $ option , $ review_type , $ org_id );
110227 $ reviews = $ opio_handler ->get_business ();
111228
112- // Extract schema scripts from the feed HTML
229+ // Extract schema if not already processed (fallback for dynamically added shortcodes)
230+ // Note: This won't add to head if wp_head already fired, but will remove from body
113231 $ schemas = $ this ->extract_schema_from_html ($ reviews );
114- if (!empty ($ schemas )) {
115- // Store schemas to be output in head
232+ if (!empty ($ schemas ) && ! did_action ( ' wp_head ' ) ) {
233+ // Only add if wp_head hasn't fired yet
116234 self ::$ schema_scripts = array_merge (self ::$ schema_scripts , $ schemas );
117- // Remove schemas from body content
118- $ reviews = $ this ->remove_schema_from_html ($ reviews );
119235 }
120236
237+ // Always remove schema scripts from body content
238+ $ reviews = $ this ->remove_schema_from_html ($ reviews );
239+
121240 // Wrap entire feed content with Nitropack exclusion wrapper
122241 echo '<div data-nitro-exclude="all" data-nitro-ignore="true" data-nitro-no-optimize="true" data-nitro-preserve-ws="true"> ' ;
123242
0 commit comments