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 +1809 -1221 1.9.2trunk View file →
@@ -1,1327 +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_filter('submenu_file', [$this, 'admin_menu_highlight'], 10, 2);
45 - add_action('admin_footer', [$this, 'admin_footer']);
46 - add_action('admin_notices', [$this, 'admin_notices']);
47 - add_action('in_admin_header', [$this, 'in_admin_header']);
48 - add_action('wp_loaded', [$this, 'page_redirects']);
49 -
50 - // important, because ajax has another url
51 - add_action('wp_ajax_' . $this->ajax_action_item_update, [$this, 'ajax_item_update']);
52 - add_action('wp_ajax_' . $this->ajax_action_item_update_status, [$this, 'ajax_item_update_status']);
53 - add_action('wp_ajax_' . $this->ajax_action_settings_update, [$this, 'ajax_settings_update']);
54 - add_action('wp_ajax_' . $this->ajax_action_settings_get, [$this, 'ajax_settings_get']);
55 - add_action('wp_ajax_' . $this->ajax_action_delete_data, [$this, 'ajax_delete_data']);
56 - add_action('wp_ajax_' . $this->ajax_action_modal, [$this, 'ajax_modal']);
57 - } else {
58 - add_shortcode(VISION_SHORTCODE_NAME, [$this, 'shortcode']);
59 - }
60 -
61 - // only logged users with right roles can preview a vision map
62 - if(array_intersect($allowed_roles, $user->roles) || current_user_can('manage_options')) {
63 - add_filter('do_parse_request', [$this, 'do_parse_request'], 10, 3);
64 - }
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;
65 18
66 - 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']);
67 77 }
68 78
69 - function rest_api_init() {
70 - register_rest_route(
71 - VISION_PLUGIN_REST_URL, '/item/(?P<id>\d+)',
72 - [
73 - 'methods' => 'GET',
74 - 'callback' => [$this, 'rest_api_get_item'],
75 - 'permission_callback' => [$this, 'rest_api_permissions_check']
76 - ]
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
77 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 + }
78 145 }
79 146
80 - function rest_api_get_item($request) {
81 - $id = intval( $request->get_param('id') );
82 - $preview = boolval( $request->get_param('preview') );
147 + closedir($handle);
148 + }
83 149
84 - global $wpdb;
85 - $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 + );
86 161
87 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
88 - $sql = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%d AND NOT deleted", $id);
89 - $item = $wpdb->get_row($sql, OBJECT);
90 - // 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 + }
91 171
92 - $config = null;
93 - if($item->active) {
94 - $config = unserialize($item->config);
95 - } else if($preview) {
96 - $user = wp_get_current_user();
97 - $allowed_roles = $this->getAllowedRoles();
172 + function rest_api_get_item($request)
173 + {
174 + $id = intval($request->get_param('id'));
98 175
99 - if(array_intersect($allowed_roles, $user->roles) || current_user_can('manage_options')) {
100 - $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 . ');';
101 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;
102 478 }
479 + //=============================================
480 + // LAYER BEGIN
481 + $output .= '<div class="vision-layer" data-layer-id="' . esc_attr($layer->id) . '">';
103 482
104 - if($config) {
105 - return new WP_REST_Response($config);
483 + if ($layer->contentData) {
484 + $output .= do_shortcode($layer->contentData);
106 485 }
107 - 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;
108 542 }
109 543
110 - function rest_api_permissions_check() {
111 - 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;
112 556 }
113 557
114 - function filesystem_method() {
115 - 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 + }
116 605 }
117 606
118 - function request_filesystem_credentials() {
119 - 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;
120 618 }
121 619
122 - function getFileSystem() {
123 - global $wp_filesystem;
124 - $result = true;
620 + if (!file_exists(VISION_PLUGIN_UPLOAD_DIR)) {
621 + wp_mkdir_p(VISION_PLUGIN_UPLOAD_DIR);
622 + }
125 623
126 - if(!$wp_filesystem) {
127 - 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 + }
128 633
129 - add_filter('filesystem_method', [$this, 'filesystem_method']);
130 - 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 + }
131 642
132 - $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 . '?>';
133 645
134 - $result = WP_Filesystem($credentials);
646 + $wp_filesystem = $this->getFileSystem();
647 + $wp_filesystem->put_contents(VISION_PLUGIN_UPLOAD_DIR . '/' . 'index.php', $data);
648 + }
649 + }
135 650
136 - remove_filter('filesystem_method', [$this, 'filesystem_method']);
137 - 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 + });
138 750 }
139 751
140 - if($result)
141 - return $wp_filesystem;
142 - 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')]);
143 777 }
144 778
145 - function joinPaths() {
146 - $paths = [];
147 -
148 - foreach(func_get_args() as $arg) {
149 - if($arg !== '') {
150 - $paths[] = $arg;
151 - }
152 - }
153 -
154 - return preg_replace('#/+#','/',join('/', $paths));
155 - }
156 -
157 - function joinUrls() {
158 - $urls = [];
159 -
160 - foreach(func_get_args() as $arg) {
161 - if($arg !== '') {
162 - $urls[] = $arg;
163 - }
164 - }
165 -
166 - return preg_replace('/([^:])(\/{2,})/','$1/',join('/', $urls));
167 - }
168 -
169 - function IsNullOrEmptyString($str) {
170 - return(!isset($str) || trim($str)==='');
171 - }
172 -
173 - function getAllowedRoles() {
174 - $allowed_roles = ['administrator'];
175 -
176 - $settings_key = 'vision_settings';
177 - $settings_value = get_option($settings_key);
178 - if($settings_value) {
179 - $settings = unserialize($settings_value);
180 - if(is_array($settings->roles)) $allowed_roles = array_merge($allowed_roles, $settings->roles);
181 - }
182 -
183 - return $allowed_roles;
184 - }
185 -
186 - function getLoaderGlobals($timestamp) {
187 - $plugin_url = plugin_dir_url(dirname(__FILE__));
188 -
189 - $globals = [
190 - 'plan' => VISION_PLUGIN_PLAN,
191 - 'version' => $timestamp,
192 - 'effects_url' => $plugin_url . 'assets/css/vision-effects.css',
193 - 'theme_base_url' => $plugin_url . 'assets/themes/',
194 - 'plugin_base_url' => $plugin_url . 'assets/vendor/vision/',
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;
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 + }
407 1180
408 - $css = $this->getMainCss($itemData, $id) . ($itemData->customCSS->active ? $itemData->customCSS->data : '');
409 - $css = preg_replace('/[^\/\\\\a-zA-Z0-9\s\_\%\=\[\]\(\)\{\}\:\;\.\,\#\$\-\"\'\']/', '', $css);
1181 + if ($itemData === NULL || $itemConfig === NULL) {
1182 + $flag = false;
1183 + $error = true;
1184 + $data['msg'] = 'Error decoding JSON: ' . json_last_error_msg();
1185 + }
410 1186
411 - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
412 - echo '<style>' . $css . '</style>';
1187 + if ($flag) {
1188 + $itemConfig->modified = current_time('mysql', 1);
413 1189
414 - echo '<!-- vision end -->' . PHP_EOL;
1190 + if ($inputId) {
1191 + $result = false;
415 1192
416 - $output = ob_get_contents(); // get the buffered content into a var
417 - ob_end_clean(); // clean buffer
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
418 1197
419 - return $output;
420 - }
421 -
422 - return '<p>' . esc_html__('Error: the vision item can’t be found', 'vision') . '</p>';
423 - }
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));
424 1200
425 - /**
426 - * Run a filter to obtain some custom url settings, compare them to the current url
427 - * and if a match is found the custom callback is fired, the custom view is loaded
428 - * and request is stopped.
429 - */
430 - function do_parse_request($result) {
431 - if(current_filter() !== 'do_parse_request') {
432 - return $result;
433 - }
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 + }
434 1218
435 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
436 - $url = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
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));
437 1228
438 - if(preg_match('/vision\/map\/([a-z0-9_-]+)/', $url, $matches)) {
439 - $preview = filter_input(INPUT_GET, 'preview', FILTER_SANITIZE_NUMBER_INT);
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 + );
440 1244
441 - global $wpdb;
442 - $table = $wpdb->prefix . VISION_PLUGIN_NAME;
443 - $shortcode = false;
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 + }
444 1258
445 - if(is_numeric($matches[1])) {
446 - $vision_map_id = $matches[1];
1259 + if ($error) {
1260 + wp_send_json_error($data);
1261 + } else {
1262 + wp_send_json_success($data);
1263 + }
447 1264
448 - if($vision_map_id != null) {
449 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
450 - $sql = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%d AND NOT deleted", $vision_map_id);
451 - $item = $wpdb->get_row($sql, OBJECT);
452 - // phpcs:enable
453 -
454 - if($item && ($item->active || (!$item->active && $preview == 1))) {
455 - $this->vision_map_id = $item->id;
456 - $this->vision_map_version = strtotime(mysql2date('Y-m-d H:i:s', $item->modified));
457 - $shortcode = true;
458 - }
459 - }
460 - } else {
461 - $vision_map_slug = $matches[1];
1265 + wp_die(); // this is required to terminate immediately and return a proper response
1266 + }
462 1267
463 - if($vision_map_slug != null) {
464 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
465 - $sql = $wpdb->prepare("SELECT * FROM {$table} WHERE slug=%s AND NOT deleted", $vision_map_slug);
466 - $item = $wpdb->get_row($sql, OBJECT);
467 - // phpcs:enable
468 -
469 - if($item && ($item->active || (!$item->active && $preview == 1))) {
470 - $this->vision_map_id = $item->id;
471 - $this->vision_map_version = strtotime(mysql2date('Y-m-d H:i:s', $item->modified));
472 -
473 - $shortcode = true;
474 - }
475 - }
476 - }
477 -
478 - if($shortcode) {
479 - require_once(plugin_dir_path(dirname(__FILE__)) . 'includes/page-preview.php');
480 - exit();
481 - }
482 - }
483 -
484 - return $result;
485 - }
486 -
487 - /**
488 - * Prepare upload directory
489 - */
490 - function admin_notices() {
491 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
492 - if(!($page==='vision' || $page==='vision_item')) {
493 - return;
494 - }
495 -
496 - if(!file_exists(VISION_PLUGIN_UPLOAD_DIR)) {
497 - wp_mkdir_p(VISION_PLUGIN_UPLOAD_DIR);
498 - }
499 -
500 - if(!file_exists(VISION_PLUGIN_UPLOAD_DIR)) {
501 - echo '<div class="notice notice-error is-dismissible">';
502 - echo '<p>' . esc_html__('The plugin upload directory could not be created', 'vision') . '</p>';
503 - echo '<p>' . esc_html__('Please run the following commands in order to make the directory', 'vision') . '<br>';
504 - echo '<b>mkdir ' . esc_attr(VISION_PLUGIN_UPLOAD_DIR) . '</b><br>';
505 - echo '<b>chmod 777 ' . esc_attr(VISION_PLUGIN_UPLOAD_DIR) . '</b></p>';
506 - echo '</div>';
507 - return;
508 - }
509 -
510 - if(!wp_is_writable(VISION_PLUGIN_UPLOAD_DIR)) {
511 - echo '<div class="notice notice-error is-dismissible">';
512 - echo '<p>' . esc_html__('The plugin upload directory is not writable, therefore the css and js files cannot be saved.', 'vision') . '</p>';
513 - echo '<p>' . esc_html__('Please run the following commands in order to make the directory', 'vision') . '<br>';
514 - echo '<b>chmod 777 ' . esc_attr(VISION_PLUGIN_UPLOAD_DIR) . '</b></p>';
515 - echo '</div>';
516 - return;
517 - }
518 -
519 - if(!file_exists(VISION_PLUGIN_UPLOAD_DIR . '/' . 'index.php')) {
520 - $data = '<?php' . PHP_EOL . '// silence is golden' . PHP_EOL . '?>';
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 + }
521 1277
522 - $wp_filesystem = $this->getFileSystem();
523 - $wp_filesystem->put_contents(VISION_PLUGIN_UPLOAD_DIR . '/' . 'index.php', $data);
524 - }
525 - }
526 -
527 - /**
528 - * Fires at the beginning of the content section in an admin page
529 - */
530 - function in_admin_header() {
531 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
532 -
533 - if(!(($page==='vision') || ($page==='vision_item') || ($page==='vision_settings'))) {
534 - return;
535 - }
536 -
537 - remove_all_actions('admin_notices');
538 - remove_all_actions('all_admin_notices');
539 - add_action('admin_notices', [$this, 'admin_notices']);
540 - }
541 -
542 - /**
543 - * Register the administration menu for this plugin into the WordPress Dashboard menu.
544 - */
545 - function admin_menu() {
546 - // add "edit_posts" if we want to give access to author, editor and contributor roles
547 - add_menu_page(esc_html__('Vision', 'vision'), esc_html__('Vision', 'vision'), 'read', 'vision', [$this, 'admin_menu_page_items'], 'dashicons-format-image');
548 - add_submenu_page('vision', esc_html__('Vision', 'vision'), esc_html__('All Items', 'vision'), 'read', 'vision', [$this, 'admin_menu_page_items']);
549 - add_submenu_page('vision', esc_html__('Vision', 'vision'), esc_html__('Add New', 'vision'), 'read', 'vision_item', [$this, 'admin_menu_page_item']);
550 - add_submenu_page('vision', esc_html__('Vision', 'vision'), esc_html__('Settings', 'vision'), 'manage_options', 'vision_settings', [$this, 'admin_menu_page_settings']);
551 -
552 - 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']);
553 -
554 - }
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');
555 1285
556 - function admin_menu_highlight( $submenu_file, $parent_file ) {
557 - $page = sanitize_key( filter_input(INPUT_GET, 'page', FILTER_DEFAULT ) );
558 - if ( in_array( $page, [ 'vision_item' ] ) ) {
559 - $id = sanitize_key( filter_input(INPUT_GET, 'id', FILTER_DEFAULT ) );
560 - if ( !empty( $id ) ) {
561 - $submenu_file = 'vision';
562 - }
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");
563 1356 }
564 - return $submenu_file;
565 - }
566 1357
567 - function admin_footer() {
568 - if(get_current_screen() && get_current_screen()->base !== 'plugins') {
569 - return;
1358 + natsort($chunks);
1359 +
1360 + foreach ($chunks as $chunk_file) {
1361 + $collected_data[$type] .= file_get_contents($chunk_file);
1362 + wp_delete_file($chunk_file);
570 1363 }
571 1364
572 - $globals = [
573 - 'token' => $this->get_token(),
574 - 'ajax' => [
575 - 'url' => VISION_FEEDBACK_URL
576 - ]
577 - ];
1365 + $collected_data[$type] = json_decode($collected_data[$type]);
578 1366
579 - wp_enqueue_style('vision-feedback', VISION_PLUGIN_URL . 'assets/css/feedback.css', [], VISION_PLUGIN_VERSION);
580 - wp_enqueue_script('vision-feedback', VISION_PLUGIN_URL . 'assets/js/feedback.js', ['jquery'], VISION_PLUGIN_VERSION, false);
581 - wp_localize_script('vision-feedback', 'vision_feedback_globals', $globals);
1367 + if (json_last_error() !== JSON_ERROR_NONE) {
1368 + throw new Exception("Invalid JSON for $type: " . json_last_error_msg());
1369 + }
1370 + }
582 1371
583 - require_once(plugin_dir_path(dirname(__FILE__)) . 'templates/feedback.php');
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);
584 1457 }
1458 + }
585 1459
586 - function get_token() {
587 - global $wp_version;
588 - $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);
589 1469
590 - $data = [
591 - 'plugin_name' => VISION_PLUGIN_NAME,
592 - 'plugin_version' => VISION_PLUGIN_VERSION,
593 - 'wordpress' => $wp_version,
594 - 'php' => PHP_VERSION,
595 - 'email' => $current_user->user_email,
596 - 'site' => trim(str_replace(['http://', 'https://'], '', get_site_url()), '/')
597 - ];
598 - 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();
599 1498 }
600 -
601 - /**
602 - * Custom redirects
603 - */
604 - function page_redirects() {
605 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
606 1499
607 - if($page==='vision') {
608 - $action = sanitize_key(filter_input(INPUT_GET, 'action', FILTER_DEFAULT));
609 - if($action) {
610 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
611 - $url = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) );
1500 + if ($error) {
1501 + wp_send_json_error($data);
1502 + } else {
1503 + wp_send_json_success($data);
1504 + }
612 1505
613 - $url = remove_query_arg(['action', 'id', '_wpnonce'], $url );
614 - header('Refresh:0; url="' . $url . '"', true, 303);
615 - //wp_redirect($url); // does not work delete and dublicate operations on XAMPP
616 - }
617 - }
618 - }
619 -
620 - /**
621 - * Show admin menu items page
622 - */
623 - function admin_menu_page_items() {
624 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
625 -
626 - if($page==='vision') {
627 - $plugin_url = plugin_dir_url( dirname(__FILE__) );
628 - $upload_dir = wp_upload_dir();
1506 + wp_die(); // this is required to terminate immediately and return a proper response
1507 + }
629 1508
630 - wp_enqueue_style('vision_admin', $plugin_url . 'assets/css/admin.css', [], VISION_PLUGIN_VERSION, 'all' );
631 - wp_enqueue_style('vision_lucide', $plugin_url . 'assets/vendor/lucide/lucide.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));
632 1517
633 - wp_enqueue_script('vision_admin', $plugin_url . 'assets/js/admin.js', ['jquery'], VISION_PLUGIN_VERSION, false );
634 -
635 - // global settings to help ajax work
636 - $globals = [
637 - 'plan' => VISION_PLUGIN_PLAN,
638 - 'msg_pro_title' => esc_html__('Available only in Pro version', 'vision'),
639 - 'upload_url' => $upload_dir['baseurl'],
640 - 'ajax_url' => admin_url('admin-ajax.php'),
641 - 'ajax_nonce' => wp_create_nonce('vision_ajax' ),
642 - 'ajax_msg_error' => esc_html__('Uncaught Error', 'vision') //Look at the console (F12 or Ctrl+Shift+I, Console tab) for more information
643 - ];
644 - $globals['ajax_action_update'] = $this->ajax_action_item_update_status;
645 -
646 - require_once(plugin_dir_path( dirname(__FILE__) ) . 'includes/list-table-items.php');
647 - require_once(plugin_dir_path( dirname(__FILE__) ) . 'includes/page-items.php');
1518 + if (check_ajax_referer('vision_ajax', 'nonce', false)) {
1519 + switch ($type) {
1520 + case 'roles': {
1521 + $data['list'] = [];
648 1522
649 - wp_localize_script('vision_admin', 'vision_globals', $globals);
650 - }
651 - }
652 -
653 - /**
654 - * Show admin menu item page
655 - */
656 - function admin_menu_page_item() {
657 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
658 - if($page==='vision_item') {
659 - $id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT);
660 - if ( VISION_PLUGIN_PLAN == 'lite' && !$id ) {
661 - global $wpdb;
662 - $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'] = [];
663 1533
664 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
665 - $count = $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
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'] = [];
666 1543
667 - if ( $count >= 3 ) {
668 - echo '<div class="notice notice-error is-dismissible">';
669 - echo '<p>Vision: ' . esc_html__('You can create only 3 maps. If you need more, upgrade to the pro version.', 'vision') . '</p>';
670 - echo '</div>';
671 - return;
672 - }
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)]);
673 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 + }
674 1756
675 - $plugin_url = plugin_dir_url(dirname(__FILE__));
676 - $upload_dir = wp_upload_dir();
1757 + if ($error) {
1758 + wp_send_json_error($data);
1759 + } else {
1760 + wp_send_json_success($data);
1761 + }
677 1762
678 - wp_enqueue_style('vision_admin', $plugin_url . 'assets/css/admin.css', [], VISION_PLUGIN_VERSION, 'all' );
679 - wp_enqueue_style('vision_notify', $plugin_url . 'assets/css/notify.css', [], VISION_PLUGIN_VERSION, 'all' );
680 - wp_enqueue_style('vision_lucide', $plugin_url . 'assets/vendor/lucide/lucide.css', [], VISION_PLUGIN_VERSION, 'all' );
681 - wp_enqueue_style('vision_vision_effects', $plugin_url . 'assets/css/vision-effects.css', [], VISION_PLUGIN_VERSION, 'all' );
1763 + wp_die(); // this is required to terminate immediately and return a proper response
1764 + }
682 1765
683 - wp_enqueue_script('vision_notify', $plugin_url . 'assets/js/notify.js', ['jquery'], VISION_PLUGIN_VERSION, false );
684 - wp_enqueue_script('vision_ace', $plugin_url . 'assets/vendor/ace/ace.js', [], VISION_PLUGIN_VERSION, false );
685 - wp_enqueue_script('vision_url', $plugin_url . 'assets/vendor/url/url.js', [], VISION_PLUGIN_VERSION, false );
686 - wp_enqueue_script('vision_admin', $plugin_url . 'assets/js/admin.js', ['jquery'], VISION_PLUGIN_VERSION, false );
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');
687 1774
688 - wp_enqueue_media();
689 -
690 - // global settings to help ajax work
691 - $globals = [
692 - 'plan' => VISION_PLUGIN_PLAN,
693 - 'msg_pro_title' => esc_html__('Available only in Pro version', 'vision'),
694 - 'msg_custom_js_error' => esc_html__('Custom js code error', 'vision'),
695 - 'msg_layer_id_error' => esc_html__('The layer ID should be unique', 'vision'),
696 - 'wp_base_url' => get_site_url(),
697 - 'upload_base_url' => $upload_dir['baseurl'],
698 - 'plugin_base_url' => $plugin_url,
699 - 'ajax_url' => admin_url('admin-ajax.php'),
700 - 'ajax_nonce' => wp_create_nonce('vision_ajax'),
701 - 'ajax_msg_error' => esc_html__('Uncaught Error', 'vision') //Look at the console (F12 or Ctrl+Shift+I, Console tab) for more information
702 - ];
1775 + if (check_ajax_referer('vision_ajax', 'nonce', false)) {
1776 + global $wpdb;
1777 + $table = $wpdb->prefix . VISION_PLUGIN_NAME;
703 1778
704 - $globals['ajax_action_get'] = $this->ajax_action_settings_get;
705 - $globals['ajax_action_update'] = $this->ajax_action_item_update;
706 - $globals['ajax_action_modal'] = $this->ajax_action_modal;
707 - $globals['ajax_item_id'] = $id;
708 - $globals['settings'] = NULL;
709 - $globals['config'] = NULL;
710 -
711 - $settings_key = 'vision_settings';
712 - $settings_value = get_option($settings_key);
713 - if($settings_value) {
714 - $globals['settings'] = unserialize($settings_value); // json_encode(unserialize($settings_value)) problem with double quotes
715 - }
716 -
717 - // get item data from DB
718 - if($id) {
719 - global $wpdb;
720 - $table = $wpdb->prefix . VISION_PLUGIN_NAME;
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 . '/';
721 1787
722 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
723 - $query = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%s", $id);
724 - $item = $wpdb->get_row($query, OBJECT);
725 - // phpcs:enable
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);
726 1793
727 - if($item) {
728 - $globals['config'] = unserialize($item->data); // json_encode(unserialize($item->data)) problem with double quotes
729 - }
730 - } else {
731 - // new item
732 - $item = (object) [
733 - 'author' => get_current_user_id(),
734 - 'editor' => get_current_user_id(),
735 - 'created' => current_time('mysql', 1),
736 - 'modified' => current_time('mysql', 1)
737 - ];
738 - }
739 -
740 - require_once( plugin_dir_path( dirname(__FILE__) ) . 'includes/page-item.php' );
741 -
742 - // set global settings
743 - wp_localize_script('vision_admin', 'vision_globals', $globals);
744 - }
745 - }
746 -
747 - /**
748 - * Show admin menu settings page
749 - */
750 - function admin_menu_page_settings() {
751 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
752 - if($page==='vision_settings') {
753 - $plugin_url = plugin_dir_url(dirname(__FILE__));
1794 + $wp_filesystem = $this->getFileSystem();
1795 + if ($wp_filesystem->is_dir($file_root_path)) {
1796 + $wp_filesystem->rmdir($file_root_path);
1797 + }
1798 + }
1799 + }
754 1800
755 - wp_enqueue_style('vision_admin', $plugin_url . 'assets/css/admin.css', [], VISION_PLUGIN_VERSION, 'all' );
756 - wp_enqueue_style('vision_lucide', $plugin_url . 'assets/vendor/lucide/lucide.css', [], VISION_PLUGIN_VERSION, 'all' );
757 - wp_enqueue_script('vision_admin', $plugin_url . 'assets/js/admin.js', ['jquery'], VISION_PLUGIN_VERSION, false );
758 -
759 - // global settings to help ajax work
760 - $globals = [
761 - 'plan' => VISION_PLUGIN_PLAN,
762 - 'msg_pro_title' => esc_html__('Available only in Pro version', 'vision'),
763 - 'ajax_url' => admin_url('admin-ajax.php'),
764 - 'ajax_nonce' => wp_create_nonce('vision_ajax' ),
765 - 'ajax_msg_error' => esc_html__('Uncaught Error', 'vision') //Look at the console (F12 or Ctrl+Shift+I, Console tab) for more information
766 - ];
767 -
768 - $globals['ajax_action_update'] = $this->ajax_action_settings_update;
769 - $globals['ajax_action_get'] = $this->ajax_action_settings_get;
770 - $globals['ajax_action_modal'] = $this->ajax_action_modal;
771 - $globals['ajax_action_delete_data'] = $this->ajax_action_delete_data;
772 - $globals['config'] = NULL;
773 -
774 - // read settings
775 - $settings_key = 'vision_settings';
776 - $settings_value = get_option($settings_key);
777 - if($settings_value) {
778 - $globals['config'] = wp_json_encode(unserialize($settings_value));
779 - }
780 -
781 - require_once(plugin_dir_path( dirname(__FILE__) ) . 'includes/page-settings.php' );
1801 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1802 + $result = $wpdb->query("TRUNCATE TABLE {$table}");
782 1803
783 - wp_localize_script('vision_admin', 'vision_globals', $globals);
784 - }
785 - }
1804 + if ($result) {
1805 + $error = false;
1806 + $data['msg'] = esc_html__('All data deleted', 'vision');
1807 + }
1808 + }
786 1809
787 - /**
788 - * Show admin menu upgrade to pro page
789 - */
790 - function admin_menu_page_upgrade_to_pro() {
791 - $page = sanitize_key(filter_input(INPUT_GET, 'page', FILTER_DEFAULT));
792 - if($page==='vision_upgrade_to_pro') {
793 - echo '<script>window.location = "https://1.envato.market/getvision"</script>';
794 - }
1810 + if ($error) {
1811 + wp_send_json_error($data);
1812 + } else {
1813 + wp_send_json_success($data);
795 1814 }
796 1815
797 - /**
798 - * Ajax update item state
799 - */
800 - function ajax_item_update_status() {
801 - $error = false;
802 - $data = [];
803 - $config = filter_input(INPUT_POST, 'config', FILTER_UNSAFE_RAW);
804 -
805 - if(check_ajax_referer('vision_ajax', 'nonce', false)) {
806 - global $wpdb;
807 - $table = $wpdb->prefix . VISION_PLUGIN_NAME;
1816 + wp_die(); // this is required to terminate immediately and return a proper response
1817 + }
808 1818
809 - $config = json_decode($config);
810 - $result = false;
811 -
812 - if(isset($config->id) && isset($config->active)) {
813 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
814 - $query = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%s", $config->id);
815 - $item = $wpdb->get_row($query, OBJECT );
816 - // phpcs:enable
817 -
818 - if($item && (current_user_can('manage_options') || get_current_user_id()==$item->author) ) {
819 - $itemData = unserialize($item->data);
820 - $itemData->active = $config->active;
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';
821 1827
822 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
823 - $result = $wpdb->update(
824 - $table,
825 - ['active' => $itemData->active, 'data' => serialize($itemData)],
826 - ['id' => $config->id]
827 - );
828 - }
829 - }
830 -
831 - if($result) {
832 - $data['id'] = $config->id;
833 - $data['msg'] = esc_html__('The item was successfully updated', 'vision');
834 - } else {
835 - $error = true;
836 - $data['msg'] = esc_html__('The operation failed, can\'t update item', 'vision');
837 - }
838 - } else {
839 - $error = true;
840 - $data['msg'] = esc_html__('The operation failed', 'vision');
841 - }
842 -
843 - if($error) {
844 - wp_send_json_error($data);
845 - } else {
846 - wp_send_json_success($data);
847 - }
848 -
849 - wp_die(); // this is required to terminate immediately and return a proper response
850 - }
851 -
852 - /**
853 - * Ajax update item data
854 - */
855 - function ajax_item_update() {
856 - $error = false;
857 - $data = [];
858 -
859 - if(check_ajax_referer('vision_ajax', 'nonce', false)) {
860 - global $wpdb;
861 - $table = $wpdb->prefix . VISION_PLUGIN_NAME;
862 -
863 - $inputId = filter_input(INPUT_POST, 'id', FILTER_UNSAFE_RAW);
864 - $inputData = filter_input(INPUT_POST, 'data', FILTER_UNSAFE_RAW);
865 - $inputConfig = filter_input(INPUT_POST, 'config', FILTER_UNSAFE_RAW);
866 - $itemData = json_decode($inputData);
867 - $itemConfig = json_decode($inputConfig);
868 - $flag = true;
869 -
870 - if( VISION_PLUGIN_PLAN == 'lite' && !$inputId ) {
871 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
872 - $count = $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
1828 + if (file_exists($modalPath)) {
1829 + require_once($modalPath);
1830 + }
1831 + }
873 1832
874 - if ( $count >= 3 ) {
875 - $flag = false;
876 - $error = true;
877 - $data['msg'] = esc_html__('You can create only 3 maps. If you need more, upgrade to the pro version.', 'vision');
878 - }
879 - }
880 -
881 - if($flag) {
882 - $itemConfig->modified = current_time('mysql', 1);
883 -
884 - if($inputId) {
885 - $result = false;
1833 + wp_die(); // this is required to terminate immediately and return a proper response
1834 + }
886 1835
887 - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
888 - $query = $wpdb->prepare("SELECT * FROM {$table} WHERE id=%s", $inputId);
889 - $item = $wpdb->get_row($query, OBJECT);
890 - // phpcs:enable
1836 + function ajax_change_author()
1837 + {
1838 + $error = false;
1839 + $data = [];
891 1840
892 - if($item && (current_user_can('manage_options') || get_current_user_id()==$item->author) ) {
893 - $itemData->slug = sanitize_title(($itemData->slug ? $itemData->slug : $itemData->title));
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);
894 1846
895 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
896 - $result = $wpdb->update(
897 - $table,
898 - [
899 - 'title' => $itemData->title,
900 - 'slug' => $itemData->slug,
901 - 'active' => $itemData->active,
902 - 'data' => serialize($itemData),
903 - 'config' => serialize($itemConfig),
904 - //'author' => get_current_user_id(),
905 - 'editor' => get_current_user_id(),
906 - //'date' => NULL,
907 - 'modified' => current_time('mysql', 1)
908 - ],
909 - ['id' => $inputId]
910 - );
911 - }
912 -
913 - if($result) {
914 - $data['id'] = $inputId;
915 - $data['msg'] = esc_html__('The item was successfully updated', 'vision');
916 - } else {
917 - $error = true;
918 - $data['msg'] = esc_html__('The operation failed, can\'t update item', 'vision');
919 - }
920 - } else {
921 - $itemData->slug = sanitize_title(($itemData->slug ? $itemData->slug : $itemData->title));
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
922 1857
923 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
924 - $result = $wpdb->insert(
925 - $table,
926 - [
927 - 'title' => $itemData->title,
928 - 'slug' => $itemData->slug,
929 - 'active' => $itemData->active,
930 - 'data' => serialize($itemData),
931 - 'config' => serialize($itemConfig),
932 - 'author' => get_current_user_id(),
933 - 'editor' => get_current_user_id(),
934 - 'created' => current_time('mysql', 1),
935 - 'modified' => current_time('mysql', 1)
936 - ]);
937 -
938 - if($result) {
939 - $data['id'] = $inputId = $wpdb->insert_id;
940 - $data['msg'] = esc_html__('The item was successfully created', 'vision');
941 - } else {
942 - $error = true;
943 - $data['msg'] = esc_html__('The operation failed, can\'t create item', 'vision');
944 - }
945 - }
946 - }
947 - } else {
948 - $error = true;
949 - $data['msg'] = esc_html__('The operation failed', 'vision');
950 - }
951 -
952 - if($error) {
953 - wp_send_json_error($data);
954 - } else {
955 - wp_send_json_success($data);
956 - }
957 -
958 - wp_die(); // this is required to terminate immediately and return a proper response
959 - }
960 -
961 - /**
962 - * Ajax update settings data
963 - */
964 - function ajax_settings_update() {
965 - $error = false;
966 - $data = [];
967 - $config = filter_input(INPUT_POST, 'config', FILTER_UNSAFE_RAW);
968 -
969 - if(check_ajax_referer('vision_ajax', 'nonce', false)) {
970 - $settings_key = 'vision_settings';
971 - $settings_value = serialize(json_decode($config));
972 - $result = false;
973 -
974 - if(get_option($settings_key) == false) {
975 - $autoload = 'no';
976 - $result = add_option($settings_key, $settings_value, "", $autoload);
977 - } else {
978 - $old_settings_value = get_option($settings_key);
979 - if($old_settings_value === $settings_value) {
980 - $result = true;
981 - } else {
982 - $result = update_option($settings_key, $settings_value);
983 - }
984 - }
985 -
986 - if($result) {
987 - $data['msg'] = esc_html__('The settings were successfully updated', 'vision');
988 - } else {
989 - $error = true;
990 - $data['msg'] = esc_html__('The operation failed, can\'t update settings', 'vision');
991 - }
992 - }
993 -
994 - if($error) {
995 - wp_send_json_error($data);
996 - } else {
997 - wp_send_json_success($data);
998 - }
999 -
1000 - wp_die(); // this is required to terminate immediately and return a proper response
1001 - }
1002 -
1003 - /**
1004 - * Ajax settings get data
1005 - */
1006 - function ajax_settings_get() {
1007 - $error = false;
1008 - $data = [];
1009 - $type = sanitize_key(filter_input(INPUT_POST, 'type', FILTER_DEFAULT));
1010 -
1011 - if(check_ajax_referer('vision_ajax', 'nonce', false)) {
1012 - switch($type) {
1013 - case 'roles': {
1014 - $data['list'] = [];
1015 -
1016 - $roles = wp_roles()->roles;
1017 - foreach($roles as $key => $role) {
1018 - if(array_key_exists('read', $role['capabilities'])) {
1019 - array_push($data['list'], ['id' => $key, 'name' => translate_user_role($role['name'])]);
1020 - }
1021 - }
1022 - }
1023 - break;
1024 - case 'themes': {
1025 - $data['list'] = [];
1026 -
1027 - $files = glob(plugin_dir_path( dirname(__FILE__) ) . 'assets/themes/*.css');
1028 - foreach($files as $file) {
1029 - $filename = basename($file, '.css');
1030 - array_push($data['list'], ['id' => $filename, 'title' => str_replace('-', ' ', $filename)]);
1031 - }
1032 - }
1033 - break;
1034 - case 'editor-themes': {
1035 - $data['list'] = [];
1036 -
1037 - $files = glob(plugin_dir_path( dirname(__FILE__) ) . 'assets/vendor/ace/theme-*.js');
1038 - foreach($files as $file) {
1039 - $filename = str_replace('theme-','',basename($file, '.js'));
1040 - array_push($data['list'], ['id' => $filename, 'title' => str_replace('_', ' ', $filename)]);
1041 - }
1042 - }
1043 - break;
1044 - case 'fonts': {
1045 - $data['list'] = array(
1046 - array('fontname' => 'none'),
1047 - array('fontname' => 'Aclonica'),
1048 - array('fontname' => 'Allan'),
1049 - array('fontname' => 'Annie+Use+Your+Telescope'),
1050 - array('fontname' => 'Anonymous+Pro'),
1051 - array('fontname' => 'Allerta+Stencil'),
1052 - array('fontname' => 'Allerta'),
1053 - array('fontname' => 'Amaranth'),
1054 - array('fontname' => 'Anton'),
1055 - array('fontname' => 'Architects+Daughter'),
1056 - array('fontname' => 'Arimo'),
1057 - array('fontname' => 'Artifika'),
1058 - array('fontname' => 'Arvo'),
1059 - array('fontname' => 'Asset'),
1060 - array('fontname' => 'Astloch'),
1061 - array('fontname' => 'Bangers'),
1062 - array('fontname' => 'Bentham'),
1063 - array('fontname' => 'Bevan'),
1064 - array('fontname' => 'Bigshot+One'),
1065 - array('fontname' => 'Bowlby+One'),
1066 - array('fontname' => 'Bowlby+One+SC'),
1067 - array('fontname' => 'Brawler'),
1068 - array('fontname' => 'Cabin'),
1069 - array('fontname' => 'Calligraffitti'),
1070 - array('fontname' => 'Candal'),
1071 - array('fontname' => 'Cantarell'),
1072 - array('fontname' => 'Cardo'),
1073 - array('fontname' => 'Carter One'),
1074 - array('fontname' => 'Caudex'),
1075 - array('fontname' => 'Cedarville+Cursive'),
1076 - array('fontname' => 'Cherry+Cream+Soda'),
1077 - array('fontname' => 'Chewy'),
1078 - array('fontname' => 'Coda'),
1079 - array('fontname' => 'Coming+Soon'),
1080 - array('fontname' => 'Copse'),
1081 - array('fontname' => 'Cousine'),
1082 - array('fontname' => 'Covered+By+Your+Grace'),
1083 - array('fontname' => 'Crafty+Girls'),
1084 - array('fontname' => 'Crimson+Text'),
1085 - array('fontname' => 'Crushed'),
1086 - array('fontname' => 'Cuprum'),
1087 - array('fontname' => 'Damion'),
1088 - array('fontname' => 'Dancing+Script'),
1089 - array('fontname' => 'Dawning+of+a+New+Day'),
1090 - array('fontname' => 'Didact+Gothic'),
1091 - array('fontname' => 'Droid+Sans'),
1092 - array('fontname' => 'Droid+Sans+Mono'),
1093 - array('fontname' => 'Droid+Serif'),
1094 - array('fontname' => 'EB+Garamond'),
1095 - array('fontname' => 'Expletus+Sans'),
1096 - array('fontname' => 'Fontdiner+Swanky'),
1097 - array('fontname' => 'Forum'),
1098 - array('fontname' => 'Francois+One'),
1099 - array('fontname' => 'Geo'),
1100 - array('fontname' => 'Give+You+Glory'),
1101 - array('fontname' => 'Goblin+One'),
1102 - array('fontname' => 'Goudy+Bookletter+1911'),
1103 - array('fontname' => 'Gravitas+One'),
1104 - array('fontname' => 'Gruppo'),
1105 - array('fontname' => 'Hammersmith+One'),
1106 - array('fontname' => 'Holtwood+One+SC'),
1107 - array('fontname' => 'Homemade+Apple'),
1108 - array('fontname' => 'Inconsolata'),
1109 - array('fontname' => 'Indie+Flower'),
1110 - array('fontname' => 'IM+Fell+DW+Pica'),
1111 - array('fontname' => 'IM+Fell+DW+Pica+SC'),
1112 - array('fontname' => 'IM+Fell+Double+Pica'),
1113 - array('fontname' => 'IM+Fell+Double+Pica+SC'),
1114 - array('fontname' => 'IM+Fell+English'),
1115 - array('fontname' => 'IM+Fell+English+SC'),
1116 - array('fontname' => 'IM+Fell+French+Canon'),
1117 - array('fontname' => 'IM+Fell+French+Canon+SC'),
1118 - array('fontname' => 'IM+Fell+Great+Primer'),
1119 - array('fontname' => 'IM+Fell+Great+Primer+SC'),
1120 - array('fontname' => 'Irish+Grover'),
1121 - array('fontname' => 'Irish+Growler'),
1122 - array('fontname' => 'Istok+Web'),
1123 - array('fontname' => 'Josefin+Sans'),
1124 - array('fontname' => 'Josefin+Slab'),
1125 - array('fontname' => 'Judson'),
1126 - array('fontname' => 'Jura'),
1127 - array('fontname' => 'Just+Another+Hand'),
1128 - array('fontname' => 'Just+Me+Again+Down+Here'),
1129 - array('fontname' => 'Kameron'),
1130 - array('fontname' => 'Kenia'),
1131 - array('fontname' => 'Kranky'),
1132 - array('fontname' => 'Kreon'),
1133 - array('fontname' => 'Kristi'),
1134 - array('fontname' => 'La+Belle+Aurore'),
1135 - array('fontname' => 'Lato'),
1136 - array('fontname' => 'League+Script'),
1137 - array('fontname' => 'Lekton'),
1138 - array('fontname' => 'Limelight'),
1139 - array('fontname' => 'Lobster'),
1140 - array('fontname' => 'Lobster Two'),
1141 - array('fontname' => 'Lora'),
1142 - array('fontname' => 'Love+Ya+Like+A+Sister'),
1143 - array('fontname' => 'Loved+by+the+King'),
1144 - array('fontname' => 'Luckiest+Guy'),
1145 - array('fontname' => 'Maiden+Orange'),
1146 - array('fontname' => 'Mako'),
1147 - array('fontname' => 'Maven+Pro'),
1148 - array('fontname' => 'Meddon'),
1149 - array('fontname' => 'MedievalSharp'),
1150 - array('fontname' => 'Megrim'),
1151 - array('fontname' => 'Merriweather'),
1152 - array('fontname' => 'Metrophobic'),
1153 - array('fontname' => 'Michroma'),
1154 - array('fontname' => 'Miltonian+Tattoo'),
1155 - array('fontname' => 'Miltonian'),
1156 - array('fontname' => 'Modern Antiqua'),
1157 - array('fontname' => 'Monofett'),
1158 - array('fontname' => 'Molengo'),
1159 - array('fontname' => 'Mountains of Christmas'),
1160 - array('fontname' => 'Muli'),
1161 - array('fontname' => 'Neucha'),
1162 - array('fontname' => 'Neuton'),
1163 - array('fontname' => 'News+Cycle'),
1164 - array('fontname' => 'Nixie+One'),
1165 - array('fontname' => 'Nobile'),
1166 - array('fontname' => 'Nova+Cut'),
1167 - array('fontname' => 'Nova+Flat'),
1168 - array('fontname' => 'Nova+Mono'),
1169 - array('fontname' => 'Nova+Oval'),
1170 - array('fontname' => 'Nova+Round'),
1171 - array('fontname' => 'Nova+Script'),
1172 - array('fontname' => 'Nova+Slim'),
1173 - array('fontname' => 'Nova+Square'),
1174 - array('fontname' => 'Nunito'),
1175 - array('fontname' => 'OFL+Sorts+Mill+Goudy+TT'),
1176 - array('fontname' => 'Old+Standard+TT'),
1177 - array('fontname' => 'Open+Sans'),
1178 - array('fontname' => 'Orbitron'),
1179 - array('fontname' => 'Oswald'),
1180 - array('fontname' => 'Over+the+Rainbow'),
1181 - array('fontname' => 'Reenie+Beanie'),
1182 - array('fontname' => 'Pacifico'),
1183 - array('fontname' => 'Patrick+Hand'),
1184 - array('fontname' => 'Paytone+One'),
1185 - array('fontname' => 'Permanent+Marker'),
1186 - array('fontname' => 'Philosopher'),
1187 - array('fontname' => 'Play'),
1188 - array('fontname' => 'Playfair+Display'),
1189 - array('fontname' => 'Podkova'),
1190 - array('fontname' => 'PT+Sans'),
1191 - array('fontname' => 'PT+Sans+Narrow'),
1192 - array('fontname' => 'PT+Serif'),
1193 - array('fontname' => 'PT+Serif Caption'),
1194 - array('fontname' => 'Puritan'),
1195 - array('fontname' => 'Quattrocento'),
1196 - array('fontname' => 'Quattrocento+Sans'),
1197 - array('fontname' => 'Radley'),
1198 - array('fontname' => 'Redressed'),
1199 - array('fontname' => 'Rock+Salt'),
1200 - array('fontname' => 'Rokkitt'),
1201 - array('fontname' => 'Ruslan+Display'),
1202 - array('fontname' => 'Schoolbell'),
1203 - array('fontname' => 'Shadows+Into+Light'),
1204 - array('fontname' => 'Shanti'),
1205 - array('fontname' => 'Sigmar+One'),
1206 - array('fontname' => 'Six+Caps'),
1207 - array('fontname' => 'Slackey'),
1208 - array('fontname' => 'Smythe'),
1209 - array('fontname' => 'Special+Elite'),
1210 - array('fontname' => 'Stardos+Stencil'),
1211 - array('fontname' => 'Sue+Ellen+Francisco'),
1212 - array('fontname' => 'Sunshiney'),
1213 - array('fontname' => 'Swanky+and+Moo+Moo'),
1214 - array('fontname' => 'Syncopate'),
1215 - array('fontname' => 'Tangerine'),
1216 - array('fontname' => 'Tenor+Sans'),
1217 - array('fontname' => 'Terminal+Dosis+Light'),
1218 - array('fontname' => 'The+Girl+Next+Door'),
1219 - array('fontname' => 'Tinos'),
1220 - array('fontname' => 'Ubuntu'),
1221 - array('fontname' => 'Ultra'),
1222 - array('fontname' => 'Unkempt'),
1223 - array('fontname' => 'UnifrakturMaguntia'),
1224 - array('fontname' => 'Varela'),
1225 - array('fontname' => 'Varela Round'),
1226 - array('fontname' => 'Vibur'),
1227 - array('fontname' => 'Vollkorn'),
1228 - array('fontname' => 'VT323'),
1229 - array('fontname' => 'Waiting+for+the+Sunrise'),
1230 - array('fontname' => 'Wallpoet'),
1231 - array('fontname' => 'Walter+Turncoat'),
1232 - array('fontname' => 'Wire+One'),
1233 - array('fontname' => 'Yanone+Kaffeesatz'),
1234 - array('fontname' => 'Yeseva+One'),
1235 - array('fontname' => 'Zeyada')
1236 - );
1237 - }
1238 - break;
1239 - default: {
1240 - $error = true;
1241 - $data['msg'] = esc_html__('The operation failed', 'vision');
1242 - }
1243 - break;
1244 - }
1245 - } else {
1246 - $error = true;
1247 - $data['msg'] = esc_html__('The operation failed', 'vision');
1248 - }
1249 -
1250 - if($error) {
1251 - wp_send_json_error($data);
1252 - } else {
1253 - wp_send_json_success($data);
1254 - }
1255 -
1256 - wp_die(); // this is required to terminate immediately and return a proper response
1257 - }
1258 -
1259 - /**
1260 - * Ajax delete all data from tables
1261 - */
1262 - function ajax_delete_data() {
1263 - $error = true;
1264 - $data = [];
1265 - $data['msg'] = esc_html__('The operation failed, can\'t delete data', 'vision');
1266 -
1267 - if(check_ajax_referer('vision_ajax', 'nonce', false)) {
1268 - global $wpdb;
1269 - $table = $wpdb->prefix . VISION_PLUGIN_NAME;
1858 + $current_user_id = get_current_user_id();
1270 1859
1271 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1272 - foreach($wpdb->get_results("SELECT id FROM {$table}") as $key => $item) {
1273 - // [filemanager] delete file
1274 - if(wp_is_writable(VISION_PLUGIN_UPLOAD_DIR)) {
1275 - $file_json = 'config.json';
1276 - $file_main_css = 'main.css';
1277 - $file_custom_css = 'custom.css';
1278 - $file_root_path = VISION_PLUGIN_UPLOAD_DIR . '/' . $item->id . '/';
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 + );
1279 1872
1280 - if(file_exists($file_root_path . $file_json)) {
1281 - wp_delete_file($file_root_path . $file_json);
1282 - }
1283 - wp_delete_file($file_root_path . $file_main_css);
1284 - wp_delete_file($file_root_path . $file_custom_css);
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 + }
1285 1890
1286 - $wp_filesystem = $this->getFileSystem();
1287 - if($wp_filesystem->is_dir($file_root_path)) {
1288 - $wp_filesystem->rmdir($file_root_path);
1289 - }
1290 - }
1291 - }
1891 + if ($error) {
1892 + wp_send_json_error($data);
1893 + } else {
1894 + wp_send_json_success($data);
1895 + }
1292 1896
1293 - // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
1294 - $result = $wpdb->query("TRUNCATE TABLE {$table}");
1295 -
1296 - if($result) {
1297 - $error = false;
1298 - $data['msg'] = esc_html__('All data deleted', 'vision');
1299 - }
1300 - }
1301 -
1302 - if($error) {
1303 - wp_send_json_error($data);
1304 - } else {
1305 - wp_send_json_success($data);
1306 - }
1307 -
1308 - wp_die(); // this is required to terminate immediately and return a proper response
1309 - }
1310 -
1311 - /**
1312 - * Ajax settings get data
1313 - */
1314 - function ajax_modal() {
1315 - if(check_ajax_referer('vision_ajax', 'nonce', false)) {
1316 - $modalName = sanitize_file_name(filter_input(INPUT_GET, 'name', FILTER_DEFAULT));
1317 - $modalPath = plugin_dir_path( dirname(__FILE__) ) . 'includes/modal-' . $modalName . '.php';
1318 -
1319 - if(file_exists($modalPath)) {
1320 - require_once( $modalPath );
1321 - }
1322 - }
1323 -
1324 - wp_die(); // this is required to terminate immediately and return a proper response
1325 - }
1326 -}
1327 -?>
1897 + wp_die();
1898 + }
1899 +
1900 + function remove_directory($path)
1901 + {
1902 + global $wp_filesystem;
1903 +
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 +}