Forum

Thread tagged as: Question, Comments, Shop

Problem Adding Comments to Product Page

I have a product detail page which pulls a particular template seen below. I would like to include a comments function for reviews, but given that it is a function - it doesn't work in the template. I can't remove the function from the template and inject it afterward, because it's part of a tab system on the product description page and requires pulling perch:shop tags for data from the template. How can I include this comments section without causing a php error?

<perch:categories id="category" set="products" label="Category">
    <a href="/<perch:category id="catPath">"><perch:category id="catTitle" /></a>
</perch:categories>
<div style="max-width: 1200px">
    <h1><perch:shop id="title" type="text" label="Title" required="true" order="2" /></h1>
</div>
<perch:shop id="slug" type="slug" editable="true" indelible="true" label="Slug" for="title sku" order="10" divider-before="Meta data" suppress="true" />

<section id="product-listing">
    <div id="product-images">
        <div id="sku-block">SKU: <perch:shop id="sku" type="text" label="SKU" required="true" order="1" /></div>
        <perch:shop id="image" type="image" label="Main Product Image" order="4" width="800" height="800" suppress="true"/>
        <a class="gallery" href="<perch:shop id="image" type="image" width="600">"><img id="main-image" src="<perch:shop id="image" type="image" width="430" height="430" crop="true" />" alt="<perch:shop id="title" type="text" /> image"/></a>
        <perch:shop id="image" type="image" width="80" height="80" density="1.6" crop="true" suppress="true" />
        <perch:repeater id="additional-images" label="Additional Images">
            <perch:before>
                <div id="img-gallery">
                    <ul>
            </perch:before>
                        <li>
                            <a class="gallery" href="<perch:shop id="extra-img" type="image" label="Additional Photo" width="600" required="false" />"><img src="<perch:shop id="extra-img" type="image" width="90" height="90" crop="true" />" alt="Additional Product Photo" /></a>
                        </li>
            <perch:after>
                    </ul>
                </div>
                <script>
                    jQuery('a.gallery').colorbox();
                </script>
            </perch:after>
        </perch:repeater>
    </div>

    <aside id="buy-info">
        <h3>Description:</h3>
        <p><perch:shop id="description" type="textarea" label="Short Description" editor="textile" order="3" markitup="true" size="s" /></p>
        <perch:repeater id="highlights" label="Product Highlights">
            <perch:before>
                <h3>Product Highlights:</h3>
                <ul class="info-list">
            </perch:before>
                <li><perch:shop id="highlight" type="text" label="Focus Feature" required="false" /></li>
            <perch:after>
                </ul>
            </perch:after>
        </perch:repeater>

        <hr />

        <span class="price">
            <perch:member has-tag="dealer">
                <perch:shop id="trade_price" type="shop_currency_value" label="Dealer Price" size="s" min="0" step="any" runway="true" />
            <perch:else:member />
                <perch:shop id="on_sale" type="checkbox" value="1" label="Use Sale Price" suppress="true" />
                <perch:if id="on_sale" value="1">
                    <perch:shop id="sale_price" type="shop_currency_value" label="Sale Price" size="s" min="0" step="any" />
                <perch:else />
                    <perch:shop id="price" type="shop_currency_value" label="Price" size="s" min="0" step="any" />
                </perch:if>
            </perch:member>
        </span>

        <h3><perch:shop id="stock_status" type="shop_stock_status" label="Stock Status" divider-before="Stock" required="true" /></h3>
        <perch:shop id="stock_level" type="number" label="Stock Level" size="s" suppress="true" />
        <perch:shop id="stock_location" type="shop_stock_location" label="Count Stock" suppress="true" />
        <perch:shop id="max_in_cart" type="number" label="Max Quantity in Cart" size="s" suppress="true" />

        <perch:form id="add_to_cart" app="perch_shop" action="/bag">
            <perch:input id="product" type="hidden" env-autofill="false" value="<perch:shop id="productID" type="hidden" env-autofill="false" />" />
            <perch:input type="submit" value="Add to Bag" class="buybutton" />
            <perch:input id="qty" value="1" type="number" min="0" placeholder="1" class="quantity" />
        </perch:form>

        <perch:shop id="catalog_only" type="shop_catalog_only" label="Catalog Only" type="hidden" suppress="true" />
        <perch:shop id="tax_group" type="shop_tax_group" label="Tax Group" required="true" suppress="true" />
        <perch:shop id="status" type="shop_status" label="Status" suppress="true" />
        <perch:shop id="brand" type="shop_brand" label="Brand" allowempty="true" suppress="true" />
        <perch:shop id="requires_shipping" type="shop_requires_shipping" label="Requires Shipping" divider-before="Shipping" suppress="true" />
        <perch:shop id="weight" type="number" label="Shipping weight" size="s" suppress="true" />
        <perch:shop id="width" type="number" label="Width" size="s" suppress="true" />
        <perch:shop id="height" type="number" label="Height" size="s" suppress="true" />
        <perch:shop id="depth" type="number" label="Depth" size="s" suppress="true" />

        <div class="sharethis-inline-share-buttons"></div>
    </aside>

    <div id="details" class="box-node">
        <div class="tab">
            <button class="tablinks" onclick="openTab(event, 'Overview')" id="defaultOpen">Overview</button>
            <button class="tablinks" onclick="openTab(event, 'Specs')">Specs</button>
            <button class="tablinks" onclick="openTab(event, 'Included')">In The Box</button>
            <button class="tablinks" onclick="openTab(event, 'Reviews')">Reviews</button>
        </div>

        <div id="Overview" class="tabcontent">
            <perch:shop id="overview" type="textarea" label="Overview" editor="ckeditor" html="true" size="s" />
        </div>

        <div id="Specs" class="tabcontent">
            <table id="specs">
                <tr>
                    <th>Weight:</th>
                    <td><perch:shop id="weight" /></td>
                </tr>
                <tr>
                    <th>Width:</th>
                    <td><perch:shop id="width" /></td>
                </tr>
                <tr>
                    <th>Height:</th>
                    <td><perch:shop id="height" /></td>
                </tr>
                <tr>
                    <th>Depth:</th>
                    <td><perch:shop id="depth" /></td>
                </tr>
            </table>
        </div>

        <div id="Included" class="tabcontent">
            <perch:repeater id="included-list" label="In the Box">
                <perch:before>
                    <ul class="info-list">
                </perch:before>
                    <li><perch:shop id="included-qty" type="text" format="#:0" maxlength="3" label="Quantity Included" required="true" /> - <perch:shop id="in-the-box" type="text" label="Included Item" required="true" /></li>
                <perch:after>
                    </ul>
                </perch:after>
            </perch:repeater>
        </div>

        <div id="Reviews" class="tabcontent">
            <?php 
                perch_comments_form('<perch:shop id="sku" type="text" />', '<perch:shop id="title" type="text" />', array(
                    template => 'review_form.html'
                )); 
                perch_comments('<perch:shop id="sku" type="text" />', array(
                    template => 'review.html'
                )); 
            ?>
        </div>
    </div>
</section>
<script>
    function openTab(evt, tabName) {
        var i, tabcontent, tablinks;

        tabcontent = document.getElementsByClassName("tabcontent");
        for (i = 0; i < tabcontent.length; i++) {
            tabcontent[i].style.display = "none";
        }

        tablinks = document.getElementsByClassName("tablinks");
        for (i = 0; i < tablinks.length; i++) {
            tablinks[i].className = tablinks[i].className.replace(" active", "");
        }

        document.getElementById(tabName).style.display = "block";
        evt.currentTarget.className += " active";
    }
    document.getElementById("defaultOpen").click();
</script>
Kevin Wolff

Kevin Wolff 0 points

  • 3 years ago
Drew McLellan

Drew McLellan 2638 points
Perch Support

Do it the other way around - generate the comments first, then pass the result into the product template.

My trouble with that was I would need to pull the SKU and title of the product from the shop template and then pass it back into the comments function as a variable for the ID and name tags. If I run the comments function first, apply the contents to a variable, and pass it into the shop product function, I wouldn't be able to see it in code to add the shop tags before it was processed by the product template.

Drew McLellan

Drew McLellan 2638 points
Perch Support

There's a few ways you can deal with that. One might be to use an each callback to add the comments.

Another is to use a combination of skip-template and return-html to get both the data about the product and the templated HTML. Include a placeholder string in your template, then replace it out before displaying.

Something roughly along the lines of:

$product = perch_shop_product($productSlug, [
'return-html' => true,
'skip-template' => true,
]);

$comments = perch_comments($product[0]['sku'], true);

$html = str_replace('<!-- COMMENTS HERE --!>', $comments, $product['html']);

echo $html;

Thanks, that makes a lot of sense to use str_replace. I seem to be misunderstanding the arrays passed into the $product var when comments are called. Perch is throwing an error in debug saying that the [0] and ['sku'] are undefined. I would define the sku, but I thought that was the purpose of using it passed into the $product without a template?

My Page:

perch_layout('global.top', [
        'page_title' => perch_page_title(true),
    ]);

    $productSlug = perch_get('s');

    $product = perch_shop_product($productSlug, [
        'return-html' => true,
        'skip-template' => true,
    ]);

    $comments = perch_comments($product[0]['sku'], true);

    $html = str_replace('<!-- REVIEWS HERE -->', $comments, $product['html']);

    echo $html;

    perch_layout('global.footer'); 
Hussein Al Hammad

Hussein Al Hammad 105 points
Registered Developer

Hello Kevin,

You can always print your array, check what you have and adjust your code accordingly:

print '<pre>';
print_r($product);
print '</pre>';

If $product[0] is undefined, it might be because perch_shop_product() could not find a product with the slug you passed to it.

I see how it would make sense to check for using a particular user entered SKU, but given that this product was displaying fine before this modification with just perch_shop_product() I think it's more my misunderstanding of what Drew was doing in that particular portion of his answer. This has been my testing SKU page for several days working fine, so it has to be something with the new pass through for comments.

Hussein Al Hammad

Hussein Al Hammad 105 points
Registered Developer

The skip-template option returns the product content in an array. The return-html option returns the templated product content.

$product here is an array. What skip-template returns would normally be in $product[0]. What return-html returns would be in $product['html'].

If perch_shop_product() finds your product, $product[0] should not be undefined.

So, does $productSlug = perch_get('s') get you the product slug?

It seems that perch_get('s'); is not pulling the slug as you said; it simply outputs a blank value. But when perch_shop_product(); is called, it works just fine. Is the variable being passed to the page from the previous link a different method? There are no errors or content when I just get and echo the results.

Hussein Al Hammad

Hussein Al Hammad 105 points
Registered Developer

What does your product page URL look like? And if you are using Runway, what does your route look like?

The product page URL is https://www.site.com/product/slug-goes-here. I am using Runway, so my routes for the page are: products/[slug:catPath] and products/[slug/slug:catPath]. I was able to use standard PHP to grab the URL and trim it to a variable for the template to load just to see if the rest of the code worked with a slug, but it seems to break the comments, repeaters, and members checks.

Hussein Al Hammad

Hussein Al Hammad 105 points
Registered Developer

Ok. First lets tackle the basics:

perch_get(): https://docs.grabaperch.com/functions/utilities/perch-get/

The perch_get() function does the same job as $_GET in PHP, but has some convenient safeguards and options built in.

If you have a page product.php and a product URL is example.com/product?unicorn=not-real, perch_get('unicorn') returns not-real.

If you want to display the same page as example.com/product/not-real, your route for that page would be product/[slug:unicorn]. Now perch_get('unicorn) still returns not-real.

If your page is https://www.site.com/product/slug-goes-here, your route should be product/[slug:s] to be able to get the slug with perch_get('s'). This is the equivalent to site.com/product?s=slug-goes-here.


Now let's look at your pages and routes

so my routes for the page are: products/[slug:catPath] and products/[slug/slug:catPath]

The routes you listed looks like are used for product listing on the page /products. What page do you want to display the product details on? Same page?

I read the documentation on perch_get(), but wasn't sure if that meant the product routes were adding queries to every page before rewrite.

The page that lists the categories is /products. The current page I'm working on is the /product page, which displays a single product's details.

Hussein Al Hammad

Hussein Al Hammad 105 points
Registered Developer

In that case, you should add a route to /product. Adding product/[slug:s] should do the trick. Then you can use perch_get('s') to get the product slug.

Oh hang on a second, I apologize - I was looking at the wrong page with my phone instead of on desktop when I copied the routes. The current route for the product display page I'm working on is already product/[slug:s]. Which is why when I used standard PHP to pull the slug off the URL and assign it to the variable, it grabbed the page. I'm not sure why perch_get('s'); isn't getting the same result.

I see that the page is loading with my standard PHP variable of the URL slug with the comments, but for some reason my prices are not listening to the members conditionals, and the buy button and quantity fields are being displayed blank.

https://interfitphoto.com/product/interfit-s1-acbattery-powered-hss-ttl-flash/

Hussein Al Hammad

Hussein Al Hammad 105 points
Registered Developer

Ok so the route is actually working. If you look at your debug message, you'll see:

Matched route: product/[slug]


the buy button and quantity fields are being displayed blank

If you open your browser dev tools, you will find your perch:form and perch:members tags are not rendered.

I would actually use Drew's other suggestion:

One might be to use an each callback to add the comments.

$productSlug = perch_get('s');

perch_shop_product($productSlug, [
  'each' => function($item) {
    $comments_form = perch_comments_form($item['sku'], $item['title'], [], true);
    $comments = perch_comments($item['sku'], [], true);
    $item['comments_form'] = $comments_form;
    $item['comments'] = $comments;
    return $item;
  }
]);

And in your product template you can display the comments form and the comments like so:

<perch:shop id="comments_form" html="true" />
<perch:shop id="comments" html="true" />

That works! The only strange thing is that perch_get('s'); still doesn't seem to work. I have been using basic PHP to get the slug:

$url = "https://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$productSlug = trim(parse_url($url, PHP_URL_PATH), 'product/'); 

As always, thank you so much for your help!