Plugin Directory

Changeset 3361266


Ignore:
Timestamp:
09/14/2025 01:14:55 PM (7 months ago)
Author:
multiwoomanager
Message:

Prepare release 1.2.0: tag editing, image sync, readme update, changelog update.

Location:
multi-woo-manager/trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • multi-woo-manager/trunk/README.txt

    r3349241 r3361266  
    66Tested up to: 6.7.1
    77Requires PHP: 8.0
    8 Stable tag: 1.1.9
     8Stable tag: 1.2.0
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    5757== Changelog ==
    5858
     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
    5962= 1.1.9 =
    6063* Implemented product duplication functionality
  • multi-woo-manager/trunk/admin/class-multi-woo-manager-admin.php

    r3349241 r3361266  
    177177            'permission_callback' => function () {
    178178                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');
    179217            },
    180218        ));
     
    9801018
    9811019        } catch (Exception $e) {
    982             error_log('Product duplication failed: ' . $e->getMessage());
    983 
    9841020            return new WP_Error(
    9851021                'duplication_failed',
     
    10891125
    10901126        } catch (Exception $e) {
    1091             error_log('Variation duplication failed: ' . $e->getMessage());
    10921127            // Continue with other variations even if one fails
    10931128            return false;
    10941129        }
    10951130    }
     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    }
    10961643}
  • multi-woo-manager/trunk/multi-woo-manager.php

    r3349241 r3361266  
    1818 * Plugin URI:        https://multiwoomanager.com
    1919 * 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.9
     20 * Version:           1.2.0
    2121 * Author:            MultiWooManager
    2222 * Author URI:        https://multiwoomanager.com/
     
    3232}
    3333
    34 define( 'MULTI_WOO_MANAGER_VERSION', '1.1.9' );
     34define( 'MULTI_WOO_MANAGER_VERSION', '1.2.0' );
    3535
    3636/**
Note: See TracChangeset for help on using the changeset viewer.