Skip to content

Commit ced61b9

Browse files
authored
Merge pull request #31 from magefan/8739-lazy-load-webp-on-category-page
8739 lazy load webp on category page
2 parents b04d655 + 48f0597 commit ced61b9

File tree

6 files changed

+346
-21
lines changed

6 files changed

+346
-21
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
/**
3+
* Copyright © Magefan ([email protected]). All rights reserved.
4+
* Please visit Magefan.com for license details (https://magefan.com/end-user-license-agreement).
5+
*
6+
* Glory to Ukraine! Glory to the heroes!
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace Magefan\LazyLoad\Block\Adminhtml\System\Config\Form;
12+
13+
use Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray;
14+
15+
class DynamicRow extends AbstractFieldArray
16+
{
17+
/**
18+
* Prepare rendering the new field by adding all the needed columns
19+
*/
20+
protected function _prepareToRender()
21+
{
22+
$this->addColumn('block_identifier', ['label' => __('Block Identifier'), 'style' => 'width:170px']);
23+
$this->addColumn('first_images_to_skip', ['label' => __('First Images To Skip'), 'style' => 'width:170px']);
24+
$this->_addAfter = false;
25+
$this->_addButtonLabel = __('Add');
26+
}
27+
}

Model/Config.php

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
namespace Magefan\LazyLoad\Model;
1212

13+
use Magento\Framework\App\Helper\Context;
14+
use Magento\Framework\Serialize\SerializerInterface;
15+
1316
/**
1417
* Lazy load config
1518
*/
@@ -32,6 +35,24 @@ class Config extends \Magento\Framework\App\Helper\AbstractHelper
3235
*/
3336
protected $enabled;
3437

38+
/**
39+
* @var SerializerInterface
40+
*/
41+
protected $serializer;
42+
43+
/**
44+
* @param Context $context
45+
* @param SerializerInterface $serializer
46+
*/
47+
public function __construct(
48+
Context $context,
49+
SerializerInterface $serializer
50+
)
51+
{
52+
$this->serializer = $serializer;
53+
parent::__construct($context);
54+
}
55+
3556
/**
3657
* Retrieve store config value
3758
* @param string $path
@@ -68,16 +89,35 @@ public function isNoScriptEnabled(): bool
6889
*/
6990
public function getBlocks(): array
7091
{
92+
return array_keys($this->getBlocksInfo());
93+
}
94+
95+
/**
96+
* @param $blockIdentifier
97+
* @return int
98+
*/
99+
public function getBlockFirstImagesToSkip($blockIdentifier): int {
100+
return (int)($this->getBlocksInfo()[$blockIdentifier] ?? 0);
101+
}
102+
103+
/**
104+
* @return array
105+
*/
106+
public function getBlocksInfo(): array {
71107
if (null === $this->blocks) {
72-
$blocks = $this->getConfig(self::XML_PATH_LAZY_BLOCKS);
73-
74-
$blocks = str_replace(["\r\n", "\n'\r", "\n"], "\r", $blocks);
75-
$blocks = explode("\r", $blocks);
76-
$this->blocks = [];
77-
foreach ($blocks as $block) {
78-
if ($block = trim($block)) {
79-
$this->blocks[] = $block;
108+
try {
109+
$blocks = $this->serializer->unserialize($this->getConfig(self::XML_PATH_LAZY_BLOCKS));
110+
}
111+
catch (\InvalidArgumentException $e) {
112+
return [];
113+
}
114+
115+
foreach ($blocks as $blockData) {
116+
if (!isset($blockData['block_identifier']) || !isset($blockData['first_images_to_skip'])) {
117+
continue;
80118
}
119+
120+
$this->blocks[$blockData['block_identifier']] = $blockData['first_images_to_skip'];
81121
}
82122
}
83123

Plugin/BlockPlugin.php

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class BlockPlugin
1919
{
2020
const LAZY_TAG = '<!-- MAGEFAN_LAZY_LOAD -->';
2121

22+
const REPLACEMENT_LABEL = 'mf-lazy';
23+
2224
/**
2325
* Request
2426
* @var \Magento\Framework\App\RequestInterface
@@ -44,6 +46,11 @@ class BlockPlugin
4446
*/
4547
protected $config;
4648

49+
/**
50+
* @var array
51+
*/
52+
protected $labelsValues = [];
53+
4754
/**
4855
* @param \Magento\Framework\App\RequestInterface $request
4956
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
@@ -71,8 +78,14 @@ public function afterToHtml(\Magento\Framework\View\Element\AbstractBlock $block
7178
return $html;
7279
}
7380

74-
if ($this->config->getIsJavascriptLazyLoadMethod()) {
81+
$blockIdentifier = $this->getBlockIdentifier($block);
82+
$numberOfReplacements = $this->config->getBlockFirstImagesToSkip($blockIdentifier);
83+
84+
if ($numberOfReplacements) {
85+
$html = $this->removeFirstNImagesWithCustomLabel($html, $numberOfReplacements);
86+
}
7587

88+
if ($this->config->getIsJavascriptLazyLoadMethod()) {
7689
$pixelSrc = ' src="' . $block->getViewFileUrl('Magefan_LazyLoad::images/pixel.jpg') . '"';
7790
$tmpSrc = 'TMP_SRC';
7891

@@ -113,9 +126,75 @@ public function afterToHtml(\Magento\Framework\View\Element\AbstractBlock $block
113126
', $html);
114127
}
115128

129+
if ($numberOfReplacements) {
130+
$html = $this->revertFirstNImageToInital($html);
131+
return $this->deleteFirstNLoadingLazy($html, $numberOfReplacements);
132+
}
133+
116134
return $html;
117135
}
118136

137+
/**
138+
* @param \Magento\Framework\View\Element\AbstractBlock $block
139+
* @return string
140+
*/
141+
protected function getBlockIdentifier(\Magento\Framework\View\Element\AbstractBlock $block): string {
142+
$blockName = $block->getBlockId() ?: $block->getNameInLayout();
143+
$blockTemplate = $block->getTemplate();
144+
$blocks = $this->config->getBlocks();
145+
146+
if (in_array($blockName, $blocks)) {
147+
return $blockName;
148+
}
149+
else if (in_array(get_class($block), $blocks)) {
150+
return get_class($block);
151+
}
152+
else if (in_array($blockTemplate, $blocks)) {
153+
return $blockTemplate;
154+
}
155+
156+
return '';
157+
}
158+
159+
/**
160+
* @param $html
161+
* @param int $numberOfReplacements
162+
* @return array|string|string[]|null
163+
*/
164+
protected function removeFirstNImagesWithCustomLabel($html, int $numberOfReplacements) {
165+
$count = 0;
166+
return preg_replace_callback('#<img([^>]*)(?:\ssrc="([^"]*)")([^>]*)\/?>#isU', function ($match) use (&$count, &$numberOfReplacements) {
167+
$count++;
168+
if ($count <= $numberOfReplacements) {
169+
$label = self::REPLACEMENT_LABEL . '_' . $count;
170+
$this->labelsValues[$label] = $match[0];
171+
172+
return $label;
173+
}
174+
175+
return $match[0];
176+
}, $html, $numberOfReplacements);
177+
}
178+
179+
/**
180+
* @param $html
181+
* @return array|string|string[]|null
182+
*/
183+
protected function revertFirstNImageToInital($html) {
184+
return preg_replace_callback('/' . self::REPLACEMENT_LABEL .'_\d+\b(.*?)/', function($match) use (&$count) {
185+
return $this->labelsValues[$match[0]] ?? $match[0];
186+
}, $html);
187+
}
188+
189+
/**
190+
* @param $html
191+
* @param int $numberOfDeletions
192+
* @return array|string|string[]|null
193+
*/
194+
protected function deleteFirstNLoadingLazy($html,int $numberOfDeletions) {
195+
return preg_replace('/loading="lazy"/', '', $html, $numberOfDeletions, $count);
196+
}
197+
119198
/**
120199
* Check if lazy load is available for block
121200
* @param \Magento\Framework\View\Element\AbstractBlock $block
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
<?php
2+
/**
3+
* Copyright © Magefan ([email protected]). All rights reserved.
4+
* Please visit Magefan.com for license details (https://magefan.com/end-user-license-agreement).
5+
*
6+
* Glory to Ukraine! Glory to the heroes!
7+
*/
8+
9+
declare(strict_types = 1);
10+
11+
namespace Magefan\LazyLoad\Setup\Patch\Data;
12+
13+
use Magento\Framework\Setup\Patch\DataPatchInterface;
14+
use Magento\Framework\Setup\ModuleDataSetupInterface;
15+
use Magefan\LazyLoad\Model\Config as Config;
16+
use Magento\Config\Model\ResourceModel\Config as Resource;
17+
use Magento\Framework\Serialize\SerializerInterface;
18+
use Psr\Log\LoggerInterface;
19+
20+
class ConvertConfigToJsonPatch implements DataPatchInterface
21+
{
22+
/**
23+
* @var ModuleDataSetupInterface
24+
*/
25+
private $moduleDataSetup;
26+
27+
/**
28+
* @var Config
29+
*/
30+
private $config;
31+
32+
/**
33+
* @var Resource
34+
*/
35+
private $resource;
36+
37+
/**
38+
* @var SerializerInterface
39+
*/
40+
private $serializer;
41+
42+
/**
43+
* @var LoggerInterface
44+
*/
45+
private $logger;
46+
47+
/**
48+
* @param ModuleDataSetupInterface $moduleDataSetup
49+
* @param Config $config
50+
* @param Resource $resource
51+
* @param SerializerInterface $serializer
52+
* @param LoggerInterface $logger
53+
*/
54+
public function __construct(
55+
ModuleDataSetupInterface $moduleDataSetup,
56+
Config $config,
57+
Resource $resource,
58+
SerializerInterface $serializer,
59+
LoggerInterface $logger
60+
) {
61+
$this->moduleDataSetup = $moduleDataSetup;
62+
$this->config = $config;
63+
$this->resource = $resource;
64+
$this->serializer = $serializer;
65+
$this->logger = $logger;
66+
}
67+
68+
/**
69+
* @return void
70+
*/
71+
public function apply()
72+
{
73+
$this->moduleDataSetup->startSetup();
74+
75+
$connection = $this->moduleDataSetup->getConnection();
76+
$tableName = $this->moduleDataSetup->getTable('core_config_data');
77+
78+
$query = $connection->select()
79+
->from($tableName, ['config_id','value'])
80+
->where(
81+
'path = ?',
82+
Config::XML_PATH_LAZY_BLOCKS
83+
)
84+
->order('config_id ASC');
85+
86+
$result = $connection->fetchAll($query);
87+
88+
foreach ($result as $scope) {
89+
if (!isset($scope['config_id']) || !isset($scope['value'])) {
90+
continue;
91+
}
92+
93+
$blocks = $this->getBlocks($scope['value']);
94+
if (empty($blocks)) {
95+
continue;
96+
}
97+
98+
$jsonBlocks = $this->getJsonForBlocks($blocks);
99+
100+
try {
101+
$connection->update(
102+
$tableName,
103+
['value' => $jsonBlocks],
104+
[
105+
'config_id = ?' => $scope['config_id']
106+
],
107+
);
108+
} catch (\Exception $e) {
109+
$this->logger->debug(__('Magefan LazyLoad ERROR: while converting to json for config_id: ') . $scope['config_id']);
110+
continue;
111+
}
112+
}
113+
114+
$this->moduleDataSetup->endSetup();
115+
}
116+
117+
/**
118+
* @param $blocks
119+
* @return bool|string
120+
*/
121+
protected function getJsonForBlocks($blocks) {
122+
$arrayBlocks = [];
123+
$counter = 1;
124+
foreach ($blocks as $block) {
125+
$arrayBlocks[(string)$counter] =
126+
[
127+
'block_identifier' => $block,
128+
'first_images_to_skip' => ($block == 'category.products.list') ? '2' : '0'
129+
];
130+
131+
$counter++;
132+
}
133+
134+
return $this->serializer->serialize($arrayBlocks);
135+
}
136+
137+
/**
138+
* @return array|string[]
139+
*/
140+
public static function getDependencies()
141+
{
142+
return [];
143+
}
144+
145+
/**
146+
* @return array|string[]
147+
*/
148+
public function getAliases()
149+
{
150+
return [];
151+
}
152+
153+
/**
154+
* @param $blocks
155+
* @return array
156+
*/
157+
protected function getBlocks ($blocks): array {
158+
json_decode($blocks);
159+
if (json_last_error() === JSON_ERROR_NONE) {
160+
return [];
161+
}
162+
163+
$blocks = str_replace(["\r\n", "\n'\r", "\n"], "\r", $blocks);
164+
$blocks = explode("\r", $blocks);
165+
$result = [];
166+
foreach ($blocks as $block) {
167+
if ($block = trim($block)) {
168+
$result[] = $block;
169+
}
170+
}
171+
172+
return $result;
173+
}
174+
}

0 commit comments

Comments
 (0)