Changeset 3253459
- Timestamp:
- 03/10/2025 03:56:02 PM (13 months ago)
- Location:
- limbo/trunk
- Files:
-
- 9 edited
-
admin/export/js/limbo-export.js (modified) (6 diffs)
-
admin/export/limbo-export-functions.php (modified) (6 diffs)
-
admin/import/js/limbo-import.js (modified) (2 diffs)
-
admin/import/limbo-import-functions.php (modified) (8 diffs)
-
admin/import/limbo-import.php (modified) (1 diff)
-
admin/limbo-connections/js/limbo-connections.js (modified) (2 diffs)
-
js/product-page.js (modified) (2 diffs)
-
limbo.php (modified) (1 diff)
-
readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
limbo/trunk/admin/export/js/limbo-export.js
r3198812 r3253459 16 16 17 17 function createProductElement(product) { 18 const mainImage = product.images && product.images.length > 0 19 ? product.images[0].thumbnail 20 : limboExportData.placeholderImage; 18 console.log('Product:', product.name); 19 console.log('Image URLs:', product.images.map(img => img.full)); 20 21 let mainImage = limboExportData.placeholderImage; 22 if (product.images && Array.isArray(product.images) && product.images.length > 0) { 23 mainImage = product.images[0].thumbnail || product.images[0].full || limboExportData.placeholderImage; 24 } 21 25 22 26 const attributesHtml = product.attributes.map(attr => ` … … 41 45 alt="${product.name}" 42 46 class="product-thumbnail" 47 onerror="this.src='${limboExportData.placeholderImage}'" 43 48 > 44 49 <div class="product-info"> … … 227 232 exportButton.textContent = limboExportData.strings.exporting; 228 233 234 // Get the site URL 235 const shopDomain = window.location.origin; 236 229 237 const response = await fetch(limboExportData.ajaxurl, { 230 238 method: 'POST', … … 236 244 security: limboExportData.nonce, 237 245 productIds: Array.from(selectedProducts), 238 sellerId: JSON.parse(localStorage.getItem('limboUserData'))?.id 246 sellerId: JSON.parse(localStorage.getItem('limboUserData'))?.id, 247 shopDomain: shopDomain 239 248 }) 240 249 }); … … 249 258 throw new Error(limboExportData.strings.noProductsFormatted); 250 259 } 260 261 console.log('Formatted products for export:', data.data); 262 data.data.forEach(product => { 263 console.log(`Image URLs for product "${product.productName}":`, product.images); 264 }); 251 265 252 266 const backendResponse = await fetch(limboExportData.apiEndpoint, { … … 258 272 body: JSON.stringify({ 259 273 woocommerceProducts: data.data, 260 sellerId: JSON.parse(localStorage.getItem('limboUserData'))?.id 274 sellerId: JSON.parse(localStorage.getItem('limboUserData'))?.id, 275 shopDomain: shopDomain 261 276 }) 262 277 }); -
limbo/trunk/admin/export/limbo-export-functions.php
r3198812 r3253459 111 111 function limbo_get_product_images($product) { 112 112 $images = array(); 113 $image_ids = array_ merge(113 $image_ids = array_filter(array_merge( 114 114 array($product->get_image_id()), 115 115 $product->get_gallery_image_ids() 116 ) ;116 )); 117 117 118 118 foreach ($image_ids as $image_id) { 119 119 if (!empty($image_id)) { 120 $image_full = wp_get_attachment_image_src($image_id, 'full'); 121 $image_thumb = wp_get_attachment_image_src($image_id, 'thumbnail'); 122 123 if ($image_full && $image_thumb) { 120 $file_path = get_attached_file($image_id); 121 122 if ($file_path && file_exists($file_path)) { 123 // Get image content and convert to base64 124 $image_data = file_get_contents($file_path); 125 $mime_type = mime_content_type($file_path); 126 $base64 = base64_encode($image_data); 127 $base64_url = "data:{$mime_type};base64,{$base64}"; 128 124 129 $images[] = array( 125 130 'id' => $image_id, 126 'full' => $ image_full[0],127 'thumbnail' => $ image_thumb[0],131 'full' => $base64_url, 132 'thumbnail' => $base64_url, 128 133 'alt' => get_post_meta($image_id, '_wp_attachment_image_alt', true) 129 134 ); … … 131 136 } 132 137 } 138 139 // If no images found, return array with placeholder 140 if (empty($images)) { 141 $images[] = array( 142 'id' => 0, 143 'full' => LIMBO_PLUGIN_URL . 'assets/images/placeholder.png', 144 'thumbnail' => LIMBO_PLUGIN_URL . 'assets/images/placeholder.png', 145 'alt' => __('Product image placeholder', 'limbo') 146 ); 147 } 148 133 149 return $images; 134 150 } … … 163 179 $product_ids_raw = isset($_POST['productIds']) ? sanitize_text_field(wp_unslash($_POST['productIds'])) : ''; 164 180 $seller_id_raw = isset($_POST['sellerId']) ? sanitize_text_field(wp_unslash($_POST['sellerId'])) : ''; 181 $shop_domain = isset($_POST['shopDomain']) ? esc_url_raw(wp_unslash($_POST['shopDomain'])) : ''; 165 182 166 183 // Validate required data 167 if (empty($product_ids_raw) || empty($seller_id_raw) ) {184 if (empty($product_ids_raw) || empty($seller_id_raw) || empty($shop_domain)) { 168 185 wp_send_json_error(['message' => __('Missing required data', 'limbo')]); 169 186 return; … … 191 208 }, $images); 192 209 210 // Get categories and ensure we have a default value 193 211 $categories = wp_get_post_terms($product_id, 'product_cat', ['fields' => 'names']); 194 $category = !empty($categories) ? $categories[0] : ' ';212 $category = !empty($categories) ? $categories[0] : 'Uncategorized'; 195 213 196 214 $attributes = limbo_get_product_attributes($product); … … 208 226 209 227 $formatted_products[] = [ 228 'id' => $product_id, 210 229 'productName' => sanitize_text_field($product->get_name()), 211 230 'description' => wp_kses_post($product->get_description() ?: $product->get_short_description()), … … 213 232 'sellerId' => $seller_id, 214 233 'quantity' => absint(50), 215 'images' => array_map('esc_url_raw', $image_urls),234 'images' => $image_urls, 216 235 'category' => sanitize_text_field($category), 217 236 'options' => $options, -
limbo/trunk/admin/import/js/limbo-import.js
r3198812 r3253459 166 166 167 167 try { 168 // Get the full product data for selected IDs169 168 const selectedProductsData = allProducts.filter(product => 170 169 selectedProducts.has(product._id) 171 170 ); 171 172 console.log('Selected products data:', selectedProductsData); 173 console.log('Selected products count:', selectedProductsData.length); 172 174 173 175 const response = await fetch(limboImportData.ajaxurl, { … … 179 181 action: 'limbo_import_products', 180 182 security: limboImportData.nonce, 181 products: JSON.stringify(selectedProductsData) 183 products: JSON.stringify(selectedProductsData), 184 shopDomain: window.location.origin 182 185 }) 183 186 }); 184 187 185 const result = await response.json(); 188 const responseText = await response.text(); 189 console.log('Raw server response:', responseText); 190 191 let result; 192 try { 193 result = JSON.parse(responseText); 194 } catch (e) { 195 console.error('Failed to parse server response:', responseText); 196 throw new Error('Server returned invalid JSON response'); 197 } 198 199 console.log('Parsed response:', result); 186 200 187 201 if (result.success) { 188 202 alert(`Successfully imported ${result.data.imported} products!`); 189 // Clear selections after successful import 203 if (result.data.errors && result.data.errors.length > 0) { 204 console.warn('Import warnings:', result.data.errors); 205 } 190 206 selectedProducts.clear(); 191 207 updateSelectedCount(); 192 // Optionally refresh the product list193 208 displayProducts(); 194 209 } else { 195 throw new Error(result.data .message || 'Import failed');210 throw new Error(result.data?.message || 'Import failed'); 196 211 } 197 212 } catch (error) { 198 213 console.error('Import error:', error); 199 alert( 'Error importing products. Please try again.');214 alert(`Error importing products: ${error.message}`); 200 215 } finally { 201 216 importButton.disabled = false; -
limbo/trunk/admin/import/limbo-import-functions.php
r3198812 r3253459 18 18 */ 19 19 function limbo_handle_product_import() { 20 // Increase memory limit for large imports using WordPress function 21 wp_raise_memory_limit('admin'); 22 20 23 try { 21 check_ajax_referer('limbo-import-nonce', 'security'); 24 // Verify nonce first 25 if (!check_ajax_referer('limbo-import-nonce', 'security', false)) { 26 wp_send_json_error([ 27 'message' => 'Security check failed', 28 'code' => 'security_error' 29 ]); 30 return; 31 } 22 32 23 33 if (!current_user_can('manage_woocommerce')) { 24 wp_send_json_error(['message' => esc_html__('Permission denied', 'limbo')]); 34 wp_send_json_error([ 35 'message' => 'Permission denied', 36 'code' => 'permission_error' 37 ]); 25 38 return; 26 39 } 27 40 28 // Validate and sanitizeinput41 // Validate input 29 42 if (!isset($_POST['products'])) { 30 wp_send_json_error(['message' => esc_html__('No products data received', 'limbo')]); 43 wp_send_json_error([ 44 'message' => 'No products data received', 45 'code' => 'no_data' 46 ]); 31 47 return; 32 48 } 33 49 50 // Get and decode products data with proper sanitization 34 51 $products_raw = sanitize_text_field(wp_unslash($_POST['products'])); 35 36 if (!is_string($products_raw)) { 37 wp_send_json_error(['message' => esc_html__('Invalid products data format', 'limbo')]); 52 $products = json_decode($products_raw, true); 53 54 // JSON decode error check 55 if (json_last_error() !== JSON_ERROR_NONE) { 56 wp_send_json_error([ 57 'message' => 'Invalid JSON data: ' . json_last_error_msg(), 58 'code' => 'json_error' 59 ]); 38 60 return; 39 61 } 40 62 41 $products = json_decode($products_raw, true); 42 if (json_last_error() !== JSON_ERROR_NONE) { 43 wp_send_json_error(['message' => esc_html__('Invalid JSON data', 'limbo')]); 63 if (!is_array($products)) { 64 wp_send_json_error([ 65 'message' => 'Products data must be an array', 66 'code' => 'invalid_format' 67 ]); 44 68 return; 45 69 } 46 70 47 if (!is_array($products)) { 48 wp_send_json_error(['message' => esc_html__('Products data must be an array', 'limbo')]); 49 return; 50 } 71 limbo_log_error("Starting import of " . count($products) . " products"); 51 72 52 73 $imported_count = 0; … … 55 76 foreach ($products as $product) { 56 77 try { 57 // Sanitize and validate product data 78 limbo_log_error("Processing product: " . $product['productName']); 79 80 // Sanitize product data 58 81 $product_data = limbo_sanitize_product_data($product); 59 82 … … 63 86 if ($product_id) { 64 87 $imported_count++; 65 limbo_log_error("Successfully imported product: " . $product_data['productName']); 88 limbo_log_error("Successfully created product ID: " . $product_id); 89 } else { 90 throw new Exception("Failed to create product"); 66 91 } 67 92 68 93 } catch (Exception $e) { 69 /* translators: %1$s: product name, %2$s: error message */ 70 $errors[] = sprintf( 71 esc_html__('Error importing %1$s: %2$s', 'limbo'), 72 isset($product['productName']) ? sanitize_text_field($product['productName']) : 'unknown product', 94 $error_msg = sprintf( 95 'Error importing %s: %s', 96 isset($product['productName']) ? $product['productName'] : 'unknown product', 73 97 $e->getMessage() 74 98 ); 75 limbo_log_error("Import error: " . $e->getMessage()); 99 $errors[] = $error_msg; 100 limbo_log_error($error_msg); 76 101 } 77 102 } … … 80 105 wp_send_json_success([ 81 106 'imported' => $imported_count, 82 'errors' => $errors 107 'errors' => $errors, 108 'message' => "Successfully imported {$imported_count} products" 83 109 ]); 84 110 } else { 85 111 wp_send_json_error([ 86 'message' => esc_html__('No products were imported', 'limbo'), 87 'errors' => $errors 88 ]); 89 } 112 'message' => 'No products were imported', 113 'errors' => $errors, 114 'code' => 'import_failed' 115 ]); 116 } 117 90 118 } catch (Exception $e) { 91 limbo_log_error("Import error: " . $e->getMessage()); 92 /* translators: %s: error message */ 119 limbo_log_error("Critical import error: " . $e->getMessage()); 93 120 wp_send_json_error([ 94 'message' => sprintf( 95 esc_html__('Import failed: %s', 'limbo'), 96 $e->getMessage() 97 ), 121 'message' => 'Import failed: ' . $e->getMessage(), 122 'code' => 'critical_error', 98 123 'errors' => isset($errors) ? $errors : [] 99 124 ]); … … 191 216 } 192 217 218 // Create connection with Limbo server 219 $connection_result = limbo_create_product_connection($product_id, $product_data['_id'], $product_data['sellerId']); 220 if (!$connection_result['success']) { 221 limbo_log_error("Warning: Failed to create Limbo connection: " . $connection_result['message']); 222 } 223 193 224 return $product_id; 194 225 } … … 277 308 */ 278 309 function limbo_handle_product_images($product_id, $wc_product, $images) { 310 limbo_log_error("=== Starting product image import for product ID: " . $product_id . " ==="); 311 limbo_log_error("Number of images to process: " . count($images)); 312 279 313 $image_ids = array(); 280 281 foreach ($images as $image_url) { 282 if (empty($image_url)) continue; 283 314 $failed_images = array(); 315 316 foreach ($images as $index => $image_url) { 317 limbo_log_error("Processing image " . ($index + 1) . " of " . count($images)); 318 319 if (empty($image_url)) { 320 limbo_log_error("❌ Empty image URL at index " . $index); 321 continue; 322 } 323 324 // Add small delay between image processing to prevent overload 325 if ($index > 0) { 326 sleep(1); 327 } 328 284 329 $image_id = limbo_import_image($image_url); 330 285 331 if ($image_id) { 286 332 $image_ids[] = $image_id; 333 limbo_log_error("✓ Image " . ($index + 1) . " successfully processed"); 334 } else { 335 $failed_images[] = $image_url; 336 limbo_log_error("❌ Failed to process image " . ($index + 1)); 287 337 } 288 338 } 289 339 290 340 if (!empty($image_ids)) { 291 set_post_thumbnail($product_id, $image_ids[0]); 292 $wc_product->set_image_id($image_ids[0]); 293 294 $gallery_image_ids = array_slice($image_ids, 1); 295 if (!empty($gallery_image_ids)) { 296 $wc_product->set_gallery_image_ids($gallery_image_ids); 297 } 298 299 $wc_product->save(); 300 } 301 } 302 303 /** 304 * Import an image from URL 341 // Set featured image 342 $featured_result = set_post_thumbnail($product_id, $image_ids[0]); 343 limbo_log_error($featured_result ? "✓ Featured image set" : "❌ Failed to set featured image"); 344 345 // Set gallery images 346 $gallery_ids = array_slice($image_ids, 1); 347 if (!empty($gallery_ids)) { 348 $wc_product->set_gallery_image_ids($gallery_ids); 349 limbo_log_error("✓ Gallery images set: " . implode(', ', $gallery_ids)); 350 } 351 352 $save_result = $wc_product->save(); 353 limbo_log_error($save_result ? "✓ Product saved with images" : "❌ Failed to save product with images"); 354 } 355 356 limbo_log_error("=== End product image import ==="); 357 return count($image_ids); 358 } 359 360 /** 361 * Import an image from URL using WordPress Filesystem 305 362 */ 306 363 function limbo_import_image($url) { … … 309 366 require_once(ABSPATH . 'wp-admin/includes/image.php'); 310 367 368 // Detailed URL logging 369 limbo_log_error("=== Starting image import ==="); 370 limbo_log_error("Original URL: " . $url); 371 372 // URL cleanup and validation 373 $url = str_replace(' ', '%20', $url); 374 $decoded_url = urldecode($url); 375 limbo_log_error("Decoded URL: " . $decoded_url); 376 311 377 if (empty($url) || !filter_var($url, FILTER_VALIDATE_URL)) { 378 limbo_log_error("❌ Invalid URL format: " . $url); 312 379 return false; 313 380 } 314 381 315 $existing_attachment = attachment_url_to_postid($url); 316 if ($existing_attachment) { 317 return $existing_attachment; 318 } 319 382 // Test URL accessibility 383 $response = wp_remote_head($url); 384 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) { 385 limbo_log_error("❌ URL not accessible: " . $url); 386 return false; 387 } 388 389 // Download file 320 390 $tmp = download_url($url); 321 391 if (is_wp_error($tmp)) { 392 limbo_log_error("❌ Download failed: " . $tmp->get_error_message()); 322 393 return false; 323 394 } 324 395 396 // Prepare file array 325 397 $file_array = array( 326 'name' => basename(wp_parse_url($url, PHP_URL_PATH)),398 'name' => wp_basename($url), 327 399 'tmp_name' => $tmp 328 400 ); 329 401 402 // Handle sideload 330 403 $id = media_handle_sideload($file_array, 0); 331 404 332 405 if (is_wp_error($id)) { 406 limbo_log_error("❌ Sideload failed: " . $id->get_error_message()); 333 407 wp_delete_file($tmp); 334 408 return false; 335 409 } 336 410 411 limbo_log_error("✓ Successfully imported image. ID: " . $id); 412 limbo_log_error("=== End image import ==="); 413 337 414 return $id; 338 415 } … … 393 470 394 471 /** 395 * Log error messages 472 * Log error messages with proper debug checking 396 473 */ 397 474 function limbo_log_error($message) { 398 $logger = wc_get_logger(); 399 $logger->error( 400 sprintf( 401 '[Limbo Plugin] %s', 402 wp_privacy_anonymize_data($message, 'text') 475 if (defined('WP_DEBUG') && WP_DEBUG) { 476 if (function_exists('wc_get_logger')) { 477 $logger = wc_get_logger(); 478 $logger->error($message, ['source' => 'limbo-import']); 479 } 480 } 481 } 482 483 /** 484 * Check filesystem permissions using WordPress Filesystem 485 */ 486 function limbo_check_filesystem_permissions() { 487 global $wp_filesystem; 488 require_once(ABSPATH . 'wp-admin/includes/file.php'); 489 WP_Filesystem(); 490 491 $upload_dir = wp_upload_dir(); 492 $base_dir = $upload_dir['basedir']; 493 494 limbo_log_error("Checking upload directory permissions:"); 495 limbo_log_error("Upload base dir: " . $base_dir); 496 limbo_log_error("Is writable: " . ($wp_filesystem->is_writable($base_dir) ? 'Yes' : 'No')); 497 limbo_log_error("Directory permissions: " . substr(sprintf('%o', $wp_filesystem->getchmod($base_dir)), -4)); 498 499 // Get process user info safely 500 $process_user = function_exists('posix_getpwuid') ? posix_getpwuid(posix_geteuid())['name'] : 'Unknown'; 501 limbo_log_error("PHP process user: " . $process_user); 502 } 503 504 /** 505 * Create connection between WooCommerce and Limbo products 506 */ 507 function limbo_create_product_connection($wc_product_id, $limbo_product_id, $seller_id) { 508 // Verify nonce 509 if (!check_ajax_referer('limbo-import-nonce', 'security', false)) { 510 return array( 511 'success' => false, 512 'message' => 'Security check failed' 513 ); 514 } 515 516 // Validate and sanitize shop domain 517 $shop_domain = ''; 518 if (isset($_POST['shopDomain'])) { 519 $shop_domain = sanitize_text_field(wp_unslash($_POST['shopDomain'])); 520 } 521 522 if (empty($shop_domain)) { 523 return array( 524 'success' => false, 525 'message' => 'Invalid shop domain' 526 ); 527 } 528 529 $body = array( 530 'woocommerceProductId' => (string)$wc_product_id, 531 'limboProductId' => $limbo_product_id, 532 'sellerId' => $seller_id, 533 'shopDomain' => $shop_domain 534 ); 535 536 $response = wp_remote_post('https://www.serverlimbo.com/seller/woocommerce/create-connection', array( 537 'method' => 'POST', 538 'timeout' => 45, 539 'redirection' => 5, 540 'httpversion' => '1.0', 541 'blocking' => true, 542 'headers' => array( 543 'Content-Type' => 'application/json' 403 544 ), 404 array('source' => 'limbo-import') 545 'body' => json_encode($body), 546 'cookies' => array() 547 )); 548 549 if (is_wp_error($response)) { 550 limbo_log_error("Connection API error: " . $response->get_error_message()); 551 return array( 552 'success' => false, 553 'message' => $response->get_error_message() 554 ); 555 } 556 557 $response_body = json_decode(wp_remote_retrieve_body($response), true); 558 $status_code = wp_remote_retrieve_response_code($response); 559 560 if ($status_code !== 201) { 561 limbo_log_error("Connection API failed with status {$status_code}"); 562 return array( 563 'success' => false, 564 'message' => isset($response_body['message']) ? $response_body['message'] : 'Unknown error' 565 ); 566 } 567 568 limbo_log_error("Successfully created connection with Limbo server"); 569 return array( 570 'success' => true, 571 'message' => 'Connection created successfully', 572 'data' => $response_body 405 573 ); 406 574 } -
limbo/trunk/admin/import/limbo-import.php
r3198812 r3253459 39 39 wp_localize_script('limbo-import-script', 'limboImportData', array( 40 40 'ajaxurl' => admin_url('admin-ajax.php'), 41 'nonce' => wp_create_nonce('limbo-import-nonce') 41 'nonce' => wp_create_nonce('limbo-import-nonce'), 42 'pluginUrl' => plugins_url('', dirname(__FILE__)) 42 43 )); 43 44 -
limbo/trunk/admin/limbo-connections/js/limbo-connections.js
r3198812 r3253459 193 193 194 194 try { 195 const shopDomain = window.location. hostname;195 const shopDomain = window.location.origin; 196 196 const connectionsToDelete = Array.from(selectedConnections).map(connectionId => ({ 197 197 shopDomain, … … 384 384 console.log('User data found:', parsedUserData.id); 385 385 386 const shopDomain = window.location. hostname;386 const shopDomain = window.location.origin; 387 387 console.log('Shop domain:', shopDomain); 388 388 -
limbo/trunk/js/product-page.js
r3198812 r3253459 2 2 $('#limbo-redirect-btn').on('click', async function() { 3 3 const woocommerceProductId = $(this).data('product-id'); 4 const shopDomain = limboData.shopDomain;4 const shopDomain = window.location.origin; 5 5 6 6 try { 7 // Call the Limbo API to get the Limbo product ID 8 const response = await fetch('https://www.serverlimbo.com/seller/woocommerce/get-limbo-product-id', { 7 const response = await fetch('https://www.serverlimbo.com/seller/woocommerce/get-connection-by-woocommerce-product-id', { 9 8 method: 'POST', 10 9 headers: { … … 19 18 const data = await response.json(); 20 19 21 if (data.success && data.limboProductId) { 22 // Open new window with Limbo product page 23 window.open(limboData.limboPublicUrl + data.limboProductId, '_blank'); 20 if (data.success && data.connection && data.connection.limboProductId) { 21 // Open popup window 22 const width = 500; 23 const height = 600; 24 const left = (window.screen.width / 2) - (width / 2); 25 const top = (window.screen.height / 2) - (height / 2); 26 27 window.open( 28 limboData.limboPublicUrl + data.connection.limboProductId, 29 'LimboProduct', 30 `width=${width},height=${height},top=${top},left=${left},resizable=yes,scrollbars=yes` 31 ); 24 32 } else { 25 33 console.error('Failed to get Limbo product ID:', data.message); 26 alert(data.message || ' This product is not available on Limbo.');34 alert(data.message || 'No connection found for this product.'); 27 35 } 28 36 } catch (error) { -
limbo/trunk/limbo.php
r3198812 r3253459 134 134 add_action( 'admin_enqueue_scripts', 'limbo_enqueue_admin_scripts' ); 135 135 136 // Add Limbo button to product page136 // Add Limbo button and modal to product page 137 137 function limbo_add_button() { 138 138 global $product; 139 139 if ( $product ) { 140 echo '<button type="button" id="limbo-redirect-btn" class="button alt" data-product-id="' . esc_attr( $product->get_id() ) . '">' . esc_html__( 'View on Limbo', 'limbo' ) . '</button>'; 140 ?> 141 <!-- Limbo Button --> 142 <button type="button" id="limbo-redirect-btn" class="button alt" data-product-id="<?php echo esc_attr( $product->get_id() ); ?>"> 143 <?php echo esc_html__( 'View on Limbo', 'limbo' ); ?> 144 </button> 145 146 <!-- Limbo Modal --> 147 <div id="limbo-modal" class="limbo-modal"> 148 <div class="limbo-modal-content"> 149 <div class="limbo-modal-header"> 150 <span class="limbo-close">×</span> 151 </div> 152 <div class="limbo-modal-body"> 153 <iframe id="limbo-iframe" frameborder="0" allowfullscreen></iframe> 154 </div> 155 </div> 156 </div> 157 <?php 141 158 } 142 159 } -
limbo/trunk/readme.txt
r3198812 r3253459 3 3 Tags: woocommerce, products, import, export 4 4 Requires at least: 5.0 5 Tested up to: 6. 76 Stable tag: 1.0. 15 Tested up to: 6.8 6 Stable tag: 1.0.2 7 7 Requires PHP: 7.2 8 8 License: GPLv2 or later … … 269 269 270 270 == Changelog == 271 = 1.0.1 = 271 = 1.0.2 = 272 * Added new features and improved existing ones 273 * Fixed bugs and enhanced overall functionality 274 * Updated documentation and added more detailed instructions 275 * Improved user interface and usability 276 * Added new configuration options 277 * Enhanced security measures and data protection 272 278 * Initial release
Note: See TracChangeset
for help on using the changeset viewer.