Changeset 3361266
- Timestamp:
- 09/14/2025 01:14:55 PM (7 months ago)
- Location:
- multi-woo-manager/trunk
- Files:
-
- 3 edited
-
README.txt (modified) (2 diffs)
-
admin/class-multi-woo-manager-admin.php (modified) (3 diffs)
-
multi-woo-manager.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
multi-woo-manager/trunk/README.txt
r3349241 r3361266 6 6 Tested up to: 6.7.1 7 7 Requires PHP: 8.0 8 Stable tag: 1. 1.98 Stable tag: 1.2.0 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 57 57 == Changelog == 58 58 59 = 1.2.0 = 60 * Added Tag Mapping system & Tag editing functionality. Introduced Smart Image synchronization for faster product image updates across multiple stores. 61 59 62 = 1.1.9 = 60 63 * Implemented product duplication functionality -
multi-woo-manager/trunk/admin/class-multi-woo-manager-admin.php
r3349241 r3361266 177 177 'permission_callback' => function () { 178 178 return current_user_can('edit_posts'); 179 }, 180 )); 181 182 /* Register the gallery sync endpoints 183 * @since 1.2.0 184 */ 185 186 // Get product gallery with detailed metadata 187 register_rest_route('wc/v3', '/mwm-gallery/(?P<product_id>\d+)', array( 188 'methods' => 'GET', 189 'callback' => [$this, 'rest_mwm_get_product_gallery'], 190 'permission_callback' => function () { 191 return current_user_can('edit_posts'); 192 }, 193 'args' => array( 194 'product_id' => array( 195 'validate_callback' => function($param, $request, $key) { 196 return is_numeric($param); 197 } 198 ), 199 ), 200 )); 201 202 // Check if media exists by filename 203 register_rest_route('wc/v3', '/mwm-media-exists/', array( 204 'methods' => 'POST', 205 'callback' => [$this, 'rest_mwm_check_media_exists'], 206 'permission_callback' => function () { 207 return current_user_can('upload_files'); 208 }, 209 )); 210 211 // Sync gallery from source with smart duplicate detection 212 register_rest_route('wc/v3', '/mwm-sync-gallery/', array( 213 'methods' => 'POST', 214 'callback' => [$this, 'rest_mwm_sync_gallery'], 215 'permission_callback' => function () { 216 return current_user_can('edit_posts') && current_user_can('upload_files'); 179 217 }, 180 218 )); … … 980 1018 981 1019 } catch (Exception $e) { 982 error_log('Product duplication failed: ' . $e->getMessage());983 984 1020 return new WP_Error( 985 1021 'duplication_failed', … … 1089 1125 1090 1126 } catch (Exception $e) { 1091 error_log('Variation duplication failed: ' . $e->getMessage());1092 1127 // Continue with other variations even if one fails 1093 1128 return false; 1094 1129 } 1095 1130 } 1131 1132 /** 1133 * Get product gallery with detailed metadata for smart sync 1134 * 1135 * @param WP_REST_Request $request 1136 * @return WP_REST_Response 1137 * @since 1.2.0 1138 */ 1139 function rest_mwm_get_product_gallery($request) { 1140 $product_id = $request['product_id']; 1141 1142 try { 1143 $product = wc_get_product($product_id); 1144 1145 if (!$product) { 1146 return new WP_REST_Response([ 1147 'success' => false, 1148 'message' => 'Product not found' 1149 ], 404); 1150 } 1151 1152 $gallery_ids = $product->get_gallery_image_ids(); 1153 $featured_image_id = $product->get_image_id(); 1154 1155 // Combine featured image + gallery images in order 1156 $all_image_ids = array_filter(array_merge( 1157 $featured_image_id ? [$featured_image_id] : [], 1158 $gallery_ids 1159 )); 1160 1161 $images = []; 1162 foreach ($all_image_ids as $index => $attachment_id) { 1163 $attachment = get_post($attachment_id); 1164 if (!$attachment) continue; 1165 1166 $images[] = [ 1167 'id' => $attachment_id, 1168 'filename' => basename($attachment->guid), 1169 'original_filename' => get_post_meta($attachment_id, '_wp_attached_file', true), 1170 'title' => $attachment->post_title, 1171 'alt' => get_post_meta($attachment_id, '_wp_attachment_image_alt', true), 1172 'caption' => $attachment->post_excerpt, 1173 'description' => $attachment->post_content, 1174 'url' => wp_get_attachment_url($attachment_id), 1175 'mime_type' => $attachment->post_mime_type, 1176 'filesize' => filesize(get_attached_file($attachment_id)), 1177 'is_featured' => $index === 0 && $featured_image_id, 1178 'order' => $index, 1179 'date_created' => $attachment->post_date, 1180 'metadata' => wp_get_attachment_metadata($attachment_id) 1181 ]; 1182 } 1183 1184 return new WP_REST_Response([ 1185 'success' => true, 1186 'images' => $images, 1187 'total' => count($images) 1188 ], 200); 1189 1190 } catch (Exception $e) { 1191 return new WP_REST_Response([ 1192 'success' => false, 1193 'message' => 'Error retrieving gallery: ' . $e->getMessage() 1194 ], 500); 1195 } 1196 } 1197 1198 /** 1199 * Check if media exists by filename to avoid duplicate uploads 1200 * 1201 * @param WP_REST_Request $request 1202 * @return WP_REST_Response 1203 * @since 1.2.0 1204 */ 1205 function rest_mwm_check_media_exists($request) { 1206 $params = $request->get_json_params(); 1207 1208 if (!isset($params['filenames']) || !is_array($params['filenames'])) { 1209 return new WP_REST_Response([ 1210 'success' => false, 1211 'message' => 'Missing filenames array' 1212 ], 400); 1213 } 1214 1215 try { 1216 $results = []; 1217 1218 foreach ($params['filenames'] as $filename) { 1219 // Search for attachments with matching filename 1220 $attachments = get_posts([ 1221 'post_type' => 'attachment', 1222 'meta_query' => [ 1223 [ 1224 'key' => '_wp_attached_file', 1225 'value' => $filename, 1226 'compare' => 'LIKE' 1227 ] 1228 ], 1229 'posts_per_page' => 1 1230 ]); 1231 1232 if (!empty($attachments)) { 1233 $attachment = $attachments[0]; 1234 $results[$filename] = [ 1235 'exists' => true, 1236 'id' => $attachment->ID, 1237 'url' => wp_get_attachment_url($attachment->ID), 1238 'title' => $attachment->post_title, 1239 'alt' => get_post_meta($attachment->ID, '_wp_attachment_image_alt', true) 1240 ]; 1241 } else { 1242 $results[$filename] = [ 1243 'exists' => false, 1244 'id' => null 1245 ]; 1246 } 1247 } 1248 1249 return new WP_REST_Response([ 1250 'success' => true, 1251 'results' => $results 1252 ], 200); 1253 1254 } catch (Exception $e) { 1255 return new WP_REST_Response([ 1256 'success' => false, 1257 'message' => 'Error checking media existence: ' . $e->getMessage() 1258 ], 500); 1259 } 1260 } 1261 1262 /** 1263 * Sync gallery from source using native WooCommerce REST API approach 1264 * 1265 * @param WP_REST_Request $request 1266 * @return WP_REST_Response 1267 * @since 1.2.0 1268 */ 1269 function rest_mwm_sync_gallery($request) { 1270 $params = $request->get_json_params(); 1271 1272 if (!isset($params['product_id']) || !isset($params['source_images'])) { 1273 return new WP_REST_Response([ 1274 'success' => false, 1275 'message' => 'Missing product_id or source_images' 1276 ], 400); 1277 } 1278 1279 try { 1280 $product_id = intval($params['product_id']); 1281 $source_images = $params['source_images']; 1282 $product = wc_get_product($product_id); 1283 1284 if (!$product) { 1285 return new WP_REST_Response([ 1286 'success' => false, 1287 'message' => 'Product not found' 1288 ], 404); 1289 } 1290 1291 // Prepare image data for WooCommerce REST API format 1292 $wc_images = []; 1293 $uploaded_count = 0; 1294 $reused_count = 0; 1295 $errors = []; 1296 1297 foreach ($source_images as $index => $source_image) { 1298 try { 1299 // Check if we have a valid image URL 1300 $image_url = ''; 1301 if (isset($source_image['src']) && !empty($source_image['src'])) { 1302 $image_url = $source_image['src']; 1303 } elseif (isset($source_image['url']) && !empty($source_image['url'])) { 1304 $image_url = $source_image['url']; 1305 } 1306 1307 if (empty($image_url)) { 1308 $errors[] = "Image {$index}: No valid URL found"; 1309 continue; 1310 } 1311 1312 // Check if image already exists by filename to avoid duplicates 1313 $filename = basename(parse_url($image_url, PHP_URL_PATH)); 1314 $existing_attachment = $this->find_existing_attachment($filename, $image_url); 1315 1316 if ($existing_attachment) { 1317 // Reuse existing attachment 1318 $wc_images[] = [ 1319 'id' => $existing_attachment->ID, 1320 'src' => wp_get_attachment_url($existing_attachment->ID), 1321 'alt' => get_post_meta($existing_attachment->ID, '_wp_attachment_image_alt', true) ?: ($source_image['alt'] ?? ''), 1322 'name' => get_the_title($existing_attachment->ID) ?: ($source_image['name'] ?? ''), 1323 'position' => $index 1324 ]; 1325 $reused_count++; 1326 } else { 1327 // New image - will be uploaded 1328 $wc_images[] = [ 1329 'src' => $image_url, 1330 'alt' => $source_image['alt'] ?? '', 1331 'name' => $source_image['name'] ?? $source_image['title'] ?? '', 1332 'position' => $index 1333 ]; 1334 $uploaded_count++; 1335 } 1336 1337 } catch (Exception $e) { 1338 $errors[] = "Image {$index}: " . $e->getMessage(); 1339 } 1340 } 1341 1342 if (empty($wc_images)) { 1343 return new WP_REST_Response([ 1344 'success' => false, 1345 'message' => 'No valid images to sync', 1346 'errors' => $errors 1347 ], 400); 1348 } 1349 1350 // Use WooCommerce REST API approach to handle images 1351 if (!empty($wc_images)) { 1352 // Prepare images for WooCommerce REST API format 1353 $processed_attachments = []; 1354 1355 foreach ($wc_images as $index => $image_data) { 1356 1357 if (isset($image_data['id'])) { 1358 // Existing attachment - just use the ID 1359 $processed_attachments[] = $image_data['id']; 1360 } else { 1361 // New image - download and create attachment 1362 $attachment_id = $this->simple_create_attachment($image_data['src'], $product_id, $image_data); 1363 if ($attachment_id && !is_wp_error($attachment_id)) { 1364 $processed_attachments[] = $attachment_id; 1365 } 1366 } 1367 } 1368 1369 // Set product images 1370 if (!empty($processed_attachments)) { 1371 $featured_image_id = array_shift($processed_attachments); 1372 $product->set_image_id($featured_image_id); 1373 $product->set_gallery_image_ids($processed_attachments); 1374 $product->save(); 1375 } 1376 } 1377 1378 // Get the updated product to return fresh data 1379 $updated_product = wc_get_product($product_id); 1380 $final_images = []; 1381 1382 // Get featured image 1383 if ($updated_product->get_image_id()) { 1384 $featured_id = $updated_product->get_image_id(); 1385 $featured_src = wp_get_attachment_url($featured_id); 1386 1387 $final_images[] = [ 1388 'id' => (int) $featured_id, 1389 'src' => $featured_src, 1390 'alt' => get_post_meta($featured_id, '_wp_attachment_image_alt', true) ?: '', 1391 'title' => get_the_title($featured_id) ?: '', 1392 'filename' => basename(get_attached_file($featured_id)) ?: '' 1393 ]; 1394 } 1395 1396 // Get gallery images 1397 foreach ($updated_product->get_gallery_image_ids() as $gallery_id) { 1398 $gallery_src = wp_get_attachment_url($gallery_id); 1399 1400 $final_images[] = [ 1401 'id' => (int) $gallery_id, 1402 'src' => $gallery_src, 1403 'alt' => get_post_meta($gallery_id, '_wp_attachment_image_alt', true) ?: '', 1404 'title' => get_the_title($gallery_id) ?: '', 1405 'filename' => basename(get_attached_file($gallery_id)) ?: '' 1406 ]; 1407 } 1408 1409 return new WP_REST_Response([ 1410 'success' => true, 1411 'message' => "Gallery synced successfully using WooCommerce REST API", 1412 'stats' => [ 1413 'total_images' => count($source_images), 1414 'uploaded' => $uploaded_count, 1415 'reused' => $reused_count, 1416 'errors' => count($errors), 1417 'final_count' => count($final_images) 1418 ], 1419 'synced_images' => $final_images, 1420 'errors' => $errors 1421 ], 200); 1422 1423 } catch (Exception $e) { 1424 return new WP_REST_Response([ 1425 'success' => false, 1426 'message' => 'Error syncing gallery: ' . $e->getMessage() 1427 ], 500); 1428 } 1429 } 1430 1431 /** 1432 * Helper function to find existing attachment by URL 1433 * 1434 * @param string $image_url 1435 * @return WP_Post|null Attachment post or null if not found 1436 */ 1437 public function get_attachment_by_url($image_url) { 1438 // Extract filename from URL 1439 $filename = basename(parse_url($image_url, PHP_URL_PATH)); 1440 1441 if (empty($filename)) { 1442 return null; 1443 } 1444 1445 // Search for attachments with matching filename 1446 $attachments = get_posts([ 1447 'post_type' => 'attachment', 1448 'post_status' => 'inherit', 1449 'meta_query' => [ 1450 [ 1451 'key' => '_wp_attached_file', 1452 'value' => $filename, 1453 'compare' => 'LIKE' 1454 ] 1455 ], 1456 'posts_per_page' => 1 1457 ]); 1458 1459 if (!empty($attachments)) { 1460 return $attachments[0]; 1461 } 1462 1463 // Also try searching by guid (full URL) 1464 $attachments = get_posts([ 1465 'post_type' => 'attachment', 1466 'post_status' => 'inherit', 1467 'meta_query' => [ 1468 [ 1469 'key' => '_wp_attachment_url', 1470 'value' => $image_url, 1471 'compare' => '=' 1472 ] 1473 ], 1474 'posts_per_page' => 1 1475 ]); 1476 1477 return !empty($attachments) ? $attachments[0] : null; 1478 } 1479 1480 /** 1481 * Find existing attachment by filename or URL 1482 * 1483 * @param string $filename 1484 * @param string $image_url 1485 * @return WP_Post|null 1486 */ 1487 public function find_existing_attachment($filename, $image_url) { 1488 // First, try to find by exact filename 1489 $attachments = get_posts([ 1490 'post_type' => 'attachment', 1491 'post_status' => 'inherit', 1492 'meta_query' => [ 1493 [ 1494 'key' => '_wp_attached_file', 1495 'value' => $filename, 1496 'compare' => 'LIKE' 1497 ] 1498 ], 1499 'posts_per_page' => 1 1500 ]); 1501 1502 if (!empty($attachments)) { 1503 return $attachments[0]; 1504 } 1505 1506 // Try to find by URL (guid) 1507 $attachments = get_posts([ 1508 'post_type' => 'attachment', 1509 'post_status' => 'inherit', 1510 'meta_query' => [ 1511 [ 1512 'key' => '_wp_attachment_url', 1513 'value' => $image_url, 1514 'compare' => '=' 1515 ] 1516 ], 1517 'posts_per_page' => 1 1518 ]); 1519 1520 if (!empty($attachments)) { 1521 return $attachments[0]; 1522 } 1523 1524 // Try searching by post title (name) 1525 $name_from_filename = pathinfo($filename, PATHINFO_FILENAME); 1526 $attachments = get_posts([ 1527 'post_type' => 'attachment', 1528 'post_status' => 'inherit', 1529 'title' => $name_from_filename, 1530 'posts_per_page' => 1 1531 ]); 1532 1533 if (!empty($attachments)) { 1534 return $attachments[0]; 1535 } 1536 1537 return null; 1538 } 1539 1540 /** 1541 * Simple method to create attachment from URL 1542 * 1543 * @param string $image_url 1544 * @param int $product_id 1545 * @param array $image_data 1546 * @return int|false 1547 */ 1548 public function simple_create_attachment($image_url, $product_id, $image_data = []) { 1549 require_once(ABSPATH . 'wp-admin/includes/media.php'); 1550 require_once(ABSPATH . 'wp-admin/includes/file.php'); 1551 require_once(ABSPATH . 'wp-admin/includes/image.php'); 1552 1553 // Download the image 1554 $temp_file = download_url($image_url); 1555 if (is_wp_error($temp_file)) { 1556 return false; 1557 } 1558 1559 // Prepare file array 1560 $file_array = [ 1561 'name' => basename($image_url), 1562 'tmp_name' => $temp_file 1563 ]; 1564 1565 // Create attachment 1566 $attachment_id = media_handle_sideload($file_array, $product_id); 1567 1568 // Clean up 1569 if (file_exists($temp_file)) { 1570 unlink($temp_file); 1571 } 1572 1573 if (is_wp_error($attachment_id)) { 1574 return false; 1575 } 1576 1577 // Set alt text if provided 1578 if (!empty($image_data['alt'])) { 1579 update_post_meta($attachment_id, '_wp_attachment_image_alt', $image_data['alt']); 1580 } 1581 1582 return $attachment_id; 1583 } 1584 1585 /** 1586 * Create attachment from URL using WooCommerce's image handling 1587 * 1588 * @param string $image_url 1589 * @param int $product_id 1590 * @param array $image_data 1591 * @return int|false Attachment ID or false on failure 1592 */ 1593 public function create_attachment_from_url($image_url, $product_id, $image_data = []) { 1594 // Use WooCommerce's built-in image upload functionality 1595 require_once(ABSPATH . 'wp-admin/includes/media.php'); 1596 require_once(ABSPATH . 'wp-admin/includes/file.php'); 1597 require_once(ABSPATH . 'wp-admin/includes/image.php'); 1598 1599 try { 1600 // Download the image 1601 $temp_file = download_url($image_url); 1602 1603 if (is_wp_error($temp_file)) { 1604 return false; 1605 } 1606 1607 // Get file info 1608 $file_info = pathinfo($image_url); 1609 $filename = $image_data['name'] ?: $file_info['basename']; 1610 1611 // Prepare file array 1612 $file_array = [ 1613 'name' => $filename, 1614 'tmp_name' => $temp_file 1615 ]; 1616 1617 // Create attachment 1618 $attachment_id = media_handle_sideload($file_array, $product_id, $image_data['alt'] ?? ''); 1619 1620 // Clean up temp file 1621 if (file_exists($temp_file)) { 1622 unlink($temp_file); 1623 } 1624 1625 if (is_wp_error($attachment_id)) { 1626 return false; 1627 } 1628 1629 // Set alt text if provided 1630 if (!empty($image_data['alt'])) { 1631 update_post_meta($attachment_id, '_wp_attachment_image_alt', sanitize_text_field($image_data['alt'])); 1632 } 1633 1634 return $attachment_id; 1635 1636 } catch (Exception $e) { 1637 if (isset($temp_file) && file_exists($temp_file)) { 1638 unlink($temp_file); 1639 } 1640 return false; 1641 } 1642 } 1096 1643 } -
multi-woo-manager/trunk/multi-woo-manager.php
r3349241 r3361266 18 18 * Plugin URI: https://multiwoomanager.com 19 19 * Description: WooCommerce products management, the easy way. Plugin extends WooCommerce REST API in order to provide optimized API routes for product management. 20 * Version: 1. 1.920 * Version: 1.2.0 21 21 * Author: MultiWooManager 22 22 * Author URI: https://multiwoomanager.com/ … … 32 32 } 33 33 34 define( 'MULTI_WOO_MANAGER_VERSION', '1. 1.9' );34 define( 'MULTI_WOO_MANAGER_VERSION', '1.2.0' ); 35 35 36 36 /**
Note: See TracChangeset
for help on using the changeset viewer.