PluginProbe
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits / 3.1.9
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits v3.1.9
3.2.2 3.2.3 3.2.1 3.2.0 3.1.9 3.1.8 3.1.7 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.9 trunk 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.3 1.1.4 1.1.5 All 174 releases
master-addons / inc / admin / widget-builder / class-rest-controller.php

class-rest-controller.php in Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits 3.1.9, at inc/admin/widget-builder/class-rest-controller.php

747 lines 26.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Widget Builder REST API Controller
4 *
5 * Provides REST endpoints for:
6 * - GET /assets - Fetches registered WP/Elementor scripts and styles
7 * - POST /widgets/move - Moves widget between categories
8 * - GET/POST/PUT /widgets - CRUD operations for widgets
9 *
10 * @package MasterAddons
11 * @subpackage WidgetBuilder
12 */
13
14 namespace MasterAddons\Inc\Admin\WidgetBuilder;
15
16 use WP_REST_Controller;
17 use WP_REST_Server;
18 use WP_REST_Request;
19 use WP_REST_Response;
20 use WP_Error;
21
22 if (!defined('ABSPATH')) {
23 exit;
24 }
25
26 class REST_Controller extends WP_REST_Controller {
27
28 /**
29 * Namespace
30 */
31 protected $namespace = 'jltma/v1';
32
33 /**
34 * Constructor
35 */
36 public function __construct() {
37 }
38
39 /**
40 * Register routes
41 */
42 public function register_routes() {
43 // GET /assets - Get registered WP/Elementor dependencies
44 register_rest_route($this->namespace, '/assets', [
45 [
46 'methods' => WP_REST_Server::READABLE,
47 'callback' => [$this, 'get_assets'],
48 'permission_callback' => [$this, 'check_permission'],
49 ],
50 ]);
51
52 // POST /widgets/move - Move widget between categories
53 register_rest_route($this->namespace, '/widgets/move', [
54 [
55 'methods' => WP_REST_Server::CREATABLE,
56 'callback' => [$this, 'move_widget'],
57 'permission_callback' => [$this, 'check_permission'],
58 'args' => [
59 'id' => [
60 'required' => true,
61 'type' => 'integer',
62 'sanitize_callback' => 'absint',
63 ],
64 'category' => [
65 'required' => true,
66 'type' => 'string',
67 'sanitize_callback' => 'sanitize_text_field',
68 ],
69 ],
70 ],
71 ]);
72
73 // POST /widgets - Create new widget
74 register_rest_route($this->namespace, '/widgets', [
75 [
76 'methods' => WP_REST_Server::CREATABLE,
77 'callback' => [$this, 'create_widget'],
78 'permission_callback' => [$this, 'check_permission'],
79 ],
80 ]);
81
82 // GET/PUT/DELETE /widgets/{id} - CRUD operations for single widget
83 register_rest_route($this->namespace, '/widgets/(?P<id>\d+)', [
84 [
85 'methods' => WP_REST_Server::READABLE,
86 'callback' => [$this, 'get_widget'],
87 'permission_callback' => [$this, 'check_permission'],
88 ],
89 [
90 'methods' => WP_REST_Server::EDITABLE,
91 'callback' => [$this, 'update_widget'],
92 'permission_callback' => [$this, 'check_permission'],
93 ],
94 [
95 'methods' => WP_REST_Server::DELETABLE,
96 'callback' => [$this, 'delete_widget'],
97 'permission_callback' => [$this, 'check_permission'],
98 ],
99 ]);
100
101 // POST /widgets/{id}/controls - Add control to widget section
102 register_rest_route($this->namespace, '/widgets/(?P<id>\d+)/controls', [
103 [
104 'methods' => WP_REST_Server::CREATABLE,
105 'callback' => [$this, 'add_control'],
106 'permission_callback' => [$this, 'check_permission'],
107 ],
108 ]);
109
110 // DELETE /widgets/{id}/controls/{control_id} - Delete control
111 register_rest_route($this->namespace, '/widgets/(?P<id>\d+)/controls/(?P<control_id>[a-zA-Z0-9_]+)', [
112 [
113 'methods' => WP_REST_Server::DELETABLE,
114 'callback' => [$this, 'delete_control'],
115 'permission_callback' => [$this, 'check_permission'],
116 ],
117 ]);
118
119 // GET /categories - Get Elementor widget categories
120 register_rest_route($this->namespace, '/categories', [
121 [
122 'methods' => WP_REST_Server::READABLE,
123 'callback' => [$this, 'get_categories'],
124 'permission_callback' => [$this, 'check_permission'],
125 ],
126 ]);
127
128 // POST /categories - Register new Elementor category
129 register_rest_route($this->namespace, '/categories', [
130 [
131 'methods' => WP_REST_Server::CREATABLE,
132 'callback' => [$this, 'register_category'],
133 'permission_callback' => [$this, 'check_permission'],
134 'args' => [
135 'name' => [
136 'required' => true,
137 'type' => 'string',
138 'sanitize_callback' => 'sanitize_text_field',
139 ],
140 'slug' => [
141 'required' => false,
142 'type' => 'string',
143 'sanitize_callback' => 'sanitize_title',
144 ],
145 ],
146 ],
147 ]);
148 }
149
150 /**
151 * Permission callback
152 */
153 public function check_permission() {
154 return current_user_can('manage_options');
155 }
156
157 /**
158 * GET /assets
159 * Returns registered WordPress and Elementor scripts/styles
160 */
161 public function get_assets(WP_REST_Request $request) {
162 global $wp_scripts, $wp_styles;
163
164 $response = [
165 'wp_scripts' => [],
166 'wp_styles' => [],
167 'elementor' => defined('ELEMENTOR_VERSION'),
168 'elementor_scripts' => [],
169 'elementor_styles' => [],
170 ];
171
172 // WordPress scripts
173 if (!empty($wp_scripts->registered)) {
174 foreach ($wp_scripts->registered as $handle => $script) {
175 $response['wp_scripts'][] = [
176 'handle' => $handle,
177 'src' => $script->src,
178 'deps' => $script->deps,
179 'version' => $script->ver,
180 ];
181 }
182 }
183
184 // WordPress styles
185 if (!empty($wp_styles->registered)) {
186 foreach ($wp_styles->registered as $handle => $style) {
187 $response['wp_styles'][] = [
188 'handle' => $handle,
189 'src' => $style->src,
190 'deps' => $style->deps,
191 'version' => $style->ver,
192 ];
193 }
194 }
195
196 // Elementor dependencies (if available)
197 if (defined('ELEMENTOR_VERSION')) {
198 $elementor_scripts = apply_filters('jltma_elementor_scripts', [
199 'elementor-frontend',
200 'elementor-waypoints',
201 'swiper',
202 'elementor-dialog',
203 ]);
204
205 foreach ($elementor_scripts as $handle) {
206 if (isset($wp_scripts->registered[$handle])) {
207 $script = $wp_scripts->registered[$handle];
208 $response['elementor_scripts'][] = [
209 'handle' => $handle,
210 'src' => $script->src,
211 'deps' => $script->deps,
212 'version' => $script->ver,
213 ];
214 }
215 }
216 }
217
218 return new WP_REST_Response($response, 200);
219 }
220
221 /**
222 * POST /widgets/move
223 * Move widget to different category
224 */
225 public function move_widget(WP_REST_Request $request) {
226 $widget_id = $request->get_param('id');
227 $category = $request->get_param('category');
228
229 // Verify widget exists
230 $widget = get_post($widget_id);
231 if (!$widget || $widget->post_type !== 'jltma_widget') {
232 return new WP_Error('widget_not_found', 'Widget not found', ['status' => 404]);
233 }
234
235 // Update category meta
236 $updated = update_post_meta($widget_id, '_jltma_widget_category', $category);
237
238 if ($updated !== false) {
239 return new WP_REST_Response([
240 'success' => true,
241 'message' => 'Widget category updated successfully',
242 'data' => [
243 'id' => $widget_id,
244 'category' => $category,
245 ],
246 ], 200);
247 }
248
249 return new WP_Error('update_failed', 'Failed to update widget category', ['status' => 500]);
250 }
251
252 /**
253 * GET /widgets/{id}
254 * Get single widget data
255 */
256 public function get_widget(WP_REST_Request $request) {
257 $widget_id = $request->get_param('id');
258
259 $widget = get_post($widget_id);
260 if (!$widget || $widget->post_type !== 'jltma_widget') {
261 return new WP_Error('widget_not_found', 'Widget not found', ['status' => 404]);
262 }
263
264 // Get the unified widget data that includes HTML/CSS/JS code
265 $unified_data = get_post_meta($widget_id, '_jltma_widget_data', true);
266
267 // Get includes data and ensure proper structure
268 $includes = get_post_meta($widget_id, '_jltma_widget_includes', true);
269 if (empty($includes) || !is_array($includes)) {
270 $includes = array(
271 'css_libraries' => array(),
272 'js_libraries' => array()
273 );
274 }
275
276 // Get dependencies data and ensure proper structure
277 $dependencies = get_post_meta($widget_id, '_jltma_widget_dependencies', true);
278 if (empty($dependencies) || !is_array($dependencies)) {
279 $dependencies = array(
280 'wp' => array(),
281 'elementor' => array()
282 );
283 }
284
285 $widget_data = [
286 'id' => $widget_id,
287 'title' => $widget->post_title,
288 'category' => get_post_meta($widget_id, '_jltma_widget_category', true) ?: 'general',
289 'sections' => get_post_meta($widget_id, '_jltma_widget_sections', true) ?: [],
290 'includes' => $includes,
291 'dependencies' => $dependencies,
292 // Include HTML/CSS/JS code from unified widget data
293 'html_code' => isset($unified_data['html_code']) ? $unified_data['html_code'] : '',
294 'css_code' => isset($unified_data['css_code']) ? $unified_data['css_code'] : '',
295 'js_code' => isset($unified_data['js_code']) ? $unified_data['js_code'] : '',
296 'icon' => isset($unified_data['icon']) ? $unified_data['icon'] : 'eicon-code',
297 ];
298
299 return new WP_REST_Response($widget_data, 200);
300 }
301
302 /**
303 * POST /widgets
304 * Create new widget
305 */
306 public function create_widget(WP_REST_Request $request) {
307 $data = $request->get_json_params();
308
309 $widget_id = wp_insert_post([
310 'post_title' => sanitize_text_field($data['title'] ?? 'New Widget'),
311 'post_type' => 'jltma_widget',
312 'post_status' => 'publish',
313 'post_content' => '',
314 ]);
315
316 if (is_wp_error($widget_id)) {
317 return new WP_Error('create_failed', 'Failed to create widget', ['status' => 500]);
318 }
319
320 // Save meta data
321 $this->save_widget_meta($widget_id, $data);
322
323 // Generate widget files
324 $this->generate_widget_files($widget_id);
325
326 return new WP_REST_Response([
327 'success' => true,
328 'message' => 'Widget created successfully',
329 'data' => [
330 'id' => $widget_id,
331 'title' => get_the_title($widget_id),
332 'edit_url' => admin_url('admin.php?page=jltma-widget-editor&widget_id=' . $widget_id),
333 ],
334 ], 201);
335 }
336
337 /**
338 * PUT /widgets/{id}
339 * Update widget
340 */
341 public function update_widget(WP_REST_Request $request) {
342 $widget_id = $request->get_param('id');
343 $data = $request->get_json_params();
344
345 $widget = get_post($widget_id);
346
347 if (!$widget || $widget->post_type !== 'jltma_widget') {
348 $error_details = [
349 'widget_id' => $widget_id,
350 'widget_exists' => !empty($widget),
351 'widget_post_type' => $widget ? $widget->post_type : null,
352 ];
353
354 return new WP_Error('widget_not_found', 'Widget not found', ['status' => 404, 'details' => $error_details]);
355 }
356
357 // Update post
358 wp_update_post([
359 'ID' => $widget_id,
360 'post_title' => sanitize_text_field($data['title'] ?? $widget->post_title),
361 ]);
362
363 // Save meta data
364 $this->save_widget_meta($widget_id, $data);
365
366 // Generate widget files
367 $this->generate_widget_files($widget_id);
368
369 return new WP_REST_Response([
370 'success' => true,
371 'message' => 'Widget updated successfully',
372 'data' => [
373 'id' => $widget_id,
374 ],
375 ], 200);
376 }
377
378 /**
379 * DELETE /widgets/{id}
380 * Delete widget
381 */
382 public function delete_widget(WP_REST_Request $request) {
383 $widget_id = $request->get_param('id');
384
385 $widget = get_post($widget_id);
386 if (!$widget || $widget->post_type !== 'jltma_widget') {
387 return new WP_Error('widget_not_found', 'Widget not found', ['status' => 404]);
388 }
389
390 $deleted = wp_delete_post($widget_id, true);
391
392 if (!$deleted) {
393 return new WP_Error('delete_failed', 'Failed to delete widget', ['status' => 500]);
394 }
395
396 // Delete generated widget files
397 Widget_Generator::delete_widget_files($widget_id);
398
399 return new WP_REST_Response([
400 'success' => true,
401 'message' => 'Widget deleted successfully',
402 ], 200);
403 }
404
405 /**
406 * POST /widgets/{id}/controls
407 * Add control to widget section
408 */
409 public function add_control(WP_REST_Request $request) {
410 $widget_id = $request->get_param('id');
411 $data = $request->get_json_params();
412
413 $sections = get_post_meta($widget_id, '_jltma_widget_sections', true) ?: [];
414
415 // Find section and add control
416 $section_id = $data['section_id'] ?? null;
417 foreach ($sections as &$section) {
418 if ($section['id'] === $section_id) {
419 $section['controls'][] = $data['control'];
420 break;
421 }
422 }
423
424 update_post_meta($widget_id, '_jltma_widget_sections', $sections);
425
426 return new WP_REST_Response([
427 'success' => true,
428 'message' => 'Control added successfully',
429 ], 200);
430 }
431
432 /**
433 * DELETE /widgets/{id}/controls/{control_id}
434 * Delete control from widget
435 */
436 public function delete_control(WP_REST_Request $request) {
437 $widget_id = $request->get_param('id');
438 $control_id = $request->get_param('control_id');
439
440 $sections = get_post_meta($widget_id, '_jltma_widget_sections', true) ?: [];
441
442 // Find and remove control
443 foreach ($sections as &$section) {
444 $section['controls'] = array_filter($section['controls'], function($control) use ($control_id) {
445 return $control['id'] !== $control_id;
446 });
447 $section['controls'] = array_values($section['controls']); // Re-index
448 }
449
450 update_post_meta($widget_id, '_jltma_widget_sections', $sections);
451
452 return new WP_REST_Response([
453 'success' => true,
454 'message' => 'Control deleted successfully',
455 ], 200);
456 }
457
458 /**
459 * GET /categories
460 * Get all registered Elementor widget categories
461 */
462 public function get_categories(WP_REST_Request $request) {
463 $categories = [];
464
465 // Check if Elementor is active
466 if (did_action('elementor/loaded')) {
467 $elements_manager = \Elementor\Plugin::$instance->elements_manager;
468 $elementor_categories = $elements_manager->get_categories();
469
470 foreach ($elementor_categories as $slug => $category_data) {
471 $categories[] = [
472 'slug' => $slug,
473 'title' => isset($category_data['title']) ? $category_data['title'] : $slug,
474 'icon' => isset($category_data['icon']) ? $category_data['icon'] : '',
475 ];
476 }
477 }
478
479 // Add custom categories from options
480 $custom_categories = get_option('jltma_custom_widget_categories', []);
481 if (!empty($custom_categories) && is_array($custom_categories)) {
482 foreach ($custom_categories as $slug => $title) {
483 // Check if not already in list
484 $exists = false;
485 foreach ($categories as $cat) {
486 if ($cat['slug'] === $slug) {
487 $exists = true;
488 break;
489 }
490 }
491 if (!$exists) {
492 $categories[] = [
493 'slug' => $slug,
494 'title' => $title,
495 'icon' => '',
496 ];
497 }
498 }
499 }
500
501 return new WP_REST_Response($categories, 200);
502 }
503
504 /**
505 * POST /categories
506 * Register a new Elementor widget category
507 */
508 public function register_category(WP_REST_Request $request) {
509 $name = $request->get_param('name');
510 $slug = $request->get_param('slug');
511
512 // Generate slug from name if not provided
513 if (empty($slug)) {
514 $slug = sanitize_title($name);
515 }
516
517 // Store in custom categories option
518 $custom_categories = get_option('jltma_custom_widget_categories', []);
519 $custom_categories[$slug] = $name;
520 update_option('jltma_custom_widget_categories', $custom_categories);
521
522 // Register with Elementor if active
523 if (did_action('elementor/loaded')) {
524 $elements_manager = \Elementor\Plugin::$instance->elements_manager;
525 $elements_manager->add_category(
526 $slug,
527 [
528 'title' => $name,
529 'icon' => 'eicon-posts-ticker',
530 ]
531 );
532 }
533
534 return new WP_REST_Response([
535 'success' => true,
536 'message' => 'Category registered successfully',
537 'data' => [
538 'slug' => $slug,
539 'title' => $name,
540 ],
541 ], 201);
542 }
543
544 /**
545 * Helper: Save widget meta data
546 */
547 private function save_widget_meta($widget_id, $data) {
548
549
550 if (isset($data['category'])) {
551 update_post_meta($widget_id, '_jltma_widget_category', sanitize_text_field($data['category']));
552 }
553
554 if (isset($data['sections'])) {
555 // Debug log sections and controls data in cleaner format
556
557 // Group sections by tab
558 $widget_details = [
559 'widget_settings' => [
560 'title' => get_the_title($widget_id),
561 'icon' => isset($data['icon']) ? $data['icon'] : 'eicon-code',
562 'category' => isset($data['category']) ? $data['category'] : 'general',
563 ],
564 'content_tab' => [],
565 'style_tab' => [],
566 'advanced_tab' => []
567 ];
568
569 foreach ($data['sections'] as $section) {
570 $tab = isset($section['tab']) ? $section['tab'] : 'content';
571 $section_data = [
572 'section_id' => isset($section['id']) ? $section['id'] : '',
573 'section_label' => isset($section['label']) ? $section['label'] : '',
574 'controls' => []
575 ];
576
577 if (isset($section['controls']) && is_array($section['controls'])) {
578 foreach ($section['controls'] as $control) {
579 // Only include non-empty values
580 $clean_control = [];
581 foreach ($control as $key => $value) {
582 // Skip empty values and default values
583 if ($value !== '' && $value !== null && $value !== [] &&
584 !($key === 'show_label' && $value == 1) &&
585 !($key === 'label_on' && $value === 'Yes') &&
586 !($key === 'label_off' && $value === 'No') &&
587 !($key === 'return_value' && $value === 'yes') &&
588 !($key === 'separator' && $value === 'default') &&
589 !($key === 'language' && $value === 'html') &&
590 !($key === 'minute_increment' && $value == 1) &&
591 !($key === 'skin' && $value === 'inline') &&
592 !($key === 'prevent_empty' && $value == 1) &&
593 !($key === 'media_types' && is_array($value) && count($value) === 1 && $value[0] === 'image') &&
594 !($key === 'allowed_dimensions' && is_array($value) && count($value) === 4)
595 ) {
596 $clean_control[$key] = $value;
597 }
598 }
599 $section_data['controls'][] = $clean_control;
600 }
601 }
602
603 // Add to appropriate tab
604 $tab_key = $tab . '_tab';
605 $widget_details[$tab_key][] = $section_data;
606 }
607
608 update_post_meta($widget_id, '_jltma_widget_sections', $data['sections']);
609 }
610
611 // Declaring external CSS/JS libraries is a premium-only capability. The
612 // free build stores nothing (default: an empty set); the Pro build
613 // re-enables saving by returning the candidate on this filter — see
614 // MasterAddons\Pro\Classes\Pro_Modules. The candidate is normalised and
615 // URL-validated before it is offered to the filter.
616 if (isset($data['includes'])) {
617 $includes = apply_filters(
618 'master_addons/widget_builder/persist_includes',
619 Widget_Builder_Init::normalize_includes(null),
620 Widget_Builder_Init::normalize_includes($data['includes']),
621 $data
622 );
623
624 update_post_meta(
625 $widget_id,
626 '_jltma_widget_includes',
627 Widget_Builder_Init::normalize_includes($includes)
628 );
629 }
630
631 if (isset($data['dependencies'])) {
632 update_post_meta($widget_id, '_jltma_widget_dependencies', $data['dependencies']);
633 }
634
635 // Generate widget name from title
636 $widget_name = get_post_meta($widget_id, '_jltma_widget_name', true);
637 if (empty($widget_name) && isset($data['title'])) {
638 update_post_meta($widget_id, '_jltma_widget_name', sanitize_title($data['title']));
639 }
640
641 // Sanitize code data before saving
642 $html_code = '';
643 $css_code = '';
644 $js_code = '';
645
646 // Sanitize HTML code. PHP is NEVER allowed (no arbitrary code execution),
647 // regardless of capability. Inline <script> is stripped — JavaScript belongs
648 // in the JS field, which is enqueued separately. Dynamic values use {{placeholders}}.
649 if (isset($data['html_code'])) {
650 $html_code = $this->strip_php_tags($data['html_code']);
651 $html_code = preg_replace('#<script\b[^>]*>.*?</script>#is', '', $html_code);
652 $html_code = preg_replace('#</?script\b[^>]*>#i', '', $html_code);
653 }
654
655 // Sanitize CSS code - strip PHP/script/style tags and dangerous constructs.
656 if (isset($data['css_code'])) {
657 $css_code = $this->sanitize_css($data['css_code']);
658 }
659
660 // Sanitize JavaScript code. PHP is NEVER allowed; <script> tags are stripped so
661 // the value cannot break out of the generated/enqueued script context.
662 if (isset($data['js_code'])) {
663 $js_code = $this->strip_php_tags($data['js_code']);
664 $js_code = preg_replace('#</?script\b[^>]*>#i', '', $js_code);
665 }
666
667 // Capability gate: only users who can post unfiltered HTML may store raw
668 // JavaScript or unrestricted HTML. Everyone else gets a strict allowlist
669 // and no raw JS. On multisite, unfiltered_html is granted to super admins
670 // only, so site admins there cannot persist raw code.
671 if (!current_user_can('unfiltered_html')) {
672 $html_code = wp_kses_post($html_code);
673 $js_code = '';
674 }
675
676 // Custom CSS/JavaScript is available to every build. Both values are
677 // already PHP-/script-stripped above, and raw JS is additionally gated on
678 // the unfiltered_html capability.
679
680 // Also save data in unified format for widget generator
681 $widget_data = [
682 'title' => get_the_title($widget_id),
683 'icon' => isset($data['icon']) ? sanitize_text_field($data['icon']) : 'eicon-code',
684 'category' => isset($data['category']) ? sanitize_text_field($data['category']) : 'master-addons',
685 'sections' => isset($data['sections']) ? $data['sections'] : [],
686 'html_code' => $html_code,
687 'css_code' => $css_code,
688 'js_code' => $js_code
689 ];
690
691 update_post_meta($widget_id, '_jltma_widget_data', $widget_data);
692 }
693
694 /**
695 * Strip every PHP open/close tag (and null bytes) from a string so user input
696 * can never become executable PHP once written into a generated widget file.
697 *
698 * @param string $code
699 * @return string
700 */
701 private function strip_php_tags($code) {
702 if (!is_string($code) || '' === $code) {
703 return '';
704 }
705 $code = str_replace(chr(0), '', $code);
706 $code = preg_replace('/<\?php/i', '', $code);
707 $code = str_replace(array('<?=', '<?', '?>'), '', $code);
708 return $code;
709 }
710
711 /**
712 * Sanitize CSS code
713 *
714 * @param string $css
715 * @return string
716 */
717 private function sanitize_css($css) {
718 // Remove any PHP tags
719 $css = $this->strip_php_tags($css);
720
721 // Remove any <style>/<script> tags so the value cannot break out of the
722 // generated inline <style> block.
723 $css = preg_replace('#</?style\b[^>]*>#i', '', $css);
724 $css = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $css);
725 $css = preg_replace('#</?script\b[^>]*>#i', '', $css);
726
727 // Remove dangerous CSS constructs.
728 $css = preg_replace('/@import\b/i', '', $css);
729 $css = preg_replace('/expression\s*\(/i', '', $css);
730 $css = preg_replace('/(javascript|vbscript)\s*:/i', '', $css);
731 $css = preg_replace('/behavior\s*:/i', '', $css);
732
733 // Remove any JavaScript event handlers
734 $css = preg_replace('/on\w+\s*=\s*["\'].*?["\']/i', '', $css);
735
736 return $css;
737 }
738
739 /**
740 * Generate widget files
741 */
742 private function generate_widget_files($widget_id) {
743 $generator = new Widget_Generator($widget_id);
744 $result = $generator->generate();
745 }
746 }
747