PluginProbe
Vision – Interactive Image Map with Hotspots Builder / trunk
Vision – Interactive Image Map with Hotspots Builder vtrunk
1.12.2 1.12.1 1.12.0 1.11.0 1.6.0 1.6.1 1.6.2 1.7.1 1.7.2 1.7.3 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.9.0 1.9.1 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 All 33 releases
← All changes | includes/plugin.php +1819 -1211 1.8.2trunk View file →
@@ -1,1307 +1,1915 @@
1 1 <?php
2 2 defined('ABSPATH') || exit;
3 3
4 -class Vision_Builder {
5 - private $pluginBasename = NULL;
4 +class Vision_Builder
5 +{
6 + private $pluginBasename = NULL;
6 7
7 - private $ajax_action_item_update = NULL;
8 - private $ajax_action_item_update_status = NULL;
9 - private $ajax_action_settings_update = NULL;
10 - private $ajax_action_settings_get = NULL;
11 - private $ajax_action_delete_data = NULL;
12 - private $ajax_action_modal = NULL;
13 -
14 - private $vision_map_id = null;
15 - private $vision_map_version = null;
16 - private $shortcodes = [];
17 -
18 - function __construct($pluginBasename) {
19 - $this->pluginBasename = $pluginBasename;
20 - }
21 -
22 - function run() {
23 - $upload_dir = wp_upload_dir();
24 - $plugin_url = plugin_dir_url(dirname(__FILE__));
25 -
26 - define('VISION_PLUGIN_UPLOAD_DIR', wp_normalize_path($upload_dir['basedir'] . '/vision'));
27 - define('VISION_PLUGIN_UPLOAD_URL', set_url_scheme($upload_dir['baseurl'] . '/vision/'));
28 -
29 - define('VISION_PLUGIN_PLAN', 'lite');
30 -
31 - $user = wp_get_current_user(); //is_super_admin()
32 - $allowed_roles = $this->getAllowedRoles();
33 - if((array_intersect($allowed_roles, $user->roles) || current_user_can('manage_options')) && is_admin()) {
34 - $this->ajax_action_item_update = 'vision_ajax_item_update';
35 - $this->ajax_action_item_update_status = 'vision_ajax_item_update_status';
36 - $this->ajax_action_settings_update = 'vision_ajax_settings_update';
37 - $this->ajax_action_settings_get = 'vision_ajax_settings_get';
38 - $this->ajax_action_delete_data = 'vision_ajax_delete_data';
39 - $this->ajax_action_modal = 'vision_ajax_modal';
40 -
41 - load_plugin_textdomain('vision', false, dirname(dirname(plugin_basename(__FILE__))) . '/languages/');
42 -
43 - add_action('admin_menu', [$this, 'admin_menu']);
44 - add_action('admin_footer', [$this, 'admin_footer']);
45 - add_action('admin_notices', [$this, 'admin_notices']);
46 - add_action('in_admin_header', [$this, 'in_admin_header']);
47 - add_action('wp_loaded', [$this, 'page_redirects']);
48 -
49 - // important, because ajax has another url
50 - add_action('wp_ajax_' . $this->ajax_action_item_update, [$this, 'ajax_item_update']);
51 - add_action('wp_ajax_' . $this->ajax_action_item_update_status, [$this, 'ajax_item_update_status']);
52 - add_action('wp_ajax_' . $this->ajax_action_settings_update, [$this, 'ajax_settings_update']);
53 - add_action('wp_ajax_' . $this->ajax_action_settings_get, [$this, 'ajax_settings_get']);
54 - add_action('wp_ajax_' . $this->ajax_action_delete_data, [$this, 'ajax_delete_data']);
55 - add_action('wp_ajax_' . $this->ajax_action_modal, [$this, 'ajax_modal']);
56 - } else {
57 - add_shortcode(VISION_SHORTCODE_NAME, [$this, 'shortcode']);
58 - }
59 -
60 - // only logged users with right roles can preview a vision map
61 - if(array_intersect($allowed_roles, $user->roles) || current_user_can('manage_options')) {
62 - add_filter('do_parse_request', [$this, 'do_parse_request'], 10, 3);
63 - }
8 + private $ajax_action_item_save_chunk = NULL;
9 + private $ajax_action_item_save_complete = NULL;
10 + private $ajax_action_item_update = NULL;
11 + private $ajax_action_item_update_status = NULL;
12 + private $ajax_action_settings_update = NULL;
13 + private $ajax_action_settings_get = NULL;
14 + private $ajax_action_delete_data = NULL;
15 + private $ajax_action_modal = NULL;
16 + private $ajax_action_change_author = NULL;
17 + private $ajax_action_picpoints_promo = NULL;
64 18
65 - add_action('rest_api_init', array($this, 'rest_api_init'));
19 + private $vision_map_id = null;
20 + private $vision_map_version = null;
21 + private $shortcodes = [];
22 +
23 + function __construct($pluginBasename)
24 + {
25 + $this->pluginBasename = $pluginBasename;
26 +
27 + add_action('clean_old_chunks', [$this, 'clean_old_chunks']);
28 + }
29 +
30 + function run()
31 + {
32 + $upload_dir = wp_upload_dir();
33 + $plugin_url = plugin_dir_url(dirname(__FILE__));
34 +
35 + define('VISION_PLUGIN_UPLOAD_DIR', wp_normalize_path($upload_dir['basedir'] . '/vision'));
36 + define('VISION_PLUGIN_UPLOAD_URL', set_url_scheme($upload_dir['baseurl'] . '/vision/'));
37 +
38 + define('VISION_PLUGIN_PLAN', 'lite');
39 +
40 + $user = wp_get_current_user(); //is_super_admin()
41 + $allowed_roles = $this->getAllowedRoles();
42 + if ((array_intersect($allowed_roles, $user->roles) || current_user_can('manage_options')) && is_admin()) {
43 + $this->ajax_action_item_save_chunk = 'vision_ajax_item_save_chunk';
44 + $this->ajax_action_item_save_complete = 'vision_ajax_item_save_complete';
45 + $this->ajax_action_item_update = 'vision_ajax_item_update';
46 + $this->ajax_action_item_update_status = 'vision_ajax_item_update_status';
47 + $this->ajax_action_settings_update = 'vision_ajax_settings_update';
48 + $this->ajax_action_settings_get = 'vision_ajax_settings_get';
49 + $this->ajax_action_delete_data = 'vision_ajax_delete_data';
50 + $this->ajax_action_modal = 'vision_ajax_modal';
51 + $this->ajax_action_change_author = 'vision_ajax_change_author';
52 + $this->ajax_action_picpoints_promo = 'vision_ajax_picpoints_promo';
53 +
54 + load_plugin_textdomain('vision', false, dirname(dirname(plugin_basename(__FILE__))) . '/languages/');
55 +
56 + add_action('admin_menu', [$this, 'admin_menu']);
57 + add_filter('submenu_file', [$this, 'admin_menu_highlight'], 10, 2);
58 + add_action('admin_footer', [$this, 'admin_footer']);
59 + add_action('admin_notices', [$this, 'admin_notices']);
60 + add_action('admin_notices', [$this, 'admin_notices_picpoints']);
61 + add_action('in_admin_header', [$this, 'in_admin_header']);
62 + add_action('wp_loaded', [$this, 'page_redirects']);
63 +
64 + // important, because ajax has another url
65 + add_action('wp_ajax_' . $this->ajax_action_item_save_chunk, [$this, 'ajax_item_save_chunk']);
66 + add_action('wp_ajax_' . $this->ajax_action_item_save_complete, [$this, 'ajax_item_save_complete']);
67 + add_action('wp_ajax_' . $this->ajax_action_item_update, [$this, 'ajax_item_update']);
68 + add_action('wp_ajax_' . $this->ajax_action_item_update_status, [$this, 'ajax_item_update_status']);
69 + add_action('wp_ajax_' . $this->ajax_action_settings_update, [$this, 'ajax_settings_update']);
70 + add_action('wp_ajax_' . $this->ajax_action_settings_get, [$this, 'ajax_settings_get']);
71 + add_action('wp_ajax_' . $this->ajax_action_delete_data, [$this, 'ajax_delete_data']);
72 + add_action('wp_ajax_' . $this->ajax_action_modal, [$this, 'ajax_modal']);
73 + add_action('wp_ajax_' . $this->ajax_action_change_author, [$this, 'ajax_change_author']);
74 + add_action('wp_ajax_' . $this->ajax_action_picpoints_promo, [$this, 'ajax_picpoints_promo']);
75 + } else {
76 + add_shortcode(VISION_SHORTCODE_NAME, [$this, 'shortcode']);
66 77 }
67 78
68 - function rest_api_init() {
69 - register_rest_route(
70 - VISION_PLUGIN_REST_URL, '/item/(?P<id>\d+)',
71 - [
72 - 'methods' => 'GET',
73 - 'callback' => [$this, 'rest_api_get_item'],
74 - 'permission_callback' => [$this, 'rest_api_permissions_check']
75 - ]
79 + // only logged users with right roles can preview a vision map
80 + if (array_intersect($allowed_roles, $user->roles) || current_user_can('manage_options')) {
81 + add_filter('do_parse_request', [$this, 'do_parse_request'], 10, 3);
82 + }
83 +
84 + add_action('rest_api_init', [$this, 'rest_api_init']);
85 +
86 + if (!wp_next_scheduled('clean_old_chunks')) {
87 + wp_schedule_event(time(), 'hourly', 'clean_old_chunks');
88 + }
89 + }
90 +
91 + function clean_old_chunks()
92 + {
93 + $upload_dir = wp_upload_dir();
94 + $chunks_dir = trailingslashit($upload_dir['basedir']) . '/vision/chunks/';
95 +
96 + if (!file_exists($chunks_dir) || !is_dir($chunks_dir)) {
97 + if (defined('WP_DEBUG') && WP_DEBUG) {
98 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions
99 + error_log('Vision chunks directory not found');
100 + }
101 + return;
102 + }
103 +
104 + $handle = opendir($chunks_dir);
105 + if (!$handle) {
106 + return;
107 + }
108 +
109 + while (($entry = readdir($handle)) !== false) {
110 + if ($entry === '.' || $entry === '..') continue;
111 +
112 + $full_path = $chunks_dir . $entry;
113 + $is_dir = is_dir($full_path);
114 +
115 + if (!$is_dir) {
116 + if (defined('WP_DEBUG') && WP_DEBUG) {
117 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions
118 + error_log("Skipping non-directory: $full_path");
119 + }
120 + continue;
121 + }
122 +
123 +
124 + if (filemtime($full_path) < time() - 21600) {
125 + $files = new RecursiveIteratorIterator(
126 + new RecursiveDirectoryIterator($full_path, FilesystemIterator::SKIP_DOTS),
127 + RecursiveIteratorIterator::CHILD_FIRST
76 128 );
129 +
130 + foreach ($files as $file) {
131 + if ($file->isDir()) {
132 + $this->remove_directory($file->getRealPath());
133 + } else {
134 + wp_delete_file($file->getRealPath());
135 + }
136 + }
137 +
138 + $this->remove_directory($full_path);
139 +
140 + if (defined('WP_DEBUG') && WP_DEBUG) {
141 + // phpcs:ignore WordPress.PHP.DevelopmentFunctions
142 + error_log("Deleted old chunk session: $entry");
143 + }
144 + }
77 145 }
78 146
79 - function rest_api_get_item($request) {
80 - $id = intval( $request->get_param('id') );
81 - $preview = boolval( $request->get_param('preview') );
147 + closedir($handle);
148 + }
82 149
83 - global $wpdb;
84 - $table = $wpdb->prefix . VISION_PLUGIN_NAME;
150 + function rest_api_init()
151 + {
152 + register_rest_route(
153 + VISION_PLUGIN_REST_URL,
154 + '/item/(?P<id>\d+)',
155 + [
156 + 'methods' => 'GET',
157 + 'callback' => [$this, 'rest_api_get_item'],
158 + 'permission_callback' => '__return_true',
159 + ]
160 + );
85 161
86 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
87 - $sql = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%d AND NOT deleted", $id);
88 - $item = $wpdb->get_row($sql, OBJECT);
89 - // phpcs:enable
162 + register_rest_route(
163 + VISION_PLUGIN_REST_URL,
164 + '/preview/(?P<id>\d+)', [
165 + 'methods' => 'GET',
166 + 'callback' => [$this, 'rest_api_get_preview'],
167 + 'permission_callback' => function() { return is_user_logged_in(); },
168 + ]
169 + );
170 + }
90 171
91 - $config = null;
92 - if($item->active) {
93 - $config = unserialize($item->config);
94 - } else if($preview) {
95 - $user = wp_get_current_user();
96 - $allowed_roles = $this->getAllowedRoles();
172 + function rest_api_get_item($request)
173 + {
174 + $id = intval($request->get_param('id'));
97 175
98 - if(array_intersect($allowed_roles, $user->roles) || current_user_can('manage_options')) {
99 - $config = unserialize($item->config);
176 + global $wpdb;
177 + $table = $wpdb->prefix . VISION_PLUGIN_NAME;
178 +
179 + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
180 + $sql = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%d AND NOT deleted", $id);
181 + $item = $wpdb->get_row($sql, OBJECT);
182 + // phpcs:enable
183 +
184 + $config = null;
185 + if ($item->active) {
186 + $config = unserialize($item->config);
187 + }
188 +
189 + if ($config) {
190 + return new WP_REST_Response($config);
191 + }
192 +
193 + return new WP_REST_Response(null, 404);
194 + }
195 +
196 + function rest_api_get_preview($request)
197 + {
198 + $id = intval($request->get_param('id'));
199 +
200 + global $wpdb;
201 + $table = $wpdb->prefix . VISION_PLUGIN_NAME;
202 +
203 + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
204 + $sql = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%d AND NOT deleted", $id);
205 + $item = $wpdb->get_row($sql, OBJECT);
206 + // phpcs:enable
207 +
208 + if (!$item) {
209 + return new WP_REST_Response(null, 404);
210 + }
211 +
212 + $user = wp_get_current_user();
213 + $allowed_roles = $this->getAllowedRoles();
214 +
215 + if (array_intersect($allowed_roles, $user->roles) || current_user_can('manage_options')) {
216 + $config = unserialize($item->config);
217 + return new WP_REST_Response($config);
218 + }
219 +
220 + return new WP_REST_Response(null, 403);
221 + }
222 +
223 + function filesystem_method()
224 + {
225 + return 'direct';
226 + }
227 +
228 + function request_filesystem_credentials()
229 + {
230 + return true;
231 + }
232 +
233 + function getFileSystem()
234 + {
235 + global $wp_filesystem;
236 + $result = true;
237 +
238 + if (!$wp_filesystem) {
239 + require_once(ABSPATH . '/wp-admin/includes/file.php');
240 +
241 + add_filter('filesystem_method', [$this, 'filesystem_method']);
242 + add_filter('request_filesystem_credentials', [$this, 'request_filesystem_credentials']);
243 +
244 + $credentials = request_filesystem_credentials(site_url(), '', true, false, null);
245 +
246 + $result = WP_Filesystem($credentials);
247 +
248 + remove_filter('filesystem_method', [$this, 'filesystem_method']);
249 + remove_filter('request_filesystem_credentials', [$this, 'request_filesystem_credentials']);
250 + }
251 +
252 + if ($result)
253 + return $wp_filesystem;
254 + return null;
255 + }
256 +
257 + function joinPaths()
258 + {
259 + $paths = [];
260 +
261 + foreach (func_get_args() as $arg) {
262 + if ($arg !== '') {
263 + $paths[] = $arg;
264 + }
265 + }
266 +
267 + return preg_replace('#/+#', '/', join('/', $paths));
268 + }
269 +
270 + function joinUrls()
271 + {
272 + $urls = [];
273 +
274 + foreach (func_get_args() as $arg) {
275 + if ($arg !== '') {
276 + $urls[] = $arg;
277 + }
278 + }
279 +
280 + return preg_replace('/([^:])(\/{2,})/', '$1/', join('/', $urls));
281 + }
282 +
283 + function IsNullOrEmptyString($str)
284 + {
285 + return (!isset($str) || trim($str) === '');
286 + }
287 +
288 + function getAllowedRoles()
289 + {
290 + $allowed_roles = ['administrator'];
291 +
292 + $settings_key = 'vision_settings';
293 + $settings_value = get_option($settings_key);
294 + if ($settings_value) {
295 + $settings = unserialize($settings_value);
296 + if (is_array($settings->roles)) $allowed_roles = array_merge($allowed_roles, $settings->roles);
297 + }
298 +
299 + return $allowed_roles;
300 + }
301 +
302 + function getLoaderGlobals($timestamp)
303 + {
304 + $plugin_url = plugin_dir_url(dirname(__FILE__));
305 +
306 + $globals = [
307 + 'plan' => VISION_PLUGIN_PLAN,
308 + 'version' => $timestamp,
309 + 'effects_url' => $plugin_url . 'assets/css/vision-effects.css',
310 + 'theme_base_url' => $plugin_url . 'assets/themes/',
311 + 'plugin_base_url' => $plugin_url . 'assets/vendor/vision/',
312 + 'plugin_version' => VISION_PLUGIN_VERSION,
313 + 'ssl' => is_ssl(),
314 + 'api' => [
315 + 'nonce' => wp_create_nonce('wp_rest'),
316 + 'url' => esc_url_raw(rest_url(VISION_PLUGIN_REST_URL))
317 + ]
318 + ];
319 +
320 + return $globals;
321 + }
322 +
323 + function embedLoader($in_footer, $timestamp)
324 + {
325 + $plugin_url = plugin_dir_url(dirname(__FILE__));
326 + wp_enqueue_script('vision_loader', $plugin_url . 'assets/js/loader.js', ['jquery'], VISION_PLUGIN_VERSION, $in_footer);
327 + wp_localize_script('vision_loader', 'vision_globals', $this->getLoaderGlobals($timestamp));
328 + }
329 +
330 + /**
331 + * generate main css text
332 + */
333 + function getMainCss($itemData, $itemId)
334 + {
335 + $upload_dir = wp_upload_dir();
336 +
337 + // create main css
338 + $main_css = '';
339 + $main_css .= '.vision-map-' . $itemId . ' {';
340 +
341 + $main_css .= (!$this->IsNullOrEmptyString($itemData->background->color) ? 'background-color:' . $itemData->background->color . ';' : '');
342 + if (!$this->IsNullOrEmptyString($itemData->background->image->url)) {
343 + $imageUrl = ($itemData->background->image->relative ? $upload_dir['baseurl'] : '') . $itemData->background->image->url;
344 + $main_css .= 'background-image:url(' . $imageUrl . ');';
345 + }
346 + $main_css .= ($itemData->background->size ? 'background-size:' . $itemData->background->size . ';' : '');
347 + $main_css .= ($itemData->background->repeat ? 'background-repeat:' . $itemData->background->repeat . ';' : '');
348 + $main_css .= ($itemData->background->position ? 'background-position:' . $itemData->background->position . ';' : '');
349 +
350 + $main_css .= '}';
351 +
352 + $layerId = 0;
353 + foreach ($itemData->layers as $layerKey => $layer) {
354 + if (!$layer->visible) {
355 + continue;
356 + }
357 +
358 + $layerId++;
359 + $layerSelector = '.vision-map-' . $itemId . ' .vision-layers [data-layer-id="' . $layer->id . '"] .vision-body';
360 +
361 + // main
362 + $main_css .= $layerSelector . ' {';
363 + switch ($layer->type) {
364 + case 'link': {
365 + $main_css .= ($layer->link->normalColor ? 'background-color:' . $layer->link->normalColor . ';' : '');
366 + $main_css .= ($layer->link->radius != NULL ? 'border-radius:' . $layer->link->radius . ';' : '');
367 + }
368 + break;
369 + case 'image': {
370 + $main_css .= (!$this->IsNullOrEmptyString($layer->image->background->color) ? 'background-color:' . $layer->image->background->color . ';' : '');
371 + if (!$this->IsNullOrEmptyString($layer->image->background->file->url)) {
372 + $imageUrl = ($layer->image->background->file->relative ? $upload_dir['baseurl'] : '') . $layer->image->background->file->url;
373 + $main_css .= 'background-image:url(' . $imageUrl . ');';
100 374 }
375 + $main_css .= ($layer->image->background->size ? 'background-size:' . $layer->image->background->size . ';' : '');
376 + $main_css .= ($layer->image->background->repeat ? 'background-repeat:' . $layer->image->background->repeat . ';' : '');
377 + $main_css .= ($layer->image->background->position ? 'background-position:' . $layer->image->background->position . ';' : '');
378 + }
379 + break;
380 + case 'text': {
381 + $main_css .= (!$this->IsNullOrEmptyString($layer->text->background->color) ? 'background-color:' . $layer->text->background->color . ';' : '');
382 + if (!$this->IsNullOrEmptyString($layer->text->background->file->url)) {
383 + $imageUrl = ($layer->text->background->file->relative ? $upload_dir['baseurl'] : '') . $layer->text->background->file->url;
384 + $main_css .= 'background-image:url(' . $imageUrl . ');';
385 + }
386 + $main_css .= ($layer->text->background->size ? 'background-size:' . $layer->text->background->size . ';' : '');
387 + $main_css .= ($layer->text->background->repeat ? 'background-repeat:' . $layer->text->background->repeat . ';' : '');
388 + $main_css .= ($layer->text->background->position ? 'background-position:' . $layer->text->background->position . ';' : '');
389 +
390 + $main_css .= ($layer->text->font ? 'font-family:"' . str_replace('+', ' ', $layer->text->font) . '",sans-serif;' : '');
391 + $main_css .= ($layer->text->color ? 'color:' . $layer->text->color . ';' : '');
392 + $main_css .= ($layer->text->size != NULL ? 'font-size:' . $layer->text->size . 'px;' : '');
393 + $main_css .= ($layer->text->lineHeight != NULL ? 'line-height:' . $layer->text->lineHeight . 'px;' : '');
394 + $main_css .= ($layer->text->align ? 'text-align:' . $layer->text->align . ';' : '');
395 + $main_css .= ($layer->text->letterSpacing != NULL ? 'letter-spacing:' . $layer->text->letterSpacing . 'px;' : '');
396 + }
397 + break;
398 + }
399 + $main_css .= '}';
400 +
401 + if ($layer->type == 'link') {
402 + $main_css .= $layerSelector . ':hover {';
403 + $main_css .= ($layer->link->hoverColor ? 'background-color:' . $layer->link->hoverColor . ';' : '');
404 + $main_css .= '}';
405 + }
406 + }
407 +
408 + return $main_css;
409 + }
410 +
411 + /**
412 + * Shortcode output for the plugin
413 + */
414 + function shortcode($atts)
415 + {
416 + extract(shortcode_atts(['id' => 0, 'slug' => NULL, 'class' => NULL], $atts, VISION_SHORTCODE_NAME));
417 +
418 + if (!$id && !$slug) {
419 + return '<p>' . esc_html__('Error: invalid vision identifier attribute', 'vision') . '</p>';
420 + }
421 +
422 + $id = intval($id, 10);
423 + $slug = sanitize_key($slug);
424 + $class = sanitize_text_field($class);
425 +
426 + global $wpdb;
427 + $table = $wpdb->prefix . VISION_PLUGIN_NAME;
428 + $upload_dir = wp_upload_dir();
429 +
430 + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
431 + $sql = ($id ? $wpdb->prepare("SELECT * FROM {$table} WHERE id=%d AND NOT deleted", $id) : $wpdb->prepare("SELECT * FROM {$table} WHERE slug=%s AND NOT deleted LIMIT 0, 1", $slug));
432 + $item = $wpdb->get_row($sql, OBJECT);
433 + // phpcs:enable
434 +
435 + $preview = filter_input(INPUT_GET, 'preview', FILTER_SANITIZE_NUMBER_INT);
436 +
437 + if ($item && ($item->active || (!$item->active && $preview == 1))) {
438 + $version = strtotime(mysql2date('d M Y H:i:s', $item->modified));
439 + $itemData = unserialize($item->data);
440 + $id = $item->id;
441 + $id_postfix = strtolower(wp_generate_password(5, false)); // generate unique postfix for $id to avoid clashes with multiple same shortcode use
442 + $id_element = 'vision-' . $id . '-' . $id_postfix;
443 +
444 + array_push($this->shortcodes, ['id' => $item->id, 'version' => $version]);
445 +
446 + if (sizeof($this->shortcodes) == 1) {
447 + $this->embedLoader(true, $version);
448 + }
449 +
450 + $output = '';
451 +
452 + $output .= '<div ';
453 + $output .= (property_exists($itemData, 'containerId') && $itemData->containerId ? 'id="' . esc_attr($itemData->containerId) . '" ' : '');
454 + $output .= 'class="vision-map vision-map-' . esc_attr($id . ($class ? ' ' . $class : '')) . '"';
455 +
456 + $json_src = esc_url_raw(rest_url(VISION_PLUGIN_REST_URL)) . '/item/' . esc_attr($item->id);
457 + if ($preview == 1) {
458 + $json_src = esc_url_raw(rest_url(VISION_PLUGIN_REST_URL)) . '/preview/' . esc_attr($item->id);
459 + }
460 + $output .= 'data-json-src="' . $json_src . '" ';
461 +
462 + $output .= 'data-item-id="' . esc_attr($item->id) . '" ';
463 + $output .= 'tabindex="1" ';
464 + $output .= '>';
465 + if (property_exists($itemData, 'image')) {
466 + $upload_dir = wp_upload_dir();
467 + $imageUrl = ($itemData->image->relative ? $upload_dir['baseurl'] : '') . $itemData->image->url;
468 + $output .= '<div class="vision-img-placeholder"><img src="' . esc_url($imageUrl) . '" width="100%"></div>';
469 + }
470 + //=============================================
471 + // STORE BEGIN
472 + $output .= '<div class="vision-store" style="display:none;">';
473 +
474 + $output .= '<div class="vision-layers-data">';
475 + foreach ($itemData->layers as $layerKey => $layer) {
476 + if (!$layer->visible) {
477 + continue;
101 478 }
479 + //=============================================
480 + // LAYER BEGIN
481 + $output .= '<div class="vision-layer" data-layer-id="' . esc_attr($layer->id) . '">';
102 482
103 - if($config) {
104 - return new WP_REST_Response($config);
483 + if ($layer->contentData) {
484 + $output .= do_shortcode($layer->contentData);
105 485 }
106 - return new WP_REST_Response(null, 404);
486 + if ($layer->type == 'text') {
487 + $output .= wp_kses_post($layer->text->data);
488 + }
489 +
490 + $output .= '</div>';
491 + // LAYER END
492 + //=============================================
493 + }
494 + $output .= '</div>';
495 +
496 + $output .= '<div class="vision-tooltips-data">';
497 + foreach ($itemData->layers as $layerKey => $layer) {
498 + if (!$layer->visible) {
499 + continue;
500 + }
501 + //=============================================
502 + // TOOLTIP BEGIN
503 + $output .= '<div class="vision-data" data-layer-id="' . esc_attr($layer->id) . '">';
504 + $output .= do_shortcode($layer->tooltip->data);
505 + $output .= '</div>';
506 + // TOOLTIP END
507 + //=============================================
508 + }
509 + $output .= '</div>';
510 +
511 + $output .= '<div class="vision-popovers-data">';
512 + foreach ($itemData->layers as $layerKey => $layer) {
513 + if (!$layer->visible) {
514 + continue;
515 + }
516 + //=============================================
517 + // POPOVER BEGIN
518 + $output .= '<div class="vision-data" data-layer-id="' . esc_attr($layer->id) . '">';
519 + $output .= do_shortcode($layer->popover->data);
520 + $output .= '</div>';
521 + // POPOVER END
522 + //=============================================
523 + }
524 +
525 + $output .= '</div>';
526 + $output .= '</div>';
527 + // STORE END
528 + //=============================================
529 +
530 + $output .= '</div>';
531 +
532 + $css = $this->getMainCss($itemData, $id) . ($itemData->customCSS->active ? $itemData->customCSS->data : '');
533 + $css = preg_replace('/[^\/\\\\a-zA-Z0-9\s\_\%\=\[\]\(\)\{\}\:\;\.\,\#\$\-\"\'\!@]/', '', $css);
534 +
535 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
536 + $output .= '<style>' . $css . '</style>';
537 +
538 + $output = preg_replace('/\s+/', ' ', $output);
539 + $output = force_balance_tags($output);
540 +
541 + return $output;
107 542 }
108 543
109 - function rest_api_permissions_check() {
110 - return true;
544 + return '<p>' . esc_html__('Error: the vision item can’t be found', 'vision') . '</p>';
545 + }
546 +
547 + /**
548 + * Run a filter to obtain some custom url settings, compare them to the current url
549 + * and if a match is found the custom callback is fired, the custom view is loaded
550 + * and request is stopped.
551 + */
552 + function do_parse_request($result)
553 + {
554 + if (current_filter() !== 'do_parse_request') {
555 + return $result;
111 556 }
112 557
113 - function filesystem_method() {
114 - return 'direct';
558 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
559 + $url = sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI']));
560 +
561 + if (preg_match('/vision\/map\/([a-z0-9_-]+)/', $url, $matches)) {
562 + $preview = filter_input(INPUT_GET, 'preview', FILTER_SANITIZE_NUMBER_INT);
563 +
564 + global $wpdb;
565 + $table = $wpdb->prefix . VISION_PLUGIN_NAME;
566 + $shortcode = false;
567 +
568 + if (is_numeric($matches[1])) {
569 + $vision_map_id = $matches[1];
570 +
571 + if ($vision_map_id != null) {
572 + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
573 + $sql = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%d AND NOT deleted", $vision_map_id);
574 + $item = $wpdb->get_row($sql, OBJECT);
575 + // phpcs:enable
576 +
577 + if ($item && ($item->active || (!$item->active && $preview == 1))) {
578 + $this->vision_map_id = $item->id;
579 + $this->vision_map_version = strtotime(mysql2date('Y-m-d H:i:s', $item->modified));
580 + $shortcode = true;
581 + }
582 + }
583 + } else {
584 + $vision_map_slug = $matches[1];
585 +
586 + if ($vision_map_slug != null) {
587 + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
588 + $sql = $wpdb->prepare("SELECT * FROM {$table} WHERE slug=%s AND NOT deleted", $vision_map_slug);
589 + $item = $wpdb->get_row($sql, OBJECT);
590 + // phpcs:enable
591 +
592 + if ($item && ($item->active || (!$item->active && $preview == 1))) {
593 + $this->vision_map_id = $item->id;
594 + $this->vision_map_version = strtotime(mysql2date('Y-m-d H:i:s', $item->modified));
595 +
596 + $shortcode = true;
597 + }
598 + }
599 + }
600 +
601 + if ($shortcode) {
602 + require_once(plugin_dir_path(dirname(__FILE__)) . 'includes/page-preview.php');
603 + exit();
604 + }
115 605 }
116 606
117 - function request_filesystem_credentials() {
118 - return true;
607 + return $result;
608 + }
609 +
610 + /**
611 + * Prepare upload directory
612 + */
613 + function admin_notices()
614 + {
615 + $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
616 + if (!($page === 'vision' || $page === 'vision_item')) {
617 + return;
119 618 }
120 619
121 - function getFileSystem() {
122 - global $wp_filesystem;
123 - $result = true;
620 + if (!file_exists(VISION_PLUGIN_UPLOAD_DIR)) {
621 + wp_mkdir_p(VISION_PLUGIN_UPLOAD_DIR);
622 + }
124 623
125 - if(!$wp_filesystem) {
126 - require_once(ABSPATH . '/wp-admin/includes/file.php');
624 + if (!file_exists(VISION_PLUGIN_UPLOAD_DIR)) {
625 + echo '<div class="notice notice-error is-dismissible">';
626 + echo '<p>' . esc_html__('The plugin upload directory could not be created', 'vision') . '</p>';
627 + echo '<p>' . esc_html__('Please run the following commands in order to make the directory', 'vision') . '<br>';
628 + echo '<b>mkdir ' . esc_attr(VISION_PLUGIN_UPLOAD_DIR) . '</b><br>';
629 + echo '<b>chmod 777 ' . esc_attr(VISION_PLUGIN_UPLOAD_DIR) . '</b></p>';
630 + echo '</div>';
631 + return;
632 + }
127 633
128 - add_filter('filesystem_method', [$this, 'filesystem_method']);
129 - add_filter('request_filesystem_credentials', [$this, 'request_filesystem_credentials']);
634 + if (!wp_is_writable(VISION_PLUGIN_UPLOAD_DIR)) {
635 + echo '<div class="notice notice-error is-dismissible">';
636 + echo '<p>' . esc_html__('The plugin upload directory is not writable, therefore the css and js files cannot be saved.', 'vision') . '</p>';
637 + echo '<p>' . esc_html__('Please run the following commands in order to make the directory', 'vision') . '<br>';
638 + echo '<b>chmod 777 ' . esc_attr(VISION_PLUGIN_UPLOAD_DIR) . '</b></p>';
639 + echo '</div>';
640 + return;
641 + }
130 642
131 - $credentials = request_filesystem_credentials(site_url(), '', true, false, null);
643 + if (!file_exists(VISION_PLUGIN_UPLOAD_DIR . '/' . 'index.php')) {
644 + $data = '<?php' . PHP_EOL . '// silence is golden' . PHP_EOL . '?>';
132 645
133 - $result = WP_Filesystem($credentials);
646 + $wp_filesystem = $this->getFileSystem();
647 + $wp_filesystem->put_contents(VISION_PLUGIN_UPLOAD_DIR . '/' . 'index.php', $data);
648 + }
649 + }
134 650
135 - remove_filter('filesystem_method', [$this, 'filesystem_method']);
136 - remove_filter('request_filesystem_credentials', [$this, 'request_filesystem_credentials']);
651 + /**
652 + * PicPoints promo notice
653 + */
654 + function admin_notices_picpoints()
655 + {
656 + if (!current_user_can('manage_options')) {
657 + return;
658 + }
659 +
660 + global $pagenow;
661 + $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
662 +
663 + $is_main_dashboard = ($pagenow === 'index.php' && empty($_GET['page']));
664 + $is_my_plugin_page = ($page === 'vision' || $page === 'vision_item' || $page === 'vision_settings');
665 +
666 + if (!($is_main_dashboard || $is_my_plugin_page)) {
667 + return;
668 + }
669 +
670 +
671 + $promo = get_option('vision_picpoints_promo');
672 + if (!is_array($promo)) {
673 + $promo = ['status' => 'active', 'remind_at' => 0];
674 + }
675 +
676 + $status = isset($promo['status']) ? $promo['status'] : 'active';
677 + $remind_at = isset($promo['remind_at']) ? (int) $promo['remind_at'] : 0;
678 +
679 + if ($status === 'dismissed') {
680 + return;
681 + }
682 + if ($status === 'remind' && time() < $remind_at) {
683 + return;
684 + }
685 +
686 + wp_enqueue_script('jquery');
687 +
688 + $nonce = wp_create_nonce('vision_ajax');
689 + $ajax_url = esc_url(admin_url('admin-ajax.php'));
690 +
691 + echo '<div class="notice notice-info vision-picpoints-notice" style="position:relative;">';
692 +
693 + echo '<div style="display:flex;gap:100px;padding:10px 0;">';
694 +
695 + echo '<div style="max-width:600px;">';
696 + echo '<p style="font-weight:600;font-size:14px;">';
697 + echo esc_html__('To our amazing Vision community!', 'vision');
698 + echo '</p>';
699 + echo '<p style="font-weight:600;font-size:14px;margin:0 0 10px 0;">';
700 + echo esc_html__('We are proud to introduce PicPoints, our next-generation interactive image map builder. 🚀', 'vision');
701 + echo '</p>';
702 + echo '<p style="margin-bottom:10px;">';
703 + echo esc_html__('We built PicPoints from the ground up on modern web standards to solve old, frustrating layout problems once and for all.', 'vision');
704 + echo '</p>';
705 + echo '<p style="margin-bottom:10px;">';
706 + echo esc_html__('Why you will love it:', 'vision');
707 + echo '</p>';
708 + echo '<p>';
709 + echo esc_html__('✅ Easy-to-use editor - place markers, draw shapes and add polygons just like in Figma.', 'vision');
710 + echo '</p>';
711 + echo '<p>';
712 + echo esc_html__("✅ No theme conflicts - your maps always look perfect and won't break your site's design.", 'vision');
713 + echo '</p>';
714 + echo '<p>';
715 + echo esc_html__('✅ Fast & smooth - works flawlessly even with large floor plans or interactive product catalogs.', 'vision');
716 + echo '</p>';
717 + echo '</div>';
718 +
719 + echo '<style>@media(max-width:1279px){.vision-mod-hide{display:none!important}}</style>';
720 + echo '<iframe class="vision-mod-hide" style="padding:10px; box-sizing: border-box; border-radius:5px; box-shadow: 0 0 2px rgba(0, 0, 0, .7); height:240px; width:auto; aspect-ratio:16/9;" src="https://www.youtube.com/embed/CUHjfWBPMp4" frameborder="0" allow="encrypted-media; web-share" allowfullscreen></iframe>';
721 +
722 + echo '</div>';
723 +
724 + echo '<p style="margin-top:20px;display:flex;gap:8px;flex-wrap:wrap;justify-content:space-between;">';
725 + echo '<span style="display:flex;gap:8px;flex-wrap:wrap;">';
726 + echo '<a href="https://wordpress.org/plugins/picpoints/" target="_blank" rel="noopener" class="button button-primary button-vision-picpoints-promo">Try Free Version</a>';
727 + echo '<a href="https://checkout.freemius.com/product/30659/?billing_cycle=annual&billing_cycle_selector=list&coupon=vision35" target="_blank" rel="noopener" class="button button-primary button-vision-picpoints-promo" style="background:#8e44ad;border-color:#8e44ad;">Get 35% OFF for PRO</a>';
728 + echo '</span>';
729 + echo '<span style="display:flex;gap:8px;flex-wrap:wrap;">';
730 + echo '<button type="button" class="button button-vision-picpoints-remind">' . esc_html__('Remind me later', 'vision') . '</button>';
731 + echo '<button type="button" class="button button-vision-picpoints-dismiss">' . esc_html__('No, thanks', 'vision') . '</button>';
732 + echo '</span>';
733 + echo '</p>';
734 + echo '</div>';
735 +
736 + // Inline JS
737 + echo '<script>
738 + (function($){
739 + $(function(){
740 + var $notice = $(".vision-picpoints-notice");
741 + var ajaxUrl = ' . wp_json_encode($ajax_url) . ';
742 + var nonce = ' . wp_json_encode($nonce) . ';
743 +
744 + function sendAction(action){
745 + $.post(ajaxUrl, {
746 + action: "vision_ajax_picpoints_promo",
747 + nonce: nonce,
748 + promo_action: action
749 + });
137 750 }
138 751
139 - if($result)
140 - return $wp_filesystem;
141 - return null;
752 + $notice.on("click", ".button-vision-picpoints-dismiss", function(){
753 + sendAction("dismiss");
754 + $notice.slideUp(function(){ $notice.remove(); });
755 + });
756 +
757 + $notice.on("click", ".button-vision-picpoints-remind", function(){
758 + sendAction("remind");
759 + $notice.slideUp(function(){ $notice.remove(); });
760 + });
761 +
762 + $notice.on("click", ".button-vision-picpoints-promo", function(){
763 + $notice.slideUp(function(){ $notice.remove(); });
764 + });
765 + });
766 + })(jQuery);
767 + </script>';
768 + }
769 +
770 + /**
771 + * Ajax: handle PicPoints promo action (dismiss / remind)
772 + */
773 + function ajax_picpoints_promo()
774 + {
775 + if (!check_ajax_referer('vision_ajax', 'nonce', false)) {
776 + wp_send_json_error(['msg' => esc_html__('The operation failed', 'vision')]);
142 777 }
143 778
144 - function joinPaths() {
145 - $paths = [];
146 -
147 - foreach(func_get_args() as $arg) {
148 - if($arg !== '') {
149 - $paths[] = $arg;
150 - }
151 - }
152 -
153 - return preg_replace('#/+#','/',join('/', $paths));
154 - }
155 -
156 - function joinUrls() {
157 - $urls = [];
158 -
159 - foreach(func_get_args() as $arg) {
160 - if($arg !== '') {
161 - $urls[] = $arg;
162 - }
163 - }
164 -
165 - return preg_replace('/([^:])(\/{2,})/','$1/',join('/', $urls));
166 - }
167 -
168 - function IsNullOrEmptyString($str) {
169 - return(!isset($str) || trim($str)==='');
170 - }
171 -
172 - function getAllowedRoles() {
173 - $allowed_roles = ['administrator'];
174 -
175 - $settings_key = 'vision_settings';
176 - $settings_value = get_option($settings_key);
177 - if($settings_value) {
178 - $settings = unserialize($settings_value);
179 - if(is_array($settings->roles)) $allowed_roles = array_merge($allowed_roles, $settings->roles);
180 - }
181 -
182 - return $allowed_roles;
183 - }
184 -
185 - function getLoaderGlobals($timestamp) {
186 - $plugin_url = plugin_dir_url(dirname(__FILE__));
187 -
188 - $globals = [
189 - 'plan' => VISION_PLUGIN_PLAN,
190 - 'version' => $timestamp,
191 - 'effects_url' => $plugin_url . 'assets/css/vision-effects.css',
192 - 'theme_base_url' => $plugin_url . 'assets/themes/',
193 - 'plugin_base_url' => $plugin_url . 'assets/vendor/vision/',
194 - 'plugin_upload_base_url' => VISION_PLUGIN_UPLOAD_URL,
195 - 'plugin_version' => VISION_PLUGIN_VERSION,
196 - 'ssl' => is_ssl(),
197 - 'api' => [
198 - 'nonce' => wp_create_nonce( 'wp_rest' ),
199 - 'url' => esc_url_raw( rest_url( VISION_PLUGIN_REST_URL ) )
200 - ]
779 + if (!current_user_can('manage_options')) {
780 + wp_send_json_error(['msg' => esc_html__('Permission denied', 'vision')]);
781 + }
782 +
783 + $action = sanitize_key(filter_input(INPUT_POST, 'promo_action'));
784 + $now = time();
785 +
786 + if ($action === 'dismiss') {
787 + update_option('vision_picpoints_promo', [
788 + 'status' => 'dismissed',
789 + 'remind_at' => 0,
790 + ], false);
791 + } elseif ($action === 'remind') {
792 + update_option('vision_picpoints_promo', [
793 + 'status' => 'remind',
794 + 'remind_at' => $now + MONTH_IN_SECONDS,
795 + ], false);
796 + } else {
797 + wp_send_json_error(['msg' => esc_html__('Invalid action', 'vision')]);
798 + }
799 +
800 + wp_send_json_success(['msg' => 'ok']);
801 + wp_die();
802 + }
803 +
804 + /**
805 + * Fires at the beginning of the content section in an admin page
806 + */
807 + function in_admin_header()
808 + {
809 + $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
810 +
811 + if (!(($page === 'vision') || ($page === 'vision_item') || ($page === 'vision_settings'))) {
812 + return;
813 + }
814 +
815 + remove_all_actions('admin_notices');
816 + remove_all_actions('all_admin_notices');
817 + add_action('admin_notices', [$this, 'admin_notices']);
818 + add_action('admin_notices', [$this, 'admin_notices_picpoints']);
819 + }
820 +
821 + /**
822 + * Register the administration menu for this plugin into the WordPress Dashboard menu.
823 + */
824 + function admin_menu()
825 + {
826 + // add "edit_posts" if we want to give access to author, editor and contributor roles
827 + add_menu_page(esc_html__('Vision', 'vision'), esc_html__('Vision', 'vision'), 'read', 'vision', [$this, 'admin_menu_page_items'], 'dashicons-format-image');
828 + add_submenu_page('vision', esc_html__('Vision', 'vision'), esc_html__('All Items', 'vision'), 'read', 'vision', [$this, 'admin_menu_page_items']);
829 + add_submenu_page('vision', esc_html__('Vision', 'vision'), esc_html__('Add New', 'vision'), 'read', 'vision_item', [$this, 'admin_menu_page_item']);
830 + add_submenu_page('vision', esc_html__('Vision', 'vision'), esc_html__('Settings', 'vision'), 'manage_options', 'vision_settings', [$this, 'admin_menu_page_settings']);
831 +
832 + add_submenu_page('vision', esc_html__('Vision', 'vision'), esc_html__('Upgrade to Pro', 'vision'), 'manage_options', 'vision_upgrade_to_pro', [$this, 'admin_menu_page_upgrade_to_pro']);
833 +
834 + }
835 +
836 + function admin_menu_highlight($submenu_file, $parent_file)
837 + {
838 + $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
839 + if (in_array($page, ['vision_item'])) {
840 + $id = sanitize_key(filter_input(INPUT_GET, 'id', FILTER_DEFAULT));
841 + if (!empty($id)) {
842 + $submenu_file = 'vision';
843 + }
844 + }
845 + return $submenu_file;
846 + }
847 +
848 + function admin_footer()
849 + {
850 + if (get_current_screen() && get_current_screen()->base !== 'plugins') {
851 + return;
852 + }
853 +
854 + $globals = [
855 + 'token' => $this->get_token(),
856 + 'ajax' => [
857 + 'url' => VISION_FEEDBACK_URL
858 + ]
859 + ];
860 +
861 + wp_enqueue_style('vision-feedback', VISION_PLUGIN_URL . 'assets/css/feedback.css', [], VISION_PLUGIN_VERSION);
862 + wp_enqueue_script('vision-feedback', VISION_PLUGIN_URL . 'assets/js/feedback.js', ['jquery'], VISION_PLUGIN_VERSION, false);
863 + wp_localize_script('vision-feedback', 'vision_feedback_globals', $globals);
864 +
865 + require_once(plugin_dir_path(dirname(__FILE__)) . 'templates/feedback.php');
866 + }
867 +
868 + function get_token()
869 + {
870 + global $wp_version;
871 + $current_user = wp_get_current_user();
872 +
873 + $data = [
874 + 'plugin_name' => VISION_PLUGIN_NAME,
875 + 'plugin_version' => VISION_PLUGIN_VERSION,
876 + 'wordpress' => $wp_version,
877 + 'php' => PHP_VERSION,
878 + 'email' => $current_user->user_email,
879 + 'site' => trim(str_replace(['http://', 'https://'], '', get_site_url()), '/')
880 + ];
881 + return base64_encode(wp_json_encode($data));
882 + }
883 +
884 + /**
885 + * Custom redirects
886 + */
887 + function page_redirects()
888 + {
889 + $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
890 +
891 + if ($page === 'vision') {
892 + $action = sanitize_key(filter_input(INPUT_GET, 'action', FILTER_DEFAULT));
893 + if ($action) {
894 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
895 + $url = sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI']));
896 +
897 + $url = remove_query_arg(['action', 'id', '_wpnonce'], $url);
898 + header('Refresh:0; url="' . $url . '"', true, 303);
899 + //wp_redirect($url); // does not work delete and dublicate operations on XAMPP
900 + }
901 + }
902 + }
903 +
904 + /**
905 + * Show admin menu items page
906 + */
907 + function admin_menu_page_items()
908 + {
909 + $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
910 +
911 + if ($page === 'vision') {
912 + $plugin_url = plugin_dir_url(dirname(__FILE__));
913 + $upload_dir = wp_upload_dir();
914 +
915 + wp_enqueue_style('vision_admin', $plugin_url . 'assets/css/admin.css', [], VISION_PLUGIN_VERSION, 'all');
916 + wp_enqueue_style('vision_lucide', $plugin_url . 'assets/vendor/lucide/lucide.css', [], VISION_PLUGIN_VERSION, 'all');
917 + wp_enqueue_script('vision_admin', $plugin_url . 'assets/js/admin.js', ['jquery'], VISION_PLUGIN_VERSION, false);
918 +
919 + // global settings to help ajax work
920 + $globals = [
921 + 'plan' => VISION_PLUGIN_PLAN,
922 + 'msg_pro_title' => esc_html__('Available only in Pro version', 'vision'),
923 + 'upload_url' => $upload_dir['baseurl'],
924 + 'ajax_url' => admin_url('admin-ajax.php'),
925 + 'ajax_nonce' => wp_create_nonce('vision_ajax'),
926 + 'ajax_msg_error' => esc_html__('Uncaught Error', 'vision') //Look at the console (F12 or Ctrl+Shift+I, Console tab) for more information
927 + ];
928 + $globals['ajax_action_update'] = $this->ajax_action_item_update_status;
929 + $globals['ajax_action_change_author'] = $this->ajax_action_change_author;
930 +
931 + require_once(plugin_dir_path(dirname(__FILE__)) . 'includes/list-table-items.php');
932 + require_once(plugin_dir_path(dirname(__FILE__)) . 'includes/page-items.php');
933 +
934 + wp_localize_script('vision_admin', 'vision_globals', $globals);
935 + }
936 + }
937 +
938 + /**
939 + * Show admin menu item page
940 + */
941 + function admin_menu_page_item()
942 + {
943 + $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
944 + if ($page === 'vision_item') {
945 + $id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
946 +
947 + $plugin_url = plugin_dir_url(dirname(__FILE__));
948 + $upload_dir = wp_upload_dir();
949 +
950 + wp_enqueue_style('vision_admin', $plugin_url . 'assets/css/admin.css', [], VISION_PLUGIN_VERSION, 'all');
951 +
952 + if (VISION_PLUGIN_PLAN == 'lite' && !$id) {
953 + global $wpdb;
954 + $table = $wpdb->prefix . VISION_PLUGIN_NAME;
955 +
956 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
957 + $count = $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
958 +
959 + if ($count >= 1) {
960 + echo '<p>Vision: ' . esc_html__('You can create only 1 map. If you need more, upgrade to the pro version.', 'vision') . '</p>';
961 + return;
962 + }
963 + }
964 +
965 + wp_enqueue_style('vision_notify', $plugin_url . 'assets/css/notify.css', [], VISION_PLUGIN_VERSION, 'all');
966 + wp_enqueue_style('vision_lucide', $plugin_url . 'assets/vendor/lucide/lucide.css', [], VISION_PLUGIN_VERSION, 'all');
967 + wp_enqueue_style('vision_vision_effects', $plugin_url . 'assets/css/vision-effects.css', [], VISION_PLUGIN_VERSION, 'all');
968 +
969 + wp_enqueue_script('vision_notify', $plugin_url . 'assets/js/notify.js', ['jquery'], VISION_PLUGIN_VERSION, false);
970 + wp_enqueue_script('vision_ace', $plugin_url . 'assets/vendor/ace/ace.js', [], VISION_PLUGIN_VERSION, false);
971 + wp_enqueue_script('vision_url', $plugin_url . 'assets/vendor/url/url.js', [], VISION_PLUGIN_VERSION, false);
972 + wp_enqueue_script('vision_admin', $plugin_url . 'assets/js/admin.js', ['jquery'], VISION_PLUGIN_VERSION, false);
973 +
974 + wp_enqueue_media();
975 +
976 + // global settings to help ajax work
977 + $globals = [
978 + 'plan' => VISION_PLUGIN_PLAN,
979 + 'msg_pro_title' => esc_html__('Available only in Pro version', 'vision'),
980 + 'msg_custom_js_error' => esc_html__('Custom js code error', 'vision'),
981 + 'msg_layer_id_error' => esc_html__('The layer ID should be unique', 'vision'),
982 + 'wp_base_url' => get_site_url(),
983 + 'upload_base_url' => $upload_dir['baseurl'],
984 + 'plugin_base_url' => $plugin_url,
985 + 'ajax_url' => admin_url('admin-ajax.php'),
986 + 'ajax_nonce' => wp_create_nonce('vision_ajax'),
987 + 'ajax_msg_error' => esc_html__('Uncaught Error', 'vision') //Look at the console (F12 or Ctrl+Shift+I, Console tab) for more information
988 + ];
989 +
990 + $globals['ajax_action_get'] = $this->ajax_action_settings_get;
991 + $globals['ajax_action_save_chunk'] = $this->ajax_action_item_save_chunk;
992 + $globals['ajax_action_save_complete'] = $this->ajax_action_item_save_complete;
993 + $globals['ajax_action_update'] = $this->ajax_action_item_update;
994 + $globals['ajax_action_modal'] = $this->ajax_action_modal;
995 + $globals['ajax_item_id'] = $id;
996 + $globals['settings'] = NULL;
997 + $globals['config'] = NULL;
998 +
999 + $settings_key = 'vision_settings';
1000 + $settings_value = get_option($settings_key);
1001 + if ($settings_value) {
1002 + $globals['settings'] = unserialize($settings_value); // json_encode(unserialize($settings_value)) problem with double quotes
1003 + }
1004 +
1005 + // get item data from DB
1006 + if ($id) {
1007 + global $wpdb;
1008 + $table = $wpdb->prefix . VISION_PLUGIN_NAME;
1009 +
1010 + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1011 + $query = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%s", $id);
1012 + $item = $wpdb->get_row($query, OBJECT);
1013 + // phpcs:enable
1014 +
1015 + if ($item) {
1016 + $globals['config'] = unserialize($item->data); // json_encode(unserialize($item->data)) problem with double quotes
1017 + }
1018 +
1019 + if ($item) {
1020 + $current_user_id = get_current_user_id();
1021 + if (!current_user_can('manage_options') && $current_user_id != $item->author) {
1022 + echo '<p>' . esc_html__('You do not have permission to edit this item.', 'vision') . '</p>';
1023 + return;
1024 + }
1025 + }
1026 + } else {
1027 + // new item
1028 + $item = (object) [
1029 + 'author' => get_current_user_id(),
1030 + 'editor' => get_current_user_id(),
1031 + 'created' => current_time('mysql', 1),
1032 + 'modified' => current_time('mysql', 1)
201 1033 ];
202 -
203 - return $globals;
204 - }
205 -
206 - function embedLoader($in_footer, $timestamp) {
207 - $plugin_url = plugin_dir_url(dirname(__FILE__));
208 - wp_enqueue_script('vision_loader', $plugin_url . 'assets/js/loader.js', ['jquery'], VISION_PLUGIN_VERSION, $in_footer);
209 - wp_localize_script('vision_loader', 'vision_globals', $this->getLoaderGlobals($timestamp));
210 - }
1034 + }
211 1035
212 - /**
213 - * generate main css text
214 - */
215 - function getMainCss($itemData, $itemId) {
216 - $upload_dir = wp_upload_dir();
217 -
218 - // create main css
219 - $main_css = '';
220 - $main_css .= '.vision-map-' . $itemId . ' {' . PHP_EOL;
221 -
222 - $main_css .= (!$this->IsNullOrEmptyString($itemData->background->color) ? 'background-color:' . $itemData->background->color . ';' . PHP_EOL : '');
223 - if(!$this->IsNullOrEmptyString($itemData->background->image->url)) {
224 - $imageUrl = ($itemData->background->image->relative ? $upload_dir['baseurl'] : '') . $itemData->background->image->url;
225 - $main_css .= 'background-image:url(' . $imageUrl . ');' . PHP_EOL;
226 - }
227 - $main_css .= ($itemData->background->size ? 'background-size:' . $itemData->background->size . ';' . PHP_EOL : '');
228 - $main_css .= ($itemData->background->repeat ? 'background-repeat:' . $itemData->background->repeat . ';' . PHP_EOL : '');
229 - $main_css .= ($itemData->background->position ? 'background-position:' . $itemData->background->position . ';' . PHP_EOL : '');
230 -
231 - $main_css .= '}' . PHP_EOL;
232 -
233 - $layerId = 0;
234 - foreach($itemData->layers as $layerKey => $layer) {
235 - if(!$layer->visible) {
236 - continue;
237 - }
238 -
239 - $layerId++;
240 - $layerSelector = '.vision-map-' . $itemId . ' .vision-layers [data-layer-id="' . $layer->id . '"] .vision-body';
241 -
242 - // main
243 - $main_css .= $layerSelector . ' {' . PHP_EOL;
244 - switch($layer->type) {
245 - case 'link': {
246 - $main_css .= ($layer->link->normalColor ? 'background-color:' . $layer->link->normalColor . ';' . PHP_EOL : '');
247 - $main_css .= ($layer->link->radius != NULL ? 'border-radius:' . $layer->link->radius . ';' . PHP_EOL : '');
248 - } break;
249 - case 'image': {
250 - $main_css .= (!$this->IsNullOrEmptyString($layer->image->background->color) ? 'background-color:' . $layer->image->background->color . ';' . PHP_EOL : '');
251 - if(!$this->IsNullOrEmptyString($layer->image->background->file->url)) {
252 - $imageUrl = ($layer->image->background->file->relative ? $upload_dir['baseurl'] : '') . $layer->image->background->file->url;
253 - $main_css .= 'background-image:url(' . $imageUrl . ');' . PHP_EOL;
254 - }
255 - $main_css .= ($layer->image->background->size ? 'background-size:' . $layer->image->background->size . ';' . PHP_EOL : '');
256 - $main_css .= ($layer->image->background->repeat ? 'background-repeat:' . $layer->image->background->repeat . ';' . PHP_EOL : '');
257 - $main_css .= ($layer->image->background->position ? 'background-position:' . $layer->image->background->position . ';' . PHP_EOL : '');
258 - } break;
259 - case 'text': {
260 - $main_css .= (!$this->IsNullOrEmptyString($layer->text->background->color) ? 'background-color:' . $layer->text->background->color . ';' . PHP_EOL : '');
261 - if(!$this->IsNullOrEmptyString($layer->text->background->file->url)) {
262 - $imageUrl = ($layer->text->background->file->relative ? $upload_dir['baseurl'] : '') . $layer->text->background->file->url;
263 - $main_css .= 'background-image:url(' . $imageUrl . ');' . PHP_EOL;
264 - }
265 - $main_css .= ($layer->text->background->size ? 'background-size:' . $layer->text->background->size . ';' . PHP_EOL : '');
266 - $main_css .= ($layer->text->background->repeat ? 'background-repeat:' . $layer->text->background->repeat . ';' . PHP_EOL : '');
267 - $main_css .= ($layer->text->background->position ? 'background-position:' . $layer->text->background->position . ';' . PHP_EOL : '');
268 -
269 - $main_css .= ($layer->text->font ? 'font-family:"' . str_replace('+', ' ', $layer->text->font) . '",sans-serif;' . PHP_EOL : '');
270 - $main_css .= ($layer->text->color ? 'color:' . $layer->text->color . ';' . PHP_EOL : '');
271 - $main_css .= ($layer->text->size != NULL ? 'font-size:' . $layer->text->size . 'px;' . PHP_EOL : '');
272 - $main_css .= ($layer->text->lineHeight != NULL ? 'line-height:' . $layer->text->lineHeight . 'px;' . PHP_EOL : '');
273 - $main_css .= ($layer->text->align ? 'text-align:' . $layer->text->align . ';' . PHP_EOL : '');
274 - $main_css .= ($layer->text->letterSpacing != NULL ? 'letter-spacing:' . $layer->text->letterSpacing . 'px;' . PHP_EOL : '');
275 - } break;
276 - }
277 - $main_css .= '}' . PHP_EOL;
278 -
279 - if($layer->type == 'link') {
280 - $main_css .= $layerSelector . ':hover {' . PHP_EOL;
281 - $main_css .= ($layer->link->hoverColor ? 'background-color:' . $layer->link->hoverColor . ';' . PHP_EOL : '');
282 - $main_css .= '}' . PHP_EOL;
283 - }
284 - }
285 -
286 - return $main_css;
287 - }
288 -
289 - /**
290 - * Shortcode output for the plugin
291 - */
292 - function shortcode($atts) {
293 - extract(shortcode_atts(['id'=>0, 'slug'=>NULL, 'class'=>NULL], $atts, VISION_SHORTCODE_NAME));
294 -
295 - if(!$id && !$slug) {
296 - return '<p>' . esc_html__('Error: invalid vision identifier attribute', 'vision') . '</p>';
297 - }
1036 + require_once(plugin_dir_path(dirname(__FILE__)) . 'includes/page-item.php');
298 1037
299 - $id = intval($id, 10);
300 - $slug = sanitize_key($slug);
301 - $class = sanitize_text_field($class);
302 -
303 - global $wpdb;
304 - $table = $wpdb->prefix . VISION_PLUGIN_NAME;
305 - $upload_dir = wp_upload_dir();
1038 + // set global settings
1039 + wp_localize_script('vision_admin', 'vision_globals', $globals);
1040 + }
1041 + }
306 1042
1043 + /**
1044 + * Show admin menu settings page
1045 + */
1046 + function admin_menu_page_settings()
1047 + {
1048 + $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
1049 + if ($page === 'vision_settings') {
1050 + $plugin_url = plugin_dir_url(dirname(__FILE__));
1051 +
1052 + wp_enqueue_style('vision_admin', $plugin_url . 'assets/css/admin.css', [], VISION_PLUGIN_VERSION, 'all');
1053 + wp_enqueue_style('vision_lucide', $plugin_url . 'assets/vendor/lucide/lucide.css', [], VISION_PLUGIN_VERSION, 'all');
1054 + wp_enqueue_script('vision_admin', $plugin_url . 'assets/js/admin.js', ['jquery'], VISION_PLUGIN_VERSION, false);
1055 +
1056 + // global settings to help ajax work
1057 + $globals = [
1058 + 'plan' => VISION_PLUGIN_PLAN,
1059 + 'msg_pro_title' => esc_html__('Available only in Pro version', 'vision'),
1060 + 'ajax_url' => admin_url('admin-ajax.php'),
1061 + 'ajax_nonce' => wp_create_nonce('vision_ajax'),
1062 + 'ajax_msg_error' => esc_html__('Uncaught Error', 'vision') //Look at the console (F12 or Ctrl+Shift+I, Console tab) for more information
1063 + ];
1064 +
1065 + $globals['ajax_action_update'] = $this->ajax_action_settings_update;
1066 + $globals['ajax_action_get'] = $this->ajax_action_settings_get;
1067 + $globals['ajax_action_modal'] = $this->ajax_action_modal;
1068 + $globals['ajax_action_delete_data'] = $this->ajax_action_delete_data;
1069 + $globals['config'] = NULL;
1070 +
1071 + // read settings
1072 + $settings_key = 'vision_settings';
1073 + $settings_value = get_option($settings_key);
1074 + if ($settings_value) {
1075 + $globals['config'] = wp_json_encode(unserialize($settings_value));
1076 + }
1077 +
1078 + require_once(plugin_dir_path(dirname(__FILE__)) . 'includes/page-settings.php');
1079 +
1080 + wp_localize_script('vision_admin', 'vision_globals', $globals);
1081 + }
1082 + }
1083 +
1084 + /**
1085 + * Show admin menu upgrade to pro page
1086 + */
1087 + function admin_menu_page_upgrade_to_pro()
1088 + {
1089 + $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
1090 + if ($page === 'vision_upgrade_to_pro') {
1091 + echo '<script>window.location = "https://1.envato.market/getvision"</script>';
1092 + }
1093 + }
1094 +
1095 + /**
1096 + * Ajax update item state
1097 + */
1098 + function ajax_item_update_status()
1099 + {
1100 + $error = false;
1101 + $data = [];
1102 + $config = filter_input(INPUT_POST, 'config');
1103 +
1104 + if (check_ajax_referer('vision_ajax', 'nonce', false)) {
1105 + global $wpdb;
1106 + $table = $wpdb->prefix . VISION_PLUGIN_NAME;
1107 +
1108 + $config = json_decode($config);
1109 + $result = false;
1110 +
1111 + if (isset($config->id) && isset($config->active)) {
307 1112 // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
308 - $sql = ($id ? $wpdb->prepare("SELECT * FROM {$table} WHERE id=%d AND NOT deleted", $id) : $wpdb->prepare("SELECT * FROM {$table} WHERE slug=%s AND NOT deleted LIMIT 0, 1", $slug));
309 - $item = $wpdb->get_row($sql, OBJECT);
1113 + $query = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%s", $config->id);
1114 + $item = $wpdb->get_row($query, OBJECT);
310 1115 // phpcs:enable
311 1116
312 - $preview = filter_input(INPUT_GET, 'preview', FILTER_SANITIZE_NUMBER_INT);
1117 + if ($item && (current_user_can('manage_options') || get_current_user_id() == $item->author)) {
1118 + $itemData = unserialize($item->data);
1119 + $itemData->active = $config->active;
313 1120
314 - if($item && ($item->active || (!$item->active && $preview == 1))) {
315 - $version = strtotime(mysql2date('d M Y H:i:s', $item->modified));
316 - $itemData = unserialize($item->data);
317 - $id = $item->id;
318 - $id_postfix = strtolower(wp_generate_password(5, false)); // generate unique postfix for $id to avoid clashes with multiple same shortcode use
319 - $id_element = 'vision-' . $id . '-' . $id_postfix;
320 -
321 - array_push($this->shortcodes, ['id' => $item->id, 'version' => $version]);
322 -
323 - if(sizeof($this->shortcodes) == 1) {
324 - $this->embedLoader(true, $version);
325 - }
1121 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1122 + $result = $wpdb->update(
1123 + $table,
1124 + ['active' => $itemData->active, 'data' => serialize($itemData)],
1125 + ['id' => $config->id]
1126 + );
1127 + }
1128 + }
326 1129
327 - ob_start(); // turn on buffering
1130 + if ($result) {
1131 + $data['id'] = $config->id;
1132 + $data['msg'] = esc_html__('The item was successfully updated', 'vision');
1133 + } else {
1134 + $error = true;
1135 + $data['msg'] = esc_html__('The operation failed, can\'t update item', 'vision');
1136 + }
1137 + } else {
1138 + $error = true;
1139 + $data['msg'] = esc_html__('The operation failed', 'vision');
1140 + }
328 1141
329 - echo '<!-- vision begin -->' . PHP_EOL;
330 - echo '<div ';
331 - echo (property_exists($itemData, 'containerId') && $itemData->containerId ? 'id="' . esc_attr($itemData->containerId) . '" ':'');
332 - echo 'class="vision-map vision-map-' . esc_attr($id . ($class ? ' ' . $class : '')) . '"';
333 - echo 'data-json-src="'. esc_url_raw( rest_url( VISION_PLUGIN_REST_URL ) ) . '/item/' . esc_attr($item->id) . ($preview ? '?preview=1' : '') . '" ';
334 - echo 'data-item-id="' . esc_attr($item->id) . '" ';
335 - echo 'tabindex="1" ';
336 - echo '>' . PHP_EOL;
337 - if (property_exists($itemData, 'image')) {
338 - $upload_dir = wp_upload_dir();
339 - $imageUrl = ($itemData->image->relative ? $upload_dir['baseurl'] : '') . $itemData->image->url;
340 - echo "<img src='" . esc_url($imageUrl). "' class='vision-img-placeholder' width='100%'>";
341 - }
1142 + if ($error) {
1143 + wp_send_json_error($data);
1144 + } else {
1145 + wp_send_json_success($data);
1146 + }
342 1147
343 - //=============================================
344 - // STORE BEGIN
345 - echo '<div class="vision-store" style="display:none;">' . PHP_EOL;
346 - echo '<div class="vision-layers-data">' . PHP_EOL;
347 - foreach($itemData->layers as $layerKey => $layer) {
348 - if(!$layer->visible) {
349 - continue;
350 - }
351 -
352 - //=============================================
353 - // LAYER BEGIN
354 - echo '<div class="vision-layer" data-layer-id="' . esc_attr($layer->id) . '">';
355 -
356 - if($layer->contentData) {
357 - echo do_shortcode($layer->contentData);
358 - }
359 -
360 - if($layer->type == 'text') {
361 - echo wp_kses_post($layer->text->data);
362 - }
1148 + wp_die(); // this is required to terminate immediately and return a proper response
1149 + }
363 1150
364 - echo '</div>' . PHP_EOL;
365 - // LAYER END
366 - //=============================================
367 - }
368 - echo '</div>' . PHP_EOL;
1151 + /**
1152 + * Ajax update item data
1153 + */
1154 + function ajax_item_update()
1155 + {
1156 + $error = false;
1157 + $data = [];
369 1158
370 - echo '<div class="vision-tooltips-data">' . PHP_EOL;
371 - foreach($itemData->layers as $layerKey => $layer) {
372 - if(!$layer->visible) {
373 - continue;
374 - }
375 -
376 - //=============================================
377 - // TOOLTIP BEGIN
378 - echo '<div class="vision-data" data-layer-id="' . esc_attr($layer->id) . '">';
379 - echo do_shortcode($layer->tooltip->data);
380 - echo '</div>' . PHP_EOL;
381 - // TOOLTIP END
382 - //=============================================
383 - }
384 - echo '</div>' . PHP_EOL;
1159 + if (check_ajax_referer('vision_ajax', 'nonce', false)) {
1160 + global $wpdb;
1161 + $table = $wpdb->prefix . VISION_PLUGIN_NAME;
385 1162
386 - echo '<div class="vision-popovers-data">' . PHP_EOL;
387 - foreach($itemData->layers as $layerKey => $layer) {
388 - if(!$layer->visible) {
389 - continue;
390 - }
391 -
392 - //=============================================
393 - // POPOVER BEGIN
394 - echo '<div class="vision-data" data-layer-id="' . esc_attr($layer->id) . '">';
395 - echo do_shortcode($layer->popover->data);
396 - echo '</div>' . PHP_EOL;
397 - // POPOVER END
398 - //=============================================
399 - }
400 - echo '</div>' . PHP_EOL;
1163 + $inputId = filter_input(INPUT_POST, 'id');
1164 + $inputData = filter_input(INPUT_POST, 'data');
1165 + $inputConfig = filter_input(INPUT_POST, 'config');
1166 + $itemData = json_decode($inputData);
1167 + $itemConfig = json_decode($inputConfig);
1168 + $flag = true;
401 1169
402 - echo '</div>' . PHP_EOL;
403 - // STORE END
404 - //=============================================
1170 + if (VISION_PLUGIN_PLAN == 'lite' && !$inputId) {
1171 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1172 + $count = $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
405 1173
406 - echo '</div>' . PHP_EOL;
407 - echo '<!-- vision end -->' . PHP_EOL;
1174 + if ($count >= 1) {
1175 + $flag = false;
1176 + $error = true;
1177 + $data['msg'] = esc_html__('You can create only 1 map. If you need more, upgrade to the pro version.', 'vision');
1178 + }
1179 + }
408 1180
409 - $output = ob_get_contents(); // get the buffered content into a var
410 - ob_end_clean(); // clean buffer
1181 + if ($itemData === NULL || $itemConfig === NULL) {
1182 + $flag = false;
1183 + $error = true;
1184 + $data['msg'] = 'Error decoding JSON: ' . json_last_error_msg();
1185 + }
411 1186
412 - return $output;
413 - }
414 -
415 - return '<p>' . esc_html__('Error: the vision item can’t be found', 'vision') . '</p>';
416 - }
1187 + if ($flag) {
1188 + $itemConfig->modified = current_time('mysql', 1);
417 1189
418 - /**
419 - * Run a filter to obtain some custom url settings, compare them to the current url
420 - * and if a match is found the custom callback is fired, the custom view is loaded
421 - * and request is stopped.
422 - */
423 - function do_parse_request($result) {
424 - if(current_filter() !== 'do_parse_request') {
425 - return $result;
426 - }
1190 + if ($inputId) {
1191 + $result = false;
427 1192
428 - if(preg_match('/vision\/map\/([a-z0-9_-]+)/', $_SERVER['REQUEST_URI'], $matches)) {
429 - $preview = filter_input(INPUT_GET, 'preview', FILTER_SANITIZE_NUMBER_INT);
1193 + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1194 + $query = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%s", $inputId);
1195 + $item = $wpdb->get_row($query, OBJECT);
1196 + // phpcs:enable
430 1197
431 - global $wpdb;
432 - $table = $wpdb->prefix . VISION_PLUGIN_NAME;
433 - $shortcode = false;
1198 + if ($item && (current_user_can('manage_options') || get_current_user_id() == $item->author)) {
1199 + $itemData->slug = sanitize_title(($itemData->slug ? $itemData->slug : $itemData->title));
434 1200
435 - if(is_numeric($matches[1])) {
436 - $vision_map_id = $matches[1];
1201 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1202 + $result = $wpdb->update(
1203 + $table,
1204 + [
1205 + 'title' => $itemData->title,
1206 + 'slug' => $itemData->slug,
1207 + 'active' => $itemData->active,
1208 + 'data' => serialize($itemData),
1209 + 'config' => serialize($itemConfig),
1210 + //'author' => get_current_user_id(),
1211 + 'editor' => get_current_user_id(),
1212 + //'date' => NULL,
1213 + 'modified' => current_time('mysql', 1)
1214 + ],
1215 + ['id' => $inputId]
1216 + );
1217 + }
437 1218
438 - if($vision_map_id != null) {
439 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
440 - $sql = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%d AND NOT deleted", $vision_map_id);
441 - $item = $wpdb->get_row($sql, OBJECT);
442 - // phpcs:enable
443 -
444 - if($item && ($item->active || (!$item->active && $preview == 1))) {
445 - $this->vision_map_id = $item->id;
446 - $this->vision_map_version = strtotime(mysql2date('Y-m-d H:i:s', $item->modified));
447 - $shortcode = true;
448 - }
449 - }
450 - } else {
451 - $vision_map_slug = $matches[1];
1219 + if ($result) {
1220 + $data['id'] = $inputId;
1221 + $data['msg'] = esc_html__('The item was successfully updated', 'vision');
1222 + } else {
1223 + $error = true;
1224 + $data['msg'] = esc_html__('The operation failed, can\'t update item', 'vision');
1225 + }
1226 + } else {
1227 + $itemData->slug = sanitize_title(($itemData->slug ? $itemData->slug : $itemData->title));
452 1228
453 - if($vision_map_slug != null) {
454 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
455 - $sql = $wpdb->prepare("SELECT * FROM {$table} WHERE slug=%s AND NOT deleted", $vision_map_slug);
456 - $item = $wpdb->get_row($sql, OBJECT);
457 - // phpcs:enable
458 -
459 - if($item && ($item->active || (!$item->active && $preview == 1))) {
460 - $this->vision_map_id = $item->id;
461 - $this->vision_map_version = strtotime(mysql2date('Y-m-d H:i:s', $item->modified));
462 -
463 - $shortcode = true;
464 - }
465 - }
466 - }
467 -
468 - if($shortcode) {
469 - require_once(plugin_dir_path(dirname(__FILE__)) . 'includes/page-preview.php');
470 - exit();
471 - }
472 - }
473 -
474 - return $result;
475 - }
476 -
477 - /**
478 - * Prepare upload directory
479 - */
480 - function admin_notices() {
481 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
482 - if(!($page==='vision' || $page==='vision_item')) {
483 - return;
484 - }
485 -
486 - if(!file_exists(VISION_PLUGIN_UPLOAD_DIR)) {
487 - wp_mkdir_p(VISION_PLUGIN_UPLOAD_DIR);
488 - }
489 -
490 - if(!file_exists(VISION_PLUGIN_UPLOAD_DIR)) {
491 - echo '<div class="notice notice-error is-dismissible">';
492 - echo '<p>' . esc_html__('The plugin upload directory could not be created', 'vision') . '</p>';
493 - echo '<p>' . esc_html__('Please run the following commands in order to make the directory', 'vision') . '<br>';
494 - echo '<b>mkdir ' . esc_attr(VISION_PLUGIN_UPLOAD_DIR) . '</b><br>';
495 - echo '<b>chmod 777 ' . esc_attr(VISION_PLUGIN_UPLOAD_DIR) . '</b></p>';
496 - echo '</div>';
497 - return;
498 - }
499 -
500 - if(!wp_is_writable(VISION_PLUGIN_UPLOAD_DIR)) {
501 - echo '<div class="notice notice-error is-dismissible">';
502 - echo '<p>' . esc_html__('The plugin upload directory is not writable, therefore the css and js files cannot be saved.', 'vision') . '</p>';
503 - echo '<p>' . esc_html__('Please run the following commands in order to make the directory', 'vision') . '<br>';
504 - echo '<b>chmod 777 ' . esc_attr(VISION_PLUGIN_UPLOAD_DIR) . '</b></p>';
505 - echo '</div>';
506 - return;
507 - }
508 -
509 - if(!file_exists(VISION_PLUGIN_UPLOAD_DIR . '/' . 'index.php')) {
510 - $data = '<?php' . PHP_EOL . '// silence is golden' . PHP_EOL . '?>';
1229 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
1230 + $result = $wpdb->insert(
1231 + $table,
1232 + [
1233 + 'title' => $itemData->title,
1234 + 'slug' => $itemData->slug,
1235 + 'active' => $itemData->active,
1236 + 'data' => serialize($itemData),
1237 + 'config' => serialize($itemConfig),
1238 + 'author' => get_current_user_id(),
1239 + 'editor' => get_current_user_id(),
1240 + 'created' => current_time('mysql', 1),
1241 + 'modified' => current_time('mysql', 1)
1242 + ]
1243 + );
511 1244
512 - $wp_filesystem = $this->getFileSystem();
513 - $wp_filesystem->put_contents(VISION_PLUGIN_UPLOAD_DIR . '/' . 'index.php', $data);
514 - }
515 - }
516 -
517 - /**
518 - * Fires at the beginning of the content section in an admin page
519 - */
520 - function in_admin_header() {
521 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
522 -
523 - if(!(($page==='vision') || ($page==='vision_item') || ($page==='vision_settings'))) {
524 - return;
525 - }
526 -
527 - remove_all_actions('admin_notices');
528 - remove_all_actions('all_admin_notices');
529 - add_action('admin_notices', [$this, 'admin_notices']);
530 - }
531 -
532 - /**
533 - * Register the administration menu for this plugin into the WordPress Dashboard menu.
534 - */
535 - function admin_menu() {
536 - // add "edit_posts" if we want to give access to author, editor and contributor roles
537 - add_menu_page(esc_html__('Vision', 'vision'), esc_html__('Vision', 'vision'), 'read', 'vision', [$this, 'admin_menu_page_items'], 'dashicons-format-image');
538 - add_submenu_page('vision', esc_html__('Vision', 'vision'), esc_html__('All Items', 'vision'), 'read', 'vision', [$this, 'admin_menu_page_items']);
539 - add_submenu_page('vision', esc_html__('Vision', 'vision'), esc_html__('Add New', 'vision'), 'read', 'vision_item', [$this, 'admin_menu_page_item']);
540 - add_submenu_page('vision', esc_html__('Vision', 'vision'), esc_html__('Settings', 'vision'), 'manage_options', 'vision_settings', [$this, 'admin_menu_page_settings']);
541 -
542 - add_submenu_page('vision', esc_html__('Vision', 'vision'), esc_html__('Upgrade to Pro', 'vision'), 'manage_options', 'vision_upgrade_to_pro', [$this, 'admin_menu_page_upgrade_to_pro']);
543 -
544 - }
1245 + if ($result) {
1246 + $data['id'] = $inputId = $wpdb->insert_id;
1247 + $data['msg'] = esc_html__('The item was successfully created', 'vision');
1248 + } else {
1249 + $error = true;
1250 + $data['msg'] = esc_html__('The operation failed, can\'t create item', 'vision');
1251 + }
1252 + }
1253 + }
1254 + } else {
1255 + $error = true;
1256 + $data['msg'] = esc_html__('The operation failed', 'vision');
1257 + }
545 1258
546 - function admin_footer() {
547 - if(get_current_screen() && get_current_screen()->base !== 'plugins') {
548 - return;
1259 + if ($error) {
1260 + wp_send_json_error($data);
1261 + } else {
1262 + wp_send_json_success($data);
1263 + }
1264 +
1265 + wp_die(); // this is required to terminate immediately and return a proper response
1266 + }
1267 +
1268 + /**
1269 + * Ajax save item data
1270 + */
1271 + function ajax_item_save_chunk()
1272 + {
1273 + try {
1274 + if (!check_ajax_referer('vision_ajax', 'nonce', false)) {
1275 + throw new Exception('Nonce verification failed');
1276 + }
1277 +
1278 + $session_id = sanitize_text_field(filter_input(INPUT_POST, 'session_id'));
1279 + $chunk_index = (int)filter_input(INPUT_POST, 'chunk_index');
1280 + $total_chunks = (int)filter_input(INPUT_POST, 'total_chunks');
1281 + $chunk = filter_input(INPUT_POST, 'chunk');
1282 + $data_type = sanitize_text_field(filter_input(INPUT_POST, 'data_type')); // 'data' или 'config'
1283 + $is_last_chunk = filter_input(INPUT_POST, 'is_last_chunk') === '1';
1284 + $item_id = (int)filter_input(INPUT_POST, 'item_id');
1285 +
1286 + if (empty($session_id) || !is_string($session_id)) {
1287 + throw new Exception('Invalid session ID');
1288 + }
1289 +
1290 + if (!in_array($data_type, ['data', 'config'], true)) {
1291 + throw new Exception('Invalid data_type');
1292 + }
1293 +
1294 + if ($chunk_index < 0 || $total_chunks < 1 || $chunk_index >= $total_chunks) {
1295 + throw new Exception('Invalid chunk index or total chunks');
1296 + }
1297 +
1298 + if (strlen($chunk) > 5 * 1024 * 1024) {
1299 + throw new Exception('Chunk too large');
1300 + }
1301 +
1302 + $upload_dir = wp_upload_dir();
1303 + $chunk_dir = $upload_dir['basedir'] . '/vision/chunks/' . $session_id . '/';
1304 +
1305 + if (!file_exists($chunk_dir)) {
1306 + wp_mkdir_p($chunk_dir);
1307 + }
1308 +
1309 + $chunk_file = $chunk_dir . $data_type . '_' . $chunk_index . '.chunk';
1310 +
1311 + $wp_filesystem = $this->getFileSystem();
1312 + if (!$wp_filesystem->put_contents($chunk_file, $chunk)) {
1313 + throw new Exception('Failed to write chunk');
1314 + }
1315 +
1316 + wp_send_json_success([
1317 + 'status' => 'chunk_saved',
1318 + 'type' => $data_type,
1319 + 'index' => $chunk_index
1320 + ]);
1321 + } catch (Exception $e) {
1322 + wp_send_json_error($e->getMessage(), 400);
1323 + }
1324 +
1325 + wp_die();
1326 + }
1327 +
1328 + /**
1329 + * Ajax save item data complete
1330 + */
1331 + function ajax_item_save_complete()
1332 + {
1333 + $data = [];
1334 +
1335 + try {
1336 + if (!check_ajax_referer('vision_ajax', 'nonce', false)) {
1337 + throw new Exception('Nonce verification failed');
1338 + }
1339 +
1340 + $session_id = sanitize_text_field(filter_input(INPUT_POST, 'session_id'));
1341 + $item_id = (int)filter_input(INPUT_POST, 'item_id');
1342 +
1343 + $upload_dir = wp_upload_dir();
1344 + $chunk_dir = $upload_dir['basedir'] . '/vision/chunks/' . $session_id . '/';
1345 +
1346 + $collected_data = [
1347 + 'data' => '',
1348 + 'config' => ''
1349 + ];
1350 +
1351 + foreach (['data', 'config'] as $type) {
1352 + $chunks = glob($chunk_dir . $type . '_*.chunk');
1353 +
1354 + if (count($chunks) === 0) {
1355 + throw new Exception("No chunks found for $type");
549 1356 }
550 1357
551 - $globals = [
552 - 'token' => $this->get_token(),
553 - 'ajax' => [
554 - 'url' => VISION_FEEDBACK_URL
555 - ]
556 - ];
1358 + natsort($chunks);
557 1359
558 - wp_enqueue_style('vision-feedback', VISION_PLUGIN_URL . 'assets/css/feedback.css', [], VISION_PLUGIN_VERSION);
559 - wp_enqueue_script('vision-feedback', VISION_PLUGIN_URL . 'assets/js/feedback.js', ['jquery'], VISION_PLUGIN_VERSION, false);
560 - wp_localize_script('vision-feedback', 'vision_feedback_globals', $globals);
1360 + foreach ($chunks as $chunk_file) {
1361 + $collected_data[$type] .= file_get_contents($chunk_file);
1362 + wp_delete_file($chunk_file);
1363 + }
561 1364
562 - require_once(plugin_dir_path(dirname(__FILE__)) . 'templates/feedback.php');
1365 + $collected_data[$type] = json_decode($collected_data[$type]);
1366 +
1367 + if (json_last_error() !== JSON_ERROR_NONE) {
1368 + throw new Exception("Invalid JSON for $type: " . json_last_error_msg());
1369 + }
1370 + }
1371 +
1372 + $this->remove_directory($chunk_dir);
1373 +
1374 + // save to DB
1375 + global $wpdb;
1376 + $table = $wpdb->prefix . VISION_PLUGIN_NAME;
1377 +
1378 + if (VISION_PLUGIN_PLAN == 'lite' && !$item_id) {
1379 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1380 + $count = $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
1381 +
1382 + if ($count >= 1) {
1383 + throw new Exception(esc_html__('You can create only 1 map. If you need more, upgrade to the pro version.', 'vision'));
1384 + }
1385 + }
1386 +
1387 + $itemData = $collected_data['data'];
1388 + $itemConfig = $collected_data['config'];
1389 + $itemConfig->modified = current_time('mysql', 1);
1390 +
1391 + if ($item_id) {
1392 + $result = false;
1393 +
1394 + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1395 + $query = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%s", $item_id);
1396 + $item = $wpdb->get_row($query, OBJECT);
1397 + // phpcs:enable
1398 +
1399 + if ($item && (current_user_can('manage_options') || get_current_user_id() == $item->author)) {
1400 + $itemData->slug = sanitize_title(($itemData->slug ? $itemData->slug : $itemData->title));
1401 +
1402 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1403 + $result = $wpdb->update(
1404 + $table,
1405 + [
1406 + 'title' => $itemData->title,
1407 + 'slug' => $itemData->slug,
1408 + 'active' => $itemData->active,
1409 + 'data' => serialize($itemData),
1410 + 'config' => serialize($itemConfig),
1411 + //'author' => get_current_user_id(),
1412 + 'editor' => get_current_user_id(),
1413 + //'date' => NULL,
1414 + 'modified' => current_time('mysql', 1)
1415 + ],
1416 + ['id' => $item_id]
1417 + );
1418 + }
1419 +
1420 + if ($result) {
1421 + $data['id'] = $item_id;
1422 + $data['msg'] = esc_html__('The item was successfully updated', 'vision');
1423 + wp_send_json_success($data);
1424 + } else {
1425 + throw new Exception(esc_html__('The operation failed, can\'t update item', 'vision'));
1426 + }
1427 + } else {
1428 + $itemData->slug = sanitize_title($itemData->slug ? $itemData->slug : $itemData->title);
1429 +
1430 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1431 + $result = $wpdb->insert(
1432 + $table,
1433 + [
1434 + 'title' => $itemData->title,
1435 + 'slug' => $itemData->slug,
1436 + 'active' => $itemData->active,
1437 + 'data' => serialize($itemData),
1438 + 'config' => serialize($itemConfig),
1439 + 'author' => get_current_user_id(),
1440 + 'editor' => get_current_user_id(),
1441 + 'created' => current_time('mysql', 1),
1442 + 'modified' => current_time('mysql', 1)
1443 + ]
1444 + );
1445 +
1446 + if ($result) {
1447 + $data['id'] = $item_id = $wpdb->insert_id;
1448 + $data['msg'] = esc_html__('The item was successfully created', 'vision');
1449 + wp_send_json_success($data);
1450 + } else {
1451 + throw new Exception(esc_html__('The operation failed, can\'t create item', 'vision'));
1452 + }
1453 + }
1454 + } catch (Exception $e) {
1455 + $data['msg'] = $e->getMessage();
1456 + wp_send_json_error($data, 400);
563 1457 }
1458 + }
564 1459
565 - function get_token() {
566 - global $wp_version;
567 - $current_user = wp_get_current_user();
1460 + /**
1461 + * Ajax update settings data
1462 + */
1463 + function ajax_settings_update()
1464 + {
1465 + $error = false;
1466 + $data = [];
1467 + $config = filter_input(INPUT_POST, 'config');
1468 + $config = json_decode($config);
568 1469
569 - $data = [
570 - 'plugin_name' => VISION_PLUGIN_NAME,
571 - 'plugin_version' => VISION_PLUGIN_VERSION,
572 - 'wordpress' => $wp_version,
573 - 'php' => PHP_VERSION,
574 - 'email' => $current_user->user_email,
575 - 'site' => trim(str_replace(['http://', 'https://'], '', get_site_url()), '/')
576 - ];
577 - return base64_encode(wp_json_encode($data));
1470 + if ($config !== NULL) {
1471 + if (check_ajax_referer('vision_ajax', 'nonce', false)) {
1472 + $settings_key = 'vision_settings';
1473 + $settings_value = serialize($config);
1474 + $result = false;
1475 +
1476 + if (get_option($settings_key) == false) {
1477 + $autoload = 'no';
1478 + $result = add_option($settings_key, $settings_value, "", $autoload);
1479 + } else {
1480 + $old_settings_value = get_option($settings_key);
1481 + if ($old_settings_value === $settings_value) {
1482 + $result = true;
1483 + } else {
1484 + $result = update_option($settings_key, $settings_value);
1485 + }
1486 + }
1487 +
1488 + if ($result) {
1489 + $data['msg'] = esc_html__('The settings were successfully updated', 'vision');
1490 + } else {
1491 + $error = true;
1492 + $data['msg'] = esc_html__('The operation failed, can\'t update settings', 'vision');
1493 + }
1494 + }
1495 + } else {
1496 + $error = true;
1497 + $data['msg'] = 'Error decoding JSON: ' . json_last_error_msg();
578 1498 }
579 -
580 - /**
581 - * Custom redirects
582 - */
583 - function page_redirects() {
584 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
585 -
586 - if($page==='vision') {
587 - $action = sanitize_key(filter_input(INPUT_GET, 'action', FILTER_DEFAULT));
588 - if($action) {
589 - $url = remove_query_arg(['action', 'id', '_wpnonce'], $_SERVER['REQUEST_URI']);
590 - header('Refresh:0; url="' . $url . '"', true, 303);
591 - //wp_redirect($url); // does not work delete and dublicate operations on XAMPP
592 - }
593 - }
594 - }
595 -
596 - /**
597 - * Show admin menu items page
598 - */
599 - function admin_menu_page_items() {
600 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
601 -
602 - if($page==='vision') {
603 - $plugin_url = plugin_dir_url( dirname(__FILE__) );
604 - $upload_dir = wp_upload_dir();
605 1499
606 - wp_enqueue_style('vision_admin', $plugin_url . 'assets/css/admin.css', [], VISION_PLUGIN_VERSION, 'all' );
607 - wp_enqueue_style('vision_lucide', $plugin_url . 'assets/vendor/lucide/lucide.css', [], VISION_PLUGIN_VERSION, 'all' );
608 - wp_enqueue_script('vision_admin', $plugin_url . 'assets/js/admin.js', ['jquery'], VISION_PLUGIN_VERSION, false );
609 -
610 - // global settings to help ajax work
611 - $globals = [
612 - 'plan' => VISION_PLUGIN_PLAN,
613 - 'msg_pro_title' => esc_html__('Available only in Pro version', 'vision'),
614 - 'upload_url' => $upload_dir['baseurl'],
615 - 'ajax_url' => admin_url('admin-ajax.php'),
616 - 'ajax_nonce' => wp_create_nonce('vision_ajax' ),
617 - 'ajax_msg_error' => esc_html__('Uncaught Error', 'vision') //Look at the console (F12 or Ctrl+Shift+I, Console tab) for more information
618 - ];
619 - $globals['ajax_action_update'] = $this->ajax_action_item_update_status;
620 -
621 - require_once(plugin_dir_path( dirname(__FILE__) ) . 'includes/list-table-items.php');
622 - require_once(plugin_dir_path( dirname(__FILE__) ) . 'includes/page-items.php');
1500 + if ($error) {
1501 + wp_send_json_error($data);
1502 + } else {
1503 + wp_send_json_success($data);
1504 + }
623 1505
624 - wp_localize_script('vision_admin', 'vision_globals', $globals);
625 - }
626 - }
627 -
628 - /**
629 - * Show admin menu item page
630 - */
631 - function admin_menu_page_item() {
632 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
633 - if($page==='vision_item') {
634 - $plugin_url = plugin_dir_url(dirname(__FILE__));
635 - $upload_dir = wp_upload_dir();
1506 + wp_die(); // this is required to terminate immediately and return a proper response
1507 + }
636 1508
637 - wp_enqueue_style('vision_admin', $plugin_url . 'assets/css/admin.css', [], VISION_PLUGIN_VERSION, 'all' );
638 - wp_enqueue_style('vision_lucide', $plugin_url . 'assets/vendor/lucide/lucide.css', [], VISION_PLUGIN_VERSION, 'all' );
639 - wp_enqueue_style('vision_vision_effects', $plugin_url . 'assets/css/vision-effects.css', [], VISION_PLUGIN_VERSION, 'all' );
1509 + /**
1510 + * Ajax settings get data
1511 + */
1512 + function ajax_settings_get()
1513 + {
1514 + $error = false;
1515 + $data = [];
1516 + $type = sanitize_key(filter_input(INPUT_POST, 'type', FILTER_DEFAULT));
640 1517
641 - wp_enqueue_script('vision_ace', $plugin_url . 'assets/vendor/ace/ace.js', [], VISION_PLUGIN_VERSION, false );
642 - wp_enqueue_script('vision_url', $plugin_url . 'assets/vendor/url/url.js', [], VISION_PLUGIN_VERSION, false );
643 - wp_enqueue_script('vision_admin', $plugin_url . 'assets/js/admin.js', ['jquery'], VISION_PLUGIN_VERSION, false );
1518 + if (check_ajax_referer('vision_ajax', 'nonce', false)) {
1519 + switch ($type) {
1520 + case 'roles': {
1521 + $data['list'] = [];
644 1522
645 - wp_enqueue_media();
646 -
647 - // global settings to help ajax work
648 - $globals = [
649 - 'plan' => VISION_PLUGIN_PLAN,
650 - 'msg_pro_title' => esc_html__('Available only in Pro version', 'vision'),
651 - 'msg_edit_text' => esc_html__('Press Enter to edit text', 'vision'),
652 - 'msg_custom_js_error' => esc_html__('Custom js code error', 'vision'),
653 - 'msg_layer_id_error' => esc_html__('The layer ID should be unique', 'vision'),
654 - 'wp_base_url' => get_site_url(),
655 - 'upload_base_url' => $upload_dir['baseurl'],
656 - 'plugin_base_url' => $plugin_url,
657 - 'ajax_url' => admin_url('admin-ajax.php'),
658 - 'ajax_nonce' => wp_create_nonce('vision_ajax'),
659 - 'ajax_msg_error' => esc_html__('Uncaught Error', 'vision') //Look at the console (F12 or Ctrl+Shift+I, Console tab) for more information
660 - ];
661 -
662 - $id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
663 -
664 - $globals['ajax_action_get'] = $this->ajax_action_settings_get;
665 - $globals['ajax_action_update'] = $this->ajax_action_item_update;
666 - $globals['ajax_action_modal'] = $this->ajax_action_modal;
667 - $globals['ajax_item_id'] = $id;
668 - $globals['settings'] = NULL;
669 - $globals['config'] = NULL;
670 -
671 - $settings_key = 'vision_settings';
672 - $settings_value = get_option($settings_key);
673 - if($settings_value) {
674 - $globals['settings'] = unserialize($settings_value); // json_encode(unserialize($settings_value)) problem with double quotes
675 - }
676 -
677 - // get item data from DB
678 - if($id) {
679 - global $wpdb;
680 - $table = $wpdb->prefix . VISION_PLUGIN_NAME;
1523 + $roles = wp_roles()->roles;
1524 + foreach ($roles as $key => $role) {
1525 + if (array_key_exists('read', $role['capabilities'])) {
1526 + array_push($data['list'], ['id' => $key, 'name' => translate_user_role($role['name'])]);
1527 + }
1528 + }
1529 + }
1530 + break;
1531 + case 'themes': {
1532 + $data['list'] = [];
681 1533
682 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
683 - $query = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%s", $id);
684 - $item = $wpdb->get_row($query, OBJECT);
685 - // phpcs:enable
1534 + $files = glob(plugin_dir_path(dirname(__FILE__)) . 'assets/themes/*.css');
1535 + foreach ($files as $file) {
1536 + $filename = basename($file, '.css');
1537 + array_push($data['list'], ['id' => $filename, 'title' => str_replace('-', ' ', $filename)]);
1538 + }
1539 + }
1540 + break;
1541 + case 'editor-themes': {
1542 + $data['list'] = [];
686 1543
687 - if($item) {
688 - $globals['config'] = unserialize($item->data); // json_encode(unserialize($item->data)) problem with double quotes
689 - }
690 - } else {
691 - // new item
692 - $item = (object) [
693 - 'author' => get_current_user_id(),
694 - 'editor' => get_current_user_id(),
695 - 'created' => current_time('mysql', 1),
696 - 'modified' => current_time('mysql', 1)
697 - ];
698 - }
699 -
700 - require_once( plugin_dir_path( dirname(__FILE__) ) . 'includes/page-item.php' );
701 -
702 - // set global settings
703 - wp_localize_script('vision_admin', 'vision_globals', $globals);
704 - }
705 - }
706 -
707 - /**
708 - * Show admin menu settings page
709 - */
710 - function admin_menu_page_settings() {
711 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
712 - if($page==='vision_settings') {
713 - $plugin_url = plugin_dir_url(dirname(__FILE__));
1544 + $files = glob(plugin_dir_path(dirname(__FILE__)) . 'assets/vendor/ace/theme-*.js');
1545 + foreach ($files as $file) {
1546 + $filename = str_replace('theme-', '', basename($file, '.js'));
1547 + array_push($data['list'], ['id' => $filename, 'title' => str_replace('_', ' ', $filename)]);
1548 + }
1549 + }
1550 + break;
1551 + case 'fonts': {
1552 + $data['list'] = array(
1553 + array('fontname' => 'none'),
1554 + array('fontname' => 'Aclonica'),
1555 + array('fontname' => 'Allan'),
1556 + array('fontname' => 'Annie+Use+Your+Telescope'),
1557 + array('fontname' => 'Anonymous+Pro'),
1558 + array('fontname' => 'Allerta+Stencil'),
1559 + array('fontname' => 'Allerta'),
1560 + array('fontname' => 'Amaranth'),
1561 + array('fontname' => 'Anton'),
1562 + array('fontname' => 'Architects+Daughter'),
1563 + array('fontname' => 'Arimo'),
1564 + array('fontname' => 'Artifika'),
1565 + array('fontname' => 'Arvo'),
1566 + array('fontname' => 'Asset'),
1567 + array('fontname' => 'Astloch'),
1568 + array('fontname' => 'Bangers'),
1569 + array('fontname' => 'Bentham'),
1570 + array('fontname' => 'Bevan'),
1571 + array('fontname' => 'Bigshot+One'),
1572 + array('fontname' => 'Bowlby+One'),
1573 + array('fontname' => 'Bowlby+One+SC'),
1574 + array('fontname' => 'Brawler'),
1575 + array('fontname' => 'Cabin'),
1576 + array('fontname' => 'Calligraffitti'),
1577 + array('fontname' => 'Candal'),
1578 + array('fontname' => 'Cantarell'),
1579 + array('fontname' => 'Cardo'),
1580 + array('fontname' => 'Carter One'),
1581 + array('fontname' => 'Caudex'),
1582 + array('fontname' => 'Cedarville+Cursive'),
1583 + array('fontname' => 'Cherry+Cream+Soda'),
1584 + array('fontname' => 'Chewy'),
1585 + array('fontname' => 'Coda'),
1586 + array('fontname' => 'Coming+Soon'),
1587 + array('fontname' => 'Copse'),
1588 + array('fontname' => 'Cousine'),
1589 + array('fontname' => 'Covered+By+Your+Grace'),
1590 + array('fontname' => 'Crafty+Girls'),
1591 + array('fontname' => 'Crimson+Text'),
1592 + array('fontname' => 'Crushed'),
1593 + array('fontname' => 'Cuprum'),
1594 + array('fontname' => 'Damion'),
1595 + array('fontname' => 'Dancing+Script'),
1596 + array('fontname' => 'Dawning+of+a+New+Day'),
1597 + array('fontname' => 'Didact+Gothic'),
1598 + array('fontname' => 'Droid+Sans'),
1599 + array('fontname' => 'Droid+Sans+Mono'),
1600 + array('fontname' => 'Droid+Serif'),
1601 + array('fontname' => 'EB+Garamond'),
1602 + array('fontname' => 'Expletus+Sans'),
1603 + array('fontname' => 'Fontdiner+Swanky'),
1604 + array('fontname' => 'Forum'),
1605 + array('fontname' => 'Francois+One'),
1606 + array('fontname' => 'Geo'),
1607 + array('fontname' => 'Give+You+Glory'),
1608 + array('fontname' => 'Goblin+One'),
1609 + array('fontname' => 'Goudy+Bookletter+1911'),
1610 + array('fontname' => 'Gravitas+One'),
1611 + array('fontname' => 'Gruppo'),
1612 + array('fontname' => 'Hammersmith+One'),
1613 + array('fontname' => 'Holtwood+One+SC'),
1614 + array('fontname' => 'Homemade+Apple'),
1615 + array('fontname' => 'Inconsolata'),
1616 + array('fontname' => 'Indie+Flower'),
1617 + array('fontname' => 'IM+Fell+DW+Pica'),
1618 + array('fontname' => 'IM+Fell+DW+Pica+SC'),
1619 + array('fontname' => 'IM+Fell+Double+Pica'),
1620 + array('fontname' => 'IM+Fell+Double+Pica+SC'),
1621 + array('fontname' => 'IM+Fell+English'),
1622 + array('fontname' => 'IM+Fell+English+SC'),
1623 + array('fontname' => 'IM+Fell+French+Canon'),
1624 + array('fontname' => 'IM+Fell+French+Canon+SC'),
1625 + array('fontname' => 'IM+Fell+Great+Primer'),
1626 + array('fontname' => 'IM+Fell+Great+Primer+SC'),
1627 + array('fontname' => 'Irish+Grover'),
1628 + array('fontname' => 'Irish+Growler'),
1629 + array('fontname' => 'Istok+Web'),
1630 + array('fontname' => 'Josefin+Sans'),
1631 + array('fontname' => 'Josefin+Slab'),
1632 + array('fontname' => 'Judson'),
1633 + array('fontname' => 'Jura'),
1634 + array('fontname' => 'Just+Another+Hand'),
1635 + array('fontname' => 'Just+Me+Again+Down+Here'),
1636 + array('fontname' => 'Kameron'),
1637 + array('fontname' => 'Kenia'),
1638 + array('fontname' => 'Kranky'),
1639 + array('fontname' => 'Kreon'),
1640 + array('fontname' => 'Kristi'),
1641 + array('fontname' => 'La+Belle+Aurore'),
1642 + array('fontname' => 'Lato'),
1643 + array('fontname' => 'League+Script'),
1644 + array('fontname' => 'Lekton'),
1645 + array('fontname' => 'Limelight'),
1646 + array('fontname' => 'Lobster'),
1647 + array('fontname' => 'Lobster Two'),
1648 + array('fontname' => 'Lora'),
1649 + array('fontname' => 'Love+Ya+Like+A+Sister'),
1650 + array('fontname' => 'Loved+by+the+King'),
1651 + array('fontname' => 'Luckiest+Guy'),
1652 + array('fontname' => 'Maiden+Orange'),
1653 + array('fontname' => 'Mako'),
1654 + array('fontname' => 'Maven+Pro'),
1655 + array('fontname' => 'Meddon'),
1656 + array('fontname' => 'MedievalSharp'),
1657 + array('fontname' => 'Megrim'),
1658 + array('fontname' => 'Merriweather'),
1659 + array('fontname' => 'Metrophobic'),
1660 + array('fontname' => 'Michroma'),
1661 + array('fontname' => 'Miltonian+Tattoo'),
1662 + array('fontname' => 'Miltonian'),
1663 + array('fontname' => 'Modern Antiqua'),
1664 + array('fontname' => 'Monofett'),
1665 + array('fontname' => 'Molengo'),
1666 + array('fontname' => 'Mountains of Christmas'),
1667 + array('fontname' => 'Muli'),
1668 + array('fontname' => 'Neucha'),
1669 + array('fontname' => 'Neuton'),
1670 + array('fontname' => 'News+Cycle'),
1671 + array('fontname' => 'Nixie+One'),
1672 + array('fontname' => 'Nobile'),
1673 + array('fontname' => 'Nova+Cut'),
1674 + array('fontname' => 'Nova+Flat'),
1675 + array('fontname' => 'Nova+Mono'),
1676 + array('fontname' => 'Nova+Oval'),
1677 + array('fontname' => 'Nova+Round'),
1678 + array('fontname' => 'Nova+Script'),
1679 + array('fontname' => 'Nova+Slim'),
1680 + array('fontname' => 'Nova+Square'),
1681 + array('fontname' => 'Nunito'),
1682 + array('fontname' => 'OFL+Sorts+Mill+Goudy+TT'),
1683 + array('fontname' => 'Old+Standard+TT'),
1684 + array('fontname' => 'Open+Sans'),
1685 + array('fontname' => 'Orbitron'),
1686 + array('fontname' => 'Oswald'),
1687 + array('fontname' => 'Over+the+Rainbow'),
1688 + array('fontname' => 'Reenie+Beanie'),
1689 + array('fontname' => 'Pacifico'),
1690 + array('fontname' => 'Patrick+Hand'),
1691 + array('fontname' => 'Paytone+One'),
1692 + array('fontname' => 'Permanent+Marker'),
1693 + array('fontname' => 'Philosopher'),
1694 + array('fontname' => 'Play'),
1695 + array('fontname' => 'Playfair+Display'),
1696 + array('fontname' => 'Podkova'),
1697 + array('fontname' => 'PT+Sans'),
1698 + array('fontname' => 'PT+Sans+Narrow'),
1699 + array('fontname' => 'PT+Serif'),
1700 + array('fontname' => 'PT+Serif Caption'),
1701 + array('fontname' => 'Puritan'),
1702 + array('fontname' => 'Quattrocento'),
1703 + array('fontname' => 'Quattrocento+Sans'),
1704 + array('fontname' => 'Radley'),
1705 + array('fontname' => 'Redressed'),
1706 + array('fontname' => 'Rock+Salt'),
1707 + array('fontname' => 'Rokkitt'),
1708 + array('fontname' => 'Ruslan+Display'),
1709 + array('fontname' => 'Schoolbell'),
1710 + array('fontname' => 'Shadows+Into+Light'),
1711 + array('fontname' => 'Shanti'),
1712 + array('fontname' => 'Sigmar+One'),
1713 + array('fontname' => 'Six+Caps'),
1714 + array('fontname' => 'Slackey'),
1715 + array('fontname' => 'Smythe'),
1716 + array('fontname' => 'Special+Elite'),
1717 + array('fontname' => 'Stardos+Stencil'),
1718 + array('fontname' => 'Sue+Ellen+Francisco'),
1719 + array('fontname' => 'Sunshiney'),
1720 + array('fontname' => 'Swanky+and+Moo+Moo'),
1721 + array('fontname' => 'Syncopate'),
1722 + array('fontname' => 'Tangerine'),
1723 + array('fontname' => 'Tenor+Sans'),
1724 + array('fontname' => 'Terminal+Dosis+Light'),
1725 + array('fontname' => 'The+Girl+Next+Door'),
1726 + array('fontname' => 'Tinos'),
1727 + array('fontname' => 'Ubuntu'),
1728 + array('fontname' => 'Ultra'),
1729 + array('fontname' => 'Unkempt'),
1730 + array('fontname' => 'UnifrakturMaguntia'),
1731 + array('fontname' => 'Varela'),
1732 + array('fontname' => 'Varela Round'),
1733 + array('fontname' => 'Vibur'),
1734 + array('fontname' => 'Vollkorn'),
1735 + array('fontname' => 'VT323'),
1736 + array('fontname' => 'Waiting+for+the+Sunrise'),
1737 + array('fontname' => 'Wallpoet'),
1738 + array('fontname' => 'Walter+Turncoat'),
1739 + array('fontname' => 'Wire+One'),
1740 + array('fontname' => 'Yanone+Kaffeesatz'),
1741 + array('fontname' => 'Yeseva+One'),
1742 + array('fontname' => 'Zeyada')
1743 + );
1744 + }
1745 + break;
1746 + default: {
1747 + $error = true;
1748 + $data['msg'] = esc_html__('The operation failed', 'vision');
1749 + }
1750 + break;
1751 + }
1752 + } else {
1753 + $error = true;
1754 + $data['msg'] = esc_html__('The operation failed', 'vision');
1755 + }
714 1756
715 - wp_enqueue_style('vision_admin', $plugin_url . 'assets/css/admin.css', [], VISION_PLUGIN_VERSION, 'all' );
716 - wp_enqueue_style('vision_lucide', $plugin_url . 'assets/vendor/lucide/lucide.css', [], VISION_PLUGIN_VERSION, 'all' );
717 - wp_enqueue_script('vision_admin', $plugin_url . 'assets/js/admin.js', ['jquery'], VISION_PLUGIN_VERSION, false );
718 -
719 - // global settings to help ajax work
720 - $globals = [
721 - 'plan' => VISION_PLUGIN_PLAN,
722 - 'msg_pro_title' => esc_html__('Available only in Pro version', 'vision'),
723 - 'ajax_url' => admin_url('admin-ajax.php'),
724 - 'ajax_nonce' => wp_create_nonce('vision_ajax' ),
725 - 'ajax_msg_error' => esc_html__('Uncaught Error', 'vision') //Look at the console (F12 or Ctrl+Shift+I, Console tab) for more information
726 - ];
727 -
728 - $globals['ajax_action_update'] = $this->ajax_action_settings_update;
729 - $globals['ajax_action_get'] = $this->ajax_action_settings_get;
730 - $globals['ajax_action_modal'] = $this->ajax_action_modal;
731 - $globals['ajax_action_delete_data'] = $this->ajax_action_delete_data;
732 - $globals['config'] = NULL;
733 -
734 - // read settings
735 - $settings_key = 'vision_settings';
736 - $settings_value = get_option($settings_key);
737 - if($settings_value) {
738 - $globals['config'] = wp_json_encode(unserialize($settings_value));
739 - }
740 -
741 - require_once(plugin_dir_path( dirname(__FILE__) ) . 'includes/page-settings.php' );
1757 + if ($error) {
1758 + wp_send_json_error($data);
1759 + } else {
1760 + wp_send_json_success($data);
1761 + }
742 1762
743 - wp_localize_script('vision_admin', 'vision_globals', $globals);
744 - }
745 - }
1763 + wp_die(); // this is required to terminate immediately and return a proper response
1764 + }
746 1765
747 - /**
748 - * Show admin menu upgrade to pro page
749 - */
750 - function admin_menu_page_upgrade_to_pro() {
751 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
752 - if($page==='vision_upgrade_to_pro') {
753 - echo '<script>window.location = "https://1.envato.market/getvision"</script>';
1766 + /**
1767 + * Ajax delete all data from tables
1768 + */
1769 + function ajax_delete_data()
1770 + {
1771 + $error = true;
1772 + $data = [];
1773 + $data['msg'] = esc_html__('The operation failed, can\'t delete data', 'vision');
1774 +
1775 + if (check_ajax_referer('vision_ajax', 'nonce', false)) {
1776 + global $wpdb;
1777 + $table = $wpdb->prefix . VISION_PLUGIN_NAME;
1778 +
1779 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1780 + foreach ($wpdb->get_results("SELECT id FROM {$table}") as $key => $item) {
1781 + // [filemanager] delete file
1782 + if (wp_is_writable(VISION_PLUGIN_UPLOAD_DIR)) {
1783 + $file_json = 'config.json';
1784 + $file_main_css = 'main.css';
1785 + $file_custom_css = 'custom.css';
1786 + $file_root_path = VISION_PLUGIN_UPLOAD_DIR . '/' . $item->id . '/';
1787 +
1788 + if (file_exists($file_root_path . $file_json)) {
1789 + wp_delete_file($file_root_path . $file_json);
1790 + }
1791 + wp_delete_file($file_root_path . $file_main_css);
1792 + wp_delete_file($file_root_path . $file_custom_css);
1793 +
1794 + $wp_filesystem = $this->getFileSystem();
1795 + if ($wp_filesystem->is_dir($file_root_path)) {
1796 + $wp_filesystem->rmdir($file_root_path);
1797 + }
754 1798 }
1799 + }
1800 +
1801 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1802 + $result = $wpdb->query("TRUNCATE TABLE {$table}");
1803 +
1804 + if ($result) {
1805 + $error = false;
1806 + $data['msg'] = esc_html__('All data deleted', 'vision');
1807 + }
755 1808 }
756 1809
757 - /**
758 - * Ajax update item state
759 - */
760 - function ajax_item_update_status() {
761 - $error = false;
762 - $data = [];
763 - $config = filter_input(INPUT_POST, 'config', FILTER_UNSAFE_RAW);
764 -
765 - if(check_ajax_referer('vision_ajax', 'nonce', false)) {
766 - global $wpdb;
767 - $table = $wpdb->prefix . VISION_PLUGIN_NAME;
1810 + if ($error) {
1811 + wp_send_json_error($data);
1812 + } else {
1813 + wp_send_json_success($data);
1814 + }
768 1815
769 - $config = json_decode($config);
770 - $result = false;
771 -
772 - if(isset($config->id) && isset($config->active)) {
773 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
774 - $query = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%s", $config->id);
775 - $item = $wpdb->get_row($query, OBJECT );
776 - // phpcs:enable
777 -
778 - if($item && (current_user_can('manage_options') || get_current_user_id()==$item->author) ) {
779 - $itemData = unserialize($item->data);
780 - $itemData->active = $config->active;
1816 + wp_die(); // this is required to terminate immediately and return a proper response
1817 + }
781 1818
782 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
783 - $result = $wpdb->update(
784 - $table,
785 - ['active' => $itemData->active, 'data' => serialize($itemData)],
786 - ['id' => $config->id]
787 - );
788 - }
789 - }
790 -
791 - if($result) {
792 - $data['id'] = $config->id;
793 - $data['msg'] = esc_html__('The item was successfully updated', 'vision');
794 - } else {
795 - $error = true;
796 - $data['msg'] = esc_html__('The operation failed, can\'t update item', 'vision');
797 - }
798 - } else {
799 - $error = true;
800 - $data['msg'] = esc_html__('The operation failed', 'vision');
801 - }
802 -
803 - if($error) {
804 - wp_send_json_error($data);
805 - } else {
806 - wp_send_json_success($data);
807 - }
808 -
809 - wp_die(); // this is required to terminate immediately and return a proper response
810 - }
811 -
812 - /**
813 - * Ajax update item data
814 - */
815 - function ajax_item_update() {
816 - $error = false;
817 - $data = [];
818 -
819 - if(check_ajax_referer('vision_ajax', 'nonce', false)) {
820 - global $wpdb;
821 - $table = $wpdb->prefix . VISION_PLUGIN_NAME;
822 -
823 - $inputId = filter_input(INPUT_POST, 'id', FILTER_UNSAFE_RAW);
824 - $inputData = filter_input(INPUT_POST, 'data', FILTER_UNSAFE_RAW);
825 - $inputConfig = filter_input(INPUT_POST, 'config', FILTER_UNSAFE_RAW);
826 - $itemData = json_decode($inputData);
827 - $itemConfig = json_decode($inputConfig);
828 - $flag = true;
829 -
830 - if(VISION_PLUGIN_PLAN == 'lite') {
831 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
832 - $rowcount = $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
833 -
834 - if(!($rowcount == 0 || ($rowcount == 1 && $inputId))) {
835 - $flag = false;
836 - $error = true;
837 - $data['msg'] = esc_html__('The operation failed, you can work only with one item. To create more, buy the pro version.', 'vision');
838 - }
839 - }
840 -
841 - if($flag) {
842 - $itemConfig->modified = current_time('mysql', 1);
843 -
844 - if($inputId) {
845 - $result = false;
1819 + /**
1820 + * Ajax settings get data
1821 + */
1822 + function ajax_modal()
1823 + {
1824 + if (check_ajax_referer('vision_ajax', 'nonce', false)) {
1825 + $modalName = sanitize_file_name(filter_input(INPUT_GET, 'name', FILTER_DEFAULT));
1826 + $modalPath = plugin_dir_path(dirname(__FILE__)) . 'includes/modal-' . $modalName . '.php';
846 1827
847 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
848 - $query = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%s", $inputId);
849 - $item = $wpdb->get_row($query, OBJECT);
850 - // phpcs:enable
1828 + if (file_exists($modalPath)) {
1829 + require_once($modalPath);
1830 + }
1831 + }
851 1832
852 - if($item && (current_user_can('manage_options') || get_current_user_id()==$item->author) ) {
853 - $itemData->slug = sanitize_title(($itemData->slug ? $itemData->slug : $itemData->title));
1833 + wp_die(); // this is required to terminate immediately and return a proper response
1834 + }
854 1835
855 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
856 - $result = $wpdb->update(
857 - $table,
858 - [
859 - 'title' => $itemData->title,
860 - 'slug' => $itemData->slug,
861 - 'active' => $itemData->active,
862 - 'data' => serialize($itemData),
863 - 'config' => serialize($itemConfig),
864 - //'author' => get_current_user_id(),
865 - 'editor' => get_current_user_id(),
866 - //'date' => NULL,
867 - 'modified' => current_time('mysql', 1)
868 - ],
869 - ['id' => $inputId]
870 - );
871 - }
872 -
873 - if($result) {
874 - $data['id'] = $inputId;
875 - $data['msg'] = esc_html__('The item was successfully updated', 'vision');
876 - } else {
877 - $error = true;
878 - $data['msg'] = esc_html__('The operation failed, can\'t update item', 'vision');
879 - }
880 - } else {
881 - $itemData->slug = sanitize_title(($itemData->slug ? $itemData->slug : $itemData->title));
1836 + function ajax_change_author()
1837 + {
1838 + $error = false;
1839 + $data = [];
882 1840
883 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
884 - $result = $wpdb->insert(
885 - $table,
886 - [
887 - 'title' => $itemData->title,
888 - 'slug' => $itemData->slug,
889 - 'active' => $itemData->active,
890 - 'data' => serialize($itemData),
891 - 'config' => serialize($itemConfig),
892 - 'author' => get_current_user_id(),
893 - 'editor' => get_current_user_id(),
894 - 'created' => current_time('mysql', 1),
895 - 'modified' => current_time('mysql', 1)
896 - ]);
897 -
898 - if($result) {
899 - $data['id'] = $inputId = $wpdb->insert_id;
900 - $data['msg'] = esc_html__('The item was successfully created', 'vision');
901 - } else {
902 - $error = true;
903 - $data['msg'] = esc_html__('The operation failed, can\'t create item', 'vision');
904 - }
905 - }
906 - }
1841 + if (check_ajax_referer('vision_ajax', 'nonce', false)) {
1842 + global $wpdb;
1843 + $table = $wpdb->prefix . VISION_PLUGIN_NAME;
1844 + $item_id = (int) filter_input(INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT);
1845 + $author_id = (int) filter_input(INPUT_POST, 'author_id', FILTER_SANITIZE_NUMBER_INT);
907 1846
908 - // [filemanager] create an external file
909 - if(!$error && wp_is_writable(VISION_PLUGIN_UPLOAD_DIR)) {
910 - $file_json = 'config.json';
911 - $file_main_css = 'main.css';
912 - $file_custom_css = 'custom.css';
913 - $file_root_path = VISION_PLUGIN_UPLOAD_DIR . '/' . $inputId . '/';
1847 + $target_user = get_userdata($author_id);
1848 + if (!$target_user) {
1849 + $error = true;
1850 + $data['msg'] = esc_html__('Invalid user', 'vision');
1851 + } else {
1852 + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1853 + $item = $wpdb->get_row(
1854 + $wpdb->prepare("SELECT * FROM {$table} WHERE id=%d AND NOT deleted", $item_id)
1855 + );
1856 + // phpcs:enable
914 1857
915 - $wp_filesystem = $this->getFileSystem();
916 - if(!$wp_filesystem->is_dir($file_root_path)) {
917 - $wp_filesystem->mkdir($file_root_path);
918 - }
1858 + $current_user_id = get_current_user_id();
919 1859
920 - if(file_exists($file_root_path . $file_json)) {
921 - wp_delete_file($file_root_path . $file_json);
922 - }
1860 + // permission check, admin - any record, other - only own
1861 + if ($item && (current_user_can('manage_options') || $current_user_id == $item->author)) {
1862 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1863 + $result = $wpdb->update(
1864 + $table,
1865 + [
1866 + 'author' => $author_id,
1867 + 'editor' => $current_user_id,
1868 + 'modified' => current_time('mysql', 1)
1869 + ],
1870 + ['id' => $item_id]
1871 + );
923 1872
924 - $wp_filesystem->put_contents($file_root_path . $file_main_css, $this->getMainCss($itemData, $inputId));
925 - $wp_filesystem->put_contents($file_root_path . $file_custom_css, $itemData->customCSS->data);
926 - }
927 - } else {
928 - $error = true;
929 - $data['msg'] = esc_html__('The operation failed', 'vision');
930 - }
931 -
932 - if($error) {
933 - wp_send_json_error($data);
934 - } else {
935 - wp_send_json_success($data);
936 - }
937 -
938 - wp_die(); // this is required to terminate immediately and return a proper response
939 - }
940 -
941 - /**
942 - * Ajax update settings data
943 - */
944 - function ajax_settings_update() {
945 - $error = false;
946 - $data = [];
947 - $config = filter_input(INPUT_POST, 'config', FILTER_UNSAFE_RAW);
948 -
949 - if(check_ajax_referer('vision_ajax', 'nonce', false)) {
950 - $settings_key = 'vision_settings';
951 - $settings_value = serialize(json_decode($config));
952 - $result = false;
953 -
954 - if(get_option($settings_key) == false) {
955 - $autoload = 'no';
956 - $result = add_option($settings_key, $settings_value, "", $autoload);
957 - } else {
958 - $old_settings_value = get_option($settings_key);
959 - if($old_settings_value === $settings_value) {
960 - $result = true;
961 - } else {
962 - $result = update_option($settings_key, $settings_value);
963 - }
964 - }
965 -
966 - if($result) {
967 - $data['msg'] = esc_html__('The settings were successfully updated', 'vision');
968 - } else {
969 - $error = true;
970 - $data['msg'] = esc_html__('The operation failed, can\'t update settings', 'vision');
971 - }
972 - }
973 -
974 - if($error) {
975 - wp_send_json_error($data);
976 - } else {
977 - wp_send_json_success($data);
978 - }
979 -
980 - wp_die(); // this is required to terminate immediately and return a proper response
981 - }
982 -
983 - /**
984 - * Ajax settings get data
985 - */
986 - function ajax_settings_get() {
987 - $error = false;
988 - $data = [];
989 - $type = sanitize_key(filter_input(INPUT_POST, 'type', FILTER_DEFAULT));
990 -
991 - if(check_ajax_referer('vision_ajax', 'nonce', false)) {
992 - switch($type) {
993 - case 'roles': {
994 - $data['list'] = [];
995 -
996 - $roles = wp_roles()->roles;
997 - foreach($roles as $key => $role) {
998 - if(array_key_exists('read', $role['capabilities'])) {
999 - array_push($data['list'], ['id' => $key, 'name' => translate_user_role($role['name'])]);
1000 - }
1001 - }
1002 - }
1003 - break;
1004 - case 'themes': {
1005 - $data['list'] = [];
1006 -
1007 - $files = glob(plugin_dir_path( dirname(__FILE__) ) . 'assets/themes/*.css');
1008 - foreach($files as $file) {
1009 - $filename = basename($file, '.css');
1010 - array_push($data['list'], ['id' => $filename, 'title' => str_replace('-', ' ', $filename)]);
1011 - }
1012 - }
1013 - break;
1014 - case 'editor-themes': {
1015 - $data['list'] = [];
1016 -
1017 - $files = glob(plugin_dir_path( dirname(__FILE__) ) . 'assets/vendor/ace/theme-*.js');
1018 - foreach($files as $file) {
1019 - $filename = str_replace('theme-','',basename($file, '.js'));
1020 - array_push($data['list'], ['id' => $filename, 'title' => str_replace('_', ' ', $filename)]);
1021 - }
1022 - }
1023 - break;
1024 - case 'fonts': {
1025 - $data['list'] = array(
1026 - array('fontname' => 'none'),
1027 - array('fontname' => 'Aclonica'),
1028 - array('fontname' => 'Allan'),
1029 - array('fontname' => 'Annie+Use+Your+Telescope'),
1030 - array('fontname' => 'Anonymous+Pro'),
1031 - array('fontname' => 'Allerta+Stencil'),
1032 - array('fontname' => 'Allerta'),
1033 - array('fontname' => 'Amaranth'),
1034 - array('fontname' => 'Anton'),
1035 - array('fontname' => 'Architects+Daughter'),
1036 - array('fontname' => 'Arimo'),
1037 - array('fontname' => 'Artifika'),
1038 - array('fontname' => 'Arvo'),
1039 - array('fontname' => 'Asset'),
1040 - array('fontname' => 'Astloch'),
1041 - array('fontname' => 'Bangers'),
1042 - array('fontname' => 'Bentham'),
1043 - array('fontname' => 'Bevan'),
1044 - array('fontname' => 'Bigshot+One'),
1045 - array('fontname' => 'Bowlby+One'),
1046 - array('fontname' => 'Bowlby+One+SC'),
1047 - array('fontname' => 'Brawler'),
1048 - array('fontname' => 'Cabin'),
1049 - array('fontname' => 'Calligraffitti'),
1050 - array('fontname' => 'Candal'),
1051 - array('fontname' => 'Cantarell'),
1052 - array('fontname' => 'Cardo'),
1053 - array('fontname' => 'Carter One'),
1054 - array('fontname' => 'Caudex'),
1055 - array('fontname' => 'Cedarville+Cursive'),
1056 - array('fontname' => 'Cherry+Cream+Soda'),
1057 - array('fontname' => 'Chewy'),
1058 - array('fontname' => 'Coda'),
1059 - array('fontname' => 'Coming+Soon'),
1060 - array('fontname' => 'Copse'),
1061 - array('fontname' => 'Cousine'),
1062 - array('fontname' => 'Covered+By+Your+Grace'),
1063 - array('fontname' => 'Crafty+Girls'),
1064 - array('fontname' => 'Crimson+Text'),
1065 - array('fontname' => 'Crushed'),
1066 - array('fontname' => 'Cuprum'),
1067 - array('fontname' => 'Damion'),
1068 - array('fontname' => 'Dancing+Script'),
1069 - array('fontname' => 'Dawning+of+a+New+Day'),
1070 - array('fontname' => 'Didact+Gothic'),
1071 - array('fontname' => 'Droid+Sans'),
1072 - array('fontname' => 'Droid+Sans+Mono'),
1073 - array('fontname' => 'Droid+Serif'),
1074 - array('fontname' => 'EB+Garamond'),
1075 - array('fontname' => 'Expletus+Sans'),
1076 - array('fontname' => 'Fontdiner+Swanky'),
1077 - array('fontname' => 'Forum'),
1078 - array('fontname' => 'Francois+One'),
1079 - array('fontname' => 'Geo'),
1080 - array('fontname' => 'Give+You+Glory'),
1081 - array('fontname' => 'Goblin+One'),
1082 - array('fontname' => 'Goudy+Bookletter+1911'),
1083 - array('fontname' => 'Gravitas+One'),
1084 - array('fontname' => 'Gruppo'),
1085 - array('fontname' => 'Hammersmith+One'),
1086 - array('fontname' => 'Holtwood+One+SC'),
1087 - array('fontname' => 'Homemade+Apple'),
1088 - array('fontname' => 'Inconsolata'),
1089 - array('fontname' => 'Indie+Flower'),
1090 - array('fontname' => 'IM+Fell+DW+Pica'),
1091 - array('fontname' => 'IM+Fell+DW+Pica+SC'),
1092 - array('fontname' => 'IM+Fell+Double+Pica'),
1093 - array('fontname' => 'IM+Fell+Double+Pica+SC'),
1094 - array('fontname' => 'IM+Fell+English'),
1095 - array('fontname' => 'IM+Fell+English+SC'),
1096 - array('fontname' => 'IM+Fell+French+Canon'),
1097 - array('fontname' => 'IM+Fell+French+Canon+SC'),
1098 - array('fontname' => 'IM+Fell+Great+Primer'),
1099 - array('fontname' => 'IM+Fell+Great+Primer+SC'),
1100 - array('fontname' => 'Irish+Grover'),
1101 - array('fontname' => 'Irish+Growler'),
1102 - array('fontname' => 'Istok+Web'),
1103 - array('fontname' => 'Josefin+Sans'),
1104 - array('fontname' => 'Josefin+Slab'),
1105 - array('fontname' => 'Judson'),
1106 - array('fontname' => 'Jura'),
1107 - array('fontname' => 'Just+Another+Hand'),
1108 - array('fontname' => 'Just+Me+Again+Down+Here'),
1109 - array('fontname' => 'Kameron'),
1110 - array('fontname' => 'Kenia'),
1111 - array('fontname' => 'Kranky'),
1112 - array('fontname' => 'Kreon'),
1113 - array('fontname' => 'Kristi'),
1114 - array('fontname' => 'La+Belle+Aurore'),
1115 - array('fontname' => 'Lato'),
1116 - array('fontname' => 'League+Script'),
1117 - array('fontname' => 'Lekton'),
1118 - array('fontname' => 'Limelight'),
1119 - array('fontname' => 'Lobster'),
1120 - array('fontname' => 'Lobster Two'),
1121 - array('fontname' => 'Lora'),
1122 - array('fontname' => 'Love+Ya+Like+A+Sister'),
1123 - array('fontname' => 'Loved+by+the+King'),
1124 - array('fontname' => 'Luckiest+Guy'),
1125 - array('fontname' => 'Maiden+Orange'),
1126 - array('fontname' => 'Mako'),
1127 - array('fontname' => 'Maven+Pro'),
1128 - array('fontname' => 'Meddon'),
1129 - array('fontname' => 'MedievalSharp'),
1130 - array('fontname' => 'Megrim'),
1131 - array('fontname' => 'Merriweather'),
1132 - array('fontname' => 'Metrophobic'),
1133 - array('fontname' => 'Michroma'),
1134 - array('fontname' => 'Miltonian+Tattoo'),
1135 - array('fontname' => 'Miltonian'),
1136 - array('fontname' => 'Modern Antiqua'),
1137 - array('fontname' => 'Monofett'),
1138 - array('fontname' => 'Molengo'),
1139 - array('fontname' => 'Mountains of Christmas'),
1140 - array('fontname' => 'Muli'),
1141 - array('fontname' => 'Neucha'),
1142 - array('fontname' => 'Neuton'),
1143 - array('fontname' => 'News+Cycle'),
1144 - array('fontname' => 'Nixie+One'),
1145 - array('fontname' => 'Nobile'),
1146 - array('fontname' => 'Nova+Cut'),
1147 - array('fontname' => 'Nova+Flat'),
1148 - array('fontname' => 'Nova+Mono'),
1149 - array('fontname' => 'Nova+Oval'),
1150 - array('fontname' => 'Nova+Round'),
1151 - array('fontname' => 'Nova+Script'),
1152 - array('fontname' => 'Nova+Slim'),
1153 - array('fontname' => 'Nova+Square'),
1154 - array('fontname' => 'Nunito'),
1155 - array('fontname' => 'OFL+Sorts+Mill+Goudy+TT'),
1156 - array('fontname' => 'Old+Standard+TT'),
1157 - array('fontname' => 'Open+Sans'),
1158 - array('fontname' => 'Orbitron'),
1159 - array('fontname' => 'Oswald'),
1160 - array('fontname' => 'Over+the+Rainbow'),
1161 - array('fontname' => 'Reenie+Beanie'),
1162 - array('fontname' => 'Pacifico'),
1163 - array('fontname' => 'Patrick+Hand'),
1164 - array('fontname' => 'Paytone+One'),
1165 - array('fontname' => 'Permanent+Marker'),
1166 - array('fontname' => 'Philosopher'),
1167 - array('fontname' => 'Play'),
1168 - array('fontname' => 'Playfair+Display'),
1169 - array('fontname' => 'Podkova'),
1170 - array('fontname' => 'PT+Sans'),
1171 - array('fontname' => 'PT+Sans+Narrow'),
1172 - array('fontname' => 'PT+Serif'),
1173 - array('fontname' => 'PT+Serif Caption'),
1174 - array('fontname' => 'Puritan'),
1175 - array('fontname' => 'Quattrocento'),
1176 - array('fontname' => 'Quattrocento+Sans'),
1177 - array('fontname' => 'Radley'),
1178 - array('fontname' => 'Redressed'),
1179 - array('fontname' => 'Rock+Salt'),
1180 - array('fontname' => 'Rokkitt'),
1181 - array('fontname' => 'Ruslan+Display'),
1182 - array('fontname' => 'Schoolbell'),
1183 - array('fontname' => 'Shadows+Into+Light'),
1184 - array('fontname' => 'Shanti'),
1185 - array('fontname' => 'Sigmar+One'),
1186 - array('fontname' => 'Six+Caps'),
1187 - array('fontname' => 'Slackey'),
1188 - array('fontname' => 'Smythe'),
1189 - array('fontname' => 'Special+Elite'),
1190 - array('fontname' => 'Stardos+Stencil'),
1191 - array('fontname' => 'Sue+Ellen+Francisco'),
1192 - array('fontname' => 'Sunshiney'),
1193 - array('fontname' => 'Swanky+and+Moo+Moo'),
1194 - array('fontname' => 'Syncopate'),
1195 - array('fontname' => 'Tangerine'),
1196 - array('fontname' => 'Tenor+Sans'),
1197 - array('fontname' => 'Terminal+Dosis+Light'),
1198 - array('fontname' => 'The+Girl+Next+Door'),
1199 - array('fontname' => 'Tinos'),
1200 - array('fontname' => 'Ubuntu'),
1201 - array('fontname' => 'Ultra'),
1202 - array('fontname' => 'Unkempt'),
1203 - array('fontname' => 'UnifrakturMaguntia'),
1204 - array('fontname' => 'Varela'),
1205 - array('fontname' => 'Varela Round'),
1206 - array('fontname' => 'Vibur'),
1207 - array('fontname' => 'Vollkorn'),
1208 - array('fontname' => 'VT323'),
1209 - array('fontname' => 'Waiting+for+the+Sunrise'),
1210 - array('fontname' => 'Wallpoet'),
1211 - array('fontname' => 'Walter+Turncoat'),
1212 - array('fontname' => 'Wire+One'),
1213 - array('fontname' => 'Yanone+Kaffeesatz'),
1214 - array('fontname' => 'Yeseva+One'),
1215 - array('fontname' => 'Zeyada')
1216 - );
1217 - }
1218 - break;
1219 - default: {
1220 - $error = true;
1221 - $data['msg'] = esc_html__('The operation failed', 'vision');
1222 - }
1223 - break;
1224 - }
1225 - } else {
1226 - $error = true;
1227 - $data['msg'] = esc_html__('The operation failed', 'vision');
1228 - }
1229 -
1230 - if($error) {
1231 - wp_send_json_error($data);
1232 - } else {
1233 - wp_send_json_success($data);
1234 - }
1235 -
1236 - wp_die(); // this is required to terminate immediately and return a proper response
1237 - }
1238 -
1239 - /**
1240 - * Ajax delete all data from tables
1241 - */
1242 - function ajax_delete_data() {
1243 - $error = true;
1244 - $data = [];
1245 - $data['msg'] = esc_html__('The operation failed, can\'t delete data', 'vision');
1246 -
1247 - if(check_ajax_referer('vision_ajax', 'nonce', false)) {
1248 - global $wpdb;
1249 - $table = $wpdb->prefix . VISION_PLUGIN_NAME;
1873 + if ($result !== false) {
1874 + $data['id'] = $item_id;
1875 + $data['author_name'] = $target_user->display_name;
1876 + $data['msg'] = esc_html__('Author changed successfully', 'vision');
1877 + } else {
1878 + $error = true;
1879 + $data['msg'] = esc_html__('Failed to update author', 'vision');
1880 + }
1881 + } else {
1882 + $error = true;
1883 + $data['msg'] = esc_html__('You do not have permission to edit this item', 'vision');
1884 + }
1885 + }
1886 + } else {
1887 + $error = true;
1888 + $data['msg'] = esc_html__('Security check failed', 'vision');
1889 + }
1250 1890
1251 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1252 - foreach($wpdb->get_results("SELECT id FROM {$table}") as $key => $item) {
1253 - // [filemanager] delete file
1254 - if(wp_is_writable(VISION_PLUGIN_UPLOAD_DIR)) {
1255 - $file_json = 'config.json';
1256 - $file_main_css = 'main.css';
1257 - $file_custom_css = 'custom.css';
1258 - $file_root_path = VISION_PLUGIN_UPLOAD_DIR . '/' . $item->id . '/';
1891 + if ($error) {
1892 + wp_send_json_error($data);
1893 + } else {
1894 + wp_send_json_success($data);
1895 + }
1259 1896
1260 - if(file_exists($file_root_path . $file_json)) {
1261 - wp_delete_file($file_root_path . $file_json);
1262 - }
1263 - wp_delete_file($file_root_path . $file_main_css);
1264 - wp_delete_file($file_root_path . $file_custom_css);
1897 + wp_die();
1898 + }
1265 1899
1266 - $wp_filesystem = $this->getFileSystem();
1267 - if($wp_filesystem->is_dir($file_root_path)) {
1268 - $wp_filesystem->rmdir($file_root_path);
1269 - }
1270 - }
1271 - }
1900 + function remove_directory($path)
1901 + {
1902 + global $wp_filesystem;
1272 1903
1273 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1274 - $result = $wpdb->query("TRUNCATE TABLE {$table}");
1275 -
1276 - if($result) {
1277 - $error = false;
1278 - $data['msg'] = esc_html__('All data deleted', 'vision');
1279 - }
1280 - }
1281 -
1282 - if($error) {
1283 - wp_send_json_error($data);
1284 - } else {
1285 - wp_send_json_success($data);
1286 - }
1287 -
1288 - wp_die(); // this is required to terminate immediately and return a proper response
1289 - }
1290 -
1291 - /**
1292 - * Ajax settings get data
1293 - */
1294 - function ajax_modal() {
1295 - if(check_ajax_referer('vision_ajax', 'nonce', false)) {
1296 - $modalName = sanitize_file_name(filter_input(INPUT_GET, 'name', FILTER_DEFAULT));
1297 - $modalPath = plugin_dir_path( dirname(__FILE__) ) . 'includes/modal-' . $modalName . '.php';
1298 -
1299 - if(file_exists($modalPath)) {
1300 - require_once( $modalPath );
1301 - }
1302 - }
1303 -
1304 - wp_die(); // this is required to terminate immediately and return a proper response
1305 - }
1306 -}
1307 -?>
1904 + if (empty($wp_filesystem)) {
1905 + require_once ABSPATH . '/wp-admin/includes/file.php';
1906 + WP_Filesystem();
1907 + }
1908 +
1909 + if (!$wp_filesystem) {
1910 + return false;
1911 + }
1912 +
1913 + return $wp_filesystem->rmdir($path, true);
1914 + }
1915 +}