Skip to content

[Backport] magento/magento2#9961: Unused product attributes display with value N/A or NO on storefront. #12057

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 1, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/code/Magento/Catalog/Block/Product/View/Attributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,15 @@ public function getAdditionalData(array $excludeAttr = [])

if (!$product->hasData($attribute->getAttributeCode())) {
$value = __('N/A');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we also should remove this code as there are a lot of complaints about such behavior. This code was "dead" for a long time so everyone expects that not used attribute is ignored on storefront.
Please remove this if

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change system behavior in a way desired by Community without PO approval?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, behavior was changed when instanceof Phrase was added to if below. I can see that __('N/A') code was dead for a long time (including M1) so expectations in most Magento versions not used attributes are not displayed on a storefornt product page. Support also has multiple tickets to return previous behavior (not show N/A) back.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I wan't aware about M1, and yes, such useful behavior was introduced accidentally in scope of another bugfix.

} elseif ($value instanceof Phrase) {
$value = (string)$value;
} elseif ((string)$value == '') {
$value = __('No');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is weird logic. Why empty string should be transformed to No. Looks like this some obsolete workaround that should be removed. If this cause any issue then it definitely should be fixed in another way.
Please remove this elseif logic as it has no sense.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most likely this was used to display boolean attributes with Yes/No values.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@orlangur Yes/No values at this point already handled in \Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend::getValue. And this is dangerous to substitute empty value with "No" as empty value may appear for different reasons (e.g. empty text field, dropdown without selected item, etc.). Magento should rid of such code, decrease coupling between different classes and do not base implementation on current execution flow but build reusable components with high cohesion so changes in one system part will not cause bugs in another.

} elseif ($attribute->getFrontendInput() == 'price' && is_string($value)) {
$value = $this->priceCurrency->convertAndFormat($value);
}

if ($value instanceof Phrase || (is_string($value) && strlen($value))) {
if (is_string($value) && strlen($value)) {
$data[$attribute->getAttributeCode()] = [
'label' => __($attribute->getStoreLabel()),
'value' => $value,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

namespace Magento\Catalog\Test\Unit\Block\Product\View;

use \PHPUnit\Framework\TestCase;
use \Magento\Framework\Phrase;
use \Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
use \Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend;
use \Magento\Catalog\Model\Product;
use \Magento\Framework\View\Element\Template\Context;
use \Magento\Framework\Registry;
use \Magento\Framework\Pricing\PriceCurrencyInterface;
use \Magento\Catalog\Block\Product\View\Attributes as AttributesBlock;

/**
* Test class for \Magento\Catalog\Block\Product\View\Attributes
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class AttributesTest extends TestCase
{
/**
* @var \Magento\Framework\Phrase
*/
private $phrase;

/**
* @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Eav\Model\Entity\Attribute\AbstractAttribute
*/
private $attribute;

/**
* @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend
*/
private $frontendAttribute;

/**
* @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Catalog\Model\Product
*/
private $product;

/**
* @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\View\Element\Template\Context
*/
private $context;

/**
* @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Registry
*/
private $registry;

/**
* @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Pricing\PriceCurrencyInterface
*/
private $priceCurrencyInterface;

/**
* @var \Magento\Catalog\Block\Product\View\Attributes
*/
private $attributesBlock;

protected function setUp()
{
// @codingStandardsIgnoreStart
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get rid of such annotations?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed them.

$this->phrase = new Phrase(__(''));
// @codingStandardsIgnoreEnd
$this->attribute = $this
->getMockBuilder(AbstractAttribute::class)
->disableOriginalConstructor()
->getMock();
$this->attribute
->expects($this->any())
->method('getIsVisibleOnFront')
->willReturn(true);
$this->attribute
->expects($this->any())
->method('getAttributeCode')
->willReturn('phrase');
$this->frontendAttribute = $this
->getMockBuilder(AbstractFrontend::class)
->disableOriginalConstructor()
->getMock();
$this->attribute
->expects($this->any())
->method('getFrontendInput')
->willReturn('phrase');
$this->attribute
->expects($this->any())
->method('getFrontend')
->willReturn($this->frontendAttribute);
$this->product = $this
->getMockBuilder(Product::class)
->disableOriginalConstructor()
->getMock();
$this->product
->expects($this->any())
->method('getAttributes')
->willReturn([$this->attribute]);
$this->product
->expects($this->any())
->method('hasData')
->willReturn(true);
$this->context = $this
->getMockBuilder(Context::class)
->disableOriginalConstructor()
->getMock();
$this->registry = $this
->getMockBuilder(Registry::class)
->disableOriginalConstructor()
->getMock();
$this->registry
->expects($this->any())
->method('registry')
->willReturn($this->product);
$this->priceCurrencyInterface = $this
->getMockBuilder(PriceCurrencyInterface::class)
->disableOriginalConstructor()
->getMock();
$this->attributesBlock = new AttributesBlock(
$this->context,
$this->registry,
$this->priceCurrencyInterface
);
}

/**
* @return void
*/
public function testGetAttributeNoValue()
{
// @codingStandardsIgnoreStart
$this->phrase = new Phrase(__(''));
// @codingStandardsIgnoreEnd
$this->frontendAttribute
->expects($this->any())
->method('getValue')
->willReturn($this->phrase);
$attributes = $this->attributesBlock->getAdditionalData();
$this->assertTrue(empty($attributes['phrase']));
}

/**
* @return void
*/
public function testGetAttributeHasValue()
{
$this->phrase = new Phrase(__('Yes'));
$this->frontendAttribute
->expects($this->any())
->method('getValue')
->willReturn($this->phrase);
$attributes = $this->attributesBlock->getAdditionalData();
$this->assertNotTrue(empty($attributes['phrase']));
$this->assertNotTrue(empty($attributes['phrase']['value']));
$this->assertEquals('Yes', $attributes['phrase']['value']);
}
}