PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.1
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.1
3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 2.8.17 2.8.18 All 259 releases
wpfunnels / includes / core / rest-api / Controllers / class-gutenberg-css-controller.php

class-gutenberg-css-controller.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.13.1, at includes/core/rest-api/Controllers/class-gutenberg-css-controller.php

402 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Gutenberg CSS controller
4 *
5 * @package WPFunnels\Rest\Controllers
6 */
7 namespace WPFunnels\Rest\Controllers;
8
9 use WP_Error;
10 use WP_REST_Request;
11 use WP_REST_Response;
12 use WP_REST_Server;
13 use WPFunnels\Wpfnl_functions;
14
15 class GutenbergCSSController extends Wpfnl_REST_Controller {
16
17 /**
18 * Endpoint namespace.
19 *
20 * @var string
21 */
22 protected $namespace = 'wpfunnels/v1';
23
24 /**
25 * Route base.
26 *
27 * @var string
28 */
29 protected $rest_base = 'gutenberg';
30
31 /**
32 * Check if user has valid permission
33 *
34 * @param $request
35 *
36 * @return bool|WP_Error
37 * @since 1.0.0
38 */
39 public function update_items_permissions_check($request) {
40 if (!Wpfnl_functions::wpfnl_rest_check_manager_permissions('steps', 'edit')) {
41 return new WP_Error('wpfunnels_rest_cannot_edit', __('Sorry, you cannot edit this resource.', 'wpfnl'), array('status' => rest_authorization_required_code()));
42 }
43 return true;
44 }
45
46 /**
47 * Makes sure the current user has access to READ the settings APIs.
48 *
49 * @param WP_REST_Request $request Full data about the request.
50 *
51 * @return WP_Error|boolean
52 * @since 3.0.0
53 */
54 public function get_items_permissions_check($request) {
55 if (!Wpfnl_functions::wpfnl_rest_check_manager_permissions('steps')) {
56 return new WP_Error('wpfunnels_rest_cannot_edit', __('Sorry, you cannot list resources.', 'wpfnl'), array('status' => rest_authorization_required_code()));
57 }
58 return true;
59 }
60
61
62 /**
63 * Register rest routes
64 *
65 * @since 1.0.0
66 */
67 public function register_routes() {
68 register_rest_route(
69 $this->namespace,
70 '/' . $this->rest_base ,
71 array(
72 array(
73 'methods' => 'POST',
74 'callback' => array( $this, 'append_gutenberg_css_callback' ),
75 'permission_callback' => function () {
76 return current_user_can( 'edit_posts' );
77 },
78 'args' => array(),
79 ),
80 )
81 );
82
83 // For css file save
84 register_rest_route(
85 $this->namespace,
86 '/'.$this->rest_base.'/save_block_css/',
87 array(
88 array(
89 'methods' => 'POST',
90 'callback' => array( $this, 'save_block_css' ),
91 'permission_callback' => function () {
92 return current_user_can( 'edit_posts' );
93 },
94 'args' => array(),
95 ),
96 )
97 );
98 }
99
100
101 /**
102 * Add block css
103 *
104 * @param $request
105 *
106 * @throws
107 */
108 public function append_gutenberg_css_callback( $request ) {
109 try {
110 global $wp_filesystem;
111 if ( ! $wp_filesystem ) {
112 require_once ABSPATH . 'wp-admin/includes/file.php';
113 }
114 $params = $request->get_params();
115 $css = $this->sanitize_css_content( $params['css'] );
116 $post_id = (int) sanitize_text_field( $params['post_id'] );
117 if ( $post_id ) {
118 $filename = "wpfnl-gb-css-{$post_id}.css";
119 $upload_dir = wp_upload_dir();
120 $dir = trailingslashit( $upload_dir['basedir'] ) . 'wpfunnels/css/';
121 if ( file_exists( $dir . $filename ) ) {
122 $file = fopen( $dir . $filename, 'a' );
123 fwrite( $file, $css );
124 fclose( $file );
125 }
126 $get_data = get_post_meta( $post_id, '_wpfunnels_gb_css', true );
127 update_post_meta( $post_id, '_wpfunnels_gb_css', $get_data . $css );
128
129 wp_send_json_success(
130 array(
131 'success' => true,
132 'message' => 'Update done' . $get_data,
133 )
134 );
135 }
136 } catch ( \Exception $e ) {
137 wp_send_json_error(
138 array(
139 'success' => false,
140 'message' => $e->getMessage(),
141 )
142 );
143 }
144 }
145
146
147 /**
148 * Save block css
149 * Save block css for each post in a css file and enqueue the file to the post page
150 *
151 * @since 1.0.0
152 *
153 * @throws \Exception
154 */
155 public function save_block_css($request){
156 try {
157 global $wp_filesystem;
158 if (!$wp_filesystem) {
159 require_once ABSPATH . 'wp-admin/includes/file.php';
160 }
161
162 $params = $request->get_params();
163 $post_id = (int) sanitize_text_field($params['post_id']);
164 $is_previewing = $params['isPreviewing'];
165
166 if ($params['is_remain']) {
167 $qubely_block_css = $this->sanitize_css_content($params['block_css']);
168 $filename = "wpfnl-css-{$post_id}.css";
169
170 $qubely_block_json = $this->sanitize_json_content($params['interaction']);
171 $jsonfilename = "wpfnl-json-{$post_id}.json";
172
173 $upload_dir = wp_upload_dir();
174 $dir = trailingslashit($upload_dir['basedir']) . 'wpfunnels/css/';
175
176 // Add Import in first
177 $import_first = $this->set_import_url_to_top_css($qubely_block_css);
178
179 if ($is_previewing==true) {
180 $filename = "wpfnl-preview.css";
181 $jsonfilename = "wpfnl-preview.json";
182 } else {
183 update_post_meta($post_id, '_wpfunnels_gb_css', $import_first);
184 }
185
186 WP_Filesystem(false, $upload_dir['basedir'], true);
187 if (!$wp_filesystem->is_dir($dir)) {
188 wp_mkdir_p( $dir );
189 }
190 // If fail to save css in directory, then it will show a message to user
191 if (!$wp_filesystem->put_contents($dir . $filename, $import_first)) {
192 throw new \Exception(__('CSS can not be saved due to permission!!!', 'wpfnl'));
193 }
194
195 // If fail to save css in directory, then it will show a message to user
196 if (!$wp_filesystem->put_contents($dir . $jsonfilename, $qubely_block_json)) {
197 throw new \Exception(__('JSON can not be saved due to permission!!!', 'wpfnl'));
198 }
199 } else {
200 if($is_previewing==false ){
201 delete_post_meta($post_id, '_wpfunnels_gb_css');
202 $this->delete_post_resource($post_id);
203 }
204 }
205
206 $success_message = 'WPFunnels preview css file has been updated.';
207
208 return array(
209 'success' => true,
210 'message' => $success_message,
211 'data' => $params,
212 );
213 } catch (\Exception $e) {
214 return array(
215 'success' => false,
216 'message' => $e->getMessage(),
217 );
218 }
219 }
220
221
222 /**
223 * Delete post releated data
224 *
225 * @delete post css file
226 */
227 private function delete_post_resource( $post_id = '' ) {
228 $post_id = get_the_ID();
229 if ( $post_id ) {
230 $upload_dir = wp_get_upload_dir();
231 $upload_css_dir = trailingslashit( $upload_dir['basedir'] );
232 $css_path = $upload_css_dir . "wpfunnels/css/wpfnl-css-{$post_id}.css";
233 $json_path = $upload_css_dir . "wpfunnels/css/wpfnl-json-{$post_id}.json";
234 if ( file_exists( $css_path ) ) {
235 unlink( $css_path );
236 }
237 if ( file_exists( $json_path ) ) {
238 unlink( $json_path );
239 }
240 }
241 }
242
243
244 /**
245 * Set font import to the top of the CSS file
246 *
247 * @since 1.0.2
248 */
249 public function set_import_url_to_top_css( $get_css = '' ) {
250 $css_url = "@import url('https://fonts.googleapis.com/css?family=";
251 $google_font_exists = substr_count( $get_css, $css_url );
252
253 if ( $google_font_exists ) {
254 $pattern = sprintf(
255 '/%s(.+?)%s/ims',
256 preg_quote( $css_url, '/' ),
257 preg_quote( "');", '/' )
258 );
259
260 if ( preg_match_all( $pattern, $get_css, $matches ) ) {
261 $fonts = $matches[0];
262 $get_css = str_replace( $fonts, '', $get_css );
263 if ( preg_match_all( '/font-weight[ ]?:[ ]?[\d]{3}[ ]?;/', $get_css, $matche_weight ) ) { // short out font weight
264 $weight = array_map(
265 function ( $val ) {
266 $process = trim( str_replace( array( 'font-weight', ':', ';' ), '', $val ) );
267 if ( is_numeric( $process ) ) {
268 return $process;
269 }
270 },
271 $matche_weight[0]
272 );
273 foreach ( $fonts as $key => $val ) {
274 $fonts[ $key ] = str_replace( "');", '', $val ) . ':' . implode( ',', $weight ) . "');";
275 }
276 }
277
278 // Multiple same fonts to single font
279 $fonts = array_unique( $fonts );
280 $get_css = implode( '', $fonts ) . $get_css;
281 }
282 }
283 return $get_css;
284 }
285
286 /**
287 * Sanitize CSS content to prevent XSS attacks
288 *
289 * @param string $css The CSS content to sanitize
290 * @return string Sanitized CSS content
291 * @since 3.5.27
292 */
293 private function sanitize_css_content($css){
294 if (!is_string($css)) {
295 return '';
296 }
297
298 // Remove potentially dangerous CSS functions and properties
299 $dangerous_patterns = array(
300 '/javascript\s*:/i', // Remove javascript: URLs
301 '/expression\s*\(/i', // Remove CSS expressions
302 '/behavior\s*:/i', // Remove IE behavior property
303 '/@import\s+(?!url\()/i', // Remove @import except url() imports
304 '/binding\s*:/i', // Remove binding property
305 '/-moz-binding\s*:/i', // Remove -moz-binding
306 '/data\s*:\s*text\/html/i', // Remove data:text/html
307 '/vbscript\s*:/i', // Remove vbscript: URLs
308 '/on\w+\s*=/i', // Remove event handlers
309 '/<script/i', // Remove script tags
310 '/<\/script/i', // Remove closing script tags
311 '/&lt;script/i', // Remove encoded script tags
312 '/&lt;\/script/i', // Remove encoded closing script tags
313 );
314
315 // Apply sanitization patterns
316 $sanitized_css = preg_replace($dangerous_patterns, '', $css);
317
318 // Strip any remaining HTML/XML tags that might have sneaked in
319 $sanitized_css = strip_tags($sanitized_css);
320
321 // Additional validation for CSS structure
322 $sanitized_css = $this->validate_css_structure($sanitized_css);
323
324 return $sanitized_css;
325 }
326
327 /**
328 * Validate CSS structure and remove potentially malicious content
329 *
330 * @param string $css The CSS content to validate
331 * @return string Validated CSS content
332 * @since 3.5.27
333 */
334 private function validate_css_structure($css){
335 // Remove any content that looks like HTML/XML
336 $css = preg_replace('/<[^>]*>/', '', $css);
337
338 // Remove any remaining HTML entities that could be used for XSS
339 $css = html_entity_decode($css, ENT_QUOTES | ENT_HTML5, 'UTF-8');
340 $css = preg_replace('/&[#\w]+;/', '', $css);
341
342 return $css;
343 }
344
345 /**
346 * Sanitize JSON content to prevent XSS attacks
347 *
348 * @param mixed $json The JSON content to sanitize
349 * @return string Sanitized JSON content
350 * @since 3.5.27
351 */
352 private function sanitize_json_content($json){
353 if (is_string($json)) {
354 // Decode JSON to validate structure
355 $decoded = json_decode($json, true);
356 if (json_last_error() === JSON_ERROR_NONE) {
357 // Recursively sanitize the decoded data
358 $sanitized = $this->sanitize_json_data($decoded);
359 return wp_json_encode($sanitized);
360 }
361 } elseif (is_array($json) || is_object($json)) {
362 // Sanitize array/object data
363 $sanitized = $this->sanitize_json_data($json);
364 return wp_json_encode($sanitized);
365 }
366
367 return '{}'; // Return empty JSON object if invalid
368 }
369
370 /**
371 * Recursively sanitize JSON data
372 *
373 * @param mixed $data The data to sanitize
374 * @return mixed Sanitized data
375 * @since 3.5.27
376 */
377 private function sanitize_json_data($data){
378 if (is_array($data)) {
379 $sanitized = array();
380 foreach ($data as $key => $value) {
381 $sanitized_key = sanitize_text_field($key);
382 $sanitized[$sanitized_key] = $this->sanitize_json_data($value);
383 }
384 return $sanitized;
385 } elseif (is_object($data)) {
386 $sanitized = new \stdClass();
387 foreach ($data as $key => $value) {
388 $sanitized_key = sanitize_text_field($key);
389 $sanitized->$sanitized_key = $this->sanitize_json_data($value);
390 }
391 return $sanitized;
392 } elseif (is_string($data)) {
393 // For strings, remove potential XSS vectors
394 $data = wp_kses($data, array());
395 $data = sanitize_text_field($data);
396 return $data;
397 }
398
399 return $data;
400 }
401 }
402