All M2 themes I have tested do not have proper structured data/rich snippets. The currency symbol is in the price span, and gets a warning.
An example: link to snippet
The problem code but cleaned up a bit:
<span itemprop="offers" itemscope itemtype="http://schema.org/Offer"> <span data-price-amount="54" data-price-type="finalPrice" itemprop="price"> <span class="price">$54.00</span> </span> <meta itemprop="priceCurrency" content="USD" /> </span>
Error:
price $54.00 (The property $54.00 is not a valid price specification. Find out more about http://schema.org/price.)
If the $ is taken out of <span class="price">$54.00</span> like this it validates fine:
<span itemprop="offers" itemscope itemtype="http://schema.org/Offer">$<span data-price-amount="54" data-price-type="finalPrice" itemprop="price"> <span class="price">54.00</span> </span> <meta itemprop="priceCurrency" content="USD" />
But I cannot find which files are creating the price code and which code to change. What is the best way to validate the structured data?
We experienced same issue with rich snippets related to price and currency symbol included into a product price. What we did is created new template where <meta property="productrice: amount /> is used.
Here is an example of magento/app/design/frontend/CustomTheme/default/Magento_Catalog/templates/product/view/opengraph/general.phtml template:
<?php /** @var $block \Magento\Catalog\Block\Product\View */?> <meta property="og:type" content="og:product" /> <meta property="og:title" content="<?php echo $block->escapeHtml($block->getProduct()->getName()); ?>" /> <meta property="og:image" content="<?php echo $block->escapeUrl($block->getImage($block->getProduct(), 'product_base_image')->getImageUrl()); ?>" /> <meta property="og:description" content="<?php echo $block->escapeHtml($block->stripTags($block->getProduct()->getShortDescription())); ?>" /> <meta property="og:url" content="<?php echo $block->escapeUrl($block->getProduct()->getProductUrl()); ?>" /> <?php if ($priceAmount = $block->getProduct()->getFinalPrice()):?> <meta property="product:price:amount" content="<?php /* @escapeNotVerified */ echo $priceAmount; ?>"/> <?php echo $block->getChildHtml('meta.currency'); ?> <?php endif;?>
In the template above price is rendered without currency symbol for rich snippets and currency block is rendered in a separate currency.phtml template. This general.phtml template in CustomTheme follows same path as original template from Magento\Catalog module so there is no need to add layout file.
I dont think I understand. Your template example is exactly as my base file except
$block->escapeHtml($block->getProduct()->getShortDescription());
becomes
$block->escapeHtml($block->stripTags($block->getProduct()->getShortDescription()));
And has no effect on the validation. Can you be clearer please as to how you used this file to validate the price?
The template responsible for rendering price on a page /vendor/magento/module-catalog/view/base/templates/product/price/amount/default.phtml , however it renders price amount with currency symbol as you noticed. Indeed this is something needs to be addressed for future releases.
Suggestion is to address this issue with invalid snippet by adding custom "Offer" schema.
In order to do that we have to look into the /vendor/magento/module-catalog/view/base/templates/product/price/amount/default.phtml and find a way to disable "Offer" from the template. Likely Magento\Catalog\Pricing\Render\FinalPriceBox to disable schema usage:
di.xml
<type name="Magento\Catalog\Pricing\Render\FinalPriceBox">
<plugin name="reset_schema" type="Custom\Catalog\Plugin\Product\Pricing\Render\FinalPriceBox"/>
</type>
The Custom\Catalog\Plugin\Product\Pricing\Render\FinalPriceBox class
namespace Custom\Catalog\Plugin\Product\Pricing\Render;
/**
* Class FinalPriceBox
* @package Giftsdirect\Catalog\Plugin\Product\Pricing\Render
*/
class FinalPriceBox
{
/**
* @param \Magento\Catalog\Pricing\Render\FinalPriceBox $finalPriceBox
* @param \Magento\Framework\Pricing\Amount\Base $base
* @param array $arguments
* @return array
*/
public function beforeRenderAmount(
\Magento\Catalog\Pricing\Render\FinalPriceBox $finalPriceBox,
\Magento\Framework\Pricing\Amount\Base $base,
array $arguments = []
) {
$arguments['schema'] = false;
return [
$base,
$arguments
];
}
}
By doing this we will disable rendering of invalid Price with Currency inside default.phtml template.
The next step is obviously to provide a solution for valid Offer. We can create a price.phtml template responsible for rendering proper price and currency in a separate tags.
The price.phtml template:
<?php /** @var $block \Custom\Catalog\Block\Product\Price */ ?>
<div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
<meta itemprop="priceCurrency" content="<?php /* @escapeNotVerified */ echo $block->getDisplayCurrencyCode()?>" />
<span class="schema-price" itemprop="price" content="<?php /* @escapeNotVerified */ echo $block->getPrice() ?>"><?php /* @escapeNotVerified */ echo $block->getPrice() ?></span>
<?php echo $block->getChildHtml() ?>
</div>
The Custom\Catalog\Block\Product\Price block:
namespace Custom\Catalog\Block\Product;
use Magento\Framework\Registry;
use Magento\Framework\View\Element\Template;
use Magento\Framework\Pricing\PriceCurrencyInterface;
/**
* Class Price
* @package Custom\Catalog\Block\Product
*/
class Price extends Template
{
/**
* @var PriceCurrencyInterface
*/
protected $priceCurrency;
/**
* @var Registry
*/
protected $registry;
/**
* Price constructor.
* @param Template\Context $context
* @param PriceCurrencyInterface $priceCurrency
* @param Registry $registry
* @param array $data
*/
public function __construct(
Template\Context $context,
PriceCurrencyInterface $priceCurrency,
Registry $registry,
array $data = []
) {
$this->priceCurrency = $priceCurrency;
$this->registry = $registry;
parent::__construct($context, $data);
}
/**
* @return string
*/
public function getDisplayCurrencyCode()
{
return $this->priceCurrency->getCurrency()->getCurrencyCode();
}
/**
* @return float
*/
public function getPrice()
{
/** @var \Magento\Catalog\Model\Product $product */
$product = $this->registry->registry('current_product');
if ($product) {
return round(
$product->getFinalPrice(),
PriceCurrencyInterface::DEFAULT_PRECISION
);
}
return 0;
}
}
This class will give us price of a current product and currency selected on a website.
Finally, the price.phtml together with Block class have to be included into catalog_product_view.xml layout.
<referenceContainer name="product.info.price">
<block class="Custom\Catalog\Block\Product\Price" name="product.info.price.schema" after="product.price.final" template="Custom_Catalog::product/view/price.phtml" />
</referenceContainer>
Similar approach with ListItem schema can be introduced on Catalog Category Pages and Catalog Search result page.
I get error -
Class Custom\Catalog\Block\Product\Price does not exist
Where does the price.phtml template go?
The price.phtml file should go under
app/code/Custom/Catalog/view/frontend/template/product/view/price.phtml
Hope it helps.
I am still getting the error
Class Custom\Catalog\Block\Product\Price does not exist
Thanks for the code. But maybe its a bit too unclear what goes where to be able to use it.
Are you sure you implemented the class which shows as does not exist? Maybe TYPO in the code? Please check.
I followed your code. But it is not 100% clear which files should be edited
The Custom\Catalog\Plugin\Product\Pricing\Render\FinalPriceBox class?
inside default.phtml template.?
The price.phtml template: you said was
app/code/Custom/Catalog/view/frontend/template/product/view/price.phtml
The Custom\Catalog\Block\Product\Price block:?
catalog_product_view.xml layout. - there are a few...
Late reply but will help for community.
This is solution:
Extend file of this:
vendor/magento/module-catalog/view/base/templates/product/price/amount/default.phtml
Replace:
<?php echo $block->getSchema() ? ' itemprop="price"' : '' ?>>
With:
<?php echo $block->getSchema() ? ' itemprop="price" content="' . $block->getDisplayValue() . '"': '' ?>>