PluginProbe
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits / 3.1.2
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits v3.1.2
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-widget-builder-init.php

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

272 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Master Addons Widget Builder Initialization
4 *
5 * @package MasterAddons
6 * @subpackage WidgetBuilder
7 */
8
9 namespace MasterAddons\Inc\Admin\WidgetBuilder;
10
11 if (!defined('ABSPATH')) {
12 exit;
13 }
14
15 class Widget_Builder_Init {
16
17 private static $instance = null;
18
19 public static function get_instance() {
20 if (is_null(self::$instance)) {
21 self::$instance = new self();
22 }
23 return self::$instance;
24 }
25
26 private function __construct() {
27 add_action('init', [$this, 'initialize'], 1);
28 add_action('admin_init', [$this, 'admin_redirects']);
29 add_action('admin_init', [$this, 'maybe_migrate']);
30 }
31
32 public function initialize() {
33 Widget_CPT::get_instance();
34 Widget_Admin::get_instance();
35
36 // Initialize REST API
37 add_action('rest_api_init', function() {
38 $controller = new REST_Controller();
39 $controller->register_routes();
40 });
41
42 // Initialize Shortcode Manager
43 Shortcode_Manager::get_instance();
44
45 // Register custom widgets with Elementor
46 add_action('elementor/widgets/register', [$this, 'register_custom_widgets']);
47 }
48
49 /**
50 * Register custom widgets from CPT with Elementor
51 */
52 public function register_custom_widgets($widgets_manager) {
53 // Register custom categories first
54 $this->register_custom_categories();
55
56 // Get all published widgets
57 $args = [
58 'post_type' => 'jltma_widget',
59 'post_status' => 'publish',
60 'posts_per_page' => -1,
61 'orderby' => 'date',
62 'order' => 'DESC'
63 ];
64
65 $widgets = get_posts($args);
66
67 if (empty($widgets)) {
68 return;
69 }
70
71 // Widgets are rendered at runtime by Dynamic_Widget — no generated or
72 // executed PHP. (No user input is ever written to or required as PHP.)
73 require_once __DIR__ . '/class-dynamic-widget.php';
74
75 foreach ($widgets as $widget_post) {
76 $widgets_manager->register(new Dynamic_Widget([], ['jltma_post_id' => $widget_post->ID]));
77 }
78 }
79
80 /**
81 * Register custom Elementor categories
82 */
83 private function register_custom_categories() {
84 if (!did_action('elementor/loaded')) {
85 return;
86 }
87
88 // Get custom categories from options
89 $custom_categories = get_option('jltma_custom_widget_categories', []);
90
91 if (empty($custom_categories) || !is_array($custom_categories)) {
92 return;
93 }
94
95 $elements_manager = \Elementor\Plugin::$instance->elements_manager;
96
97 // Register each custom category
98 foreach ($custom_categories as $slug => $title) {
99 // Check if category doesn't already exist
100 $existing_categories = $elements_manager->get_categories();
101
102 if (!isset($existing_categories[$slug])) {
103 $elements_manager->add_category(
104 $slug,
105 [
106 'title' => $title,
107 'icon' => 'eicon-posts-ticker',
108 ]
109 );
110 }
111 }
112 }
113
114 public function admin_redirects() {
115 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only checks of WordPress-set admin query vars for redirect flow; no form data is processed.
116 global $pagenow;
117 $target_post_type = 'jltma_widget';
118 $redirect_url = admin_url('edit.php?post_type=jltma_widget');
119
120 if ('post.php' === $pagenow && isset($_GET['post'])) {
121 if (isset($_GET['action']) && in_array($_GET['action'], ['elementor', 'trash', 'delete', 'restore', 'untrash'], true)) {
122 return;
123 }
124 $post_id = absint($_GET['post']);
125 if ($post_id && $target_post_type === get_post_type($post_id)) {
126 wp_safe_redirect($redirect_url);
127 exit;
128 }
129 }
130
131 if ('post-new.php' === $pagenow && isset($_GET['post_type'])) {
132 $current_post_type = sanitize_key($_GET['post_type']);
133 if ($target_post_type === $current_post_type) {
134 wp_safe_redirect($redirect_url);
135 exit;
136 }
137 }
138 // phpcs:enable WordPress.Security.NonceVerification.Recommended
139 }
140
141 /**
142 * One-time migration. Re-sanitizes stored widget code (strips any PHP/script that
143 * older versions may have persisted), purges stale generated files from current and
144 * legacy locations, and regenerates every widget from the now-clean data. Existing
145 * widget posts are never deleted — only their data is scrubbed and files rebuilt.
146 * Runs once per version (gated by an option) inside an authorised admin request.
147 */
148 public function maybe_migrate() {
149 $option_key = 'jltma_widget_builder_migrated';
150 $version = '3.1.2-runtime';
151
152 if (get_option($option_key) === $version) {
153 return;
154 }
155
156 if (!current_user_can('manage_options')) {
157 return;
158 }
159
160 // 1) Remove stale generated trees (current + legacy locations).
161 $upload = wp_upload_dir();
162 $base = trailingslashit($upload['basedir']);
163 $this->delete_directory($base . 'master_addons/widgets');
164 $this->delete_directory($base . 'master-addons/widget-builder/generated');
165 $this->delete_directory($base . 'master-addons/widget-builder/tmp');
166
167 // 2) Scrub persisted widget data, then regenerate files from clean data.
168 $widget_ids = get_posts([
169 'post_type' => 'jltma_widget',
170 'post_status' => 'any',
171 'posts_per_page' => -1,
172 'fields' => 'ids',
173 ]);
174
175 foreach ($widget_ids as $widget_id) {
176 $data = get_post_meta($widget_id, '_jltma_widget_data', true);
177 if (is_array($data)) {
178 if (isset($data['html_code'])) {
179 $data['html_code'] = $this->scrub_html($data['html_code']);
180 }
181 if (isset($data['css_code'])) {
182 $data['css_code'] = $this->scrub_css($data['css_code']);
183 }
184 if (isset($data['js_code'])) {
185 $data['js_code'] = $this->scrub_js($data['js_code']);
186 }
187 update_post_meta($widget_id, '_jltma_widget_data', $data);
188 }
189
190 $generator = new Widget_Generator($widget_id);
191 $generator->generate();
192 }
193
194 // 3) Drop the orphaned option left by the old option-based builder.
195 delete_option('jltma_custom_widgets');
196
197 update_option($option_key, $version);
198 }
199
200 /**
201 * Strip PHP tags and inline <script> from widget HTML.
202 *
203 * @param string $code
204 * @return string
205 */
206 private function scrub_html($code) {
207 if (!is_string($code) || '' === $code) {
208 return '';
209 }
210 $code = str_replace(chr(0), '', $code);
211 $code = preg_replace('/<\?php/i', '', $code);
212 $code = str_replace(['<?=', '<?', '?>'], '', $code);
213 $code = preg_replace('#<script\b[^>]*>.*?</script>#is', '', $code);
214 $code = preg_replace('#</?script\b[^>]*>#i', '', $code);
215 return $code;
216 }
217
218 /**
219 * Strip PHP tags and <style>/<script> tags from widget CSS.
220 *
221 * @param string $code
222 * @return string
223 */
224 private function scrub_css($code) {
225 if (!is_string($code) || '' === $code) {
226 return '';
227 }
228 $code = str_replace(chr(0), '', $code);
229 $code = preg_replace('/<\?php/i', '', $code);
230 $code = str_replace(['<?=', '<?', '?>'], '', $code);
231 $code = preg_replace('#</?style\b[^>]*>#i', '', $code);
232 $code = preg_replace('#</?script\b[^>]*>#i', '', $code);
233 return $code;
234 }
235
236 /**
237 * Strip PHP tags and <script> tags from widget JS.
238 *
239 * @param string $code
240 * @return string
241 */
242 private function scrub_js($code) {
243 if (!is_string($code) || '' === $code) {
244 return '';
245 }
246 $code = str_replace(chr(0), '', $code);
247 $code = preg_replace('/<\?php/i', '', $code);
248 $code = str_replace(['<?=', '<?', '?>'], '', $code);
249 $code = preg_replace('#</?script\b[^>]*>#i', '', $code);
250 return $code;
251 }
252
253 /**
254 * Recursively delete a directory via WP_Filesystem.
255 *
256 * @param string $dir
257 */
258 private function delete_directory($dir) {
259 if (empty($dir) || !is_dir($dir)) {
260 return;
261 }
262 global $wp_filesystem;
263 if (empty($wp_filesystem)) {
264 require_once ABSPATH . 'wp-admin/includes/file.php';
265 WP_Filesystem();
266 }
267 if (!empty($wp_filesystem)) {
268 $wp_filesystem->delete($dir, true);
269 }
270 }
271 }
272