Initial commit: is_themecore out of the box v4.1.3
This commit is contained in:
206
is_themecore/src/Controller/Admin/SettingsController.php
Normal file
206
is_themecore/src/Controller/Admin/SettingsController.php
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Controller\Admin;
|
||||
|
||||
use PrestaShop\PrestaShop\Core\Form\FormHandlerInterface;
|
||||
use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
|
||||
use PrestaShopBundle\Security\Annotation\AdminSecurity;
|
||||
use PrestaShopBundle\Security\Annotation\DemoRestricted;
|
||||
use PrestaShopBundle\Security\Annotation\ModuleActivated;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Class SettingsController
|
||||
*
|
||||
* @ModuleActivated(moduleName="is_themecore", redirectRoute="admin_module_manage")
|
||||
*/
|
||||
class SettingsController extends FrameworkBundleAdminController
|
||||
{
|
||||
/**
|
||||
* @AdminSecurity(
|
||||
* "is_granted(['read'], request.get('_legacy_controller'))",
|
||||
* message="You do not have permission to access this."
|
||||
* )
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function indexAction(Request $request): Response
|
||||
{
|
||||
$generalFormDataHandler = $this->getGeneralFormHandler();
|
||||
$webpFormDataHandler = $this->getWebpFormHandler();
|
||||
|
||||
/** @var FormInterface<string, mixed> $generalForm */
|
||||
$generalForm = $generalFormDataHandler->getForm();
|
||||
$webpForm = $webpFormDataHandler->getForm();
|
||||
|
||||
return $this->render('@Modules/is_themecore/views/templates/back/components/layouts/settings.html.twig', [
|
||||
'general_form' => $generalForm->createView(),
|
||||
'webp_form' => $webpForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @AdminSecurity(
|
||||
* "is_granted('update', request.get('_legacy_controller')) && is_granted('create', request.get('_legacy_controller')) && is_granted('delete', request.get('_legacy_controller'))",
|
||||
* message="You do not have permission to update this.",
|
||||
* redirectRoute="is_themecore_module_settings"
|
||||
* )
|
||||
*
|
||||
* @DemoRestricted(redirectRoute="is_themecore_module_settings")
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function processGeneralFormAction(Request $request)
|
||||
{
|
||||
return $this->processForm(
|
||||
$request,
|
||||
$this->getGeneralFormHandler(),
|
||||
'General'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @AdminSecurity(
|
||||
* "is_granted('update', request.get('_legacy_controller')) && is_granted('create', request.get('_legacy_controller')) && is_granted('delete', request.get('_legacy_controller'))",
|
||||
* message="You do not have permission to update this.",
|
||||
* redirectRoute="is_themecore_module_settings"
|
||||
* )
|
||||
*
|
||||
* @DemoRestricted(redirectRoute="is_themecore_module_settings")
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function processWebpFormAction(Request $request)
|
||||
{
|
||||
return $this->processForm(
|
||||
$request,
|
||||
$this->getWebpFormHandler(),
|
||||
'Webp'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @DemoRestricted(redirectRoute="is_themecore_module_settings")
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function processWebpEraseImages(Request $request)
|
||||
{
|
||||
$time_start = microtime(true);
|
||||
$eraser = $this->get('oksydan.module.is_themecore.core.webp.webp_files_eraser');
|
||||
|
||||
switch ($request->get('type')) {
|
||||
case 'all':
|
||||
$eraser->setQuery(_PS_ROOT_DIR_);
|
||||
break;
|
||||
case 'product':
|
||||
$eraser->setQuery(_PS_PROD_IMG_DIR_);
|
||||
break;
|
||||
case 'module':
|
||||
$eraser->setQuery(_PS_MODULE_DIR_);
|
||||
break;
|
||||
case 'cms':
|
||||
$eraser->setQuery(_PS_IMG_DIR_ . 'cms/');
|
||||
break;
|
||||
case 'themes':
|
||||
$eraser->setQuery(_PS_ROOT_DIR_ . '/themes/');
|
||||
break;
|
||||
default:
|
||||
$eraser->setQuery(_PS_ROOT_DIR_);
|
||||
break;
|
||||
}
|
||||
|
||||
$eraser->eraseFiles();
|
||||
|
||||
$time_end = microtime(true);
|
||||
$execution_time = round($time_end - $time_start, 2);
|
||||
|
||||
$this->addFlash('success', $this->trans('%1$s - webp images has been erased successfully in %2$ss', 'Modules.isthemecore.Admin', [$eraser->getFilesCount(), $execution_time]));
|
||||
|
||||
return $this->redirectToRoute('is_themecore_module_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Process form.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param FormHandlerInterface $formHandler
|
||||
* @param string $hookName
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
private function processForm(Request $request, FormHandlerInterface $formHandler)
|
||||
{
|
||||
$form = $formHandler->getForm();
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted()) {
|
||||
if ($form->isValid()) {
|
||||
$data = $form->getData();
|
||||
$saveErrors = $formHandler->save($data);
|
||||
|
||||
if (!empty($data['webp_enabled'])) {
|
||||
$generator = $this->get('oksydan.module.is_themecore.core.htaccess.htaccess_generator');
|
||||
|
||||
$generator->generate((bool) $data['webp_enabled']);
|
||||
$generator->writeFile();
|
||||
}
|
||||
|
||||
if (0 === count($saveErrors)) {
|
||||
$this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success'));
|
||||
} else {
|
||||
$this->flashErrors($saveErrors);
|
||||
}
|
||||
}
|
||||
|
||||
$formErrors = [];
|
||||
foreach ($form->getErrors(true) as $error) {
|
||||
$formErrors[] = $error->getMessage();
|
||||
}
|
||||
$this->flashErrors($formErrors);
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('is_themecore_module_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FormHandlerInterface
|
||||
*/
|
||||
private function getGeneralFormHandler()
|
||||
{
|
||||
/** @var FormHandlerInterface */
|
||||
$formDataHandler = $this->get('oksydan.module.is_themecore.form.settings.general_form_data_handler');
|
||||
|
||||
return $formDataHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FormHandlerInterface
|
||||
*/
|
||||
private function getWebpFormHandler()
|
||||
{
|
||||
/** @var FormHandlerInterface */
|
||||
$formDataHandler = $this->get('oksydan.module.is_themecore.form.settings.webp_form_data_handler');
|
||||
|
||||
return $formDataHandler;
|
||||
}
|
||||
}
|
||||
11
is_themecore/src/Controller/Admin/index.php
Normal file
11
is_themecore/src/Controller/Admin/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
11
is_themecore/src/Controller/Front/index.php
Normal file
11
is_themecore/src/Controller/Front/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
11
is_themecore/src/Controller/index.php
Normal file
11
is_themecore/src/Controller/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
80
is_themecore/src/Core/Breadcrumbs/ThemeBreadcrumbs.php
Normal file
80
is_themecore/src/Core/Breadcrumbs/ThemeBreadcrumbs.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\Breadcrumbs;
|
||||
|
||||
class ThemeBreadcrumbs
|
||||
{
|
||||
protected $breadcrumbs = [];
|
||||
public $pageName;
|
||||
public $translator;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->context = \Context::getContext();
|
||||
$this->pageName = $this->context->controller->getPageName();
|
||||
$this->getAvailableBreadcrumbs();
|
||||
}
|
||||
|
||||
protected function getAvailableBreadcrumbs()
|
||||
{
|
||||
$this->breadcrumbs = $this->getCommonBreadcrumbs();
|
||||
}
|
||||
|
||||
public function getBreadcrumb()
|
||||
{
|
||||
$breadcrumb = [];
|
||||
$breadcrumb['links'] = $this->getBreadcrumbByPageName();
|
||||
$breadcrumb['count'] = count($breadcrumb['links']);
|
||||
|
||||
return $breadcrumb;
|
||||
}
|
||||
|
||||
public function getBreadcrumbByPageName()
|
||||
{
|
||||
$breadcrumb = [];
|
||||
|
||||
if (isset($this->breadcrumbs[$this->pageName])) {
|
||||
$breadcrumb = $this->breadcrumbs[$this->pageName];
|
||||
}
|
||||
|
||||
return $breadcrumb;
|
||||
}
|
||||
|
||||
protected function getCommonBreadcrumbs()
|
||||
{
|
||||
$pages = [
|
||||
[
|
||||
'controller' => 'cart',
|
||||
'name' => $this->context->getTranslator()->trans('Shopping Cart', [], 'Shop.Theme.Checkout'),
|
||||
],
|
||||
[
|
||||
'controller' => 'pagenotfound',
|
||||
'name' => $this->context->getTranslator()->trans('404', [], 'Shop.Theme.Global'),
|
||||
],
|
||||
[
|
||||
'controller' => 'stores',
|
||||
'name' => $this->context->getTranslator()->trans('Our stores', [], 'Shop.Theme.Global'),
|
||||
],
|
||||
[
|
||||
'controller' => 'sitemap',
|
||||
'name' => $this->context->getTranslator()->trans('Sitemap', [], 'Shop.Theme.Global'),
|
||||
],
|
||||
];
|
||||
|
||||
$breadcrumbs = [];
|
||||
foreach ($pages as $page) {
|
||||
$breadcrumbs[$page['controller']] = [
|
||||
[
|
||||
'url' => $this->context->link->getPageLink('index'),
|
||||
'title' => $this->context->getTranslator()->trans('Home', [], 'Shop.Theme.Global'),
|
||||
],
|
||||
[
|
||||
'url' => $this->context->link->getPageLink($page['controller']),
|
||||
'title' => $page['name'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $breadcrumbs;
|
||||
}
|
||||
}
|
||||
11
is_themecore/src/Core/Breadcrumbs/index.php
Normal file
11
is_themecore/src/Core/Breadcrumbs/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
233
is_themecore/src/Core/Htaccess/HtaccessGenerator.php
Normal file
233
is_themecore/src/Core/Htaccess/HtaccessGenerator.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\Htaccess;
|
||||
|
||||
class HtaccessGenerator
|
||||
{
|
||||
private $module;
|
||||
private $domains = [];
|
||||
private $medias = [];
|
||||
|
||||
protected $moduleWebpGeneratorFile;
|
||||
protected $mediaDomains = null;
|
||||
protected $tempContent = '';
|
||||
protected $contentBefore = '';
|
||||
protected $contentAfter = '';
|
||||
|
||||
protected $wrapperBlockComments = [
|
||||
'COMMENT_START' => '~~start-is_themecore~~',
|
||||
'COMMENT_END' => '~~end-is_themecore~~',
|
||||
];
|
||||
|
||||
public function __construct(\Is_themecore $module)
|
||||
{
|
||||
$this->domains = \Tools::getDomains();
|
||||
$this->module = $module;
|
||||
$this->moduleWebpGeneratorFile = "%{ENV:REWRITEBASE}modules/{$this->module->name}/webp.php";
|
||||
}
|
||||
|
||||
public function generate($addRewrite = true): void
|
||||
{
|
||||
$htaccessFile = $this->getHtaccessFilePath();
|
||||
|
||||
if (file_exists($htaccessFile)) {
|
||||
$content = \Tools::file_get_contents($htaccessFile);
|
||||
|
||||
if (preg_match('#^(.*)\# ' . $this->wrapperBlockComments['COMMENT_START'] . '.*\# ' . $this->wrapperBlockComments['COMMENT_END'] . '[^\n]*(.*)$#s', $content, $match)) {
|
||||
$this->contentBefore = $match[1];
|
||||
$this->contentAfter = $match[2];
|
||||
} else {
|
||||
$this->contentAfter = $content;
|
||||
}
|
||||
}
|
||||
|
||||
if ($addRewrite) {
|
||||
$this->generateHtaccessHeader();
|
||||
|
||||
$this->write('<IfModule mod_rewrite.c>');
|
||||
$this->write('RewriteEngine On');
|
||||
$this->writeNl();
|
||||
$this->generateImagesRewrites();
|
||||
$this->write('</IfModule>');
|
||||
|
||||
$this->write("# {$this->wrapperBlockComments['COMMENT_END']} Do not remove this comment");
|
||||
}
|
||||
|
||||
$this->write($this->contentAfter);
|
||||
}
|
||||
|
||||
public function writeFile(): bool
|
||||
{
|
||||
$htaccessFile = $this->getHtaccessFilePath();
|
||||
|
||||
if (!$writeToFile = @fopen($htaccessFile, 'wb')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fwrite($writeToFile, $this->tempContent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose($writeToFile);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getMediaDomains(): string
|
||||
{
|
||||
if ($this->mediaDomains === null) {
|
||||
if (\Configuration::getMultiShopValues('PS_MEDIA_SERVER_1')
|
||||
&& \Configuration::getMultiShopValues('PS_MEDIA_SERVER_2')
|
||||
&& \Configuration::getMultiShopValues('PS_MEDIA_SERVER_3')
|
||||
) {
|
||||
$this->medias = [
|
||||
\Configuration::getMultiShopValues('PS_MEDIA_SERVER_1'),
|
||||
\Configuration::getMultiShopValues('PS_MEDIA_SERVER_2'),
|
||||
\Configuration::getMultiShopValues('PS_MEDIA_SERVER_3'),
|
||||
];
|
||||
}
|
||||
|
||||
$this->mediaDomains = '';
|
||||
|
||||
foreach ($this->medias as $media) {
|
||||
foreach ($media as $mediaUrl) {
|
||||
if ($mediaUrl) {
|
||||
$this->mediaDomains .= 'RewriteCond %{HTTP_HOST} ^' . $mediaUrl . '$ [OR]' . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->mediaDomains;
|
||||
}
|
||||
|
||||
protected function getDomainRewriteCond($domain): string
|
||||
{
|
||||
return "RewriteCond %{HTTP_HOST} ^$domain$";
|
||||
}
|
||||
|
||||
protected function generateImagesRewrites(): void
|
||||
{
|
||||
foreach ($this->domains as $domain => $uri) {
|
||||
$this->generateProductImagesRewrite($domain);
|
||||
$this->generateCategoryImagesRewrite($domain);
|
||||
$this->generateOtherImagesRewrite($domain);
|
||||
}
|
||||
}
|
||||
|
||||
protected function writeMediaDomainsCondition()
|
||||
{
|
||||
$mediaDomains = $this->getMediaDomains();
|
||||
|
||||
if ($mediaDomains) {
|
||||
$this->write($mediaDomains, false);
|
||||
}
|
||||
}
|
||||
|
||||
protected function generateProductImagesRewrite($domain): void
|
||||
{
|
||||
$domainRewriteCond = $this->getDomainRewriteCond($domain);
|
||||
|
||||
for ($i = 1; $i <= 7; ++$i) {
|
||||
$imgPath = $imgName = '';
|
||||
for ($j = 1; $j <= $i; ++$j) {
|
||||
$imgPath .= '$' . $j . '/';
|
||||
$imgName .= '$' . $j;
|
||||
}
|
||||
$imgName .= '$' . $j;
|
||||
|
||||
// WEBP FILE EXISTS
|
||||
$this->writeMediaDomainsCondition();
|
||||
$this->write($domainRewriteCond);
|
||||
$this->write('RewriteCond %{DOCUMENT_ROOT}/img/p/' . $imgPath . $imgName . '$' . ($j + 1) . '.webp -f');
|
||||
$this->write('RewriteRule ^' . str_repeat('([0-9])', $i) . '(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.webp$ %{ENV:REWRITEBASE}img/p/' . $imgPath . $imgName . '$' . ($j + 1) . '.webp [L]');
|
||||
$this->writeNl();
|
||||
|
||||
// WEBP FILE NOT EXISTS
|
||||
$this->writeMediaDomainsCondition();
|
||||
$this->write($domainRewriteCond);
|
||||
$this->write('RewriteCond %{DOCUMENT_ROOT}/img/p/' . $imgPath . $imgName . '$' . ($j + 1) . '.webp !-f');
|
||||
$this->write('RewriteRule ^' . str_repeat('([0-9])', $i) . '(\-[_a-zA-Z0-9-]*)?(-[0-9]+)?/.+\.webp$ ' . $this->moduleWebpGeneratorFile . '?source=%{DOCUMENT_ROOT}/img/p/' . $imgPath . $imgName . '$' . ($j + 1) . '.webp [NC,L]');
|
||||
$this->writeNl();
|
||||
}
|
||||
}
|
||||
|
||||
protected function generateCategoryImagesRewrite($domain): void
|
||||
{
|
||||
$domainRewriteCond = $this->getDomainRewriteCond($domain);
|
||||
|
||||
// WEBP FILE EXISTS
|
||||
$this->writeMediaDomainsCondition();
|
||||
$this->write($domainRewriteCond);
|
||||
$this->write('RewriteCond %{DOCUMENT_ROOT}/img/c/$1$2.webp -f');
|
||||
$this->write('RewriteRule ^c/([0-9]+)(\-[\.*_a-zA-Z0-9-]*)(-[0-9]+)?/.+\.webp$ %{ENV:REWRITEBASE}img/c/$1$2.webp [L]');
|
||||
$this->writeNl();
|
||||
|
||||
$this->writeMediaDomainsCondition();
|
||||
$this->write($domainRewriteCond);
|
||||
$this->write('RewriteCond %{DOCUMENT_ROOT}/img/c/$1$2$3.webp -f');
|
||||
$this->write('RewriteRule ^c/([0-9]+)(\-[\.*_a-zA-Z0-9-]*)(-[0-9]+)?/.+\.webp$ %{ENV:REWRITEBASE}img/c/$1$2$3.webp [L]');
|
||||
$this->writeNl();
|
||||
|
||||
// WEBP FILE NOT EXISTS
|
||||
$this->writeMediaDomainsCondition();
|
||||
$this->write($domainRewriteCond);
|
||||
$this->write('RewriteCond %{DOCUMENT_ROOT}/img/c/$1$2.webp !-f');
|
||||
$this->write('RewriteRule ^c/([0-9]+)(\-[\.*_a-zA-Z0-9-]*)(-[0-9]+)?/.+\.webp$ ' . $this->moduleWebpGeneratorFile . '?source=%{DOCUMENT_ROOT}/img/c/$1$2.webp [NC,L]');
|
||||
$this->writeNl();
|
||||
|
||||
$this->writeMediaDomainsCondition();
|
||||
$this->write($domainRewriteCond);
|
||||
$this->write('RewriteCond %{DOCUMENT_ROOT}/img/c/$1$2$3.webp !-f');
|
||||
$this->write('RewriteRule ^c/([0-9]+)(\-[\.*_a-zA-Z0-9-]*)(-[0-9]+)?/.+\.webp$ ' . $this->moduleWebpGeneratorFile . '?source=%{DOCUMENT_ROOT}/img/c/$1$2$3.webp [NC,L]');
|
||||
$this->writeNl();
|
||||
}
|
||||
|
||||
protected function generateOtherImagesRewrite($domain): void
|
||||
{
|
||||
$domainRewriteCond = $this->getDomainRewriteCond($domain);
|
||||
|
||||
// WEBP FILE NOT EXISTS
|
||||
$this->writeMediaDomainsCondition();
|
||||
$this->write($domainRewriteCond);
|
||||
$this->write('RewriteCond %{REQUEST_FILENAME} !-f');
|
||||
$this->write('RewriteRule ^(.*)\.webp$ ' . $this->moduleWebpGeneratorFile . '?source=%{DOCUMENT_ROOT}/$1.webp [NC,L]');
|
||||
$this->writeNl();
|
||||
}
|
||||
|
||||
protected function generateHtaccessHeader(): void
|
||||
{
|
||||
$this->write("# {$this->wrapperBlockComments['COMMENT_START']} Do not remove this comment");
|
||||
$this->write('# Allow webp files to be sent by Apache 2.2');
|
||||
$this->write('<IfModule !mod_authz_core.c>');
|
||||
$this->write('<Files ~ "\\.(webp)$">', true, 1);
|
||||
$this->write('Allow from all', true, 2);
|
||||
$this->write('</Files>', true, 1);
|
||||
$this->write('</IfModule>');
|
||||
$this->writeNl();
|
||||
|
||||
$this->write('# Allow webp files to be sent by Apache 2.4');
|
||||
$this->write('<IfModule mod_authz_core.c>');
|
||||
$this->write('<Files ~ "\\.(webp)$">', true, 1);
|
||||
$this->write('Require all granted', true, 2);
|
||||
$this->write('allow from all', true, 2);
|
||||
$this->write('</Files>', true, 1);
|
||||
$this->write('</IfModule>');
|
||||
$this->writeNl();
|
||||
}
|
||||
|
||||
protected function write($line, $addEOL = true, $tabs = 0): void
|
||||
{
|
||||
$this->tempContent .= str_repeat("\t", $tabs) . $line . ($addEOL ? PHP_EOL : '');
|
||||
}
|
||||
|
||||
protected function writeNl(): void
|
||||
{
|
||||
$this->write('');
|
||||
}
|
||||
|
||||
protected function getHtaccessFilePath(): string
|
||||
{
|
||||
return _PS_ROOT_DIR_ . '/.htaccess';
|
||||
}
|
||||
}
|
||||
11
is_themecore/src/Core/Htaccess/index.php
Normal file
11
is_themecore/src/Core/Htaccess/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
60
is_themecore/src/Core/ListingDisplay/ThemeListDisplay.php
Normal file
60
is_themecore/src/Core/ListingDisplay/ThemeListDisplay.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\ListingDisplay;
|
||||
|
||||
use Oksydan\Module\IsThemeCore\Form\Settings\GeneralConfiguration;
|
||||
use Symfony\Component\HttpFoundation\Cookie;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ThemeListDisplay
|
||||
{
|
||||
private $cookieName = 'listingDisplayType';
|
||||
private $displayList = [
|
||||
'grid',
|
||||
'list',
|
||||
];
|
||||
|
||||
protected function getRequest(): Request
|
||||
{
|
||||
Request::setFactory(static function ($query, $request, $attributes, $cookies, $files, $server, $content) {
|
||||
return new Request($query, $request, $attributes, $cookies, [], $server, $content);
|
||||
});
|
||||
|
||||
return Request::createFromGlobals();
|
||||
}
|
||||
|
||||
public function setDisplay($display): Response
|
||||
{
|
||||
if (!in_array($display, $this->displayList)) {
|
||||
$display = \Configuration::get(GeneralConfiguration::THEMECORE_DISPLAY_LIST);
|
||||
}
|
||||
|
||||
$response = new Response();
|
||||
|
||||
$response->headers->setCookie(new Cookie(
|
||||
$this->cookieName,
|
||||
$display,
|
||||
(new \DateTime('now'))->modify('+ 30 days')->getTimestamp(),
|
||||
'/'
|
||||
));
|
||||
|
||||
return $response->sendHeaders();
|
||||
}
|
||||
|
||||
public function getDisplay()
|
||||
{
|
||||
$displayFromCookie = $this->getRequest()->cookies->get($this->cookieName);
|
||||
|
||||
if ($displayFromCookie) {
|
||||
return $displayFromCookie;
|
||||
}
|
||||
|
||||
return \Configuration::get(GeneralConfiguration::THEMECORE_DISPLAY_LIST);
|
||||
}
|
||||
|
||||
public function getDisplayOptions()
|
||||
{
|
||||
return $this->displayList;
|
||||
}
|
||||
}
|
||||
11
is_themecore/src/Core/ListingDisplay/index.php
Normal file
11
is_themecore/src/Core/ListingDisplay/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
54
is_themecore/src/Core/Partytown/FilesInstallation.php
Normal file
54
is_themecore/src/Core/Partytown/FilesInstallation.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\Partytown;
|
||||
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
|
||||
class FilesInstallation
|
||||
{
|
||||
private \Is_themecore $module;
|
||||
|
||||
public function __construct(
|
||||
\Is_themecore $module
|
||||
) {
|
||||
$this->module = $module;
|
||||
}
|
||||
|
||||
public function installFiles(): void
|
||||
{
|
||||
$this->installPartytown();
|
||||
}
|
||||
|
||||
protected function getFileSystem(): Filesystem
|
||||
{
|
||||
return new Filesystem();
|
||||
}
|
||||
|
||||
private function installPartytown(): void
|
||||
{
|
||||
$source = _PS_MODULE_DIR_ . $this->module->name . '/public/~partytown';
|
||||
$destination = _PS_ROOT_DIR_ . '/~partytown';
|
||||
$fileSystem = $this->getFileSystem();
|
||||
|
||||
if (!file_exists($source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_exists($destination)) {
|
||||
$fileSystem->remove($destination);
|
||||
}
|
||||
|
||||
$fileSystem->mkdir($destination);
|
||||
|
||||
$directoryIterator = new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$iterator = new \RecursiveIteratorIterator($directoryIterator, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
foreach ($iterator as $item) {
|
||||
if ($item->isDir()) {
|
||||
$fileSystem->mkdir($destination . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
|
||||
} else {
|
||||
$fileSystem->copy($item, $destination . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
is_themecore/src/Core/Partytown/PartytownScript.php
Normal file
36
is_themecore/src/Core/Partytown/PartytownScript.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\Partytown;
|
||||
|
||||
class PartytownScript
|
||||
{
|
||||
private \Is_themecore $module;
|
||||
|
||||
public function __construct(
|
||||
\Is_themecore $module
|
||||
) {
|
||||
$this->module = $module;
|
||||
}
|
||||
|
||||
public function getScriptPath(): string
|
||||
{
|
||||
return _PS_MODULE_DIR_ . $this->module->name . '/public/partytown.js';
|
||||
}
|
||||
|
||||
public function getScriptUri(): string
|
||||
{
|
||||
return $this->module->getPathUri() . '/public/partytown.js';
|
||||
}
|
||||
|
||||
public function getScriptContent(): string
|
||||
{
|
||||
$script = '';
|
||||
$filePath = $this->getScriptPath();
|
||||
|
||||
if (file_exists($filePath)) {
|
||||
$script = file_get_contents($filePath);
|
||||
}
|
||||
|
||||
return $script;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\Partytown;
|
||||
|
||||
class PartytownScriptUriResolver
|
||||
{
|
||||
private \Context $context;
|
||||
|
||||
const PUBLIC_PARTYTOWN_PATH = '~partytown/';
|
||||
|
||||
public function __construct(
|
||||
\Context $context
|
||||
) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function getScriptUri(): string
|
||||
{
|
||||
return $this->context->shop->physical_uri . self::PUBLIC_PARTYTOWN_PATH;
|
||||
}
|
||||
}
|
||||
192
is_themecore/src/Core/Smarty/SmartyHelperFunctions.php
Normal file
192
is_themecore/src/Core/Smarty/SmartyHelperFunctions.php
Normal file
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\Smarty;
|
||||
|
||||
use Oksydan\Module\IsThemeCore\Core\Webp\WebpPictureGenerator;
|
||||
use Oksydan\Module\IsThemeCore\Form\Settings\WebpConfiguration;
|
||||
|
||||
class SmartyHelperFunctions
|
||||
{
|
||||
public static function generateImagesSources($params)
|
||||
{
|
||||
$image = $params['image'];
|
||||
$size = $params['size'];
|
||||
$lazyLoad = isset($params['lazyload']) ? $params['lazyload'] : true;
|
||||
$attributes = [];
|
||||
$highDpiImagesEnabled = (bool) \Configuration::get('PS_HIGHT_DPI');
|
||||
|
||||
$srcAttributePrefix = $lazyLoad ? 'data-' : '';
|
||||
|
||||
$img = $image['bySize'][$size]['url'];
|
||||
|
||||
if ($highDpiImagesEnabled) {
|
||||
$size2x = $size . '2x';
|
||||
$img2x = str_replace($size, $size2x, $img);
|
||||
$attributeName = $srcAttributePrefix . 'srcset';
|
||||
$attributes[$attributeName] = "$img, $img2x 2x";
|
||||
} else {
|
||||
$attributeName = $srcAttributePrefix . 'src';
|
||||
$attributes[$attributeName] = $img;
|
||||
}
|
||||
|
||||
if ($lazyLoad) {
|
||||
$width = $image['bySize'][$size]['width'];
|
||||
$height = $image['bySize'][$size]['height'];
|
||||
$placeholderSrc = self::generateImageSvgPlaceholder(['width' => $width, 'height' => $height]);
|
||||
|
||||
$attributes['src'] = $placeholderSrc;
|
||||
}
|
||||
|
||||
$attributesToPrint = [];
|
||||
|
||||
foreach ($attributes as $attr => $value) {
|
||||
$attributesToPrint[] = $attr . '="' . $value . '"';
|
||||
}
|
||||
|
||||
return implode(PHP_EOL, $attributesToPrint);
|
||||
}
|
||||
|
||||
public static function generateImageSvgPlaceholder($params)
|
||||
{
|
||||
$width = $params['width'];
|
||||
$height = $params['height'];
|
||||
|
||||
return "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='$width' height='$height' viewBox='0 0 1 1'%3E%3C/svg%3E";
|
||||
}
|
||||
|
||||
public static function appendParamToUrl($params)
|
||||
{
|
||||
list(
|
||||
'url' => $url,
|
||||
'key' => $key,
|
||||
'value' => $value
|
||||
) = $params;
|
||||
|
||||
$replace = false;
|
||||
|
||||
if (isset($params['replace'])) {
|
||||
$replace = $params['replace'];
|
||||
}
|
||||
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
} else {
|
||||
$replace = false;
|
||||
}
|
||||
|
||||
foreach ($value as $qValue) {
|
||||
$query = parse_url($url, PHP_URL_QUERY);
|
||||
|
||||
if ($query) {
|
||||
if ($replace) {
|
||||
parse_str($query, $queryParams);
|
||||
$queryParams[$key] = $qValue;
|
||||
$url = str_replace("?$query", '?' . http_build_query($queryParams), $url);
|
||||
} else {
|
||||
$queryParams = [];
|
||||
$queryParams[$key] = $qValue;
|
||||
$url .= '&' . http_build_query($queryParams);
|
||||
}
|
||||
} else {
|
||||
$url .= '?' . urlencode($key) . '=' . urlencode($qValue);
|
||||
}
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public static function imagesBlock($params, $content, $smarty)
|
||||
{
|
||||
$webpEnabled = isset($params['webpEnabled']) ? $params['webpEnabled'] : \Configuration::get(WebpConfiguration::THEMECORE_WEBP_ENABLED);
|
||||
|
||||
if ($webpEnabled && !empty($content)) {
|
||||
$pictureGenerator = new WebpPictureGenerator($content);
|
||||
|
||||
$pictureGenerator
|
||||
->loadContent()
|
||||
->generatePictureTags();
|
||||
|
||||
return $pictureGenerator->getContent();
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public static function displayMobileBlock($params, $content, $smarty)
|
||||
{
|
||||
if (!empty($content) && \Context::getContext()->isMobile()) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public static function displayDesktopBlock($params, $content, $smarty)
|
||||
{
|
||||
if (!empty($content) && !\Context::getContext()->isMobile()) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public static function cmsImagesBlock($params, $content, $smarty)
|
||||
{
|
||||
$doc = new \DOMDocument();
|
||||
$doc->loadHTML('<meta http-equiv="Content-Type" content="charset=utf-8">' . $content);
|
||||
$context = \Context::getContext();
|
||||
|
||||
$images = $doc->getElementsByTagName('img');
|
||||
|
||||
$domains = \Tools::getDomains();
|
||||
$medias = [
|
||||
\Configuration::get('PS_MEDIA_SERVER_1'),
|
||||
\Configuration::get('PS_MEDIA_SERVER_2'),
|
||||
\Configuration::get('PS_MEDIA_SERVER_3'),
|
||||
];
|
||||
|
||||
$internalUrls = [];
|
||||
|
||||
foreach ($domains as $domain => $options) {
|
||||
$internalUrls[] = $domain;
|
||||
}
|
||||
|
||||
foreach ($medias as $media) {
|
||||
if ($media) {
|
||||
$internalUrls[] = $media;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($images as $image) {
|
||||
$newImg = $doc->createElement('img');
|
||||
$src = urldecode($image->attributes->getNamedItem('src')->nodeValue);
|
||||
|
||||
if (!preg_match('/' . implode('|', $internalUrls) . '/i', $src)) {
|
||||
$newImg->setAttribute('data-external-url', '');
|
||||
}
|
||||
|
||||
foreach ($image->attributes as $attribute) {
|
||||
$newImg->setAttribute($attribute->nodeName, $attribute->nodeValue);
|
||||
}
|
||||
|
||||
$image->parentNode->replaceChild($newImg, $image);
|
||||
}
|
||||
|
||||
$content = $doc->saveHTML();
|
||||
$content = str_replace('<meta http-equiv="Content-Type" content="charset=utf-8">', '', $content);
|
||||
|
||||
$webpEnabled = isset($params['webpEnabled']) ? $params['webpEnabled'] : \Configuration::get(WebpConfiguration::THEMECORE_WEBP_ENABLED);
|
||||
|
||||
if ($webpEnabled && !empty($content)) {
|
||||
$pictureGenerator = new WebpPictureGenerator($content);
|
||||
|
||||
$pictureGenerator
|
||||
->loadContent()
|
||||
->generatePictureTags();
|
||||
|
||||
return $pictureGenerator->getContent();
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData;
|
||||
|
||||
use Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter\StructuredDataPresenterInterface;
|
||||
use Oksydan\Module\IsThemeCore\Core\StructuredData\Provider\StructuredDataProviderInterface;
|
||||
|
||||
abstract class AbstractStructuredData
|
||||
{
|
||||
private StructuredDataProviderInterface $provider;
|
||||
private StructuredDataPresenterInterface $presenter;
|
||||
|
||||
public function __construct(
|
||||
StructuredDataProviderInterface $provider,
|
||||
StructuredDataPresenterInterface $presenter
|
||||
) {
|
||||
$this->provider = $provider;
|
||||
$this->presenter = $presenter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return formatted json data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFormattedData(): string
|
||||
{
|
||||
$data = $this->provider->getData();
|
||||
|
||||
$jsonData = $this->presenter->present($data);
|
||||
|
||||
\Hook::exec('actionStructuredData' . ucfirst($this->getStructuredDataType()),
|
||||
[
|
||||
'jsonData' => &$jsonData,
|
||||
'rawData' => $data,
|
||||
]
|
||||
);
|
||||
|
||||
if (empty($jsonData)) {
|
||||
return '';
|
||||
} else {
|
||||
return json_encode($jsonData, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData;
|
||||
|
||||
class BreadcrumbStructuredData extends AbstractStructuredData implements StructuredDataInterface
|
||||
{
|
||||
public function getStructuredDataType(): string
|
||||
{
|
||||
return 'breadcrumb';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter;
|
||||
|
||||
class StructuredDataBreadcrumbPresenter implements StructuredDataPresenterInterface
|
||||
{
|
||||
private array $presentedData = [];
|
||||
private array $breadcrumbData = [];
|
||||
|
||||
public function present($data): array
|
||||
{
|
||||
$this->breadcrumbData = $data;
|
||||
|
||||
$this->presentBreadcrumbData();
|
||||
|
||||
return $this->presentedData;
|
||||
}
|
||||
|
||||
private function presentBreadcrumbData(): void
|
||||
{
|
||||
$breadcrumbs = $this->breadcrumbData['links'];
|
||||
|
||||
if ($this->breadcrumbData['count'] > 1) {
|
||||
$this->presentedData['@context'] = 'http://schema.org';
|
||||
$this->presentedData['@type'] = 'BreadcrumbList';
|
||||
$this->presentedData['itemListElement'] = [];
|
||||
|
||||
foreach ($breadcrumbs as $i => $breadcrumb) {
|
||||
$this->presentedData['itemListElement'][] = [
|
||||
'@type' => 'ListItem',
|
||||
'position' => $i + 1,
|
||||
'name' => $breadcrumb['title'],
|
||||
'item' => $breadcrumb['url'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter;
|
||||
|
||||
interface StructuredDataPresenterInterface
|
||||
{
|
||||
/**
|
||||
* Return formatted data
|
||||
*
|
||||
* @param array $data Data from provider
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function present($data);
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter;
|
||||
|
||||
class StructuredDataProductPresenter implements StructuredDataPresenterInterface
|
||||
{
|
||||
private $presentedData = [];
|
||||
private $productData;
|
||||
private $context;
|
||||
|
||||
public function __construct(\Context $context)
|
||||
{
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function present($data): array
|
||||
{
|
||||
$this->productData = $data;
|
||||
|
||||
$this->getProductBasics();
|
||||
$this->getProductIdentifier();
|
||||
$this->getProductBrandData();
|
||||
$this->getProductReviewsData();
|
||||
$this->getProductOffers();
|
||||
|
||||
return $this->presentedData;
|
||||
}
|
||||
|
||||
private function getProductBasics(): void
|
||||
{
|
||||
$this->presentedData['@context'] = 'http://schema.org/';
|
||||
$this->presentedData['@type'] = 'Product';
|
||||
$this->presentedData['name'] = $this->productData['name'];
|
||||
$this->presentedData['category'] = $this->productData['category_name'];
|
||||
|
||||
if (!empty($this->productData['description_short'])) {
|
||||
$this->presentedData['description'] = strip_tags($this->productData['description_short']);
|
||||
}
|
||||
|
||||
if ($this->productData['default_image']) {
|
||||
$this->presentedData['image'] = $this->productData['default_image']['large']['url'];
|
||||
}
|
||||
|
||||
if ($this->productData['reference']) {
|
||||
$this->presentedData['sku'] = $this->productData['reference'];
|
||||
}
|
||||
|
||||
if (!empty($this->productData['weight']) && $this->productData['weight'] > 0) {
|
||||
$this->presentedData['weight'] = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'QuantitativeValue',
|
||||
'value' => $this->productData['weight'],
|
||||
'unitCode' => $this->productData['weight_unit'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function getProductBrandData(): void
|
||||
{
|
||||
if (empty($this->productData['id_manufacturer'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$productManufacturer = new \Manufacturer((int) $this->productData['id_manufacturer'], $this->context->language->id);
|
||||
|
||||
if (!empty($productManufacturer->name)) {
|
||||
$this->presentedData['brand'] = [
|
||||
'@type' => 'Brand',
|
||||
'name' => $productManufacturer->name,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function getProductIdentifier(): void
|
||||
{
|
||||
if (!empty($this->productData['ean13'])) {
|
||||
$this->presentedData['gtin13'] = $this->productData['ean13'];
|
||||
} elseif (!empty($this->productData['upc'])) {
|
||||
$this->presentedData['gtin13'] = '0' . $this->productData['upc'];
|
||||
} elseif (!empty($this->productData['isbn'])) {
|
||||
$this->presentedData['isbn'] = $this->productData['isbn'];
|
||||
} elseif (!empty($this->productData['reference'])) {
|
||||
$this->presentedData['mpn'] = $this->productData['reference'];
|
||||
}
|
||||
}
|
||||
|
||||
private function getProductOffers(): void
|
||||
{
|
||||
if (!$this->productData['show_price']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->presentedData['offers'] = [
|
||||
'@type' => 'Offer',
|
||||
'name' => $this->productData['name'],
|
||||
'price' => $this->productData['price_amount'],
|
||||
'url' => $this->productData['url'],
|
||||
'priceCurrency' => $this->context->currency->iso_code,
|
||||
];
|
||||
|
||||
if (count($this->productData['images']) > 0) {
|
||||
$images = [];
|
||||
|
||||
foreach ($this->productData['images'] as $img) {
|
||||
$images[] = $img['large']['url'];
|
||||
}
|
||||
|
||||
$this->presentedData['offers']['image'] = $images;
|
||||
}
|
||||
|
||||
if ($this->productData['reference']) {
|
||||
$this->presentedData['offers']['sku'] = $this->productData['reference'];
|
||||
}
|
||||
|
||||
$this->presentedData['offers']['availability'] = $this->productData['quantity'] > 0 || $this->productData['allow_oosp'] ? 'http://schema.org/InStock' : 'http://schema.org/OutOfStock';
|
||||
|
||||
if ($this->productData['show_condition'] && isset($this->productData['condition'])) {
|
||||
$this->presentedData['offers']['itemCondition'] = $this->productData['condition']['schema_url'];
|
||||
}
|
||||
|
||||
if ($this->productData['specific_prices'] && $this->productData['specific_prices']['to'] > (new \DateTime())->format('Y-m-d H:i:s')) {
|
||||
$date = new \DateTime($this->productData['specific_prices']['to']);
|
||||
$this->presentedData['offers']['priceValidUntil'] = $date->format('Y-m-d');
|
||||
}
|
||||
}
|
||||
|
||||
private function getProductReviewsData(): void
|
||||
{
|
||||
if (empty($this->productData['productRating'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$reviews = [];
|
||||
|
||||
foreach ($this->productData['productRating']['reviews'] as $review) {
|
||||
$datePublished = new \DateTime($review['date_add']);
|
||||
|
||||
$reviews[] = [
|
||||
'@type' => 'Review',
|
||||
'author' => [
|
||||
'@type' => 'Person',
|
||||
'name' => $review['customer_name'],
|
||||
],
|
||||
'name' => $review['title'],
|
||||
'reviewBody' => $review['content'],
|
||||
'datePublished' => $datePublished->format(\DateTime::ATOM),
|
||||
'reviewRating' => [
|
||||
'@type' => 'Rating',
|
||||
'ratingValue' => $review['grade'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$aggregateRating = [
|
||||
'@type' => 'AggregateRating',
|
||||
'ratingValue' => $this->productData['productRating']['averageGrade'],
|
||||
'ratingCount' => $this->productData['productRating']['commentsNb'],
|
||||
'reviewCount' => $this->productData['productRating']['commentsNb'],
|
||||
];
|
||||
|
||||
if ($reviews) {
|
||||
$this->presentedData['review'] = $reviews;
|
||||
}
|
||||
$this->presentedData['aggregateRating'] = $aggregateRating;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter;
|
||||
|
||||
class StructuredDataShopPresenter implements StructuredDataPresenterInterface
|
||||
{
|
||||
private $presentedData = [];
|
||||
private $shopData;
|
||||
private $context;
|
||||
|
||||
public function __construct(\Context $context)
|
||||
{
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function present($data): array
|
||||
{
|
||||
$this->shopData = $data;
|
||||
|
||||
$this->presentShopData();
|
||||
|
||||
return $this->presentedData;
|
||||
}
|
||||
|
||||
private function presentShopData(): void
|
||||
{
|
||||
$this->presentedData['@context'] = 'http://schema.org';
|
||||
$this->presentedData['@type'] = 'Organization';
|
||||
$this->presentedData['name'] = $this->shopData['name'];
|
||||
$this->presentedData['url'] = $this->context->link->getPageLink('index');
|
||||
$this->presentedData['logo'] = [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $this->shopData['logo'],
|
||||
];
|
||||
|
||||
if ($this->shopData['phone']) {
|
||||
$this->presentedData['contactPoint'] = [
|
||||
'@type' => 'ContactPoint',
|
||||
'telephone' => $this->shopData['phone'],
|
||||
'contactType' => 'customer service',
|
||||
];
|
||||
}
|
||||
|
||||
$address = $this->shopData['address'];
|
||||
$postalCode = $address['postcode'];
|
||||
$city = $address['city'];
|
||||
$country = $address['country'];
|
||||
$addressRegion = $address['state'];
|
||||
$streetAddress = $address['address1'];
|
||||
|
||||
if ($postalCode || $city || $country || $addressRegion || $streetAddress) {
|
||||
$this->presentedData['address'] = [
|
||||
'@type' => 'PostalAddress',
|
||||
];
|
||||
|
||||
if ($postalCode) {
|
||||
$this->presentedData['address']['postalCode'] = $postalCode;
|
||||
}
|
||||
if ($streetAddress) {
|
||||
$this->presentedData['address']['streetAddress'] = $streetAddress;
|
||||
}
|
||||
if ($country || $city) {
|
||||
$addressLocality = '';
|
||||
if ($city) {
|
||||
$addressLocality = $city;
|
||||
}
|
||||
if ($country) {
|
||||
$addressLocality .= ($addressLocality != '' ? ', ' : '') . $country;
|
||||
}
|
||||
|
||||
$this->presentedData['address']['addressLocality'] = $addressLocality;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter;
|
||||
|
||||
class StructuredDataWebsitePresenter implements StructuredDataPresenterInterface
|
||||
{
|
||||
private $presentedData = [];
|
||||
private $websiteData;
|
||||
private $context;
|
||||
|
||||
public function __construct(\Context $context)
|
||||
{
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function present($data): array
|
||||
{
|
||||
$this->websiteData = $data;
|
||||
|
||||
$this->presentShopData();
|
||||
|
||||
return $this->presentedData;
|
||||
}
|
||||
|
||||
private function presentShopData(): void
|
||||
{
|
||||
$this->presentedData['@context'] = 'http://schema.org';
|
||||
$this->presentedData['@type'] = 'WebSite';
|
||||
$this->presentedData['url'] = $this->context->link->getPageLink('index');
|
||||
$this->presentedData['image'] = [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $this->websiteData['logo'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData;
|
||||
|
||||
class ProductStructuredData extends AbstractStructuredData implements StructuredDataInterface
|
||||
{
|
||||
public function getStructuredDataType(): string
|
||||
{
|
||||
return 'product';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData\Provider;
|
||||
|
||||
class StructuredDataBreadcrumbProvider implements StructuredDataProviderInterface
|
||||
{
|
||||
protected \Context $context;
|
||||
|
||||
public function __construct(\Context $context)
|
||||
{
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->context->controller->getBreadcrumb();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData\Provider;
|
||||
|
||||
class StructuredDataProductProvider implements StructuredDataProviderInterface
|
||||
{
|
||||
private array $data = [];
|
||||
private \Context $context;
|
||||
|
||||
public function __construct(\Context $context)
|
||||
{
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
private function provideProductCommentsDataIfModuleEnabled(): void
|
||||
{
|
||||
$commentsData = [];
|
||||
|
||||
if (\Module::isEnabled('productcomments')) {
|
||||
$productCommentRepository = $this->context->controller->getContainer()->get('product_comment_repository');
|
||||
$commentsModerate = (bool) \Configuration::get('PRODUCT_COMMENTS_MODERATE');
|
||||
$commentsNb = $productCommentRepository->getCommentsNumber($this->data['id'], $commentsModerate);
|
||||
|
||||
if ($commentsNb > 0) {
|
||||
$averageGrade = $productCommentRepository->getAverageGrade($this->data['id'], $commentsModerate);
|
||||
$reviewsData = $productCommentRepository->paginate($this->data['id'], 1, 50, $commentsModerate); // get 50 reviews
|
||||
|
||||
$commentsData = [
|
||||
'averageGrade' => $averageGrade,
|
||||
'commentsNb' => $commentsNb,
|
||||
'reviews' => $reviewsData,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->data['productRating'] = $commentsData;
|
||||
}
|
||||
|
||||
public function getProductData(): void
|
||||
{
|
||||
$this->data = $this->context->controller->getTemplateVarProduct()->jsonSerialize();
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
$this->getProductData();
|
||||
$this->provideProductCommentsDataIfModuleEnabled();
|
||||
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData\Provider;
|
||||
|
||||
interface StructuredDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* Provide data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getData();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData\Provider;
|
||||
|
||||
class StructuredDataShopProvider implements StructuredDataProviderInterface
|
||||
{
|
||||
protected \Context $context;
|
||||
|
||||
public function __construct(\Context $context)
|
||||
{
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->context->smarty->getTemplateVars('shop');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData\Provider;
|
||||
|
||||
class StructuredDataWebsiteProvider implements StructuredDataProviderInterface
|
||||
{
|
||||
protected \Context $context;
|
||||
|
||||
public function __construct(\Context $context)
|
||||
{
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->context->smarty->getTemplateVars('shop');
|
||||
}
|
||||
}
|
||||
11
is_themecore/src/Core/StructuredData/ShopStructuredData.php
Normal file
11
is_themecore/src/Core/StructuredData/ShopStructuredData.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData;
|
||||
|
||||
class ShopStructuredData extends AbstractStructuredData implements StructuredDataInterface
|
||||
{
|
||||
public function getStructuredDataType(): string
|
||||
{
|
||||
return 'shop';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData;
|
||||
|
||||
interface StructuredDataInterface
|
||||
{
|
||||
/**
|
||||
* Return formatted json data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFormattedData(): string;
|
||||
|
||||
public function getStructuredDataType(): string;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\StructuredData;
|
||||
|
||||
class WebsiteStructuredData extends AbstractStructuredData implements StructuredDataInterface
|
||||
{
|
||||
public function getStructuredDataType(): string
|
||||
{
|
||||
return 'website';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\ThemeAssets;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class ThemeAssetConfigProvider
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $fileContentRead = false;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $fileParsed = [];
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $themeAssetsFileDir;
|
||||
|
||||
public function __construct($themeDir)
|
||||
{
|
||||
$this->themeAssetsFileDir = $themeDir . 'config/assets.yml';
|
||||
}
|
||||
|
||||
public function getFileParsed(): array
|
||||
{
|
||||
if (!$this->fileContentRead) {
|
||||
if (file_exists($this->themeAssetsFileDir)) {
|
||||
$this->fileParsed = Yaml::parse(file_get_contents($this->themeAssetsFileDir));
|
||||
}
|
||||
|
||||
$this->fileContentRead = true;
|
||||
}
|
||||
|
||||
return $this->fileParsed;
|
||||
}
|
||||
|
||||
public function getCssAssets(): array
|
||||
{
|
||||
$cssAssets = [];
|
||||
|
||||
if (!empty($this->getFileParsed()['css'])) {
|
||||
$cssAssets = $this->getFileParsed()['css'];
|
||||
}
|
||||
|
||||
return $cssAssets;
|
||||
}
|
||||
|
||||
public function getJsAssets(): array
|
||||
{
|
||||
$jsAssets = [];
|
||||
|
||||
if (!empty($this->getFileParsed()['js'])) {
|
||||
$jsAssets = $this->getFileParsed()['js'];
|
||||
}
|
||||
|
||||
return $jsAssets;
|
||||
}
|
||||
}
|
||||
140
is_themecore/src/Core/ThemeAssets/ThemeAssetsRegister.php
Normal file
140
is_themecore/src/Core/ThemeAssets/ThemeAssetsRegister.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\ThemeAssets;
|
||||
|
||||
class ThemeAssetsRegister
|
||||
{
|
||||
/**
|
||||
* @var ThemeAssetConfigProvider
|
||||
*/
|
||||
private $assetsDataProvider;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $currentPageName;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $themeName;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $cssAssets = [];
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $jsAssets = [];
|
||||
|
||||
public function __construct(ThemeAssetConfigProvider $assetsDataProvider, \Context $context)
|
||||
{
|
||||
$this->assetsDataProvider = $assetsDataProvider;
|
||||
$this->context = $context;
|
||||
$this->themeName = $this->context->shop->theme->getName();
|
||||
$this->currentPageName = $this->context->controller->getPageName();
|
||||
$this->themePath = 'themes/' . $this->themeName . '/assets/';
|
||||
$this->cssAssets = $assetsDataProvider->getCssAssets();
|
||||
$this->jsAssets = $assetsDataProvider->getJsAssets();
|
||||
}
|
||||
|
||||
private function getFilteredCssAssetsByPage(): array
|
||||
{
|
||||
return $this->filterAssetsArrayByPage($this->cssAssets);
|
||||
}
|
||||
|
||||
private function getFilteredJsAssetsByPage(): array
|
||||
{
|
||||
return $this->filterAssetsArrayByPage($this->jsAssets);
|
||||
}
|
||||
|
||||
private function filterAssetsArrayByPage($assetsArray): array
|
||||
{
|
||||
$pageName = $this->currentPageName;
|
||||
|
||||
return array_filter($assetsArray, function ($asset) use ($pageName) {
|
||||
if (empty($asset['include'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($pageName, $asset['include'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($asset['include'] as $matchType) {
|
||||
$regex = str_replace(
|
||||
['\*'],
|
||||
['.*', '.'],
|
||||
preg_quote($matchType)
|
||||
);
|
||||
|
||||
if (preg_match('/^' . $regex . '$/is', $pageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public function registerThemeAssets(): void
|
||||
{
|
||||
$this->registerJsAssets();
|
||||
$this->registerCssAssets();
|
||||
}
|
||||
|
||||
public function registerJsAssets(): void
|
||||
{
|
||||
$assetsToRegister = $this->getFilteredJsAssetsByPage();
|
||||
|
||||
$default_params = [
|
||||
'position' => \AbstractAssetManager::DEFAULT_JS_POSITION,
|
||||
'priority' => \AbstractAssetManager::DEFAULT_PRIORITY,
|
||||
'inline' => false,
|
||||
'attributes' => null,
|
||||
'server' => 'local',
|
||||
];
|
||||
|
||||
foreach ($assetsToRegister as $id => $asset) {
|
||||
$params = array_merge($default_params, $asset);
|
||||
$file = $params['server'] === 'local' ? $this->themePath . 'js/' . $asset['fileName'] : $asset['fileName'];
|
||||
|
||||
$this->context->controller->registerJavascript(
|
||||
'theme-' . $id,
|
||||
$file,
|
||||
[
|
||||
'position' => $params['position'],
|
||||
'priority' => $params['priority'],
|
||||
'inline' => $params['inline'],
|
||||
'attributes' => $params['attributes'],
|
||||
'server' => $params['server'],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function registerCssAssets(): void
|
||||
{
|
||||
$assetsToRegister = $this->getFilteredCssAssetsByPage();
|
||||
|
||||
$default_params = [
|
||||
'media' => \AbstractAssetManager::DEFAULT_MEDIA,
|
||||
'priority' => \AbstractAssetManager::DEFAULT_PRIORITY,
|
||||
'inline' => false,
|
||||
'server' => 'local',
|
||||
];
|
||||
|
||||
foreach ($assetsToRegister as $id => $asset) {
|
||||
$params = array_merge($default_params, $asset);
|
||||
$file = $params['server'] === 'local' ? $this->themePath . 'css/' . $asset['fileName'] : $asset['fileName'];
|
||||
|
||||
$this->context->controller->registerStylesheet(
|
||||
'theme-' . $id,
|
||||
$file,
|
||||
[
|
||||
'media' => $params['media'],
|
||||
'priority' => $params['priority'],
|
||||
'server' => $params['server'],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
is_themecore/src/Core/Webp/RelatedImageFileFinder.php
Normal file
38
is_themecore/src/Core/Webp/RelatedImageFileFinder.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\Webp;
|
||||
|
||||
class RelatedImageFileFinder
|
||||
{
|
||||
protected $allowedImagesExtensions = ['jpg', 'png', 'jpeg'];
|
||||
|
||||
public function setAllowedImagesExtensions($allowedImagesExtensions)
|
||||
{
|
||||
$this->allowedImagesExtensions = $allowedImagesExtensions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAllowedImagesExtensions()
|
||||
{
|
||||
return $this->allowedImagesExtensions;
|
||||
}
|
||||
|
||||
public function findFile($relatedFile)
|
||||
{
|
||||
$fileData = pathinfo($relatedFile);
|
||||
$possibleFiles = [];
|
||||
|
||||
$extensions = $this->getAllowedImagesExtensions();
|
||||
|
||||
foreach ($extensions as $ext) {
|
||||
$possibleFiles[] = $fileData['dirname'] . '/' . $fileData['filename'] . '.' . $ext;
|
||||
}
|
||||
|
||||
foreach ($possibleFiles as $file) {
|
||||
if (file_exists($file)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
is_themecore/src/Core/Webp/WebpConvertLibraries.php
Normal file
60
is_themecore/src/Core/Webp/WebpConvertLibraries.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\Webp;
|
||||
|
||||
use WebPConvert\Convert\ConverterFactory;
|
||||
use WebPConvert\Convert\Exceptions\ConversionFailed\InvalidInput\ConverterNotFoundException;
|
||||
use WebPConvert\Convert\Exceptions\ConversionFailedException;
|
||||
|
||||
class WebpConvertLibraries
|
||||
{
|
||||
protected $converters = [
|
||||
'cwebp' => ['label' => 'Cwebp binary'],
|
||||
'vips' => ['label' => 'Vips PHP extension'],
|
||||
'imagick' => ['label' => 'Imagick PHP extension'],
|
||||
'gmagick' => ['label' => 'Gmagick PHP extension'],
|
||||
'imagemagick' => ['label' => 'Imagemagick binary'],
|
||||
'graphicsmagick' => ['label' => 'Graphicsmagick binary (gm)'],
|
||||
'gd' => ['label' => 'Gd PHP extension'],
|
||||
// NOT SUPPORTED
|
||||
// 'ewww' => ['label' => 'EWWW cloud service'],
|
||||
];
|
||||
|
||||
protected $exampleImgFile = _PS_MODULE_DIR_ . 'is_themecore/views/img/example.jpg';
|
||||
protected $exampleImgFileDesc = _PS_MODULE_DIR_ . 'is_themecore/views/img/example.webp';
|
||||
|
||||
public function getConvertersList(): array
|
||||
{
|
||||
$converters = $this->converters;
|
||||
|
||||
foreach ($converters as $converterId => $converterOptions) {
|
||||
$converters[$converterId]['id'] = $converterId;
|
||||
|
||||
try {
|
||||
$converterInstance = ConverterFactory::makeConverter($converterId, $this->exampleImgFile, $this->exampleImgFileDesc, []);
|
||||
$converterInstance->checkOperationality();
|
||||
$converterInstance->doConvert();
|
||||
$converters[$converterId]['disabled'] = false;
|
||||
} catch (ConversionFailedException $conversionFailedException) {
|
||||
$converters[$converterId]['disabled'] = true;
|
||||
} catch (ConverterNotFoundException $converterNotFoundException) {
|
||||
$converters[$converterId]['disabled'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $converters;
|
||||
}
|
||||
|
||||
public function getFirstAvailableConverter(): array
|
||||
{
|
||||
$list = $this->getConvertersList();
|
||||
|
||||
foreach ($list as $converter) {
|
||||
if (!$converter['disabled']) {
|
||||
return $converter;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
79
is_themecore/src/Core/Webp/WebpFilesEraser.php
Normal file
79
is_themecore/src/Core/Webp/WebpFilesEraser.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\Webp;
|
||||
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
class WebpFilesEraser
|
||||
{
|
||||
private $query = '';
|
||||
private $finder;
|
||||
private $files;
|
||||
private $excludeList = ['node_modules', 'vendor', 'app', 'var', 'classes', 'controllers', 'download'];
|
||||
private $filesCount = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->finder = new Finder();
|
||||
}
|
||||
|
||||
public function setQuery($query)
|
||||
{
|
||||
$this->query = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
public function setExcludeList(array $excludeList)
|
||||
{
|
||||
$this->excludeList = $excludeList;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExcludeList()
|
||||
{
|
||||
return $this->excludeList;
|
||||
}
|
||||
|
||||
private function setFilesCount()
|
||||
{
|
||||
$this->filesCount = iterator_count($this->files);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFilesCount()
|
||||
{
|
||||
return $this->filesCount;
|
||||
}
|
||||
|
||||
private function findFiles()
|
||||
{
|
||||
$this->files = $this->finder
|
||||
->files()
|
||||
->ignoreUnreadableDirs()
|
||||
->in($this->query)
|
||||
->exclude($this->excludeList)
|
||||
->name('*.webp');
|
||||
}
|
||||
|
||||
public function eraseFiles()
|
||||
{
|
||||
$this->findFiles();
|
||||
$this->setFilesCount();
|
||||
|
||||
foreach ($this->files as $file) {
|
||||
try {
|
||||
unlink($file->getPathname());
|
||||
} catch (\Throwable $error) {
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
is_themecore/src/Core/Webp/WebpGenerator.php
Normal file
110
is_themecore/src/Core/Webp/WebpGenerator.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\Webp;
|
||||
|
||||
use WebPConvert\WebPConvert;
|
||||
|
||||
class WebpGenerator
|
||||
{
|
||||
protected $fileFinder;
|
||||
protected $destinationFile = '';
|
||||
protected $converter = false;
|
||||
protected $debugEnabled = false;
|
||||
protected $sharpYuv = false;
|
||||
protected $quality = 90;
|
||||
|
||||
public function __construct(RelatedImageFileFinder $fileFinder)
|
||||
{
|
||||
$this->fileFinder = $fileFinder;
|
||||
}
|
||||
|
||||
public function setQuality($quality)
|
||||
{
|
||||
$this->quality = $quality;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getQuality(): int
|
||||
{
|
||||
return $this->quality;
|
||||
}
|
||||
|
||||
public function setConverter($converter)
|
||||
{
|
||||
$this->converter = $converter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getConverter(): string
|
||||
{
|
||||
return $this->converter;
|
||||
}
|
||||
|
||||
public function setSharpYuv($sharpYuv)
|
||||
{
|
||||
$this->sharpYuv = $sharpYuv;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSharpYuv(): bool
|
||||
{
|
||||
return $this->sharpYuv;
|
||||
}
|
||||
|
||||
public function setDebugEnabled($debugEnabled)
|
||||
{
|
||||
$this->debugEnabled = $debugEnabled;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDebugEnabled(): bool
|
||||
{
|
||||
return $this->debugEnabled;
|
||||
}
|
||||
|
||||
public function setDestinationFile($destinationFile)
|
||||
{
|
||||
$this->destinationFile = $destinationFile;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDestinationFile(): string
|
||||
{
|
||||
return $this->destinationFile;
|
||||
}
|
||||
|
||||
public function findRelatedFile()
|
||||
{
|
||||
return $this->fileFinder->findFile($this->getDestinationFile());
|
||||
}
|
||||
|
||||
public function convertAndServe()
|
||||
{
|
||||
$sourceFile = $this->findRelatedFile();
|
||||
|
||||
WebPConvert::serveConverted($sourceFile, $this->destinationFile, [
|
||||
'fail' => 'original',
|
||||
'show-report' => $this->getDebugEnabled(),
|
||||
|
||||
'serve-image' => [
|
||||
'headers' => [
|
||||
'cache-control' => true,
|
||||
'vary-accept' => true,
|
||||
// other headers can be toggled...
|
||||
],
|
||||
'cache-control-header' => 'max-age=2',
|
||||
],
|
||||
'convert' => [
|
||||
'stack-converters' => [$this->getConverter()],
|
||||
'quality' => $this->getQuality(),
|
||||
'encoding' => 'auto',
|
||||
'sharp-yuv' => $this->getSharpYuv(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
109
is_themecore/src/Core/Webp/WebpPictureGenerator.php
Normal file
109
is_themecore/src/Core/Webp/WebpPictureGenerator.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Core\Webp;
|
||||
|
||||
class WebpPictureGenerator
|
||||
{
|
||||
private $allowedExtensions = ['png', 'jpg', 'jpeg'];
|
||||
protected $content = '';
|
||||
private $doc;
|
||||
|
||||
public function __construct($content)
|
||||
{
|
||||
$this->content = $content;
|
||||
$this->doc = new \DOMDocument();
|
||||
}
|
||||
|
||||
public function loadContent()
|
||||
{
|
||||
$this->doc->loadHTML('<?xml encoding="utf-8" ?>' . $this->content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function generatePictureTags(): void
|
||||
{
|
||||
$images = $this->doc->getElementsByTagName('img');
|
||||
|
||||
if (0 === count($images)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($images as $image) {
|
||||
if ($image->hasAttribute('data-external-url')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->generatePictureTagFromImg($image);
|
||||
}
|
||||
|
||||
$this->content = $this->doc->saveHTML();
|
||||
$this->content = str_replace('<?xml encoding="utf-8" ?>', '', $this->content);
|
||||
}
|
||||
|
||||
private function generatePictureTagFromImg($image)
|
||||
{
|
||||
$lazyLoad = !empty($params['lazyload']) ? $params['lazyload'] : (bool) preg_match('/' . implode('|', ['lazyload', 'swiper-lazy']) . '/i', $image->ownerDocument->saveHTML($image));
|
||||
$srcAttributePrefix = $lazyLoad ? 'data-' : '';
|
||||
$containSrcset = $image->hasAttribute($srcAttributePrefix . 'srcset');
|
||||
$srcAttribute = $srcAttributePrefix . ($containSrcset ? 'srcset' : 'src');
|
||||
|
||||
$src = $image->getAttribute($srcAttribute);
|
||||
$rawSrcArray = explode(',', $src);
|
||||
$imageSrcArray = [];
|
||||
|
||||
foreach ($rawSrcArray as $rawSrc) {
|
||||
$srcWithMediaArray = explode(' ', $rawSrc);
|
||||
|
||||
$srcWithMediaArray = array_values(array_filter($srcWithMediaArray, function ($elem) {
|
||||
return !empty($elem);
|
||||
}));
|
||||
|
||||
$imageSrcArray[] = [
|
||||
'file' => $srcWithMediaArray[0] ?? null,
|
||||
'media' => $srcWithMediaArray[1] ?? null,
|
||||
'ext' => isset($srcWithMediaArray[0]) ? pathinfo($srcWithMediaArray[0], PATHINFO_EXTENSION) : null,
|
||||
];
|
||||
}
|
||||
|
||||
$picture = $this->doc->createElement('picture');
|
||||
$pict_clone = $picture->cloneNode();
|
||||
$image->parentNode->replaceChild($pict_clone, $image);
|
||||
$pict_clone->appendChild($image);
|
||||
|
||||
$source = $this->doc->createElement('source');
|
||||
$source->setAttribute('type', 'image/webp');
|
||||
$sourceWebp = '';
|
||||
|
||||
$lastKey = array_key_last($imageSrcArray);
|
||||
|
||||
foreach ($imageSrcArray as $key => $imageSrc) {
|
||||
$ext = explode('?', $imageSrc['ext']);
|
||||
$ext = $ext[0] ?? null;
|
||||
|
||||
if (!in_array($ext, $this->allowedExtensions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newWebpSrc = str_replace('.' . $imageSrc['ext'], '.webp', $imageSrc['file']);
|
||||
|
||||
$sourceWebp .= $newWebpSrc . ($imageSrc['media'] ? ' ' . $imageSrc['media'] : '');
|
||||
|
||||
if ($key != $lastKey) {
|
||||
$sourceWebp .= ', ';
|
||||
}
|
||||
}
|
||||
|
||||
if ($sourceWebp) {
|
||||
$source->setAttribute($lazyLoad ? 'data-srcset' : 'srcset', $sourceWebp);
|
||||
$src_clone = $source->cloneNode();
|
||||
$image->parentNode->replaceChild($src_clone, $image);
|
||||
$src_clone->appendChild($image);
|
||||
}
|
||||
}
|
||||
|
||||
public function getContent(): string
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
}
|
||||
11
is_themecore/src/Core/Webp/index.php
Normal file
11
is_themecore/src/Core/Webp/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
11
is_themecore/src/Core/index.php
Normal file
11
is_themecore/src/Core/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Form\ChoiceProvider;
|
||||
|
||||
use Oksydan\Module\IsThemeCore\Core\ListingDisplay\ThemeListDisplay;
|
||||
use PrestaShop\PrestaShop\Core\Form\FormChoiceProviderInterface;
|
||||
|
||||
class ListDisplayChoiceProvider implements FormChoiceProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var ThemeListDisplay
|
||||
*/
|
||||
protected $themeListDisplay;
|
||||
|
||||
/**
|
||||
* @param ThemeListDisplay $themeListDisplay
|
||||
*/
|
||||
public function __construct(ThemeListDisplay $themeListDisplay)
|
||||
{
|
||||
$this->themeListDisplay = $themeListDisplay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getChoices(): array
|
||||
{
|
||||
$choices = [];
|
||||
|
||||
foreach ($this->themeListDisplay->getDisplayOptions() as $display) {
|
||||
$choices[$display] = $display;
|
||||
}
|
||||
|
||||
return $choices;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Form\ChoiceProvider;
|
||||
|
||||
use Oksydan\Module\IsThemeCore\Core\Webp\WebpConvertLibraries;
|
||||
use PrestaShop\PrestaShop\Core\Form\FormChoiceProviderInterface;
|
||||
|
||||
class WebpLibraryChoiceProvider implements FormChoiceProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var WebpConvertLibraries
|
||||
*/
|
||||
protected $webpConvertLibraries;
|
||||
|
||||
/**
|
||||
* @param WebpConvertLibraries $webpConvertLibraries
|
||||
*/
|
||||
public function __construct(WebpConvertLibraries $webpConvertLibraries)
|
||||
{
|
||||
$this->webpConvertLibraries = $webpConvertLibraries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getChoices(): array
|
||||
{
|
||||
$choices = [];
|
||||
|
||||
foreach ($this->webpConvertLibraries->getConvertersList() as $converter) {
|
||||
$choices[$converter['label']] = $converter['id'];
|
||||
}
|
||||
|
||||
return $choices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getChoicesFull(): array
|
||||
{
|
||||
$choices = [];
|
||||
|
||||
foreach ($this->webpConvertLibraries->getConvertersList() as $converter) {
|
||||
$choices[$converter['id']] = $converter;
|
||||
}
|
||||
|
||||
return $choices;
|
||||
}
|
||||
}
|
||||
11
is_themecore/src/Form/ChoiceProvider/index.php
Normal file
11
is_themecore/src/Form/ChoiceProvider/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
132
is_themecore/src/Form/Settings/GeneralConfiguration.php
Normal file
132
is_themecore/src/Form/Settings/GeneralConfiguration.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Form\Settings;
|
||||
|
||||
use PrestaShop\PrestaShop\Core\Configuration\AbstractMultistoreConfiguration;
|
||||
use PrestaShopBundle\Service\Form\MultistoreCheckboxEnabler;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Configuration is used to save data to configuration table and retrieve from it
|
||||
*/
|
||||
final class GeneralConfiguration extends AbstractMultistoreConfiguration
|
||||
{
|
||||
private const CONFIGURATION_FIELDS = [
|
||||
'list_display_settings',
|
||||
'early_hints',
|
||||
'preload_css',
|
||||
'load_party_town',
|
||||
'debug_party_town',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const THEMECORE_DISPLAY_LIST = 'THEMECORE_DISPLAY_LIST';
|
||||
public const THEMECORE_EARLY_HINTS = 'THEMECORE_EARLY_HINTS';
|
||||
public const THEMECORE_PRELOAD_CSS = 'THEMECORE_PRELOAD_CSS';
|
||||
public const THEMECORE_LOAD_PARTY_TOWN = 'THEMECORE_LOAD_PARTY_TOWN';
|
||||
public const THEMECORE_DEBUG_PARTY_TOWN = 'THEMECORE_DEBUG_PARTY_TOWN';
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private array $fields = [
|
||||
'list_display_settings' => self::THEMECORE_DISPLAY_LIST,
|
||||
'early_hints' => self::THEMECORE_EARLY_HINTS,
|
||||
'preload_css' => self::THEMECORE_PRELOAD_CSS,
|
||||
'load_party_town' => self::THEMECORE_LOAD_PARTY_TOWN,
|
||||
'debug_party_town' => self::THEMECORE_DEBUG_PARTY_TOWN,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getConfiguration(): array
|
||||
{
|
||||
$configurationValues = [];
|
||||
|
||||
foreach ($this->fields as $field => $configurationKey) {
|
||||
$configurationValues[$field] = $this->configuration->get($configurationKey);
|
||||
}
|
||||
|
||||
return $configurationValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array<string, mixed> $configuration
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function updateConfiguration(array $configuration): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
if (!$this->validateConfiguration($configuration)) {
|
||||
$errors[] = [
|
||||
'key' => 'Invalid configuration',
|
||||
'parameters' => [],
|
||||
'domain' => 'Admin.Notifications.Warning',
|
||||
];
|
||||
} else {
|
||||
$shopConstraint = $this->getShopConstraint();
|
||||
|
||||
try {
|
||||
foreach ($this->fields as $field => $configurationKey) {
|
||||
$this->updateConfigurationValue($configurationKey, $field, $configuration, $shopConstraint);
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
$errors[] = [
|
||||
'key' => $exception->getMessage(),
|
||||
'parameters' => [],
|
||||
'domain' => 'Admin.Notifications.Warning',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the parameters passed are valid.
|
||||
*
|
||||
* @param array<string, mixed> $configuration
|
||||
*
|
||||
* @return bool Returns true if no exception are thrown
|
||||
*/
|
||||
public function validateConfiguration(array $configuration): bool
|
||||
{
|
||||
foreach ($this->fields as $field => $configurationKey) {
|
||||
$multistoreKey = MultistoreCheckboxEnabler::MULTISTORE_FIELD_PREFIX . $field;
|
||||
$this->fields[$multistoreKey] = '';
|
||||
}
|
||||
|
||||
foreach ($configuration as $key => $value) {
|
||||
if (!key_exists($key, $this->fields)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OptionsResolver
|
||||
*/
|
||||
protected function buildResolver(): OptionsResolver
|
||||
{
|
||||
return (new OptionsResolver())
|
||||
->setDefined(self::CONFIGURATION_FIELDS)
|
||||
->setAllowedTypes('list_display_settings', ['string', 'null'])
|
||||
->setAllowedTypes('early_hints', 'bool')
|
||||
->setAllowedTypes('preload_css', 'bool')
|
||||
->setAllowedTypes('load_party_town', 'bool')
|
||||
->setAllowedTypes('debug_party_town', 'bool');
|
||||
}
|
||||
}
|
||||
49
is_themecore/src/Form/Settings/GeneralFormDataProvider.php
Normal file
49
is_themecore/src/Form/Settings/GeneralFormDataProvider.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Form\Settings;
|
||||
|
||||
use PrestaShop\PrestaShop\Core\Configuration\DataConfigurationInterface;
|
||||
use PrestaShop\PrestaShop\Core\Form\FormDataProviderInterface;
|
||||
|
||||
/**
|
||||
* Class GeneralFormDataProvider
|
||||
*/
|
||||
class GeneralFormDataProvider implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var DataConfigurationInterface
|
||||
*/
|
||||
private $generalConfiguration;
|
||||
|
||||
/**
|
||||
* @param DataConfigurationInterface $generalConfiguration
|
||||
*/
|
||||
public function __construct(DataConfigurationInterface $generalConfiguration)
|
||||
{
|
||||
$this->generalConfiguration = $generalConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return array<string, mixed> The form data as an associative array
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->generalConfiguration->getConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*
|
||||
* @return array<int, array<string, mixed>> An array of errors messages if data can't persisted
|
||||
*/
|
||||
public function setData(array $data): array
|
||||
{
|
||||
return $this->generalConfiguration->updateConfiguration($data);
|
||||
}
|
||||
}
|
||||
104
is_themecore/src/Form/Settings/GeneralType.php
Normal file
104
is_themecore/src/Form/Settings/GeneralType.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Form\Settings;
|
||||
|
||||
use PrestaShopBundle\Form\Admin\Type\MultistoreConfigurationType;
|
||||
use PrestaShopBundle\Form\Admin\Type\SwitchType;
|
||||
use PrestaShopBundle\Form\Admin\Type\TranslatorAwareType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
|
||||
class GeneralType extends TranslatorAwareType
|
||||
{
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $displayListChoices;
|
||||
|
||||
/**
|
||||
* GeneralType constructor.
|
||||
*
|
||||
* @param TranslatorInterface $translator
|
||||
* @param array $locales
|
||||
* @param array $displayListChoices
|
||||
*/
|
||||
public function __construct(
|
||||
TranslatorInterface $translator,
|
||||
array $locales,
|
||||
array $displayListChoices
|
||||
) {
|
||||
parent::__construct($translator, $locales);
|
||||
$this->displayListChoices = $displayListChoices;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param FormBuilderInterface<string, mixed> $builder
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('list_display_settings',
|
||||
ChoiceType::class,
|
||||
[
|
||||
'choices' => $this->displayListChoices,
|
||||
'label' => $this->trans('Default list display', 'Modules.isthemecore.Admin'),
|
||||
'multistore_configuration_key' => GeneralConfiguration::THEMECORE_DISPLAY_LIST,
|
||||
]
|
||||
)
|
||||
->add('early_hints',
|
||||
SwitchType::class,
|
||||
[
|
||||
'required' => false,
|
||||
'label' => $this->trans('Early hints (HTTP 103) enabled', 'Modules.isthemecore.Admin'),
|
||||
'help' => $this->trans('Cloudflare CDN, Early hints option have to enabled. <a href="https://developers.cloudflare.com/cache/about/early-hints/">More information</a>', 'Modules.isthemecore.Admin'),
|
||||
'multistore_configuration_key' => GeneralConfiguration::THEMECORE_EARLY_HINTS,
|
||||
]
|
||||
)
|
||||
->add('preload_css',
|
||||
SwitchType::class,
|
||||
[
|
||||
'required' => false,
|
||||
'label' => $this->trans('Preload css enabled, only working with CCC for css option enabled', 'Modules.isthemecore.Admin'),
|
||||
'multistore_configuration_key' => GeneralConfiguration::THEMECORE_PRELOAD_CSS,
|
||||
]
|
||||
)
|
||||
->add('load_party_town',
|
||||
SwitchType::class,
|
||||
[
|
||||
'required' => false,
|
||||
'label' => $this->trans('Load partytown script', 'Modules.isthemecore.Admin'),
|
||||
'help' => $this->trans('Be aware that partytown is still beta. Make sure that everything is working as expected before pushing it to your production store.', 'Modules.isthemecore.Admin'),
|
||||
'multistore_configuration_key' => GeneralConfiguration::THEMECORE_LOAD_PARTY_TOWN,
|
||||
]
|
||||
)
|
||||
->add('debug_party_town',
|
||||
SwitchType::class,
|
||||
[
|
||||
'required' => false,
|
||||
'label' => $this->trans('Enable debug mode for partytown', 'Modules.isthemecore.Admin'),
|
||||
'multistore_configuration_key' => GeneralConfiguration::THEMECORE_DEBUG_PARTY_TOWN,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see MultistoreConfigurationTypeExtension
|
||||
*/
|
||||
public function getParent(): string
|
||||
{
|
||||
return MultistoreConfigurationType::class;
|
||||
}
|
||||
}
|
||||
131
is_themecore/src/Form/Settings/WebpConfiguration.php
Normal file
131
is_themecore/src/Form/Settings/WebpConfiguration.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Form\Settings;
|
||||
|
||||
use PrestaShop\PrestaShop\Adapter\Configuration;
|
||||
use PrestaShop\PrestaShop\Core\Configuration\DataConfigurationInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Configuration is used to save data to configuration table and retrieve from it
|
||||
*/
|
||||
final class WebpConfiguration implements DataConfigurationInterface
|
||||
{
|
||||
private const CONFIGURATION_FIELDS = [
|
||||
'webp_enabled',
|
||||
'webp_quality',
|
||||
'webp_converter',
|
||||
'webp_sharpyuv',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const THEMECORE_WEBP_ENABLED = 'THEMECORE_WEBP_ENABLED';
|
||||
public const THEMECORE_WEBP_QUALITY = 'THEMECORE_WEBP_QUALITY';
|
||||
public const THEMECORE_WEBP_CONVERTER = 'THEMECORE_WEBP_CONVERTER';
|
||||
public const THEMECORE_WEBP_SHARPYUV = 'THEMECORE_WEBP_SHARPYUV';
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private $fields = [
|
||||
'webp_enabled' => self::THEMECORE_WEBP_ENABLED,
|
||||
'webp_quality' => self::THEMECORE_WEBP_QUALITY,
|
||||
'webp_converter' => self::THEMECORE_WEBP_CONVERTER,
|
||||
'webp_sharpyuv' => self::THEMECORE_WEBP_SHARPYUV,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Configuration
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
public function __construct(Configuration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getConfiguration(): array
|
||||
{
|
||||
$configurationValues = [];
|
||||
|
||||
foreach ($this->fields as $field => $configurationKey) {
|
||||
$configurationValues[$field] = $this->configuration->get($configurationKey);
|
||||
}
|
||||
|
||||
return $configurationValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array<string, mixed> $configuration
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function updateConfiguration(array $configuration): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
if (!$this->validateConfiguration($configuration)) {
|
||||
$errors[] = [
|
||||
'key' => 'Invalid configuration',
|
||||
'parameters' => [],
|
||||
'domain' => 'Admin.Notifications.Warning',
|
||||
];
|
||||
} else {
|
||||
try {
|
||||
foreach ($this->fields as $field => $configurationKey) {
|
||||
$this->configuration->set($configurationKey, $configuration[$field]);
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
$errors[] = [
|
||||
'key' => $exception->getMessage(),
|
||||
'parameters' => [],
|
||||
'domain' => 'Admin.Notifications.Warning',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the parameters passed are valid.
|
||||
*
|
||||
* @param array<string, mixed> $configuration
|
||||
*
|
||||
* @return bool Returns true if no exception are thrown
|
||||
*/
|
||||
public function validateConfiguration(array $configuration): bool
|
||||
{
|
||||
foreach ($configuration as $key => $value) {
|
||||
if (!key_exists($key, $this->fields)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OptionsResolver
|
||||
*/
|
||||
protected function buildResolver(): OptionsResolver
|
||||
{
|
||||
return (new OptionsResolver())
|
||||
->setDefined(self::CONFIGURATION_FIELDS)
|
||||
->setAllowedTypes('webp_enabled', 'bool')
|
||||
->setAllowedTypes('webp_quality', 'string')
|
||||
->setAllowedTypes('webp_converter', 'string')
|
||||
->setAllowedTypes('webp_sharpyuv', 'bool');
|
||||
}
|
||||
}
|
||||
49
is_themecore/src/Form/Settings/WebpFormDataProvider.php
Normal file
49
is_themecore/src/Form/Settings/WebpFormDataProvider.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Form\Settings;
|
||||
|
||||
use PrestaShop\PrestaShop\Core\Configuration\DataConfigurationInterface;
|
||||
use PrestaShop\PrestaShop\Core\Form\FormDataProviderInterface;
|
||||
|
||||
/**
|
||||
* Class WebpFormDataProvider
|
||||
*/
|
||||
class WebpFormDataProvider implements FormDataProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var DataConfigurationInterface
|
||||
*/
|
||||
private $webpConfiguration;
|
||||
|
||||
/**
|
||||
* @param DataConfigurationInterface $webpConfiguration
|
||||
*/
|
||||
public function __construct(DataConfigurationInterface $webpConfiguration)
|
||||
{
|
||||
$this->webpConfiguration = $webpConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return array<string, mixed> The form data as an associative array
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->webpConfiguration->getConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*
|
||||
* @return array<int, array<string, mixed>> An array of errors messages if data can't persisted
|
||||
*/
|
||||
public function setData(array $data): array
|
||||
{
|
||||
return $this->webpConfiguration->updateConfiguration($data);
|
||||
}
|
||||
}
|
||||
247
is_themecore/src/Form/Settings/WebpType.php
Normal file
247
is_themecore/src/Form/Settings/WebpType.php
Normal file
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Form\Settings;
|
||||
|
||||
use PrestaShopBundle\Form\Admin\Type\IconButtonType;
|
||||
use PrestaShopBundle\Form\Admin\Type\MultistoreConfigurationType;
|
||||
use PrestaShopBundle\Form\Admin\Type\SwitchType;
|
||||
use PrestaShopBundle\Form\Admin\Type\TranslatorAwareType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Validator\Constraints\Range;
|
||||
|
||||
class WebpType extends TranslatorAwareType
|
||||
{
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $convertersList;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $convertersListFull;
|
||||
|
||||
/**
|
||||
* @var RouterInterface
|
||||
*/
|
||||
private $router;
|
||||
|
||||
/**
|
||||
* WebpType constructor.
|
||||
*
|
||||
* @param TranslatorInterface $translator
|
||||
* @param array $locales
|
||||
* @param array $convertersList
|
||||
* @param array $convertersListFull
|
||||
*/
|
||||
public function __construct(
|
||||
TranslatorInterface $translator,
|
||||
array $locales,
|
||||
array $convertersList,
|
||||
array $convertersListFull,
|
||||
RouterInterface $router
|
||||
) {
|
||||
parent::__construct($translator, $locales);
|
||||
$this->convertersList = $convertersList;
|
||||
$this->convertersListFull = $convertersListFull;
|
||||
$this->router = $router;
|
||||
}
|
||||
|
||||
private function allWebpConvertersDisabled(): bool
|
||||
{
|
||||
return array_reduce($this->convertersListFull, function ($carry, $item) {
|
||||
return $carry && $item['disabled'];
|
||||
}, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param FormBuilderInterface<string, mixed> $builder
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$webpDisabled = $this->allWebpConvertersDisabled();
|
||||
$extraAttributes = [];
|
||||
|
||||
if ($webpDisabled) {
|
||||
$extraAttributes = [
|
||||
'alert_message' => $this->trans('Webp converters not available contact your admin or hosting provider.', 'Modules.isthemecore.Admin'),
|
||||
'alert_type' => 'danger',
|
||||
'alert_position' => 'append',
|
||||
];
|
||||
}
|
||||
|
||||
$builder
|
||||
->add('webp_enabled',
|
||||
SwitchType::class,
|
||||
array_merge(
|
||||
[
|
||||
'required' => false,
|
||||
'label' => $this->trans('Enable WEBP', 'Modules.isthemecore.Admin'),
|
||||
'disabled' => $webpDisabled,
|
||||
],
|
||||
$extraAttributes
|
||||
)
|
||||
)
|
||||
->add('webp_sharpyuv',
|
||||
SwitchType::class,
|
||||
[
|
||||
'required' => false,
|
||||
'label' => $this->trans('Enable better RGB->YUV color conversion', 'Modules.isthemecore.Admin'),
|
||||
'disabled' => $webpDisabled,
|
||||
]
|
||||
)
|
||||
->add('webp_quality',
|
||||
TextType::class,
|
||||
[
|
||||
'required' => false,
|
||||
'label' => $this->trans('Webp quality', 'Modules.isthemecore.Admin'),
|
||||
'help' => $this->trans('Range 1-100', 'Modules.isthemecore.Admin'),
|
||||
'disabled' => $webpDisabled,
|
||||
'constraints' => [
|
||||
$this->getRangeConstraint(1, 100),
|
||||
$this->getNotBlankConstraint(),
|
||||
],
|
||||
]
|
||||
)
|
||||
->add('webp_converter',
|
||||
ChoiceType::class,
|
||||
[
|
||||
'choices' => $this->convertersList,
|
||||
'label' => $this->trans('Webp converter options', 'Modules.isthemecore.Admin'),
|
||||
'disabled' => $webpDisabled,
|
||||
'expanded' => true,
|
||||
'multiple' => false,
|
||||
'choice_attr' => function ($choice) {
|
||||
return ['disabled' => $this->convertersListFull[$choice]['disabled']];
|
||||
},
|
||||
'choice_label' => function ($choice) {
|
||||
return $this->convertersListFull[$choice]['label'] . ($this->convertersListFull[$choice]['disabled'] ? '<span class="ml-1 badge badge-danger">' . $this->trans('not available', 'Modules.isthemecore.Admin') . '</span>' : '');
|
||||
},
|
||||
]
|
||||
)
|
||||
->add('erase_all_webp', IconButtonType::class, [
|
||||
'label' => $this->trans('Erase all webp images', 'Modules.isthemecore.Admin'),
|
||||
'type' => 'link',
|
||||
'icon' => 'delete',
|
||||
'attr' => [
|
||||
'class' => 'btn-danger',
|
||||
'href' => $this->router->generate(
|
||||
'is_themecore_module_settings_webp_erase_all',
|
||||
[
|
||||
'type' => 'all',
|
||||
]
|
||||
),
|
||||
],
|
||||
])
|
||||
->add('erase_product_webp', IconButtonType::class, [
|
||||
'label' => $this->trans('Erase all product webp images', 'Modules.isthemecore.Admin'),
|
||||
'type' => 'link',
|
||||
'icon' => 'delete',
|
||||
'attr' => [
|
||||
'class' => 'btn-danger',
|
||||
'href' => $this->router->generate(
|
||||
'is_themecore_module_settings_webp_erase_all',
|
||||
[
|
||||
'type' => 'product',
|
||||
]
|
||||
),
|
||||
],
|
||||
])
|
||||
->add('erase_modules_webp', IconButtonType::class, [
|
||||
'label' => $this->trans('Erase all modules webp images', 'Modules.isthemecore.Admin'),
|
||||
'type' => 'link',
|
||||
'icon' => 'delete',
|
||||
'attr' => [
|
||||
'class' => 'btn-danger',
|
||||
'href' => $this->router->generate(
|
||||
'is_themecore_module_settings_webp_erase_all',
|
||||
[
|
||||
'type' => 'module',
|
||||
]
|
||||
),
|
||||
],
|
||||
])
|
||||
->add('erase_cms_webp', IconButtonType::class, [
|
||||
'label' => $this->trans('Erase all CMS webp images', 'Modules.isthemecore.Admin'),
|
||||
'type' => 'link',
|
||||
'icon' => 'delete',
|
||||
'attr' => [
|
||||
'class' => 'btn-danger',
|
||||
'href' => $this->router->generate(
|
||||
'is_themecore_module_settings_webp_erase_all',
|
||||
[
|
||||
'type' => 'cms',
|
||||
]
|
||||
),
|
||||
],
|
||||
])
|
||||
->add('erase_themes_webp', IconButtonType::class, [
|
||||
'label' => $this->trans('Erase all themes webp images', 'Modules.isthemecore.Admin'),
|
||||
'type' => 'link',
|
||||
'icon' => 'delete',
|
||||
'attr' => [
|
||||
'class' => 'btn-danger',
|
||||
'href' => $this->router->generate(
|
||||
'is_themecore_module_settings_webp_erase_all',
|
||||
[
|
||||
'type' => 'themes',
|
||||
]
|
||||
),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see MultistoreConfigurationTypeExtension
|
||||
*/
|
||||
public function getParent(): string
|
||||
{
|
||||
return MultistoreConfigurationType::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NotBlank
|
||||
*/
|
||||
private function getNotBlankConstraint()
|
||||
{
|
||||
return new NotBlank([
|
||||
'message' => $this->trans('This field cannot be empty.', 'Modules.isthemecore.Admin'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Range
|
||||
*/
|
||||
private function getRangeConstraint(int $min = 1, int $max = 100)
|
||||
{
|
||||
return new Range([
|
||||
'min' => $min,
|
||||
'max' => $max,
|
||||
'invalidMessage' => $this->trans(
|
||||
'This field value have to be between %min% and %max%.',
|
||||
'Modules.isthemecore.Admin',
|
||||
[
|
||||
'%min%' => $min,
|
||||
'%max%' => $max,
|
||||
]
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
11
is_themecore/src/Form/Settings/index.php
Normal file
11
is_themecore/src/Form/Settings/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
11
is_themecore/src/Form/index.php
Normal file
11
is_themecore/src/Form/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
25
is_themecore/src/Hook/AbstractHook.php
Normal file
25
is_themecore/src/Hook/AbstractHook.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Hook;
|
||||
|
||||
abstract class AbstractHook
|
||||
{
|
||||
public const HOOK_LIST = [];
|
||||
|
||||
protected $module;
|
||||
protected $context;
|
||||
|
||||
public function __construct(\Is_themecore $module)
|
||||
{
|
||||
$this->module = $module;
|
||||
$this->context = \Context::getContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAvailableHooks()
|
||||
{
|
||||
return static::HOOK_LIST;
|
||||
}
|
||||
}
|
||||
45
is_themecore/src/Hook/Assets.php
Normal file
45
is_themecore/src/Hook/Assets.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Hook;
|
||||
|
||||
use Oksydan\Module\IsThemeCore\Core\ThemeAssets\ThemeAssetConfigProvider;
|
||||
use Oksydan\Module\IsThemeCore\Core\ThemeAssets\ThemeAssetsRegister;
|
||||
|
||||
class Assets extends AbstractHook
|
||||
{
|
||||
public const HOOK_LIST = [
|
||||
'actionFrontControllerSetMedia',
|
||||
'actionProductSearchAfter',
|
||||
];
|
||||
|
||||
/**
|
||||
* Removing ps_faceted search module assets
|
||||
*/
|
||||
public function hookActionProductSearchAfter(): void
|
||||
{
|
||||
$this->context->controller->unregisterJavascript('facetedsearch_front');
|
||||
$this->context->controller->unregisterStylesheet('facetedsearch_front');
|
||||
|
||||
$needsJQueryUi = \Module::isEnabled('pm_advancedsearch4') && $this->context->controller instanceof \ProductListingFrontController;
|
||||
|
||||
if (!$needsJQueryUi) {
|
||||
$this->context->controller->unregisterJavascript('jquery-ui');
|
||||
$this->context->controller->unregisterStylesheet('jquery-ui');
|
||||
$this->context->controller->unregisterStylesheet('jquery-ui-theme');
|
||||
}
|
||||
}
|
||||
|
||||
public function hookActionFrontControllerSetMedia()
|
||||
{
|
||||
$assetsRegister = new ThemeAssetsRegister(
|
||||
new ThemeAssetConfigProvider(_PS_THEME_DIR_),
|
||||
$this->context
|
||||
);
|
||||
|
||||
$assetsRegister->registerThemeAssets();
|
||||
|
||||
\Media::addJsDef([
|
||||
'listDisplayAjaxUrl' => $this->context->link->getModuleLink($this->module->name, 'ajaxTheme'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
136
is_themecore/src/Hook/Header.php
Normal file
136
is_themecore/src/Hook/Header.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Hook;
|
||||
|
||||
use Oksydan\Module\IsThemeCore\Core\Breadcrumbs\ThemeBreadcrumbs;
|
||||
use Oksydan\Module\IsThemeCore\Core\ListingDisplay\ThemeListDisplay;
|
||||
use Oksydan\Module\IsThemeCore\Core\Partytown\PartytownScript;
|
||||
use Oksydan\Module\IsThemeCore\Core\Partytown\PartytownScriptUriResolver;
|
||||
use Oksydan\Module\IsThemeCore\Core\StructuredData\BreadcrumbStructuredData;
|
||||
use Oksydan\Module\IsThemeCore\Core\StructuredData\ProductStructuredData;
|
||||
use Oksydan\Module\IsThemeCore\Core\StructuredData\ShopStructuredData;
|
||||
use Oksydan\Module\IsThemeCore\Core\StructuredData\StructuredDataInterface;
|
||||
use Oksydan\Module\IsThemeCore\Core\StructuredData\WebsiteStructuredData;
|
||||
use Oksydan\Module\IsThemeCore\Form\Settings\GeneralConfiguration;
|
||||
use Oksydan\Module\IsThemeCore\Form\Settings\WebpConfiguration;
|
||||
|
||||
class Header extends AbstractHook
|
||||
{
|
||||
public const HOOK_LIST = [
|
||||
'actionFrontControllerInitBefore',
|
||||
'displayHeader',
|
||||
];
|
||||
|
||||
public function hookActionFrontControllerInitBefore(): void
|
||||
{
|
||||
$themeListDisplay = new ThemeListDisplay();
|
||||
$this->context->smarty->assign([
|
||||
'listingDisplayType' => $themeListDisplay->getDisplay(),
|
||||
'preloadCss' => \Configuration::get(GeneralConfiguration::THEMECORE_PRELOAD_CSS),
|
||||
'webpEnabled' => \Configuration::get(WebpConfiguration::THEMECORE_WEBP_ENABLED),
|
||||
'loadPartytown' => (bool) \Configuration::get(GeneralConfiguration::THEMECORE_LOAD_PARTY_TOWN),
|
||||
'debugPartytown' => (bool) \Configuration::get(GeneralConfiguration::THEMECORE_DEBUG_PARTY_TOWN),
|
||||
]);
|
||||
}
|
||||
|
||||
public function hookDisplayHeader(): string
|
||||
{
|
||||
$themeListDisplay = new ThemeListDisplay();
|
||||
$breadcrumbs = (new ThemeBreadcrumbs())->getBreadcrumb();
|
||||
|
||||
if ($breadcrumbs['count']) {
|
||||
$this->context->smarty->assign([
|
||||
'breadcrumb' => $breadcrumbs,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->context->smarty->assign([
|
||||
'jsonData' => $this->getStructuredData(),
|
||||
'partytownScript' => $this->getPartytownScript(),
|
||||
'partytownScriptUri' => $this->getPartytownScriptUri(),
|
||||
]);
|
||||
|
||||
return $this->module->fetch('module:is_themecore/views/templates/hook/head.tpl');
|
||||
}
|
||||
|
||||
private function getPartytownScriptUri(): string
|
||||
{
|
||||
try {
|
||||
$uriResolver = $this->module->get(PartytownScriptUriResolver::class);
|
||||
} catch (\Exception $e) {
|
||||
$uriResolver = null;
|
||||
}
|
||||
|
||||
if ($uriResolver) {
|
||||
return $uriResolver->getScriptUri();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getPartytownScript(): string
|
||||
{
|
||||
try {
|
||||
$partytownScript = $this->module->get(PartytownScript::class);
|
||||
} catch (\Exception $e) {
|
||||
$partytownScript = null;
|
||||
}
|
||||
|
||||
if ($partytownScript instanceof PartytownScript) {
|
||||
return $partytownScript->getScriptContent();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getStructuredData(): array
|
||||
{
|
||||
$dataArray = [];
|
||||
|
||||
if ($this->context->controller instanceof \ProductControllerCore && $this->context->controller->getProduct()->id !== null) {
|
||||
try {
|
||||
$productData = $this->module->get(ProductStructuredData::class);
|
||||
} catch (\Exception $e) {
|
||||
$productData = null;
|
||||
}
|
||||
|
||||
if ($productData instanceof StructuredDataInterface) {
|
||||
$dataArray[] = $productData->getFormattedData();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$breadcrumbData = $this->module->get(BreadcrumbStructuredData::class);
|
||||
} catch (\Exception $e) {
|
||||
$breadcrumbData = null;
|
||||
}
|
||||
|
||||
if ($breadcrumbData instanceof StructuredDataInterface) {
|
||||
$dataArray[] = $breadcrumbData->getFormattedData();
|
||||
}
|
||||
|
||||
try {
|
||||
$shopData = $this->module->get(ShopStructuredData::class);
|
||||
} catch (\Exception $e) {
|
||||
$shopData = null;
|
||||
}
|
||||
|
||||
if ($shopData instanceof StructuredDataInterface) {
|
||||
$dataArray[] = $shopData->getFormattedData();
|
||||
}
|
||||
|
||||
if ($this->context->controller->getPageName() === 'index') {
|
||||
try {
|
||||
$website = $this->module->get(WebsiteStructuredData::class);
|
||||
} catch (\Exception $e) {
|
||||
$website = null;
|
||||
}
|
||||
|
||||
if ($website instanceof StructuredDataInterface) {
|
||||
$dataArray[] = $website->getFormattedData();
|
||||
}
|
||||
}
|
||||
|
||||
return $dataArray;
|
||||
}
|
||||
}
|
||||
36
is_themecore/src/Hook/Htaccess.php
Normal file
36
is_themecore/src/Hook/Htaccess.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Hook;
|
||||
|
||||
class Htaccess extends AbstractHook
|
||||
{
|
||||
public const HOOK_LIST = [
|
||||
'actionHtaccessCreate',
|
||||
'objectShopUrlAddAfter',
|
||||
'objectShopUrlUpdateAfter',
|
||||
'objectShopUrlDeleteAfter',
|
||||
];
|
||||
|
||||
public function hookActionHtaccessCreate()
|
||||
{
|
||||
$generator = $this->module->get('oksydan.module.is_themecore.core.htaccess.htaccess_generator');
|
||||
|
||||
$generator->generate();
|
||||
$generator->writeFile();
|
||||
}
|
||||
|
||||
public function hookObjectShopUrlAddAfter()
|
||||
{
|
||||
$this->hookActionHtaccessCreate();
|
||||
}
|
||||
|
||||
public function hookObjectShopUrlUpdateAfter()
|
||||
{
|
||||
$this->hookActionHtaccessCreate();
|
||||
}
|
||||
|
||||
public function hookObjectShopUrlDeleteAfter()
|
||||
{
|
||||
$this->hookActionHtaccessCreate();
|
||||
}
|
||||
}
|
||||
111
is_themecore/src/Hook/HtmlOutput.php
Normal file
111
is_themecore/src/Hook/HtmlOutput.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Hook;
|
||||
|
||||
use Oksydan\Module\IsThemeCore\Form\Settings\GeneralConfiguration;
|
||||
use Oksydan\Module\IsThemeCore\Form\Settings\WebpConfiguration;
|
||||
|
||||
class HtmlOutput extends AbstractHook
|
||||
{
|
||||
public const HOOK_LIST = [
|
||||
'actionOutputHTMLBefore',
|
||||
];
|
||||
|
||||
public const REL_LIST = [
|
||||
'preload',
|
||||
'preconnect',
|
||||
];
|
||||
|
||||
public const PRELOAD_TYPES_TO_EARLY_HINT = [
|
||||
'image',
|
||||
'stylesheet',
|
||||
// 'font', //disabled for now causing higher LCP and weird FOUC
|
||||
];
|
||||
|
||||
private $headers = [];
|
||||
|
||||
public function hookActionOutputHTMLBefore(array $params): void
|
||||
{
|
||||
$earlyHintsEnabled = \Configuration::get(GeneralConfiguration::THEMECORE_EARLY_HINTS, false);
|
||||
$webpEnabled = \Configuration::get(WebpConfiguration::THEMECORE_WEBP_ENABLED, false);
|
||||
|
||||
if (!$earlyHintsEnabled && !$webpEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$preConfig = libxml_use_internal_errors(true);
|
||||
$html = $params['html'];
|
||||
$doc = new \DOMDocument();
|
||||
$doc->loadHTML(
|
||||
'<meta http-equiv="Content-Type" content="charset=utf-8">' . $html,
|
||||
LIBXML_NOERROR | LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
|
||||
);
|
||||
$links = $doc->getElementsByTagName('link');
|
||||
|
||||
foreach ($links as $link) {
|
||||
$rel = $link->hasAttribute('rel') ? $link->attributes->getNamedItem('rel')->nodeValue : false;
|
||||
$as = $link->hasAttribute('as') ? $link->attributes->getNamedItem('as')->nodeValue : false;
|
||||
|
||||
if ($webpEnabled && $rel === 'preload' && $as === 'image') {
|
||||
$newLink = $doc->createElement('link');
|
||||
$src = urldecode($link->attributes->getNamedItem('href')->nodeValue);
|
||||
|
||||
$newLink->setAttribute('href', str_replace(['.png', '.jpg', '.jpeg'], '.webp', $src));
|
||||
|
||||
foreach ($link->attributes as $attribute) {
|
||||
if ($attribute->nodeName !== 'href') {
|
||||
$newLink->setAttribute($attribute->nodeName, $attribute->nodeValue);
|
||||
}
|
||||
}
|
||||
|
||||
$link->parentNode->replaceChild($newLink, $link);
|
||||
}
|
||||
|
||||
if ($earlyHintsEnabled && in_array($rel, self::REL_LIST)) {
|
||||
if (isset($newLink)) {
|
||||
$link = $newLink;
|
||||
unset($newLink);
|
||||
}
|
||||
|
||||
switch ($rel) {
|
||||
case 'preload':
|
||||
$this->handlePreloadFromNodeElement($link);
|
||||
break;
|
||||
case 'preconnect':
|
||||
$this->handlePreconnectFromNodeElement($link);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($webpEnabled) {
|
||||
$content = $doc->saveHTML();
|
||||
$content = str_replace('<meta http-equiv="Content-Type" content="charset=utf-8">', '', $content);
|
||||
$params['html'] = $content;
|
||||
}
|
||||
|
||||
if (!empty($this->headers)) {
|
||||
header('Link: ' . implode(', ', $this->headers));
|
||||
}
|
||||
|
||||
libxml_use_internal_errors($preConfig);
|
||||
}
|
||||
|
||||
private function handlePreloadFromNodeElement($nodeElement)
|
||||
{
|
||||
$preloadAs = $nodeElement->attributes->getNamedItem('as')->nodeValue;
|
||||
|
||||
if (in_array($preloadAs, self::PRELOAD_TYPES_TO_EARLY_HINT)) {
|
||||
$url = $nodeElement->attributes->getNamedItem('href')->nodeValue;
|
||||
|
||||
$this->headers[] = "<$url>; rel=preload; as=$preloadAs";
|
||||
}
|
||||
}
|
||||
|
||||
private function handlePreconnectFromNodeElement($nodeElement)
|
||||
{
|
||||
$url = $nodeElement->attributes->getNamedItem('href')->nodeValue;
|
||||
|
||||
$this->headers[] = "<$url>; rel=preconnect";
|
||||
}
|
||||
}
|
||||
21
is_themecore/src/Hook/Smarty.php
Normal file
21
is_themecore/src/Hook/Smarty.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore\Hook;
|
||||
|
||||
class Smarty extends AbstractHook
|
||||
{
|
||||
public const HOOK_LIST = [
|
||||
'actionDispatcherBefore',
|
||||
];
|
||||
|
||||
public function hookActionDispatcherBefore(): void
|
||||
{
|
||||
$this->context->smarty->registerPlugin('function', 'generateImagesSources', ['Oksydan\Module\IsThemeCore\Core\Smarty\SmartyHelperFunctions', 'generateImagesSources']);
|
||||
$this->context->smarty->registerPlugin('function', 'generateImageSvgPlaceholder', ['Oksydan\Module\IsThemeCore\Core\Smarty\SmartyHelperFunctions', 'generateImageSvgPlaceholder']);
|
||||
$this->context->smarty->registerPlugin('function', 'appendParamToUrl', ['Oksydan\Module\IsThemeCore\Core\Smarty\SmartyHelperFunctions', 'appendParamToUrl']);
|
||||
$this->context->smarty->registerPlugin('block', 'images_block', ['Oksydan\Module\IsThemeCore\Core\Smarty\SmartyHelperFunctions', 'imagesBlock']);
|
||||
$this->context->smarty->registerPlugin('block', 'cms_images_block', ['Oksydan\Module\IsThemeCore\Core\Smarty\SmartyHelperFunctions', 'cmsImagesBlock']);
|
||||
$this->context->smarty->registerPlugin('block', 'display_mobile', ['Oksydan\Module\IsThemeCore\Core\Smarty\SmartyHelperFunctions', 'displayMobileBlock']);
|
||||
$this->context->smarty->registerPlugin('block', 'display_desktop', ['Oksydan\Module\IsThemeCore\Core\Smarty\SmartyHelperFunctions', 'displayDesktopBlock']);
|
||||
}
|
||||
}
|
||||
63
is_themecore/src/HookDispatcher.php
Normal file
63
is_themecore/src/HookDispatcher.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Oksydan\Module\IsThemeCore;
|
||||
|
||||
use Oksydan\Module\IsThemeCore\Hook\AbstractHook;
|
||||
use Oksydan\Module\IsThemeCore\Hook\Assets;
|
||||
use Oksydan\Module\IsThemeCore\Hook\Header;
|
||||
use Oksydan\Module\IsThemeCore\Hook\Htaccess;
|
||||
use Oksydan\Module\IsThemeCore\Hook\HtmlOutput;
|
||||
use Oksydan\Module\IsThemeCore\Hook\Smarty;
|
||||
|
||||
class HookDispatcher
|
||||
{
|
||||
public const HOOK_CLASSES = [
|
||||
Header::class,
|
||||
Assets::class,
|
||||
Smarty::class,
|
||||
HtmlOutput::class,
|
||||
Htaccess::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Hook instances.
|
||||
*
|
||||
* @var AbstractHook[]
|
||||
*/
|
||||
protected $hooks = [];
|
||||
|
||||
public function __construct(\Is_themecore $module)
|
||||
{
|
||||
foreach (static::HOOK_CLASSES as $hookClass) {
|
||||
/** @var AbstractHook $hook */
|
||||
$hook = new $hookClass($module);
|
||||
$this->hooks[] = $hook;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available hooks
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAvailableHooks()
|
||||
{
|
||||
$availableHooks = [];
|
||||
foreach ($this->hooks as $hook) {
|
||||
$availableHooks = array_merge($availableHooks, $hook->getAvailableHooks());
|
||||
}
|
||||
|
||||
return $availableHooks;
|
||||
}
|
||||
|
||||
public function dispatch($hookName, array $params = [])
|
||||
{
|
||||
foreach ($this->hooks as $hook) {
|
||||
if (method_exists($hook, $hookName)) {
|
||||
return $hook->{$hookName}($params);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
11
is_themecore/src/index.php
Normal file
11
is_themecore/src/index.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
|
||||
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
|
||||
header('Location: ../');
|
||||
exit;
|
||||
Reference in New Issue
Block a user