WCMp, also known as WC Marketplace, is one of the most popular and powerful multi-vendor plugins for WordPress and WooCommerce. Recently, WCMp rebranded itself and is now called MultivendorX. WCMp (MultivendorX) is an excellent solution at a modest price – the core plugin is free. There will always be shortcomings when you’re trying to develop a solution as complex as a multi-vendor marketplace. In order to fully customize your WCMp project, you’ll need to implement custom code snippets applied via hooks and filters. If you are new to WCMp customizations, the MultivendorX Support Forum is a good place to start.
In my development experience with WC Marketplace (MultivendorX), I have written and used many code snippets but the code snippets below have proven to be very useful and time saving for my projects and my clients. Below are the best WCMp code snippets that you won’t find anywhere else.
Bulk Duplicate Products for Vendors
When utilizing the SPMV (Single Product Multiple Vendor) feature in WCMp, duplicating products can be a slow process. Each product has to be duplicated individually. When you have 50-100 products and double-digit numbers of vendors that all carry a majority of those products, the time it takes to duplicate those products is hard to fathom. This custom solution I developed can be inserted into the (child) theme’s functions.php file or could potentially be used to create a standalone plugin.
Warning: This code snippet can result intense server resource usage when duplicating a large amount of products and can potentially cause server downtime if you overload the server. I recommend using this bulk action method of duplicating products in small batches of 10-30 products at a time depending on your server. The amount of time to process such requests may vary depending on server as well. More powerful VPS or dedicated servers may be able to handle processing bigger batches.
// Add new action to bulk action drop-down menu labeled "Copy for Vendor"
add_filter( 'bulk_actions-edit-product', 'duplicate_product_to_vendor_register_bulk_action' );
// Main duplication process handler
add_filter( 'handle_bulk_actions-edit-product', 'duplicate_product_to_vendor_action_handler', 10, 3 );
// Notification after duplicating products
add_action( 'admin_notices', 'duplicate_product_to_vendor_bulk_action_notices' );
function duplicate_product_to_vendor_bulk_action_notices() {
if ( ! empty( $_REQUEST['duplicate_product_to_vendor_done'] ) ) {
echo '<div id="message" class="updated notice is-dismissible">
<p>Duplicate product(s) created. Now assign to vendor and publish.</p>
</div>';
}
}
function duplicate_product_to_vendor_register_bulk_action($bulk_actions) {
$bulk_actions['duplicate_product_to_vendor'] = __( 'Copy for Vendor', 'duplicate-post');
return $bulk_actions;
}
function duplicate_product_to_vendor_action_handler( $redirect, $action, $post_ids ) {
$redirect = remove_query_arg( 'duplicate_product_to_vendor_done', $redirect );
if ( $action !== 'duplicate_product_to_vendor' ) {
return $redirect;
}
global $WCMp;
$counter = 0;
foreach ( $post_ids as $post_id ) {
$parent_post = get_post($post_id);
if(!empty($parent_post)){
$product = wc_get_product($post_id);
if (!function_exists('duplicate_post_plugin_activation')) {
include_once( WC_ABSPATH . 'includes/admin/class-wc-admin-duplicate-product.php' );
}
$duplicate_product_class = new WC_Admin_Duplicate_Product();
$duplicate_product = $duplicate_product_class->product_duplicate($product);
if ($duplicate_product) {
// if product title has Copy string, remove
$title = str_replace(" (Copy)", "", $parent_post->post_title);
wp_update_post(array('ID' => $duplicate_product->get_id(), 'post_author' => $duplicate_to_vendor, 'post_title' => $title));
wp_set_object_terms($duplicate_product->get_id(), absint(get_current_vendor()->term_id), $WCMp->taxonomy->taxonomy_name);
// Add GTIN, if exists
$gtin_data = wp_get_post_terms($product->get_id(), $WCMp->taxonomy->wcmp_gtin_taxonomy);
if ($gtin_data) {
$gtin_type = isset($gtin_data[0]->term_id) ? $gtin_data[0]->term_id : '';
wp_set_object_terms($duplicate_product->get_id(), $gtin_type, $WCMp->taxonomy->wcmp_gtin_taxonomy, true);
}
$gtin_code = get_post_meta($product->get_id(), '_wcmp_gtin_code', true);
if ($gtin_code)
update_post_meta($duplicate_product->get_id(), '_wcmp_gtin_code', $gtin_code);
$has_wcmp_spmv_map_id = get_post_meta($product->get_id(), '_wcmp_spmv_map_id', true);
if ($has_wcmp_spmv_map_id) {
$data = array('product_id' => $duplicate_product->get_id(), 'product_map_id' => $has_wcmp_spmv_map_id);
update_post_meta($duplicate_product->get_id(), '_wcmp_spmv_map_id', $has_wcmp_spmv_map_id);
wcmp_spmv_products_map($data, 'insert');
} else {
$data = array('product_id' => $duplicate_product->get_id());
$map_id = wcmp_spmv_products_map($data, 'insert');
if ($map_id) {
update_post_meta($duplicate_product->get_id(), '_wcmp_spmv_map_id', $map_id);
$data = array('product_id' => $product->get_id(), 'product_map_id' => $map_id);
wcmp_spmv_products_map($data, 'insert');
update_post_meta($product->get_id(), '_wcmp_spmv_map_id', $map_id);
}
update_post_meta($product->get_id(), '_wcmp_spmv_product', true);
}
update_post_meta($duplicate_product->get_id(), '_wcmp_spmv_product', true);
// Add other post meta updates here like the example below where Yoast's setting is set to not index the duplicate product. There are a huge variety of other updates that could be applicable to your particular situation.
// update_post_meta($duplicate_product->get_id(),'_yoast_wpseo_meta-robots-noindex', 1);
$duplicate_product->save();
// Uncomment this line below to remove featured product from duplicate
// wp_remove_object_terms($duplicate_product->get_id(), 'featured', 'product_visibility');
}
$counter++;
}
}
$redirect = add_query_arg( 'duplicate_product_to_vendor_done', $counter, $redirect );
return $redirect;
}
Note: All duplicates are initially created as a draft. After duplication, use bulk edit to assign the products to a vendor and change the status to ‘Published’.
Synchronize Mapped Products
Another SPMV (Single Product Multiple Vendor) issue I ran into while configuring a client’s site with WCMp was dealing with synchronizing product fields including featured image, stock, price, sale price, description, short description, and image gallery. In many cases, the vendor may not need to control products at all or may only need to control certain product details/fields. In the situation where you have multiple vendors selling the same products out of one inventory, this was extremely important. This code snippet will likely need to be customized for each specific site, but it provides a great base to work from and covers both simple and variable products. It can be inserted into the (child) theme’s functions.php file or could potentially be used to create a standalone plugin.
// Update mapped product fields after product update
add_action( 'woocommerce_update_product', 'woocommerce_update_mapped_product_sync', 10, 1 );
// Fields updated: featured image, stock, price, sale price, description, short description, image gallery
function woocommerce_update_mapped_product_sync( $product_get_id ) {
global $wpdb;
if(!user_can( get_post_field( 'post_author', $product_get_id ), 'administrator' )){
return;
}
$has_product_map_id = get_post_meta($product_get_id, '_wcmp_spmv_map_id', true);
if($has_product_map_id){
$mapped_products = $wpdb->get_results( "select m.post_id as post_id, p.post_title as post_title, p.post_content as post_content, p.post_excerpt as post_excerpt from $wpdb->posts as p JOIN $wpdb->postmeta as m ON p.ID = m.post_id where m.meta_key = '_wcmp_spmv_map_id' AND m.meta_value = $has_product_map_id AND m.post_id <> $product_get_id", ARRAY_A );
if($mapped_products){
$product = wc_get_product( $product_get_id );
$product_name = $product->get_name();
$product_stock_count = $product->get_stock_quantity();
$product_regular_price = $product->get_regular_price();
$product_price = $product->get_price();
$product_sale_price = $product->get_sale_price();
$product_description = $product->get_description();
$product_short_description = $product->get_short_description();
$product_image_id = get_post_thumbnail_id($product_get_id);
$product_image_gallery = get_post_meta( $product_get_id, '_product_image_gallery');
// Only for variable products
if( $product->is_type('variable') ){
foreach ($mapped_products as $mapped_product){
$map_product_id = $mapped_product['post_id'];
if($product_name && $product_name != $mapped_product['post_title']){
$post_product_name = array('post_title' => $product_name);
$where_product_name = array('ID' => $map_product_id);
$wpdb->update("{$wpdb->prefix}posts", $post_product_name, $where_product_name);
}
if($product_description && $product_description != $mapped_product['post_content']){
$post_description = array('post_content' => $product_description);
$where_description = array('ID' => $map_product_id);
$wpdb->update("{$wpdb->prefix}posts", $post_description, $where_description);
}
if($product_short_description && $product_description != $mapped_product['post_excerpt']){
$post_short_description = array('post_excerpt' => $product_description);
$where_short_description = array('ID' => $map_product_id);
$wpdb->update("{$wpdb->prefix}posts", $post_short_description, $where_short_description);
}
if($product_image_id){
update_post_meta( $map_product_id, '_thumbnail_id', $product_image_id );
}
if($product_image_gallery){
update_post_meta( $map_product_id, '_product_image_gallery', implode($product_image_gallery) );
}
}
foreach ( $product->get_children() as $variation_id ) {
// print_r($variation_id);exit;
$variation = wc_get_product( $variation_id );
$variation_name = $variation->get_name();
$variation_stock_count = $variation->get_stock_quantity();
$variation_regular_price = $variation->get_regular_price();
$variation_price = $variation->get_price();
$variation_sale_price = $variation->get_sale_price();
$variation_description = $variation->get_description();
$variation_short_description = $variation->get_short_description();
$variation_image_id = get_post_thumbnail_id($variation_id);
$variation_image_gallery = get_post_meta( $variation_id, '_product_image_gallery');
$variation_attributes = $variation->get_attributes();
foreach ($mapped_products as $mapped_product){
$map_variation_product_id = $mapped_product['post_id'];
$variation = wc_get_product( $map_variation_product_id );
foreach ( $variation->get_children() as $map_variation_id ) {
$map_variation = wc_get_product($map_variation_id);
if($variation_attributes == $map_variation->get_attributes()){
if(isset($variation_stock_count)) {
if($variation_stock_count > 0){
update_post_meta($map_variation_id, '_stock_status', 'instock');
}else{
update_post_meta($map_variation_id, '_stock_status', 'outofstock');
}
update_post_meta( $map_variation_id, '_stock', $variation_stock_count );
}
if($variation_regular_price){
update_post_meta( $map_variation_id, '_regular_price', $variation_regular_price );
}
if($variation_sale_price){
update_post_meta( $map_variation_id, '_sale_price', $variation_sale_price );
update_post_meta( $map_variation_id, '_price', $variation_sale_price );
}
if (!$variation_sale_price){
if(metadata_exists( 'post', $map_variation_id, '_sale_price' )){
delete_post_meta($map_variation_id,'_sale_price');
}
update_post_meta( $map_variation_id, '_price', $variation_regular_price );
}
if($variation_image_id){
update_post_meta( $map_variation_id, '_thumbnail_id', $variation_image_id );
}
wc_delete_product_transients( $map_variation_id ); // Clear/refresh the variation cache
}
}
}
// Clear/refresh the variable product cache
wc_delete_product_transients( $variation_id );
}
}else{
foreach ($mapped_products as $mapped_product){
$map_product_id = $mapped_product['post_id'];
if($product_name && $product_name != $mapped_product['post_title']){
$post_product_name = array('post_title' => $product_name);
$where_product_name = array('ID' => $map_product_id);
$wpdb->update("{$wpdb->prefix}posts", $post_product_name, $where_product_name);
}
if($product_description && $product_description != $mapped_product['post_content']){
$post_description = array('post_content' => $product_description);
$where_description = array('ID' => $map_product_id);
$wpdb->update("{$wpdb->prefix}posts", $post_description, $where_description);
}
if($product_short_description && $product_description != $mapped_product['post_excerpt']){
$post_short_description = array('post_excerpt' => $product_description);
$where_short_description = array('ID' => $map_product_id);
$wpdb->update("{$wpdb->prefix}posts", $post_short_description, $where_short_description);
}
if($product_image_id){
update_post_meta( $map_product_id, '_thumbnail_id', $product_image_id );
}
if($product_image_gallery){
update_post_meta( $map_product_id, '_product_image_gallery', implode($product_image_gallery));
}
if(isset($product_stock_count)) {
if($product_stock_count > 0){
update_post_meta($map_product_id, '_stock_status', 'instock');
}else{
update_post_meta($map_product_id, '_stock_status', 'outofstock');
}
update_post_meta( $map_product_id, '_stock', $product_stock_count );
}
if($product_regular_price){
update_post_meta( $map_product_id, '_regular_price', $product_regular_price );
}
if($product_sale_price){
update_post_meta( $map_product_id, '_sale_price', $product_sale_price );
update_post_meta( $map_product_id, '_price', $product_sale_price );
}
if(!$product_sale_price){
if(metadata_exists( 'post', $map_product_id, '_sale_price' )){
delete_post_meta($map_product_id,'_sale_price');
}
update_post_meta( $map_product_id, '_price', $product_regular_price );
}
}
}
}
}
}
Synchronize Stock on Mapped Products after Checkout Completed
Similar to the code snippet above synchronizing fields on product update, this code snipped synchronizes only stock for all mapped product when a checkout is complete. It removes the quantity purchased from the stock of all the other mapped products. It is ONLY useful in SPMV (Single Product Multiple Vendor) scenarios where there is one master inventory. For example, a site administrator has all the inventory at their location and the vendors/shop owners simply list products for sale from that inventory. It is a unique circumstance, but I ran into it so maybe someone else will too. It can be inserted into the (child) theme’s functions.php file or could potentially be used to create a standalone plugin.
add_action('woocommerce_thankyou', 'update_inventory_mapped_products', 10, 1);
function update_inventory_mapped_products( $order_id ) {
if ( ! $order_id )
return;
//declare global objects
global $WCMp, $wpdb;
// Getting an instance of the order object
$order = wc_get_order( $order_id );
if(!$order->is_paid())
return;
// iterating through each order item (getting product ID)
foreach ( $order->get_items() as $item_id => $item ) {
if( $item['variation_id'] > 0 ){
$product_id = $item['product_id'];
$variation_id = $item['variation_id']; // variable product
$variation_excerpt = get_the_excerpt($variation_id);
$has_product_map_id = get_post_meta($product_id, '_wcmp_spmv_map_id', true);
$product_manage_stock = get_post_meta($variation_id, '_manage_stock', true);
if($has_product_map_id && $product_manage_stock == 'yes'){
$product_stock_count = get_post_meta($variation_id, '_stock', true);
$mapped_products = $wpdb->get_results( "select post_id from $wpdb->postmeta where meta_key = '_wcmp_spmv_map_id' AND meta_value = $has_product_map_id AND post_id <> $product_id", ARRAY_A );
if($mapped_products){
foreach ($mapped_products as $mapped_product){
$mapped_product_product = wc_get_product($mapped_product['post_id']);
if($mapped_product_product){
// foreach( $mapped_product_product->get_available_variations() as $variation_values ){
foreach ( $mapped_product_product->get_children() as $map_variation_product_id ) {
// print_r($variation_values);
// $map_variation_product_id = $variation_values['variation_id'];
if($variation_excerpt == get_the_excerpt($map_variation_product_id)) {
if($product_stock_count > 0){
update_post_meta($map_variation_product_id, '_stock_status', 'instock');
}else{
update_post_meta($map_variation_product_id, '_stock_status', 'outofstock');
}
update_post_meta( $map_variation_product_id, '_stock', $product_stock_count );
}
}
}
}
}
}
} else {
$product_id = $item['product_id']; // simple product
$has_product_map_id = get_post_meta($product_id, '_wcmp_spmv_map_id', true);
$product_manage_stock = get_post_meta($product_id, '_manage_stock', true);
if($has_product_map_id && $product_manage_stock == 'yes'){
$product_stock_count = get_post_meta($product_id, '_stock', true);
$mapped_products = $wpdb->get_results( "select post_id from $wpdb->postmeta where meta_key = '_wcmp_spmv_map_id' AND meta_value = $has_product_map_id AND post_id <> $product_id", ARRAY_A );
if($mapped_products){
foreach ($mapped_products as $mapped_product){
// update_post_meta( $mapped_product['post_id'], '_stock', $product_stock_count );
if($product_stock_count > 0){
update_post_meta($mapped_product['post_id'], '_stock_status', 'instock');
}else{
update_post_meta($mapped_product['post_id'], '_stock_status', 'outofstock');
}
update_post_meta($mapped_product['post_id'], '_stock', $product_stock_count );
}
}
}
}
}
}
Disable WCMp Visitor Stats
This is a simple code snippet. It can be found on WCMp’s site as well, but it’s an important one to me! Plugin-based visitor tracking can decrease site performance. Collecting visitor stats in the WordPress database can also eventually cause more performance problems due to a bloated database. Even though it may be a nice stat to show vendors on their dashboard, stick with 3rd party analytics like Google Analytics, Jetpack Site Stats, or another provider to track stats unless you have a very low traffic website.
// Remove visitor stats tracking
add_filter('wcmp_is_disable_store_visitors_stats', '__return_false');
Leave your ideas, requests, or feedback on these code snippets below in the comments. Thanks!