You can achieve this by override ListProduct block in your custom module. Please follow below steps to achieve this.
Step 1: Please create di.xml file under path PackageName/Vendor/etc/
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Catalog\Block\Product\ListProduct" type="PackageName\Vendor\Block\Rewrite\Product\ListProduct" />
</config>
Step 2: Please create file ListProduct.php file under path
PackageName/Vendor/Block/Rewrite/Product/
<?php
namespace PackageName\Vendor\Block\Rewrite\Product;
class ListProduct extends \Magento\Catalog\Block\Product\ListProduct
{
/**
* @var \Magento\Catalog\Model\ProductFactory
*/
protected $_productloader;
public function __construct(
\Magento\Catalog\Block\Product\Context $context,
\Magento\Framework\Data\Helper\PostHelper $postDataHelper,
\Magento\Catalog\Model\Layer\Resolver $layerResolver,
\Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository,
\Magento\Framework\Url\Helper\Data $urlHelper,
\Magento\Catalog\Model\ProductFactory $productloader,
array $data = []
) {
$this->_productloader = $productloader;
parent::__construct($context, $postDataHelper, $layerResolver,
$categoryRepository, $urlHelper, $data);
}
public function getCustomLoadedProductCollection()
{
$productCollection = $this->_getProductCollection();
$data = array();
$productObject = $this->getLoadProduct(1818);
$i = 0;
foreach ($productCollection as $_product) {
$productArray = array();
if ($i != 0 && $i % 4 == 0) {
array_push($data, $productObject);
}
array_push($data, $_product);
$i++;
}
return $data;
}
/**
* Load product by id
*
* @param $id
* @return \Magento\Catalog\Model\Product
*/
public function getLoadProduct($id)
{
try {
$product = $this->_productloader->create()->load($id);
} catch (\Magento\Framework\Exception\NoSuchEntityException $e){
$product = false;
}
return $product;
}
}
In above code please pass product id as per you want to show. I have shown my product in the loop every 5th time. Please change iteration in loop according to your need.
Step 3: Set custom template for list.phtml file. Please create
catalog_category_view.xml file under path
PackageName/Vendor/view/frontend/layout
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance dc" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<referenceBlock name="category.products.list">
<action method="setTemplate">
<argument name="template" xsi:type="string">PackageName_Vendor::product/list.phtml</argument>
</action>
</referenceBlock>
</page>
Step 4: Please create file list.phtml under path
PackageName/Vendor/view/frontend/templates/product
Paste your theme's list.phtml file code into this custom file and change function and count as per shown in below code.
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
use Magento\Framework\App\Action\Action;
?>
<?php
/**
* Product list template
*
* @var $block \Magento\Catalog\Block\Product\ListProduct
* @var \Magento\Framework\Escaper $escaper
* @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer
*/
?>
<?php
$_productCollection = $block->getCustomLoadedProductCollection();
/** @var \Magento\Catalog\Helper\Output $_helper */
$_helper = $block->getData('outputHelper');
?>
<?php if (!count($_productCollection)):?>
<div class="message info empty">
<div><?= $escaper->escapeHtml(__('We can\'t find products matching the selection.')) ?></div>
</div>
<?php else:?>
<?= $block->getToolbarHtml() ?>
<?= $block->getAdditionalHtml() ?>
<?php
if ($block->getMode() == 'grid') {
$viewMode = 'grid';
$imageDisplayArea = 'category_page_grid';
$showDescription = false;
$templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::SHORT_VIEW;
} else {
$viewMode = 'list';
$imageDisplayArea = 'category_page_list';
$showDescription = true;
$templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::FULL_VIEW;
}
/**
* Position for actions regarding image size changing in vde if needed
*/
$pos = $block->getPositioned();
?>
<div class="products wrapper <?= /* @noEscape */ $viewMode ?> products-<?= /* @noEscape */ $viewMode ?>">
<ol class="products list items product-items">
<?php /** @var $_product \Magento\Catalog\Model\Product */ ?>
<?php foreach ($_productCollection as $_product):?>
<li class="item product product-item">
<div class="product-item-info"
id="product-item-info_<?= /* @noEscape */ $_product->getId() ?>"
data-container="product-<?= /* @noEscape */ $viewMode ?>">
<?php
$productImage = $block->getImage($_product, $imageDisplayArea);
if ($pos != null) {
$position = 'left:' . $productImage->getWidth() . 'px;'
. 'top:' . $productImage->getHeight() . 'px;';
}
?>
<?php // Product Image ?>
<a href="<?= $escaper->escapeUrl($_product->getProductUrl()) ?>"
class="product photo product-item-photo"
tabindex="-1">
<?= $productImage->toHtml() ?>
</a>
<div class="product details product-item-details">
<?php
$_productNameStripped = $block->stripTags($_product->getName(), null, true);
?>
<strong class="product name product-item-name">
<a class="product-item-link"
href="<?= $escaper->escapeUrl($_product->getProductUrl()) ?>">
<?=/* @noEscape */ $_helper->productAttribute($_product, $_product->getName(), 'name')?>
</a>
</strong>
<?= $block->getReviewsSummaryHtml($_product, $templateType) ?>
<?= /* @noEscape */ $block->getProductPrice($_product) ?>
<?php if ($_product->isAvailable()):?>
<?= $block->getProductDetailsHtml($_product) ?>
<?php endif; ?>
<div class="product-item-inner">
<div class="product actions product-item-actions">
<div class="actions-primary">
<?php if ($_product->isSaleable()):?>
<?php $postParams = $block->getAddToCartPostParams($_product); ?>
<form data-role="tocart-form"
data-product-sku="<?= $escaper->escapeHtml($_product->getSku()) ?>"
action="<?= $escaper->escapeUrl($postParams['action']) ?>"
method="post">
<input type="hidden"
name="product"
value="<?= /* @noEscape */ $postParams['data']['product'] ?>">
<input type="hidden"
name="<?= /* @noEscape */ Action::PARAM_NAME_URL_ENCODED ?>"
value="<?=
/* @noEscape */ $postParams['data'][Action::PARAM_NAME_URL_ENCODED]
?>">
<?= $block->getBlockHtml('formkey') ?>
<button type="submit"
title="<?= $escaper->escapeHtmlAttr(__('Add to Cart')) ?>"
class="action tocart primary">
<span><?= $escaper->escapeHtml(__('Add to Cart')) ?></span>
</button>
</form>
<?php else:?>
<?php if ($_product->isAvailable()):?>
<div class="stock available">
<span><?= $escaper->escapeHtml(__('In stock')) ?></span></div>
<?php else:?>
<div class="stock unavailable">
<span><?= $escaper->escapeHtml(__('Out of stock')) ?></span></div>
<?php endif; ?>
<?php endif; ?>
</div>
<?= strpos($pos, $viewMode . '-primary') ?
/* @noEscape */ $secureRenderer->renderStyleAsTag(
$position,
'product-item-info_' . $_product->getId() . ' div.actions-primary'
) : '' ?>
<div data-role="add-to-links" class="actions-secondary">
<?php if ($addToBlock = $block->getChildBlock('addto')):?>
<?= $addToBlock->setProduct($_product)->getChildHtml() ?>
<?php endif; ?>
</div>
<?= strpos($pos, $viewMode . '-secondary') ?
/* @noEscape */ $secureRenderer->renderStyleAsTag(
$position,
'product-item-info_' . $_product->getId() . ' div.actions-secondary'
) : '' ?>
</div>
<?php if ($showDescription):?>
<div class="product description product-item-description">
<?= /* @noEscape */ $_helper->productAttribute(
$_product,
$_product->getShortDescription(),
'short_description'
) ?>
<a href="<?= $escaper->escapeUrl($_product->getProductUrl()) ?>"
title="<?= /* @noEscape */ $_productNameStripped ?>"
class="action more"><?= $escaper->escapeHtml(__('Learn More')) ?></a>
</div>
<?php endif; ?>
</div>
</div>
</div>
<?= strpos($pos, $viewMode . '-actions') ?
/* @noEscape */ $secureRenderer->renderStyleAsTag(
$position,
'product-item-info_' . $_product->getId() . ' div.product-item-actions'
) : '' ?>
</li>
<?php endforeach; ?>
</ol>
</div>
<?= $block->getToolbarHtml() ?>
<?php if (!$block->isRedirectToCartEnabled()):?>
<script type="text/x-magento-init">
{
"[data-role=tocart-form], .form.map.checkout": {
"catalogAddToCart": {
"product_sku": "<?= $escaper->escapeJs($_product->getSku()) ?>"
}
}
}
</script>
<?php endif; ?>
<?php endif; ?>
This will surely work for you. Please check and let me know if you face issue anywhere.
Thanks
Cheers!