PluginProbe
Content Egg – Affiliate Product Importer & Price Comparison / 11.2.0
Content Egg – Affiliate Product Importer & Price Comparison v11.2.0
11.8.1 11.8.0 11.7.0 11.6.0 11.5.0 11.4.0 11.3.0 11.2.0 11.1.0 trunk 1.6.0 1.6.1 1.7.1 1.8.0 1.9.0 10.0.0 10.1.0 11.0.0 2.0.1 2.1.0 2.2.0 2.3.0 2.4.0 2.4.2 2.5.1 All 65 releases
content-egg / application / BlockRenderRestController.php

BlockRenderRestController.php in Content Egg – Affiliate Product Importer & Price Comparison 11.2.0, at application/BlockRenderRestController.php

233 lines 6.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ContentEgg\application;
4
5 use function ContentEgg\prnx;
6
7 defined('\ABSPATH') || exit;
8
9 /**
10 * BlockRenderRestController class file
11 *
12 * @author keywordrush.com <support@keywordrush.com>
13 * @link https://www.keywordrush.com
14 * @copyright Copyright &copy; 2026 keywordrush.com
15 */
16
17 class BlockRenderRestController
18 {
19 const REST_NAMESPACE = 'content-egg/v1';
20 const ROUTE = '/render-blocks';
21
22 const MAX_BLOCKS_PER_REQUEST = 20;
23 const RATE_LIMIT_PER_MINUTE = 60;
24
25 private static $instance = null;
26
27 public static function getInstance(): self
28 {
29 if (self::$instance === null)
30 {
31 self::$instance = new self;
32 }
33 return self::$instance;
34 }
35
36 private function __construct()
37 {
38 }
39
40 public static function init()
41 {
42 add_action('rest_api_init', array(__CLASS__, 'register_routes'));
43 }
44
45 public static function register_routes()
46 {
47 register_rest_route(self::REST_NAMESPACE, self::ROUTE, array(
48 'methods' => \WP_REST_Server::CREATABLE, // POST
49 'callback' => array(__CLASS__, 'handle_render'),
50 'permission_callback' => array(__CLASS__, 'permission_check'),
51 'args' => array(
52 'blocks' => array(
53 'required' => true,
54 'type' => 'array',
55 ),
56 ),
57 ));
58 }
59
60 public static function permission_check(\WP_REST_Request $request)
61 {
62 // If nonce is provided, require it to be valid.
63 $nonce = $request->get_header('X-WP-Nonce');
64 if ($nonce)
65 {
66 if (!wp_verify_nonce($nonce, 'wp_rest'))
67 {
68 return new WP_Error(
69 'cegg_invalid_nonce',
70 __('Invalid security token.', 'content-egg'),
71 array('status' => 403)
72 );
73 }
74 }
75
76 // Otherwise allow public rendering (content is public anyway),
77 // with rate limiting in handle_render().
78 return true;
79 }
80
81 protected static function get_client_ip()
82 {
83 // Keep simple; we can improve behind proxies/CDNs later
84 return isset($_SERVER['REMOTE_ADDR']) ? (string) $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
85 }
86
87 protected static function check_rate_limit()
88 {
89 $limit = (int) apply_filters('cegg_render_blocks_rate_limit_per_minute', self::RATE_LIMIT_PER_MINUTE);
90 if ($limit <= 0)
91 {
92 return true; // disabled
93 }
94
95 $ip = self::get_client_ip();
96 $key = 'cegg_rb_' . md5($ip);
97 $n = (int) get_transient($key);
98
99 if ($n >= $limit)
100 {
101 return new WP_Error(
102 'cegg_rate_limited',
103 __('Too many requests. Please try again shortly.', 'content-egg'),
104 array('status' => 429)
105 );
106 }
107
108 set_transient($key, $n + 1, MINUTE_IN_SECONDS);
109 return true;
110 }
111
112 protected static function normalize_atts_for_shortcode($atts)
113 {
114 $out = array();
115
116 if (!is_array($atts))
117 {
118 return $out;
119 }
120
121 foreach ($atts as $key => $value)
122 {
123 $key = sanitize_key($key);
124
125 if (is_array($value))
126 {
127 $value = array_map('sanitize_text_field', $value);
128 $out[$key] = implode(',', $value);
129 }
130 else
131 {
132 $out[$key] = sanitize_text_field((string) $value);
133 }
134 }
135
136 return $out;
137 }
138
139 public static function handle_render(\WP_REST_Request $request)
140 {
141 $rate_ok = self::check_rate_limit();
142 if (is_wp_error($rate_ok))
143 {
144 return $rate_ok;
145 }
146
147 $params = $request->get_json_params();
148 $blocks = isset($params['blocks']) ? $params['blocks'] : null;
149
150 if (!is_array($blocks) || empty($blocks))
151 {
152 return new \WP_Error(
153 'cegg_bad_request',
154 __('Invalid request payload.', 'content-egg'),
155 array('status' => 400)
156 );
157 }
158
159 $max_blocks = (int) apply_filters('cegg_render_blocks_max_blocks_per_request', self::MAX_BLOCKS_PER_REQUEST);
160 if ($max_blocks > 0 && count($blocks) > $max_blocks)
161 {
162 return new WP_Error(
163 'cegg_too_many_blocks',
164 sprintf(__('Too many blocks in one request (max %d).', 'content-egg'), $max_blocks),
165 array('status' => 400)
166 );
167 }
168
169 $results = array();
170
171 foreach ($blocks as $item)
172 {
173 // Expected shape per item:
174 // { id: "cegg-block-123", post_id: 123, atts: {...}, content: "..." }
175 $id = isset($item['id']) ? sanitize_text_field((string) $item['id']) : '';
176 $post_id = isset($item['post_id']) ? absint($item['post_id']) : 0;
177 $atts = isset($item['atts']) ? $item['atts'] : array();
178 $content = isset($item['content']) ? (string) $item['content'] : '';
179 $type = isset($item['type']) ? sanitize_key($item['type']) : '';
180
181 if (!$id)
182 {
183 // Skip items without IDs; caller can't map the response anyway.
184 continue;
185 }
186
187 if (!$post_id)
188 {
189 $results[$id] = array(
190 'html' => '',
191 'error' => __('Missing post_id.', 'content-egg'),
192 );
193 continue;
194 }
195
196 try
197 {
198 $atts_norm = self::normalize_atts_for_shortcode($atts);
199
200 // Force the correct post and avoid async recursion
201 $atts_norm['post_id'] = $post_id;
202 $atts_norm['async'] = 0;
203
204 // Render
205 if ($type === 'module' || !empty($atts_norm['module']))
206 {
207 $html = EggShortcode::getInstance()->viewData($atts_norm, $content);
208 }
209 else
210 {
211 $html = BlockShortcode::getInstance()->viewData($atts_norm, $content);
212 }
213
214 $results[$id] = array(
215 'html' => (string) $html,
216 );
217 }
218 catch (\Throwable $e)
219 {
220 $results[$id] = array(
221 'html' => '',
222 'error' => __('Render error.', 'content-egg'),
223 );
224 }
225 }
226
227 return rest_ensure_response(array(
228 'success' => true,
229 'data' => $results,
230 ));
231 }
232 }
233