| 1 |
<?php |
| 2 |
|
| 3 |
namespace MasterAddons\Inc\Admin\Templates\Includes\Classes; |
| 4 |
|
| 5 |
use MasterAddons\Inc\Admin\Templates; |
| 6 |
use MasterAddons\Inc\Admin\Templates\Includes\Sources; |
| 7 |
|
| 8 |
/** |
| 9 |
* Author Name: Liton Arefin |
| 10 |
* Author URL: https://jeweltheme.com |
| 11 |
* Date: 9/8/19 |
| 12 |
*/ |
| 13 |
|
| 14 |
|
| 15 |
if (!defined('ABSPATH')) exit; |
| 16 |
|
| 17 |
if (!class_exists(__NAMESPACE__ . '\\Manager')) { |
| 18 |
|
| 19 |
|
| 20 |
class Manager |
| 21 |
{ |
| 22 |
|
| 23 |
private static $instance = null; |
| 24 |
|
| 25 |
private $sources = array(); |
| 26 |
|
| 27 |
|
| 28 |
public function __construct() |
| 29 |
{ |
| 30 |
|
| 31 |
//Register AJAX hooks |
| 32 |
add_action('wp_ajax_jltma_get_templates', array($this, 'get_templates')); |
| 33 |
add_action('wp_ajax_nopriv_jltma_get_templates', array($this, 'get_templates')); |
| 34 |
|
| 35 |
add_action('wp_ajax_ma_el_get_templates', array($this, 'get_paginated_templates')); |
| 36 |
add_action('wp_ajax_nopriv_ma_el_get_templates', array($this, 'get_paginated_templates')); |
| 37 |
|
| 38 |
add_action('wp_ajax_jltma_inner_template', array($this, 'jltma_insert_inner_template')); |
| 39 |
add_action('wp_ajax_nopriv_jltma_inner_template', array($this, 'jltma_insert_inner_template')); |
| 40 |
|
| 41 |
|
| 42 |
if (defined('ELEMENTOR_VERSION') && version_compare(ELEMENTOR_VERSION, '2.2.8', '>')) { |
| 43 |
add_action('elementor/ajax/register_actions', array($this, 'jltma_register_ajax_actions'), 20); |
| 44 |
} else { |
| 45 |
add_action('wp_ajax_elementor_get_template_data', array($this, 'get_template_data'), -1); |
| 46 |
} |
| 47 |
|
| 48 |
$this->register_sources(); |
| 49 |
|
| 50 |
add_filter('master-addons-core/assets/editor/localize', array($this, 'localize_tabs')); |
| 51 |
} |
| 52 |
|
| 53 |
|
| 54 |
public function localize_tabs($data) |
| 55 |
{ |
| 56 |
|
| 57 |
$tabs = $this->get_template_tabs(); |
| 58 |
$ids = array_keys($tabs); |
| 59 |
$default = $ids[0]; |
| 60 |
|
| 61 |
$data['tabs'] = $this->get_template_tabs(); |
| 62 |
$data['defaultTab'] = $default; |
| 63 |
|
| 64 |
// Pass popup builder context |
| 65 |
$current_post_type = get_post_type(); |
| 66 |
$is_popup_builder = false; |
| 67 |
|
| 68 |
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only admin context, no nonce available |
| 69 |
$get_post_id = isset( $_GET['post'] ) ? absint( wp_unslash( $_GET['post'] ) ) : 0; |
| 70 |
$get_page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; |
| 71 |
if ($current_post_type === 'jltma_popup' || $current_post_type === 'popupbuilder' || |
| 72 |
( $get_post_id && get_post_type( $get_post_id ) === 'jltma_popup' ) || |
| 73 |
( $get_post_id && get_post_type( $get_post_id ) === 'popupbuilder' ) || |
| 74 |
( $get_page && strpos( $get_page, 'popup' ) !== false )) { |
| 75 |
$is_popup_builder = true; |
| 76 |
} |
| 77 |
|
| 78 |
if (isset($_GET['action']) && sanitize_key( wp_unslash( $_GET['action'] ) ) === 'elementor' && |
| 79 |
$get_post_id && (get_post_type( $get_post_id ) === 'ma_popup' || get_post_type( $get_post_id ) === 'jltma_popup' || get_post_type( $get_post_id ) === 'popupbuilder')) { |
| 80 |
$is_popup_builder = true; |
| 81 |
} |
| 82 |
// phpcs:enable WordPress.Security.NonceVerification.Recommended |
| 83 |
|
| 84 |
$data['isPopupBuilder'] = $is_popup_builder; |
| 85 |
|
| 86 |
return $data; |
| 87 |
} |
| 88 |
|
| 89 |
|
| 90 |
public function register_sources() |
| 91 |
{ |
| 92 |
$sources = array( |
| 93 |
'master-api' => Sources\Api::class, |
| 94 |
); |
| 95 |
|
| 96 |
foreach ($sources as $key => $class) { |
| 97 |
$this->add_source($key, $class); |
| 98 |
} |
| 99 |
} |
| 100 |
|
| 101 |
|
| 102 |
public function get_template_tabs() |
| 103 |
{ |
| 104 |
|
| 105 |
$tabs = Templates\master_addons_templates()->types->get_types_for_popup(); |
| 106 |
return $tabs; |
| 107 |
} |
| 108 |
|
| 109 |
|
| 110 |
public function add_source($key, $class) |
| 111 |
{ |
| 112 |
$this->sources[$key] = new $class(); |
| 113 |
} |
| 114 |
|
| 115 |
|
| 116 |
public function get_source($slug = null) |
| 117 |
{ |
| 118 |
return isset($this->sources[$slug]) ? $this->sources[$slug] : false; |
| 119 |
} |
| 120 |
|
| 121 |
|
| 122 |
|
| 123 |
public function get_templates() |
| 124 |
{ |
| 125 |
// Try multiple nonce actions for compatibility |
| 126 |
$nonce_valid = check_ajax_referer('jltma_get_templates_nonce_action', '_wpnonce', false) || |
| 127 |
check_ajax_referer('jltma_template_library_nonce', '_wpnonce', false) || check_ajax_referer('jltma_get_templates_nonce_action', 'security', false); |
| 128 |
|
| 129 |
// Enhanced security checks |
| 130 |
if (!$nonce_valid) { |
| 131 |
wp_send_json_error(array('message' => 'Permission denied - nonce failed')); |
| 132 |
} |
| 133 |
|
| 134 |
// Check user permissions |
| 135 |
if (!current_user_can('edit_posts')) { |
| 136 |
wp_send_json_error(array('message' => 'Permission denied - insufficient capabilities')); |
| 137 |
} |
| 138 |
|
| 139 |
// Rate limiting - prevent abuse |
| 140 |
// $user_id = get_current_user_id(); |
| 141 |
// $rate_limit_key = "jltma_template_requests_{$user_id}"; |
| 142 |
// $request_count = get_transient($rate_limit_key); |
| 143 |
|
| 144 |
// if ($request_count && $request_count > 500) { |
| 145 |
// wp_send_json_error(array('message' => 'Rate limit exceeded. Please wait before making more requests.')); |
| 146 |
// } |
| 147 |
|
| 148 |
// set_transient($rate_limit_key, ($request_count ? $request_count + 1 : 1), HOUR_IN_SECONDS); |
| 149 |
|
| 150 |
// Enhanced input validation - check both POST and GET for compatibility |
| 151 |
$tab = isset($_POST['tab']) ? sanitize_key($_POST['tab']) : (isset($_GET['tab']) ? sanitize_key($_GET['tab']) : null); |
| 152 |
$search = isset($_POST['search']) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : (isset($_GET['search']) ? sanitize_text_field( wp_unslash( $_GET['search'] ) ) : ''); |
| 153 |
$category = isset($_POST['category']) ? sanitize_text_field( wp_unslash( $_POST['category'] ) ) : (isset($_GET['category']) ? sanitize_text_field( wp_unslash( $_GET['category'] ) ) : 'all'); |
| 154 |
$page = isset($_POST['page']) ? absint($_POST['page']) : (isset($_GET['page']) ? absint($_GET['page']) : 1); |
| 155 |
$per_page = 15; // Templates per page |
| 156 |
|
| 157 |
|
| 158 |
if (!$tab) { |
| 159 |
wp_send_json_error(array('message' => 'Tab parameter is required')); |
| 160 |
} |
| 161 |
|
| 162 |
// Validate tab value against allowed values |
| 163 |
$allowed_tabs = $this->get_allowed_tabs(); |
| 164 |
if (!in_array($tab, $allowed_tabs, true)) { |
| 165 |
wp_send_json_error(array('message' => 'Invalid tab parameter')); |
| 166 |
} |
| 167 |
|
| 168 |
// Additional validation for tab data structure |
| 169 |
if (!$this->validate_tab_structure($tab) && $tab !== 'master_popups') { |
| 170 |
wp_send_json_error(array('message' => 'Invalid tab configuration')); |
| 171 |
} |
| 172 |
$tabs = $this->get_template_tabs(); |
| 173 |
$sources = $tabs[$tab]['sources']; |
| 174 |
|
| 175 |
$result = array( |
| 176 |
// 'ready_pages' => array(), |
| 177 |
// 'ready_widgets' => array(), |
| 178 |
'ready_headers' => array(), |
| 179 |
'ready_footers' => array(), |
| 180 |
'templates' => array(), |
| 181 |
'categories' => array(), |
| 182 |
'keywords' => array(), |
| 183 |
); |
| 184 |
|
| 185 |
foreach ($sources as $source_slug) { |
| 186 |
|
| 187 |
$source = isset($this->sources[$source_slug]) ? $this->sources[$source_slug] : false; |
| 188 |
|
| 189 |
if ($source) { |
| 190 |
// $result['ready_pages'] = array_merge( $result['ready_pages'], $source->get_items( $tab ) ); |
| 191 |
$result['ready_headers'] = array_merge($result['ready_headers'], $source->get_items($tab)); |
| 192 |
$result['ready_footers'] = array_merge($result['ready_footers'], $source->get_items($tab)); |
| 193 |
$result['templates'] = array_merge($result['templates'], $source->get_items($tab)); |
| 194 |
$result['categories'] = array_merge($result['categories'], $source->get_categories($tab)); |
| 195 |
$result['keywords'] = array_merge($result['keywords'], $source->get_keywords($tab)); |
| 196 |
} |
| 197 |
} |
| 198 |
|
| 199 |
|
| 200 |
$all_cats = array( |
| 201 |
array( |
| 202 |
'slug' => '', |
| 203 |
'title' => __('All Sections', 'master-addons' ), |
| 204 |
), |
| 205 |
); |
| 206 |
|
| 207 |
if (!empty($result['categories'])) { |
| 208 |
$result['categories'] = array_merge($all_cats, $result['categories']); |
| 209 |
} |
| 210 |
|
| 211 |
// Filter templates by category |
| 212 |
if ($category && $category !== 'all' && !empty($result['templates'])) { |
| 213 |
$result['templates'] = array_filter($result['templates'], function($template) use ($category) { |
| 214 |
if (isset($template['categories'])) { |
| 215 |
$template_categories = is_array($template['categories']) ? $template['categories'] : array($template['categories']); |
| 216 |
return in_array($category, $template_categories); |
| 217 |
} |
| 218 |
return false; |
| 219 |
}); |
| 220 |
$result['templates'] = array_values($result['templates']); // Re-index array |
| 221 |
} |
| 222 |
|
| 223 |
// Filter templates by search term |
| 224 |
if (!empty($search) && !empty($result['templates'])) { |
| 225 |
$search_lower = strtolower($search); |
| 226 |
$result['templates'] = array_filter($result['templates'], function($template) use ($search_lower) { |
| 227 |
// Search in title |
| 228 |
if (isset($template['title']) && stripos($template['title'], $search_lower) !== false) { |
| 229 |
return true; |
| 230 |
} |
| 231 |
// Search in keywords |
| 232 |
if (isset($template['keywords']) && is_array($template['keywords'])) { |
| 233 |
foreach ($template['keywords'] as $keyword) { |
| 234 |
if (stripos(strtolower($keyword), $search_lower) !== false) { |
| 235 |
return true; |
| 236 |
} |
| 237 |
} |
| 238 |
} |
| 239 |
// Search in categories |
| 240 |
if (isset($template['categories']) && is_array($template['categories'])) { |
| 241 |
foreach ($template['categories'] as $cat) { |
| 242 |
if (stripos(strtolower($cat), $search_lower) !== false) { |
| 243 |
return true; |
| 244 |
} |
| 245 |
} |
| 246 |
} |
| 247 |
return false; |
| 248 |
}); |
| 249 |
$result['templates'] = array_values($result['templates']); // Re-index array |
| 250 |
} |
| 251 |
|
| 252 |
// Calculate pagination |
| 253 |
$total_templates = count($result['templates']); |
| 254 |
$total_pages = ceil($total_templates / $per_page); |
| 255 |
$offset = ($page - 1) * $per_page; |
| 256 |
|
| 257 |
// Apply pagination |
| 258 |
$result['templates'] = array_slice($result['templates'], $offset, $per_page); |
| 259 |
|
| 260 |
|
| 261 |
|
| 262 |
if( $result ){ |
| 263 |
$base_url = wp_upload_dir()['baseurl']; |
| 264 |
$extensions = ['.jpg', '.png', '.svg', '.webp']; |
| 265 |
foreach($result as $type => $content){ |
| 266 |
if( 'ready_headers' !== $type && 'ready_footers' !== $type && 'templates' !== $type) continue; |
| 267 |
if( $type === 'templates'){ |
| 268 |
$template_type = explode('_', $tab)[1]; |
| 269 |
}else{ |
| 270 |
$template_type = explode('_', $type)[1]; |
| 271 |
} |
| 272 |
foreach($content as $index => $item){ |
| 273 |
|
| 274 |
$template_id = $item['template_id']; |
| 275 |
$file_path = $base_url .'/master_addons/templates-library/master_' . $template_type . '/images/template-'. $template_id .'-preview'; |
| 276 |
foreach ( $extensions as $extension ){ |
| 277 |
if(file_exists($file_path . $extension )){ |
| 278 |
$item['preview'] = $base_url .'/master_addons/templates-library/master_' . $template_type . '/images/template-'. $template_id .'-preview' . $extension; |
| 279 |
$item['thumbnail'] = $base_url .'/master_addons/templates-library/master_' . $template_type . '/images/template-'. $template_id .'-thumb' . $extension; |
| 280 |
break; |
| 281 |
} |
| 282 |
} |
| 283 |
$content[$index] = $item; |
| 284 |
} |
| 285 |
$result[$type] = $content; |
| 286 |
} |
| 287 |
} |
| 288 |
|
| 289 |
// Add pagination metadata |
| 290 |
$result['pagination'] = array( |
| 291 |
'current_page' => $page, |
| 292 |
'per_page' => $per_page, |
| 293 |
'total_items' => $total_templates, |
| 294 |
'total_pages' => $total_pages, |
| 295 |
'has_more' => $page < $total_pages |
| 296 |
); |
| 297 |
|
| 298 |
wp_send_json_success($result); |
| 299 |
} |
| 300 |
|
| 301 |
public function get_paginated_templates() |
| 302 |
{ |
| 303 |
// Enhanced security checks |
| 304 |
if (!check_ajax_referer('jltma_get_templates_nonce_action', '_wpnonce', false)) { |
| 305 |
wp_send_json_error(array('message' => 'Security verification failed')); |
| 306 |
} |
| 307 |
|
| 308 |
// Check user permissions |
| 309 |
if (!current_user_can('edit_posts')) { |
| 310 |
wp_send_json_error(array('message' => 'Insufficient permissions')); |
| 311 |
} |
| 312 |
|
| 313 |
// Get pagination parameters |
| 314 |
$page = absint($_POST['page'] ?? 1); |
| 315 |
$per_page = absint($_POST['per_page'] ?? 10); |
| 316 |
$tab = sanitize_key($_POST['tab'] ?? 'master_section'); |
| 317 |
|
| 318 |
// Validate tab value against allowed values |
| 319 |
$allowed_tabs = $this->get_allowed_tabs(); |
| 320 |
if (!in_array($tab, $allowed_tabs, true)) { |
| 321 |
wp_send_json_error(array('message' => 'Invalid tab parameter')); |
| 322 |
} |
| 323 |
|
| 324 |
// Get all templates for the tab |
| 325 |
$tabs = $this->get_template_tabs(); |
| 326 |
$sources = $tabs[$tab]['sources']; |
| 327 |
|
| 328 |
$all_templates = array(); |
| 329 |
|
| 330 |
foreach ($sources as $source_slug) { |
| 331 |
$source = isset($this->sources[$source_slug]) ? $this->sources[$source_slug] : false; |
| 332 |
if ($source) { |
| 333 |
$templates = $source->get_items($tab); |
| 334 |
$all_templates = array_merge($all_templates, $templates); |
| 335 |
} |
| 336 |
} |
| 337 |
|
| 338 |
// Apply pagination |
| 339 |
$total_templates = count($all_templates); |
| 340 |
$offset = ($page - 1) * $per_page; |
| 341 |
$paginated_templates = array_slice($all_templates, $offset, $per_page); |
| 342 |
|
| 343 |
// Update thumbnail URLs to use cache folder first, then remote fallback |
| 344 |
foreach ($paginated_templates as &$template) { |
| 345 |
if (class_exists('Template_Kit_Cache')) { |
| 346 |
$cache_manager = Template_Kit_Cache::get_instance(); |
| 347 |
if (!empty($template['thumbnail'])) { |
| 348 |
$cached_thumbnail = $cache_manager->get_kit_thumbnail_url('', $template['title'], $template['thumbnail']); |
| 349 |
if ($cached_thumbnail) { |
| 350 |
$template['thumbnail'] = $cached_thumbnail; |
| 351 |
} |
| 352 |
} |
| 353 |
} |
| 354 |
} |
| 355 |
|
| 356 |
wp_send_json_success(array( |
| 357 |
'templates' => $paginated_templates, |
| 358 |
'pagination' => array( |
| 359 |
'current_page' => $page, |
| 360 |
'per_page' => $per_page, |
| 361 |
'total_templates' => $total_templates, |
| 362 |
'total_pages' => ceil($total_templates / $per_page), |
| 363 |
'has_more' => ($offset + $per_page) < $total_templates |
| 364 |
) |
| 365 |
)); |
| 366 |
} |
| 367 |
|
| 368 |
public function get_template_source($source_name) |
| 369 |
{ |
| 370 |
return isset($this->sources[$source_name]) ? $this->sources[$source_name] : false; |
| 371 |
} |
| 372 |
|
| 373 |
public function get_template_defaults() |
| 374 |
{ |
| 375 |
return [ |
| 376 |
'template_id' => false, |
| 377 |
'source' => false, |
| 378 |
]; |
| 379 |
} |
| 380 |
|
| 381 |
public function sanitize_template(array $template) |
| 382 |
{ |
| 383 |
|
| 384 |
$template = array_merge($this->get_template_defaults(), $template); |
| 385 |
|
| 386 |
// Enhanced sanitization and validation |
| 387 |
$template['template_id'] = isset($template['template_id']) ? sanitize_text_field($template['template_id']) : false; |
| 388 |
$template['source'] = isset($template['source']) ? sanitize_text_field($template['source']) : false; |
| 389 |
$template['title'] = isset($template['title']) ? sanitize_text_field($template['title']) : 'Untitled Template'; |
| 390 |
|
| 391 |
// Validate template_id format (alphanumeric and dashes only) |
| 392 |
if ($template['template_id'] && !preg_match('/^[a-zA-Z0-9\-_]+$/', $template['template_id'])) { |
| 393 |
$template['template_id'] = false; |
| 394 |
} |
| 395 |
|
| 396 |
// Validate source against allowed sources |
| 397 |
$allowed_sources = ['master-api']; |
| 398 |
if ($template['source'] && !in_array($template['source'], $allowed_sources, true)) { |
| 399 |
$template['source'] = false; |
| 400 |
} |
| 401 |
|
| 402 |
return $template; |
| 403 |
} |
| 404 |
|
| 405 |
/** |
| 406 |
* Get allowed tabs for validation |
| 407 |
*/ |
| 408 |
private function get_allowed_tabs() |
| 409 |
{ |
| 410 |
return apply_filters('jltma_allowed_template_tabs', [ |
| 411 |
'master_section', |
| 412 |
'master_pages', |
| 413 |
'master_popups', |
| 414 |
'master_headers', |
| 415 |
'master_footers' |
| 416 |
]); |
| 417 |
} |
| 418 |
|
| 419 |
/** |
| 420 |
* Validate tab structure and configuration |
| 421 |
*/ |
| 422 |
private function validate_tab_structure($tab) |
| 423 |
{ |
| 424 |
$tabs = $this->get_template_tabs(); |
| 425 |
|
| 426 |
// Check if tab exists in configuration |
| 427 |
if (!isset($tabs[$tab])) { |
| 428 |
return false; |
| 429 |
} |
| 430 |
|
| 431 |
// Validate tab has required structure |
| 432 |
$required_keys = ['sources']; |
| 433 |
foreach ($required_keys as $key) { |
| 434 |
if (!isset($tabs[$tab][$key])) { |
| 435 |
return false; |
| 436 |
} |
| 437 |
} |
| 438 |
|
| 439 |
// Validate sources are not empty |
| 440 |
if (empty($tabs[$tab]['sources']) || !is_array($tabs[$tab]['sources'])) { |
| 441 |
return false; |
| 442 |
} |
| 443 |
|
| 444 |
return true; |
| 445 |
} |
| 446 |
|
| 447 |
/** |
| 448 |
* Validate template data structure |
| 449 |
*/ |
| 450 |
private function validate_template_data($template_data) |
| 451 |
{ |
| 452 |
if (!is_array($template_data)) { |
| 453 |
return false; |
| 454 |
} |
| 455 |
|
| 456 |
// Required fields |
| 457 |
$required_fields = ['content']; |
| 458 |
foreach ($required_fields as $field) { |
| 459 |
if (!isset($template_data[$field])) { |
| 460 |
return false; |
| 461 |
} |
| 462 |
} |
| 463 |
|
| 464 |
// Validate content is not empty |
| 465 |
if (empty($template_data['content'])) { |
| 466 |
return false; |
| 467 |
} |
| 468 |
|
| 469 |
// Validate content size (prevent extremely large templates) |
| 470 |
// if (strlen($template_data['content']) > 1048576) { // 1MB limit |
| 471 |
// return false; |
| 472 |
// } |
| 473 |
|
| 474 |
return true; |
| 475 |
} |
| 476 |
|
| 477 |
/** |
| 478 |
* Validate source name against registered sources |
| 479 |
*/ |
| 480 |
private function validate_source($source_name) |
| 481 |
{ |
| 482 |
if (empty($source_name)) { |
| 483 |
return false; |
| 484 |
} |
| 485 |
|
| 486 |
// Check if source is registered |
| 487 |
if (!isset($this->sources[$source_name])) { |
| 488 |
return false; |
| 489 |
} |
| 490 |
|
| 491 |
// Validate source object |
| 492 |
$source = $this->sources[$source_name]; |
| 493 |
if (!is_object($source)) { |
| 494 |
return false; |
| 495 |
} |
| 496 |
|
| 497 |
// Check if source has required methods |
| 498 |
$required_methods = ['get_item', 'get_items']; |
| 499 |
foreach ($required_methods as $method) { |
| 500 |
if (!method_exists($source, $method)) { |
| 501 |
return false; |
| 502 |
} |
| 503 |
} |
| 504 |
|
| 505 |
return true; |
| 506 |
} |
| 507 |
|
| 508 |
/* |
| 509 |
* Insert Template |
| 510 |
*/ |
| 511 |
public function jltma_insert_inner_template() |
| 512 |
{ |
| 513 |
// Enhanced security verification |
| 514 |
if (!check_ajax_referer('jltma_insert_templates_nonce_action', 'security', false)) { |
| 515 |
wp_send_json_error(array('message' => 'Security verification failed')); |
| 516 |
} |
| 517 |
|
| 518 |
// Check user permissions |
| 519 |
if (!current_user_can('edit_posts')) { |
| 520 |
wp_send_json_error(array('message' => 'Insufficient permissions')); |
| 521 |
} |
| 522 |
|
| 523 |
// Rate limiting for template insertions |
| 524 |
// $user_id = get_current_user_id(); |
| 525 |
// $rate_limit_key = "jltma_template_inserts_{$user_id}"; |
| 526 |
// $insert_count = get_transient($rate_limit_key); |
| 527 |
|
| 528 |
// if ($insert_count && $insert_count > 500) { |
| 529 |
// wp_send_json_error(array('message' => 'Template insertion rate limit exceeded')); |
| 530 |
// } |
| 531 |
|
| 532 |
// set_transient($rate_limit_key, ($insert_count ? $insert_count + 1 : 1), HOUR_IN_SECONDS); |
| 533 |
|
| 534 |
// Enhanced template validation |
| 535 |
if (!isset($_REQUEST['template'])) { |
| 536 |
wp_send_json_error(array('message' => 'Template data is required')); |
| 537 |
} |
| 538 |
|
| 539 |
$template = $this->sanitize_template( (array) wp_unslash( $_REQUEST['template'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized inside sanitize_template() |
| 540 |
|
| 541 |
if (!$template || empty($template['template_id']) || empty($template['source'])) { |
| 542 |
wp_send_json_error(array('message' => 'Invalid template data')); |
| 543 |
} |
| 544 |
|
| 545 |
// Validate source |
| 546 |
if (!$this->validate_source($template['source'])) { |
| 547 |
wp_send_json_error(array('message' => 'Invalid template source')); |
| 548 |
} |
| 549 |
|
| 550 |
$source = $this->get_template_source($template['source']); |
| 551 |
|
| 552 |
if (!$source || !$template['template_id']) { |
| 553 |
wp_send_json_error(array('message' => 'Template source or ID not found')); |
| 554 |
} |
| 555 |
|
| 556 |
$template_data = $source->get_item($template['template_id']); |
| 557 |
|
| 558 |
// Validate template data structure |
| 559 |
if (!$this->validate_template_data($template_data)) { |
| 560 |
wp_send_json_error(array('message' => 'Invalid template data structure')); |
| 561 |
} |
| 562 |
|
| 563 |
if (!empty($template_data['content'])) { |
| 564 |
// Additional validation before post creation |
| 565 |
$post_title = !empty($template['title']) ? sanitize_text_field($template['title']) : 'Master Addons Template ' . time(); |
| 566 |
|
| 567 |
// Validate post title length |
| 568 |
if (strlen($post_title) > 255) { |
| 569 |
$post_title = substr($post_title, 0, 255); |
| 570 |
} |
| 571 |
|
| 572 |
// Validate Elementor data format |
| 573 |
$elementor_data = $template_data['content']; |
| 574 |
if (is_string($elementor_data)) { |
| 575 |
$decoded_data = json_decode($elementor_data, true); |
| 576 |
if (json_last_error() !== JSON_ERROR_NONE) { |
| 577 |
wp_send_json_error(array('message' => 'Invalid Elementor data format')); |
| 578 |
} |
| 579 |
} |
| 580 |
|
| 581 |
$post_id = wp_insert_post(array( |
| 582 |
'post_type' => 'elementor_library', |
| 583 |
'post_title' => $post_title, |
| 584 |
'post_status' => 'publish', |
| 585 |
'post_author' => get_current_user_id(), |
| 586 |
'meta_input' => array( |
| 587 |
'_elementor_data' => $elementor_data, |
| 588 |
'_elementor_edit_mode' => 'builder', |
| 589 |
'_elementor_template_type' => 'section', |
| 590 |
'_jltma_template_source' => sanitize_text_field($template['source']), |
| 591 |
'_jltma_template_id' => sanitize_text_field($template['template_id']), |
| 592 |
), |
| 593 |
)); |
| 594 |
|
| 595 |
// Validate post creation success |
| 596 |
if (!$post_id || is_wp_error($post_id)) { |
| 597 |
wp_send_json_error(array('message' => 'Failed to create template post')); |
| 598 |
} |
| 599 |
} else { |
| 600 |
wp_send_json_error(array('message' => 'Template content is empty')); |
| 601 |
} |
| 602 |
|
| 603 |
wp_send_json_success(); |
| 604 |
} |
| 605 |
|
| 606 |
public function jltma_register_ajax_actions($ajax_manager) |
| 607 |
{ |
| 608 |
|
| 609 |
if (empty($_REQUEST['actions'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no nonce available for this action hook |
| 610 |
return; |
| 611 |
} |
| 612 |
|
| 613 |
$actions = (array) json_decode(stripslashes(sanitize_text_field( wp_unslash( $_REQUEST['actions'] ) )), true); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no nonce available for this action hook |
| 614 |
$data = false; |
| 615 |
|
| 616 |
foreach ($actions as $action_data) { |
| 617 |
if( in_array('get_template_data', $action_data) || in_array('save_template', $action_data) ){ |
| 618 |
$data = $action_data; |
| 619 |
} |
| 620 |
} |
| 621 |
|
| 622 |
if (!$data) { |
| 623 |
return; |
| 624 |
} |
| 625 |
|
| 626 |
if (!isset($data['data'])) { |
| 627 |
return; |
| 628 |
} |
| 629 |
|
| 630 |
if (!isset($data['data']['source'])) { |
| 631 |
return; |
| 632 |
} |
| 633 |
|
| 634 |
// Handle both single source string and array of sources |
| 635 |
$sources = $data['data']['source']; |
| 636 |
if (!is_array($sources)) { |
| 637 |
$sources = [$sources]; |
| 638 |
} |
| 639 |
|
| 640 |
foreach ( $sources as $source ) { |
| 641 |
if ( isset( $this->sources[ $source ] ) ) { |
| 642 |
// Register AJAX actions only once |
| 643 |
$ajax_manager->register_ajax_action( 'get_template_data', function( $data ) { |
| 644 |
return $this->get_template_data_array( $data ); |
| 645 |
}); |
| 646 |
|
| 647 |
$ajax_manager->register_ajax_action( 'save_template', function( $data ) { |
| 648 |
return $this->save_template_data_array( $data ); |
| 649 |
}); |
| 650 |
|
| 651 |
break; // Exit loop after registering once |
| 652 |
} |
| 653 |
} |
| 654 |
|
| 655 |
} |
| 656 |
|
| 657 |
|
| 658 |
public function save_template_data_array($data) { |
| 659 |
$post_id = sanitize_text_field($data['template_id'] ?? ''); |
| 660 |
$template_data = wp_unslash($data['template_data'] ?? ''); |
| 661 |
|
| 662 |
if ($post_id && $template_data) { |
| 663 |
return \Elementor\Plugin::$instance->templates_manager->save_template( |
| 664 |
$post_id, |
| 665 |
$template_data |
| 666 |
); |
| 667 |
} |
| 668 |
|
| 669 |
return new \WP_Error('invalid_data', 'Missing template ID or data'); |
| 670 |
} |
| 671 |
|
| 672 |
|
| 673 |
public function get_template_data_array($data) |
| 674 |
{ |
| 675 |
|
| 676 |
if (!current_user_can('edit_posts')) { |
| 677 |
return false; |
| 678 |
} |
| 679 |
|
| 680 |
if (empty($data['template_id'])) { |
| 681 |
return false; |
| 682 |
} |
| 683 |
|
| 684 |
$source_name = isset($data['source']) ? $data['source'] : ''; |
| 685 |
|
| 686 |
// Handle both single source string and array of sources |
| 687 |
if (is_array($source_name)) { |
| 688 |
$source_name = !empty($source_name) ? esc_attr($source_name[0]) : ''; |
| 689 |
} else { |
| 690 |
$source_name = esc_attr($source_name); |
| 691 |
} |
| 692 |
|
| 693 |
if (!$source_name) { |
| 694 |
return false; |
| 695 |
} |
| 696 |
|
| 697 |
$source = isset($this->sources[$source_name]) ? $this->sources[$source_name] : false; |
| 698 |
|
| 699 |
if (!$source) { |
| 700 |
return false; |
| 701 |
} |
| 702 |
|
| 703 |
if (empty($data['tab'])) { |
| 704 |
return false; |
| 705 |
} |
| 706 |
|
| 707 |
$template = $source->get_item($data['template_id'], $data['tab']); |
| 708 |
|
| 709 |
return $template; |
| 710 |
} |
| 711 |
|
| 712 |
|
| 713 |
public function get_template_data() |
| 714 |
{ |
| 715 |
|
| 716 |
// Extract and sanitize only the specific fields we need (never forward the whole superglobal). |
| 717 |
$source = isset( $_REQUEST['source'] ) ? wp_unslash( $_REQUEST['source'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only fetch gated by edit_posts capability in get_template_data_array() |
| 718 |
if ( is_array( $source ) ) { |
| 719 |
$source = array_map( 'sanitize_text_field', $source ); |
| 720 |
} else { |
| 721 |
$source = sanitize_text_field( $source ); |
| 722 |
} |
| 723 |
$request_data = array( |
| 724 |
'template_id' => isset( $_REQUEST['template_id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['template_id'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only fetch gated by edit_posts capability in get_template_data_array() |
| 725 |
'tab' => isset( $_REQUEST['tab'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['tab'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only fetch gated by edit_posts capability in get_template_data_array() |
| 726 |
'source' => $source, |
| 727 |
); |
| 728 |
|
| 729 |
$template = $this->get_template_data_array($request_data); |
| 730 |
|
| 731 |
if (!$template) { |
| 732 |
wp_send_json_error(); |
| 733 |
} |
| 734 |
|
| 735 |
wp_send_json_success($template); |
| 736 |
} |
| 737 |
|
| 738 |
|
| 739 |
public static function get_instance() |
| 740 |
{ |
| 741 |
|
| 742 |
// If the single instance hasn't been set, set it now. |
| 743 |
if (null == self::$instance) { |
| 744 |
self::$instance = new self; |
| 745 |
} |
| 746 |
return self::$instance; |
| 747 |
} |
| 748 |
} |
| 749 |
} |