[SOLVED] Magento 2 Error: A technical problem with the server created an error. Try again to continue what you were doing. If the problem persists, try again later.

Sometimes debugging on Magento can be annoying, specially if something just arises out of the blue, not while you are actually writing a code.

Magento Error - Technical Problem with server
If Problem Persists, try again later

This is something that occurs on Magento admin panel. As of now, it can be one out of 3 reasons, let us discuss them:

1. Admin Password

Try to change admin password, log out and log in again. This should fix it.

2. Some un compiled code or pending schema change

upgrade, compile, deploy, reindex, clean cache

rm -rf generated/* var/cache/* var/view_preprocessed/*
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy -f
php bin/magento indexer:reindex
php bin/magento cache:clean

3. Check for some extra output in the php files


See Line 41
One of the developers left this in index.php and this was just messing with the api calls from admin panel because it was corrupting json output.

How to tweak Magento 2 for two letter search in fulltext mode

The problem:

Magento 2 wont return any search results if the search token is 2 letters in length.

Solution:

Alter MySQL conf file. Edit my.cnf file on your server
In CentOS, use command

nano /etc/my.cnf

and add the following lines:

ft_min_word_len=2
innodb_ft_min_token_size=2

Save using CTRL+x and then restart mysqld service.
Now we need to regenerate the index in order to get updated search results. Use the following SQL commands to regenerate:

Don’t forget to replace “jack_db1” with your database name

ALTER TABLE `jack_db1`.`catalogsearch_fulltext_scope1` DROP PRIMARY KEY, ADD PRIMARY KEY (`entity_id`, `attribute_id`) USING BTREE;
ALTER TABLE `jack_db1`.`catalogsearch_fulltext_scope1` DROP INDEX `FTI_FULLTEXT_DATA_INDEX`, ADD FULLTEXT `FTI_FULLTEXT_DATA_INDEX` (`data_index`);
ALTER TABLE `jack_db1`.`catalog_eav_attribute` DROP PRIMARY KEY, ADD PRIMARY KEY (`attribute_id`) USING BTREE;
ALTER TABLE `jack_db1`.`catalog_eav_attribute` DROP INDEX `CATALOG_EAV_ATTRIBUTE_USED_IN_PRODUCT_LISTING`, ADD INDEX `CATALOG_EAV_ATTRIBUTE_USED_IN_PRODUCT_LISTING` (`used_in_product_listing`) USING BTREE;
ALTER TABLE `jack_db1`.`catalog_eav_attribute` DROP INDEX `CATALOG_EAV_ATTRIBUTE_USED_FOR_SORT_BY`, ADD INDEX `CATALOG_EAV_ATTRIBUTE_USED_FOR_SORT_BY` (`used_for_sort_by`) USING BTREE;

References:
https://magento.stackexchange.com/a/157478/32283
https://dev.mysql.com/doc/refman/5.7/en/fulltext-fine-tuning.html

Magento 2 : How to fix tablespace for table exists | base table or view already exists

Fixing General error: 1813 Tablespace for table xx Please DISCARD the tablespace before IMPORT and SQLSTATE[42S01]: Base table or view already exists

Recently I encountered this error while upgrading an extension on Magento 2.2.2
The error was

SQLSTATE[HY000]: General error: 1813 Tablespace for table '`jack_db1`.`jill_table`' exists. Please DISCARD the tablespace before IMPORT., query was: CREATE TABLE IF NOT EXISTS `jill_table`

When I tried to fix the above error, I received:

SQLSTATE[42S01]: Base table or view already exists: 1050 Table '`jack_db1`.`jill_table`' already exists, query was: CREATE TABLE IF NOT EXISTS `jill_table`

So here is how to fix it:

Important Note: Replace jack_db1 with your database Name and jill_table with your table Name

PART 1 – Fixing MySQL backend

First log in to server and check the mysql data folder (mine was /var/lib/mysql/jack_db1)
say your database name is jack_db1 and your table is jill_table
so issue

ls /var/lib/mysql/jack_db1

You will find .frm and .idb files
Note that these files are present as a couple (each frm file has a corresponding idb file)
But the table that is causing you issues will have one of them missing.
.frm was missing in my case, so copy any other frm file and name it as jill_table.frm (i copied wishlist.frm using following command )
cd /var/lib/mysql/jack_db1;cp wishlist.frm jill_table.frm
use the following command to fix ownership

chown -R mysql:mysql /var/lib/mysql/jack_db1/*

Part 2: Fixing Magento Backend

Login to phpMyAdmin if available or use MySQL CLI to perform the following actions.
In the table setup_module of your magento installation
Delete the row which is related to your corrupted extension

DELETE FROM `setup_module` WHERE module="Custom_Module"

you can also do the same from phpMyAdmin

Now delete the table which was causing the issue, (you would not be able to view the table in phpmMyAdmin)
Just run the following query in SQL

DROP TABLE IF EXISTS `jill_table`;

Part 3: Upgrade the Magento using php cli

run the following commands:

rm -rf var/cache/*
rm -rf generated/*
php bin/magento module:enable Custom_Module --clear-static-content
php bin/magento setup:upgrade
#  Hopefully this time you wont see any errors, proceed as usual if upgrade command worked.
#  run
php bin/magento setup:di:compile
#  and 
php bin/magento setup:static-content:deploy -f

Good Luck with Magento 🙂

Remove sku column from Magento 2 invoice and pdf

Edit the file
/vendor/magento/module-sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php

Inside function draw(), remove/comment following code:

   // draw SKU
        $lines[0][] = [
            'text' => $this->string->split($this->getSku($item), 17),
            'feed' => 290,
            'align' => 'right',
        ];

To remove the SKU header in table header, edit the file
/vendor/magento/module-sales/Model/Order/Pdf/Invoice.php

//$lines[0][] = ['text' => __('SKU'), 'feed' => 290, 'align' => 'right'];

and copy the file vendor/magento/module-sales/view/frontend/templates/email/items/order/default.phtml
to app/design/your/theme/Magento_Sales/templates/email/items/order/default.phtml
and remove the code

<p class="sku"><?= /* @escapeNotVerified */  __('SKU') ?>: <?= $block->escapeHtml($block->getSku($_item)) ?></p>

Magento 2 how to show configurable options stock availability on product page

addtocart.phtml

<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
// @codingStandardsIgnoreFile
/** @var $block \Magento\Catalog\Block\Product\View */
?>
<?php $_product = $block->getProduct(); ?>
<?php $buttonTitle = __('Add to Cart'); ?>
<?php if ($_product->isSaleable()): ?>
<div class="box-tocart">
<div class="fieldset">
<?php if ($block->shouldRenderQuantity()):
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$StockState = $objectManager->get('\Magento\CatalogInventory\Api\StockStateInterface');?>
<div class="field qty">
<label class="label" for="qty"><span><?= /* @escapeNotVerified */ __('Qty') ?></span></label>
<div class="control">
<?php if($_product->getTypeId()=="simple") { ?>
<select class="input-text qty" name="qty" id="qty" data-validate="<?= $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>">
<?php 
$availableQty= $StockState->getStockQty($_product->getId(), $_product->getStore()->getWebsiteId());
for($repli=1;$repli<=$availableQty;$repli++)
echo "<option value='$repli'>$repli</option>";
?>
</select>
<?php } else { 
$usedProducts = $_product->getTypeInstance()->getUsedProducts($_product);
$productStockd=$objectManager->get('Magento\CatalogInventory\Api\StockRegistryInterface');
$finalconfigqty=[];
foreach ($usedProducts as $child)
{$childid=$child->getId();
$productStockObj = $productStockd->getStockItem($childid);
$availableQty= round($productStockObj->getData('qty')); 
$finalconfigqty['c'.$childid]=$availableQty;
}
echo '<script>
var fichoo='.
json_encode($finalconfigqty).
'</script>';
echo '<select class="input-text qty fichoo" name="qty" id="qty"><option value=1>1</option></select>'; 
?> 
<!--input type="number" name="qty" id="qty" value="<?= $block->getProductDefaultQty() * 1 ?>" title="<?= __('Qty') ?>" class="input-text qty"
data-validate="<?= $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>"
/--> <?php } ?>
</div>
</div>
<?php endif; ?>
<div class="actions">
<button type="submit"
title="<?= /* @escapeNotVerified */ $buttonTitle ?>"
class="action primary tocart"
id="product-addtocart-button">
<span><?= /* @escapeNotVerified */ $buttonTitle ?></span>
</button>
<?= $block->getChildHtml('', true) ?>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($block->isRedirectToCartEnabled()) : ?>
<script type="text/x-magento-init">
{
"#product_addtocart_form": {
"Magento_Catalog/product/view/validation": {
"radioCheckboxClosest": ".nested"
}
}
}
</script>
<?php else : ?>
<script type="text/x-magento-init">
{
"#product_addtocart_form": {
"Magento_Catalog/js/validate-product": {}
}
}
</script>
<?php endif; ?>

Another important file is Magento_ConfigurableProduct/web/js/configurable.js

modified function _getSimpleProductId and added function _dofichoo

/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
/**
* @api
*/
define([
'jquery',
'underscore',
'mage/template',
'mage/translate',
'priceUtils',
'priceBox',
'jquery/ui',
'jquery/jquery.parsequery'
], function ($, _, mageTemplate, $t, priceUtils) {
'use strict';
$.widget('mage.configurable', {
options: {
superSelector: '.super-attribute-select',
selectSimpleProduct: '[name="selected_configurable_option"]',
priceHolderSelector: '.price-box',
spConfig: {},
state: {},
priceFormat: {},
optionTemplate: '<%- data.label %>' +
'<% if (typeof data.finalPrice.value !== "undefined") { %>' +
' <%- data.finalPrice.formatted %>' +
'<% } %>',
mediaGallerySelector: '[data-gallery-role=gallery-placeholder]',
mediaGalleryInitial: null,
slyOldPriceSelector: '.sly-old-price',
/**
* Defines the mechanism of how images of a gallery should be
* updated when user switches between configurations of a product.
*
* As for now value of this option can be either 'replace' or 'prepend'.
*
* @type {String}
*/
gallerySwitchStrategy: 'replace',
tierPriceTemplateSelector: '#tier-prices-template',
tierPriceBlockSelector: '[data-role="tier-price-block"]',
tierPriceTemplate: ''
},
/**
* Creates widget
* @private
*/
_create: function () {
// Initial setting of various option values
this._initializeOptions();
// Override defaults with URL query parameters and/or inputs values
this._overrideDefaults();
// Change events to check select reloads
this._setupChangeEvents();
// Fill state
this._fillState();
// Setup child and prev/next settings
this._setChildSettings();
// Setup/configure values to inputs
this._configureForValues();
$(this.element).trigger('configurable.initialized');
},
/**
* Initialize tax configuration, initial settings, and options values.
* @private
*/
_initializeOptions: function () {
var options = this.options,
gallery = $(options.mediaGallerySelector),
priceBoxOptions = $(this.options.priceHolderSelector).priceBox('option').priceConfig || null;
if (priceBoxOptions && priceBoxOptions.optionTemplate) {
options.optionTemplate = priceBoxOptions.optionTemplate;
}
if (priceBoxOptions && priceBoxOptions.priceFormat) {
options.priceFormat = priceBoxOptions.priceFormat;
}
options.optionTemplate = mageTemplate(options.optionTemplate);
options.tierPriceTemplate = $(this.options.tierPriceTemplateSelector).html();
options.settings = options.spConfig.containerId ?
$(options.spConfig.containerId).find(options.superSelector) :
$(options.superSelector);
options.values = options.spConfig.defaultValues || {};
options.parentImage = $('[data-role=base-image-container] img').attr('src');
this.inputSimpleProduct = this.element.find(options.selectSimpleProduct);
gallery.data('gallery') ?
this._onGalleryLoaded(gallery) :
gallery.on('gallery:loaded', this._onGalleryLoaded.bind(this, gallery));
},
/**
* Override default options values settings with either URL query parameters or
* initialized inputs values.
* @private
*/
_overrideDefaults: function () {
var hashIndex = window.location.href.indexOf('#');
if (hashIndex !== -1) {
this._parseQueryParams(window.location.href.substr(hashIndex + 1));
}
if (this.options.spConfig.inputsInitialized) {
this._setValuesByAttribute();
}
},
/**
* Parse query parameters from a query string and set options values based on the
* key value pairs of the parameters.
* @param {*} queryString - URL query string containing query parameters.
* @private
*/
_parseQueryParams: function (queryString) {
var queryParams = $.parseQuery({
query: queryString
});
$.each(queryParams, $.proxy(function (key, value) {
this.options.values[key] = value;
}, this));
},
/**
* Override default options values with values based on each element's attribute
* identifier.
* @private
*/
_setValuesByAttribute: function () {
this.options.values = {};
$.each(this.options.settings, $.proxy(function (index, element) {
var attributeId;
if (element.value) {
attributeId = element.id.replace(/[a-z]*/, '');
this.options.values[attributeId] = element.value;
}
}, this));
},
/**
* Set up .on('change') events for each option element to configure the option.
* @private
*/
_setupChangeEvents: function () {
$.each(this.options.settings, $.proxy(function (index, element) {
$(element).on('change', this, this._configure);
}, this));
},
_dofichoo: function(thisstock){var lsstr='';
for(var ah=1;ah<=window.fichoo['c'+thisstock];ah++) lsstr+='<option value='+ah+ ' >'+ ah + '</option>';
$('select.fichoo').html(lsstr);
},
/**
* Iterate through the option settings and set each option's element configuration,
* attribute identifier. Set the state based on the attribute identifier.
* @private
*/
_fillState: function () {
$.each(this.options.settings, $.proxy(function (index, element) {
var attributeId = element.id.replace(/[a-z]*/, '');
if (attributeId && this.options.spConfig.attributes[attributeId]) {
element.config = this.options.spConfig.attributes[attributeId];
element.attributeId = attributeId;
this.options.state[attributeId] = false;
}
}, this));
},
/**
* Set each option's child settings, and next/prev option setting. Fill (initialize)
* an option's list of selections as needed or disable an option's setting.
* @private
*/
_setChildSettings: function () {
var childSettings = [],
settings = this.options.settings,
index = settings.length,
option;
while (index--) {
option = settings[index];
if (index) {
option.disabled = true;
} else {
this._fillSelect(option);
}
_.extend(option, {
childSettings: childSettings.slice(),
prevSetting: settings[index - 1],
nextSetting: settings[index + 1]
});
childSettings.push(option);
}
},
/**
* Setup for all configurable option settings. Set the value of the option and configure
* the option, which sets its state, and initializes the option's choices, etc.
* @private
*/
_configureForValues: function () {
if (this.options.values) {
this.options.settings.each($.proxy(function (index, element) {
var attributeId = element.attributeId;
element.value = this.options.values[attributeId] || '';
this._configureElement(element);
}, this));
}
},
/**
* Event handler for configuring an option.
* @private
* @param {Object} event - Event triggered to configure an option.
*/
_configure: function (event) {
event.data._configureElement(this);
},
/**
* Configure an option, initializing it's state and enabling related options, which
* populates the related option's selection and resets child option selections.
* @private
* @param {*} element - The element associated with a configurable option.
*/
_configureElement: function (element) {
this.simpleProduct = this._getSimpleProductId(element);
if (element.value) {
this.options.state[element.config.id] = element.value;
if (element.nextSetting) {
element.nextSetting.disabled = false;
this._fillSelect(element.nextSetting);
this._resetChildren(element.nextSetting);
} else {
if (!!document.documentMode) { //eslint-disable-line
this.inputSimpleProduct.val(element.options[element.selectedIndex].config.allowedProducts[0]);
} else {
this.inputSimpleProduct.val(element.selectedOptions[0].config.allowedProducts[0]);
}
}
} else {
this._resetChildren(element);
}
this._reloadPrice();
this._displayRegularPriceBlock(this.simpleProduct);
this._displayTierPriceBlock(this.simpleProduct);
this._changeProductImage();
},
/**
* Change displayed product image according to chosen options of configurable product
*
* @private
*/
_changeProductImage: function () {
var images,
initialImages = this.options.mediaGalleryInitial,
galleryObject = $(this.options.mediaGallerySelector).data('gallery');
if (!galleryObject) {
return;
}
images = this.options.spConfig.images[this.simpleProduct];
if (images) {
if (this.options.gallerySwitchStrategy === 'prepend') {
images = images.concat(initialImages);
}
images = $.extend(true, [], images);
images = this._setImageIndex(images);
galleryObject.updateData(images);
$(this.options.mediaGallerySelector).AddFotoramaVideoEvents({
selectedOption: this.simpleProduct,
dataMergeStrategy: this.options.gallerySwitchStrategy
});
} else {
galleryObject.updateData(initialImages);
$(this.options.mediaGallerySelector).AddFotoramaVideoEvents();
}
galleryObject.first();
},
/**
* Set correct indexes for image set.
*
* @param {Array} images
* @private
*/
_setImageIndex: function (images) {
var length = images.length,
i;
for (i = 0; length > i; i++) {
images[i].i = i + 1;
}
return images;
},
/**
* For a given option element, reset all of its selectable options. Clear any selected
* index, disable the option choice, and reset the option's state if necessary.
* @private
* @param {*} element - The element associated with a configurable option.
*/
_resetChildren: function (element) {
if (element.childSettings) {
_.each(element.childSettings, function (set) {
set.selectedIndex = 0;
set.disabled = true;
});
if (element.config) {
this.options.state[element.config.id] = false;
}
}
},
/**
* Populates an option's selectable choices.
* @private
* @param {*} element - Element associated with a configurable option.
*/
_fillSelect: function (element) {
var attributeId = element.id.replace(/[a-z]*/, ''),
options = this._getAttributeOptions(attributeId),
prevConfig,
index = 1,
allowedProducts,
i,
j;
this._clearSelect(element);
element.options[0] = new Option('', '');
element.options[0].innerHTML = this.options.spConfig.chooseText;
prevConfig = false;
if (element.prevSetting) {
prevConfig = element.prevSetting.options[element.prevSetting.selectedIndex];
}
if (options) {
for (i = 0; i < options.length; i++) {
allowedProducts = [];
/* eslint-disable max-depth */
if (prevConfig) {
for (j = 0; j < options[i].products.length; j++) {
// prevConfig.config can be undefined
if (prevConfig.config &&
prevConfig.config.allowedProducts &&
prevConfig.config.allowedProducts.indexOf(options[i].products[j]) > -1) {
allowedProducts.push(options[i].products[j]);
}
}
} else {
allowedProducts = options[i].products.slice(0);
}
if (allowedProducts.length > 0) {
options[i].allowedProducts = allowedProducts;
element.options[index] = new Option(this._getOptionLabel(options[i]), options[i].id);
if (typeof options[i].price !== 'undefined') {
element.options[index].setAttribute('price', options[i].prices);
}
element.options[index].config = options[i];
index++;
}
/* eslint-enable max-depth */
}
}
},
/**
* Generate the label associated with a configurable option. This includes the option's
* label or value and the option's price.
* @private
* @param {*} option - A single choice among a group of choices for a configurable option.
* @return {String} The option label with option value and price (e.g. Black +1.99)
*/
_getOptionLabel: function (option) {
return option.label;
},
/**
* Removes an option's selections.
* @private
* @param {*} element - The element associated with a configurable option.
*/
_clearSelect: function (element) {
var i;
for (i = element.options.length - 1; i >= 0; i--) {
element.remove(i);
}
},
/**
* Retrieve the attribute options associated with a specific attribute Id.
* @private
* @param {Number} attributeId - The id of the attribute whose configurable options are sought.
* @return {Object} Object containing the attribute options.
*/
_getAttributeOptions: function (attributeId) {
if (this.options.spConfig.attributes[attributeId]) {
return this.options.spConfig.attributes[attributeId].options;
}
},
/**
* Reload the price of the configurable product incorporating the prices of all of the
* configurable product's option selections.
*/
_reloadPrice: function () {
$(this.options.priceHolderSelector).trigger('updatePrice', this._getPrices());
},
/**
* Get product various prices
* @returns {{}}
* @private
*/
_getPrices: function () {
var prices = {},
elements = _.toArray(this.options.settings),
hasProductPrice = false;
_.each(elements, function (element) {
var selected = element.options[element.selectedIndex],
config = selected && selected.config,
priceValue = {};
if (config && config.allowedProducts.length === 1 && !hasProductPrice) {
priceValue = this._calculatePrice(config);
hasProductPrice = true;
}
prices[element.attributeId] = priceValue;
}, this);
return prices;
},
/**
* Returns prices for configured products
*
* @param {*} config - Products configuration
* @returns {*}
* @private
*/
_calculatePrice: function (config) {
var displayPrices = $(this.options.priceHolderSelector).priceBox('option').prices,
newPrices = this.options.spConfig.optionPrices[_.first(config.allowedProducts)];
_.each(displayPrices, function (price, code) {
if (newPrices) {
displayPrices.amount = newPrices.amount - displayPrices.amount;
}
});
return displayPrices;
},
/**
* Returns Simple product Id
*  depending on current selected option.
*
* @private
* @param {HTMLElement} element
* @returns {String|undefined}
*/
_getSimpleProductId: function (element) {
// TODO: Rewrite algorithm. It should return ID of
//        simple product based on selected options.
var allOptions = element.config.options,
value = element.value,
config;
config = _.filter(allOptions, function (option) {
return option.id === value;
});
config = _.first(config);
if(! _.isEmpty(config)) this._dofichoo(_.first(config.allowedProducts));
return _.isEmpty(config) ?
undefined :
_.first(config.allowedProducts);
},
/**
* Show or hide regular price block
*
* @param {*} optionId
* @private
*/
_displayRegularPriceBlock: function (optionId) {
if (typeof optionId != 'undefined' &&
this.options.spConfig.optionPrices[optionId].oldPrice.amount != //eslint-disable-line eqeqeq
this.options.spConfig.optionPrices[optionId].finalPrice.amount
) {
$(this.options.slyOldPriceSelector).show();
} else {
$(this.options.slyOldPriceSelector).hide();
}
},
/**
* Callback which fired after gallery gets initialized.
*
* @param {HTMLElement} element - DOM element associated with gallery.
*/
_onGalleryLoaded: function (element) {
var galleryObject = element.data('gallery');
this.options.mediaGalleryInitial = galleryObject.returnCurrentImages();
},
/**
* Show or hide tier price block
*
* @param {*} optionId
* @private
*/
_displayTierPriceBlock: function (optionId) {
var options, tierPriceHtml;
if (typeof optionId != 'undefined' &&
this.options.spConfig.optionPrices[optionId].tierPrices != [] // eslint-disable-line eqeqeq
) {
options = this.options.spConfig.optionPrices[optionId];
if (this.options.tierPriceTemplate) {
tierPriceHtml = mageTemplate(this.options.tierPriceTemplate, {
'tierPrices': options.tierPrices,
'$t': $t,
'currencyFormat': this.options.spConfig.currencyFormat,
'priceUtils': priceUtils
});
$(this.options.tierPriceBlockSelector).html(tierPriceHtml).show();
}
} else {
$(this.options.tierPriceBlockSelector).hide();
}
}
});
return $.mage.configurable;
});

Magento 2 temporary fix for Alphabetical sorting of Configurable product options

Just re-write the file vendor/magento/module-configurable-product/Model/ConfigurableAttributeData.php

<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\ConfigurableProduct\Model;
use Magento\Catalog\Model\Product;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable\Attribute;
/**
* Class ConfigurableAttributeData
* @api
* @since 100.0.2
*/
class ConfigurableAttributeData
{
/**
* Get product attributes
*
* @param Product $product
* @param array $options
* @return array
*/
public function getAttributesData(Product $product, array $options = [])
{
$defaultValues = [];
$attributes = [];
foreach ($product->getTypeInstance()->getConfigurableAttributes($product) as $attribute) {
$attributeOptionsData = $this->getAttributeOptionsData($attribute, $options);
if ($attributeOptionsData) {
$productAttribute = $attribute->getProductAttribute();
$attributeId = $productAttribute->getId();
$attributes[$attributeId] = [
'id' => $attributeId,
'code' => $productAttribute->getAttributeCode(),
'label' => $productAttribute->getStoreLabel($product->getStoreId()),
'options' => $attributeOptionsData,
'position' => $attribute->getPosition(),
];
$defaultValues[$attributeId] = $this->getAttributeConfigValue($attributeId, $product);
}
}
return [
'attributes' => $attributes,
'defaultValues' => $defaultValues,
];
}
public function labelsort($a,$b){ return strcmp($a['label'], $b['label']);}
/**
* @param Attribute $attribute
* @param array $config
* @return array
*/
protected function getAttributeOptionsData($attribute, $config)
{
$attributeOptionsData = [];
foreach ($attribute->getOptions() as $attributeOption) {
$optionId = $attributeOption['value_index'];
$attributeOptionsData[] = [
'id' => $optionId,
'label' => $attributeOption['label'],
'products' => isset($config[$attribute->getAttributeId()][$optionId])
? $config[$attribute->getAttributeId()][$optionId]
: [],
];
}
if(count($attributeOptionsData) > 1){usort($attributeOptionsData,array($this,'labelsort'));}
return $attributeOptionsData;
}
/**
* @param int $attributeId
* @param Product $product
* @return mixed|null
*/
protected function getAttributeConfigValue($attributeId, $product)
{
return $product->hasPreconfiguredValues()
? $product->getPreconfiguredValues()->getData('super_attribute/' . $attributeId)
: null;
}
}

Show Available Stock quantity for each product in Magento 2

<?php 
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$StockState = $objectManager->get('\Magento\CatalogInventory\Api\StockStateInterface');
echo $StockState->getStockQty($product->getId(), $product->getStore()->getWebsiteId());
?>

Reference: https://magento.stackexchange.com/a/97952/32283

How to update Magento community edition using CLI

composer require magento/product-community-edition 2.2.6 --no-update
composer update
rm -rf var/di var/generation var/cache var/view_preprocessed generated/*
php bin/magento cache:clean
php bin/magento cache:flush
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento indexer:reindex
php bin/magento deploy:mode:set production

if you receive any errors post installation, try this hard fix

rm -rf app/code/Magento/ var/* vendor/*
chmod 777 -R *
composer update && composer install
php -f bin/magento setup:static-content:deploy
find . -type d -exec chmod 775 {}
find . -type f -exec chmod 660 {}
chmod u+x bin/magento
bin/magento maintenance:disable
bin/magento cache:clean

How to shift category description below products in Magento 2

In your theme folder, add Folder Magento_Catalog/layout and in layout folder add file catalog_category_view.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<move element="category.view.container" after="content" destination="main.content" />
</body>
</page>

How to add custom attributes in Magento 2 product CSV export

Core Method is to edit the file
/vendor/magento/module-catalog-import-export/Model/Export/Product.php

and Edit the variable $_exportMainAttrCodes

Add your attributes in the array like “custom1″,”custom2”, etc and save the file.

Next time you export the csv, you will see new columns in the file.