Review vendor feature

Hello team, I have seen plenty of posts regarding a feature to allow buyers to review de request or the vendor instead of the listing. As per this last one https://community.hivepress.io/t/reviews-for-requests/11093 you guys are giving good news saying you are planning to have it implemented this year 2024, but in the asana roadmap Reviews Roadmap – Asana this feature is still in the column of items for the next six months. Is 2024 still the year for us to see this feature live? Any news are much appreciated!

Thanks in advance!

Hi,

Unfortunately, there is no exact ETA, so we cannot guarantee that it will be in 2024, but we can suggest workarounds. Also, please note that the vendor rating is the average rating of its listings. If you describe your use case, we will try to help.

I understand Andrii, thanks!

The use case is as follows:

  1. Buyer publishes a request
  2. Vendor makes an offer
  3. Buyer accepts said offer
  4. Vendor delivers the service and completes the order
  5. Buyer also completes the order
  6. Buyer should receive an email with a link to give a review and score to the vendor (or at least a default listing)

Any workaround you can provide would be much appreciated.

We generated some ideas with the group of people I am working with, perhaps you can find them useful for the workaround you may suggest Andrii. If they are not feasible feel free to ignore them.

  1. As mentioned before, create a default listing for every vendor, when a request is completed send the url to review that default request via email.
  2. Use an external plugin to create a review form, send it to customers when requests are completed, capture responses and export them to the phpMyAdmin table where reviews are stored.
  3. Create a plugin (or use an existing one you may recommend) that captures vendor reviews through a form on the vendor’s profile and displays them there.

Please let us know what you think.

Hi,

Thank you for your feedback, we will implement this as described in your third point. Also, if you have a developer or are familiar with coding, we can provide general guidance as a temporary solution, as the ranking calculation and its basis is now completely dependent on listings.

Hello Andrii,

Thank you for your reply. It is great to read that! Yes I am familiar with coding so I would really appreciate your further guidance on that third point please.

Hi Gabriel,

The easiest way would be generating a hidden listing per vendor, and using it for collecting reviews. I figured that we’d probably do the same thing because this doesn’t require implementing a new review type associated with the vendor profile directly.

When the vendor profile is created or updated (e.g. you can use the hivepress/v1/models/vendor/update hook), and the vendor profile is published (has “publish” status), you can get or create a linked listing if it doesn’t exist yet. You can use a custom meta field to differentiate such listings from regular ones, also you can try to use the “private” status for these listings but I can’t say for sure if reviews will work for them, there may be “publish” status checks in the Reviews extension code.

Then you can customize the order page via the HivePress template hook (or WooCommerce template hooks), get the vendor ID ($order->get_meta('hp_vendor')) and get the linked hidden listing, then add a link to it.

The last part is customizing the template for these listings so they would look (for example) as a review form, since all the other template parts for such listings are not needed. You can also add links to such listings to the order completion email and proceed with other improvements if needed.

Hope this helps

Thanks ihor!

Let me try this out for the next couple of days and see how far I can get.

Hello Ihor and Andrii.

Just wanted to give you guys an update, I performed the following steps but reached a roadblock , any guidance you’d like to share would be much appreciated.

  1. I created a private listing per vendor using the udpate hook (this still has a little bug I am working on: the private listings are all assigned to one same vendor instead of the right vendor)
// Hook to create a hidden listing for each vendor
add_action('hivepress/v1/models/vendor/update', function($vendor_id) {
    // Ensure the hook only runs for the vendor currently being updated
    if (get_transient('creating_vendor_listing_' . $vendor_id)) {
        return;
    }
    set_transient('creating_vendor_listing_' . $vendor_id, true, 10);

    error_log("Running hook to create hidden listing for vendor ID: " . $vendor_id);

    // Verify that $vendor_id is valid
    if (!$vendor_id || !is_numeric($vendor_id)) {
        error_log("Vendor ID is invalid or missing.");
        delete_transient('creating_vendor_listing_' . $vendor_id);
        return;
    }

    // Check if a hidden listing already exists for this specific vendor
    $linked_listing = get_posts([
        'post_type' => 'hp_listing',
        'meta_query' => [
            [
                'key' => '_linked_vendor_id',
                'value' => intval($vendor_id),
                'compare' => '='
            ]
        ],
        'post_status' => 'private',
        'numberposts' => 1
    ]);

    // Create the hidden listing if it doesn't already exist
    if (empty($linked_listing)) {
        $listing_id = wp_insert_post([
            'post_type' => 'hp_listing',
            'post_title' => 'Review for Vendor ' . $vendor_id,
            'post_status' => 'private'
        ]);

        if ($listing_id) {
            add_post_meta($listing_id, '_linked_vendor_id', $vendor_id, true);
            error_log("Listing created and correctly linked to vendor ID: " . $vendor_id);
        } else {
            error_log("Error creating listing for vendor ID: " . $vendor_id);
        }
    } else {
        error_log("Hidden listing already exists for vendor ID: " . $vendor_id);
    }

    // Remove the transient to allow future executions if necessary
    delete_transient('creating_vendor_listing_' . $vendor_id);
});

  1. Then I customized the order page with a form using this snippet, I am posting an image of how it looks:
add_action('woocommerce_order_details_after_order_table', function($order) {
    // Get the vendor ID from the order metadata
    $vendor_id = $order->get_meta('hp_vendor');

    // Fetch the hidden listing linked to this vendor
    $linked_listing = get_posts([
        'post_type' => 'hp_listing',
        'meta_query' => [
            [
                'key' => '_linked_vendor_id',
                'value' => intval($vendor_id),
                'compare' => '='
            ]
        ],
        'post_status' => 'private',
        'numberposts' => 1
    ]);

    // Display the form only if the hidden listing is found
    if (!empty($linked_listing)) {
        $listing_id = $linked_listing[0]->ID;

        echo '<h3>Please rate your satisfaction with the service you received</h3>';
        echo '<form method="post">';
        echo '<label for="review_content">Your message:</label>';
        echo '<textarea id="review_content" name="review_content" required></textarea>';

        echo '<label for="review_rating">Satisfaction:</label>';
        echo '<select id="review_rating" name="review_rating" required>';
        echo '<option value="5">5 - Very satisfied</option>';
        echo '<option value="4">4 - Somewhat satisfied</option>';
        echo '<option value="3">3 - Neither satisfied nor dissatisfied</option>';
        echo '<option value="2">2 - Somewhat dissatisfied</option>';
        echo '<option value="1">1 - Very dissatisfied</option>';
        echo '</select>';

        echo '<input type="hidden" name="listing_id" value="' . esc_attr($listing_id) . '">';
        echo '<button type="submit" name="submit_review">Submit Review</button>';
        echo '</form>';
    }
});

  1. If the user submits this form, it is actually posting the review comment on the private listing correctly, but I believe I am not posting the amount of stars correctly? Here is the code I am using.

add_action('init', function() {
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit_review'])) {
        $listing_id = intval($_POST['listing_id']);
        $review_content = sanitize_textarea_field($_POST['review_content']);
        $review_rating = intval($_POST['review_rating']);

        // Verifica que se haya proporcionado el ID del anuncio
        if ($listing_id && $review_content && $review_rating) {
            // Crear el comentario asociado al anuncio oculto
            $comment_id = wp_insert_comment([
                'comment_post_ID' => $listing_id,
                'comment_content' => $review_content,
                'comment_type' => 'review',
                'user_id' => get_current_user_id(),
                'comment_approved' => 1,
            ]);

            // Si el comentario se creó, agrega la calificación como meta dato
            if ($comment_id) {
                update_comment_meta($comment_id, 'rating', $review_rating);

                // Redireccionar para evitar reenvíos del formulario
                wp_redirect(get_permalink($listing_id) . '#reviews');
                exit;
            }
        }
    }
});

Thanks in advance for your help gentlemen.

Hi,

Yes, the code seems fine, I’m not sure why listings are added to the same vendor ID, though. The code should be ok even without the transient use, you can try logging the vendor ID and try registering as a new vendor.

As for the rating, it’s stored in the “comment_karma” field instead of the comment meta, and accepts values from 1 to 5.

Hope this helps

Thank you so much Ihor.

I moved forward a little more with this. I also specified that the comment type as hp_review and updated the wp_postmeta with the meta_key: ‘hp_rating’… the good news is that the listing is finally showing the rating that the client is submitting from the order form. This is the latest code:

add_action('init', function() {
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit_review'])) {
        $listing_id = intval($_POST['listing_id']);
        $review_content = sanitize_textarea_field($_POST['review_content']);
        $review_rating = intval($_POST['review_rating']);

        // Verify that listing ID, review content, and rating are provided
        if ($listing_id && !empty($review_content) && $review_rating) {
            error_log("Processing review for listing ID: $listing_id");

            // Insert the comment associated with the hidden listing
            $comment_id = wp_insert_comment([
                'comment_post_ID' => $listing_id,
                'comment_content' => $review_content,
                'comment_type' => 'hp_review', // Set the correct type for HivePress
                'user_id' => get_current_user_id(),
                'comment_approved' => 1,
                'comment_karma' => $review_rating, // Store the rating here
            ]);

            if ($comment_id) {
                error_log("Review successfully saved. Comment ID: $comment_id");

                // Update the hp_rating meta key for the listing with the provided rating
                update_post_meta($listing_id, 'hp_rating', $review_rating);

                error_log("Updated hp_rating for listing ID: $listing_id with rating: $review_rating");

                // Trigger any HivePress or WordPress hooks related to reviews
                do_action('hivepress/v1/review/insert', $comment_id);

                // Redirect to avoid form resubmission
                wp_redirect(get_permalink($listing_id) . '#reviews');
                exit;
            } else {
                error_log("Failed to save review for listing ID: $listing_id");
            }
        } else {
            error_log("Invalid form data: listing_id = $listing_id, review_content = $review_content, review_rating = $review_rating");
        }
    }
});

The next step is to actually have this rating to be considered in the global rating from the vendor. Look what happens if I temporarily set the private listing to published, you can see its rating but it does not affect the rating of the vendor:

I checked the documentation on how the vendor profile gets the average rating but I have not been able to identify that yet. I appreciate any further guidance please.

Thanks in advance.

Hi gabriel.moya,

This is something that I am looking for, too.

Please update us when you have the final version.

Since I am not a developer with coding skills, sharing of your custom code and step-by-step guidance on how you did all these modifications on your site would be highly appreciated.

Thanks,
Blagoj

1 Like

Hi,

I guess using the “private” status for this listing causes issues because the average vendor rating is calculated for listings with “publish” status only hivepress-reviews/includes/components/class-review.php at master · hivepress/hivepress-reviews · GitHub There are 2 possible solutions, using another way to hide this listing (e.g. custom meta field to differentiate between regular listings and those generated for ratings only, or maybe creating a hidden category for such listings), or filtering this query and changing the listings status filter to ['publish', 'private'], but it would be hard to differentiate this query from other queries (maybe by setting some flag when the rating status update hook runs and unsettling it later).

Hope this helps, and thanks for sharing!

Hello team!

Thanks ihor! I am still not done but I feel we are getting closer with your first solution.
So far I am able to create a public listing with a meta field called _is_hidden_review_listing, if the buyer submits the review in the order page, the review is stored properly as other reviews created from the original listing page. Now I need help with the next 2 steps:

The situation I am having is that when the buyer is submitting the review from the order page it is not updating the global score for the listing or the buyer automatically. Let me explain, if the review is submitted from the original form in the listing page the global score is actually being updated (including the reviews that were submitted from the order page). So I need to know which hook to use to update the global score of the vendor after submitting a review so I can add it to the code I am using after submiting the review. (I tried to use this one add_action( ‘hivepress/v1/models/review/update_status’, [ $this, ‘update_rating’ ], 10, 2 ); I found on the repo ) but it did not work, maybe it is another one?

The next step is to filter out the listings that have the meta field _is_hidden_review_listing value in 1 from the listings view and the vendor view, can you confirm which add_filter should we use or in which repo is it please?

Thank you so much for your help!

Hi,

  1. After you insert a review and update the rating value for it, please try to switch the review status programmatically (e.g. insert it with a pending status and then update this comment to the approved status). It seems that when the review is inserted there’s no rating yet so this hook doesn’t affect the rating hivepress-reviews/includes/components/class-review.php at master · hivepress/hivepress-reviews · GitHub and if you insert the review without changing the status later, this hook also doesn’t fire hivepress-reviews/includes/components/class-review.php at master · hivepress/hivepress-reviews · GitHub

  2. I recommend using the WordPress core pre_get_posts hook, you can check if it’s a front-end query, if the post_type is hp_listing (and possibly other checks to prevent affecting specific queries), and add an extra condition to the meta_query.

Hello ihor!

I struggled with the hook to affect the vendor’s rating. So I took a different approach to the problem and tried to replicate the same POST call from the current review form on the listing page and apparently it is working, these are the steps I have taken so far:

  1. In functions.php add a hook to create a listing with a custom meta field:
// Hook to create a public listing for each vendor with a custom meta field
add_action('hivepress/v1/models/vendor/update', function($vendor_id) {
    // Ensure the hook only runs for the vendor currently being updated
    if (get_transient('creating_vendor_listing_' . $vendor_id)) {
        return;
    }
    set_transient('creating_vendor_listing_' . $vendor_id, true, 10);

    error_log("Running hook to create public listing for vendor ID: " . $vendor_id);

    // Verify that $vendor_id is valid
    if (!$vendor_id || !is_numeric($vendor_id)) {
        error_log("Vendor ID is invalid or missing.");
        delete_transient('creating_vendor_listing_' . $vendor_id);
        return;
    }

    // Check if a public listing with the custom meta field already exists for this specific vendor
    $linked_listing = get_posts([
        'post_type' => 'hp_listing',
        'meta_query' => [
            [
                'key' => '_linked_vendor_id',
                'value' => intval($vendor_id),
                'compare' => '='
            ],
            [
                'key' => '_is_hidden_review_listing',
                'value' => '1',
                'compare' => '='
            ]
        ],
        'post_status' => 'publish',
        'numberposts' => 1
    ]);

    // Create the public listing if it doesn't already exist
    if (empty($linked_listing)) {
        $listing_id = wp_insert_post([
            'post_type' => 'hp_listing',
            'post_title' => 'Review for Vendor ' . $vendor_id,
            'post_status' => 'publish' // Set the status to 'publish' to make it public
        ]);

        if ($listing_id) {
            // Add the linked vendor ID
            add_post_meta($listing_id, '_linked_vendor_id', $vendor_id, true);

            // Add the custom meta field to identify the listing
            add_post_meta($listing_id, '_is_hidden_review_listing', '1', true);

            error_log("Public listing created with ID: " . $listing_id . " for vendor ID: " . $vendor_id);
        } else {
            error_log("Error creating listing for vendor ID: " . $vendor_id);
        }
    } else {
        error_log("Public listing already exists for vendor ID: " . $vendor_id);
    }

    // Remove the transient to allow future executions if necessary
    delete_transient('creating_vendor_listing_' . $vendor_id);
});
  1. Added this code to functions.php to create a form on the woocommerce order page that is only visible when the order has been completed.

add_action('woocommerce_order_details_after_order_table', function($order) {
    // Verify the order status
    $order_status = $order->get_status();

    // Display the form only if the status is "completed" (delivered)
    if ($order_status !== 'completed') {
        error_log("Order status is '$order_status'. Review form is not displayed.");
        return;
    }

    // Get the vendor ID from the order
    $vendor_id = $order->get_meta('hp_vendor');

    // Get the public linked listing with the custom meta field
    $linked_listing = get_posts([
        'post_type' => 'hp_listing',
        'meta_query' => [
            [
                'key' => '_linked_vendor_id',
                'value' => intval($vendor_id),
                'compare' => '='
            ],
            [
                'key' => '_is_hidden_review_listing',
                'value' => '1',
                'compare' => '='
            ]
        ],
        'post_status' => 'publish',
        'numberposts' => 1
    ]);

    // Display the form only if the linked listing exists
    if (!empty($linked_listing)) {
        $listing_id = $linked_listing[0]->ID;

        echo '<h3>Please rate your satisfaction with the service you received</h3>';
        echo '<form id="review-form" method="post" action="#" data-action="' . esc_url(site_url('/wp-json/hivepress/v1/reviews/')) . '">';
        echo '<label for="review_content">Your message:</label>';
        echo '<textarea id="review_content" name="text"></textarea>';

        echo '<label for="review_rating">Satisfaction</label>';
        echo '<select id="review_rating" name="rating" required>';
        echo '<option value="5">5 - Very satisfied</option>';
        echo '<option value="4">4 - Somewhat satisfied</option>';
        echo '<option value="3">3 - Neither satisfied nor dissatisfied</option>';
        echo '<option value="2">2 - Somewhat dissatisfied</option>';
        echo '<option value="1">1 - Not satisfied at all</option>';
        echo '</select>';

        echo '<input type="hidden" name="listing" value="' . esc_attr($listing_id) . '">';
        echo '<button type="submit" name="submit_review">Submit review</button>';
        echo '</form>';
    } else {
        error_log("No linked listing found for vendor ID: $vendor_id.");
    }
});


  1. When said form is submitted, I call a .js file (custom-review-form.js) I saved in wp-content/themes/experthive/js that handles the call from the form.
document.getElementById('review-form').addEventListener('submit', async (event) => {
    event.preventDefault(); // Prevent the default form submission behavior
    const form = event.target;
    const actionUrl = form.getAttribute('data-action'); // Retrieve the action URL from the form's data attribute
    const payload = {
        listing: form.querySelector('input[name="listing"]').value, // Get the listing ID from the hidden input field
        rating: form.querySelector('select[name="rating"]').value, // Get the selected rating value
        text: form.querySelector('textarea[name="text"]').value // Get the review text from the textarea
    };

    try {
        const response = await fetch(actionUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json', // Indicate that the payload is JSON
                'X-WP-Nonce': wpApiSettings.nonce, // Use the nonce defined in wpApiSettings for authentication
            },
            body: JSON.stringify(payload) // Convert the payload to a JSON string
        });

        const result = await response.json();
        if (response.ok) {
            alert('Review submitted successfully!'); // Notify the user of a successful review submission
            window.location.reload(); // Reload the page to reflect the changes
        } else {
            console.error('Error submitting review:', result); // Log any errors returned by the server
            alert('Error submitting review. Please try again.'); // Notify the user of an error
        }
    } catch (error) {
        console.error('Unexpected error:', error); // Log any unexpected errors
        alert('Unexpected error. Please try again.'); // Notify the user of an unexpected error
    }
});

  1. Add this little code to functions.php in order to configure that custom js file for WordPress:
function enqueue_custom_scripts() {
    wp_enqueue_script('custom-script-js', get_template_directory_uri() . '/js/custom-review-form.js', ['jquery'], '1.0', true);
    wp_localize_script('custom-script-js', 'wpApiSettings', [
        'root' => esc_url_raw(rest_url()),
        'nonce' => wp_create_nonce('wp_rest'),
    ]);
}
add_action('wp_enqueue_scripts', 'enqueue_custom_scripts');

This has allowed us to review the vendor from the order that was created through a request. The listing that was created in step 1 to store this reviews is still shown as any other listing in the platform… I have still a task to hide it on the listings page, but we are good with having it on the vendors page for people to check out the vendors review.

There are of course lots of things to improve yet, so any feedback or warning with this approach is much appreciated.

Thanks for your patience!

1 Like

Thanks for sharing!

The solution is ok if it re-uses the same REST API endpoint, if you want to simplify it then it’s possible if you add the same HTML attributes to a custom review form as the default one uses, then custom JS code will not be required (for example, data-component="form", data-action="endpoint URL", etc).