WIP on fix-config

This commit is contained in:
2026-05-08 09:17:13 +02:00
parent abd8d60502
commit b294bcce63
146 changed files with 13016 additions and 0 deletions

BIN
is_themecore/is_themecore/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,9 @@
<?php
$config = new PrestaShop\CodingStandards\CsFixer\Config();
/** @var \Symfony\Component\Finder\Finder $finder */
$finder = $config->setUsingCache(true)->getFinder();
$finder->in(__DIR__)->exclude('vendor');
return $config;

View File

@@ -0,0 +1,21 @@
build-module-zip: build-composer build-assets build-zip
build-zip:
rm -rf is_themecore.zip
cp -Ra $(PWD) /tmp/is_themecore
rm -rf /tmp/is_themecore/config_*.xml
rm -rf /tmp/is_themecore/_theme_dev/node_modules
rm -rf /tmp/is_themecore/_module_dev/node_modules
rm -rf /tmp/is_themecore/.github
rm -rf /tmp/is_themecore/.gitignore
rm -rf /tmp/is_themecore/.php-cs-fixer.cache
rm -rf /tmp/is_themecore/.git
mv -v /tmp/is_themecore $(PWD)/is_themecore
zip -r is_themecore.zip is_themecore
rm -rf $(PWD)/is_themecore
build-composer:
composer install --no-dev -o
build-assets:
cd _module_dev && . ${HOME}/.nvm/nvm.sh && nvm install && npm install && npm run build

View File

@@ -0,0 +1,96 @@
# Theme core module
Prestashop module created for [starter theme](https://github.com/Oksydan/modern-prestashop-starter-theme)
#### How to use assets.yml file
`assets.yml` file have to be placed inside `themes/THEME_NAME/config/` to work.
Example of `assets.yml` file:
```yml
css:
product:
fileName: product.css
priority: 200
include:
- product
checkout:
fileName: checkout.css
priority: 200
include:
- cart
- order
- orderconfirmation
blog:
fileName: blog.css
priority: 200
include:
- module-blog-*
example_remote_bootstrap:
fileName: //cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css
server: remote # required to set server: remote for remote file
priority: 200
js:
product:
fileName: product.js
priority: 200
include:
- product
checkout:
fileName: checkout.js
priority: 200
include:
- cart
- order
- orderconfirmation
blog:
fileName: blog.js
priority: 200
include:
- module-blog-*
example_remote_bootstrap:
fileName: //cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js
server: remote # required to set server: remote for remote file
priority: 200
```
You are able to use windcard `*` with include page name.
#### Structured data modification
You are able to modify structured data with hooks. List of hooks:
- `actionStructuredDataBreadcrumb`
- `actionStructuredDataProduct`
- `actionStructuredDataShop`
- `actionStructuredDataWebsite`
Every hook $param is an array with two keys:
- `$data` - reference of structured data array
- `$rawData` - raw structured data array (provided by data provider)
#### Partytown
You are able to use [partytown](https://partytown.builder.io/) with this module. You have to enable it first in module configuration.
Example of usage for GTAG:
```html
<script>
window.partytown.forward.push('datalayer.push');
window.partytown.forward.push('gtag');
</script>
<script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=YOUR_GTAG_CODE"></script>
<script type="text/partytown">
dataLayer = window.dataLayer || [];
window.gtag = function () {
dataLayer.push(arguments);
};
window.gtag('js', new Date());
window.gtag('config', 'YOUR_GTAG_CODE');
</script>
```
##### Beware that partytown is still in beta, and it may not work as expected. Make sure to test it before using in production.

View File

@@ -0,0 +1 @@
v18.12.1

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
{
"name": "is_themecore_module",
"label": "Theme core module",
"version": "4.0.0",
"description": "Theme core module",
"author": "Igor Stępień",
"scripts": {
"build": "webpack --progress --mode=production --env stats=errors-only",
"js-lint": "eslint -c .eslintrc.js --ext .js,.vue ./src",
"js-lint-fix": "eslint -c .eslintrc.js --ext .js,.vue ./src --fix"
},
"devDependencies": {
"copy-webpack-plugin": "^11.0.0",
"webpack": "^5.81.0",
"webpack-cli": "^5.0.2"
},
"dependencies": {
"@builder.io/partytown": "^0.8.0"
}
}

View File

@@ -0,0 +1,10 @@
import { partytownSnippet } from '@builder.io/partytown/integration';
const snippetContent = partytownSnippet();
document.addEventListener('DOMContentLoaded', () => {
const script = document.createElement("script");
script.textContent=snippetContent;
document.body.append(script)
});

View File

@@ -0,0 +1,26 @@
// webpack.config.js
const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
const partytown = require('@builder.io/partytown/utils');
module.exports = {
entry: {
partytown: './src/index.js',
},
output: {
path: path.resolve(__dirname, '../public'),
},
resolve: {
extensions: ['.ts', '.js', '.mjs'],
},
plugins: [
new CopyPlugin({
patterns: [
{
from: partytown.libDirPath(),
to: path.join(__dirname, '../public', '~partytown'),
},
],
}),
],
};

View File

@@ -0,0 +1,4 @@
/node_modules/
/webpack/
postcss.config.js
webpack.config.js

View File

@@ -0,0 +1,27 @@
module.exports = {
root: true,
env: {
browser: true,
node: false,
es6: true,
jquery: true,
},
globals: {
google: true,
document: true,
navigator: false,
window: true,
prestashop: true,
},
extends: ['airbnb-base'],
rules: {
'max-len': ['error', {code: 140}],
'no-underscore-dangle': 'off',
'no-restricted-syntax': 'off',
'no-param-reassign': 'off',
'import/no-unresolved': 'off',
},
parserOptions: {
ecmaVersion: 2022
},
}

View File

@@ -0,0 +1 @@
v18.12.1

View File

@@ -0,0 +1,6 @@
{
"extends": "stylelint-config-recommended-scss",
"rules": {
"scss/at-extend-no-missing-placeholder": null
}
}

View 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;

View File

@@ -0,0 +1,21 @@
{
"name": "is_themecore",
"label": "Theme core module",
"version": "4.0.0",
"description": "Theme core module",
"author": "Igor Stępień",
"scripts": {
"scss-lint": "stylelint \"**/*.scss\" --fix",
"scss-lint-fix": "stylelint \"**/*.scss\" --fix",
"js-lint": "eslint -c .eslintrc.js --ext .js,.vue ./src",
"js-lint-fix": "eslint -c .eslintrc.js --ext .js,.vue ./src --fix"
},
"devDependencies": {
"stylelint": "^14.11.0",
"stylelint-config-recommended-scss": "^7.0.0",
"eslint": "^8.23.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.26.0",
"postcss": "^8.3.6"
}
}

View 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;

View 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;

View File

@@ -0,0 +1,58 @@
function initListDisplay() {
const handleClickEvent = (e) => {
const target = e.target.closest('[data-toggle-listing]');
if (target && target.dataset.toggleListing !== undefined) {
e.preventDefault();
if (target.classList.contains('active')) {
return;
}
const display = target.dataset.displayType;
const allButtons = document.querySelectorAll('[data-toggle-listing]');
allButtons.forEach((button) => {
button.classList.remove('active');
});
target.classList.add('active');
let requestData = {
displayType: display,
ajax: 1,
};
requestData = Object.keys(requestData).map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(requestData[key])}`).join('&');
fetch(window.listDisplayAjaxUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: requestData,
})
.then((resp) => resp.text())
.then((resp) => {
try {
const response = JSON.parse(resp);
if (response.success) {
prestashop.emit('updateFacets', window.location.href);
}
} catch (error) {
console.error(error); // eslint-disable-line no-console
}
})
.catch((error) => {
console.error(error); // eslint-disable-line no-console
});
}
};
document.addEventListener('click', handleClickEvent);
}
document.addEventListener('DOMContentLoaded', () => {
initListDisplay();
});

View 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;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
{
"name": "oksydan/is_themecore",
"description": "Theme core module",
"type": "prestashop-module",
"license": "AFL-3.0",
"authors": [
{
"name": "Igor Stępień",
"email": "igor@istpien.dev",
"homepage": "https://istpien.dev",
"role": "Developer"
}
],
"require": {
"php": ">=7.1.0",
"rosell-dk/webp-convert": "^2.9"
},
"require-dev": {
"prestashop/php-dev-tools": "^4.2"
},
"autoload": {
"psr-4": {
"Oksydan\\Module\\IsThemeCore\\": "src/"
}
},
"config": {
"prepend-autoloader": false,
"allow-plugins": {
"phpro/grumphp-shim": false,
"phpstan/extension-installer": false
}
}
}

2788
is_themecore/is_themecore/composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
imports:
- { resource: ../common.yml }
services:
_defaults:
public: true
oksydan.module.is_themecore.form.settings.general_type:
class: 'Oksydan\Module\IsThemeCore\Form\Settings\GeneralType'
parent: "form.type.translatable.aware"
public: true
arguments:
$displayListChoices: '@=service("oksydan.module.is_themecore.form.choice_provider.list_display_choice_provider").getChoices()'
tags:
- { name: form.type }
oksydan.module.is_themecore.form.settings.general_form_data_provider:
class: 'Oksydan\Module\IsThemeCore\Form\Settings\GeneralFormDataProvider'
arguments:
- "@oksydan.module.is_themecore.form.settings.general_configuration"
oksydan.module.is_themecore.form.settings.general_form_data_handler:
class: 'PrestaShop\PrestaShop\Core\Form\Handler'
arguments:
- "@form.factory"
- "@prestashop.core.hook.dispatcher"
- "@oksydan.module.is_themecore.form.settings.general_form_data_provider"
- 'Oksydan\Module\IsThemeCore\Form\Settings\GeneralType'
- "General"
oksydan.module.is_themecore.form.settings.webp_type:
class: 'Oksydan\Module\IsThemeCore\Form\Settings\WebpType'
parent: "form.type.translatable.aware"
public: true
arguments:
$convertersList: '@=service("oksydan.module.is_themecore.form.choice_provider.webp_library_choice_provider").getChoices()'
$convertersListFull: '@=service("oksydan.module.is_themecore.form.choice_provider.webp_library_choice_provider").getChoicesFull()'
$router: "@router"
tags:
- { name: form.type }
oksydan.module.is_themecore.form.settings.webp_form_data_provider:
class: 'Oksydan\Module\IsThemeCore\Form\Settings\WebpFormDataProvider'
arguments:
- "@oksydan.module.is_themecore.form.settings.webp_configuration"
oksydan.module.is_themecore.form.settings.webp_form_data_handler:
class: 'PrestaShop\PrestaShop\Core\Form\Handler'
arguments:
- "@form.factory"
- "@prestashop.core.hook.dispatcher"
- "@oksydan.module.is_themecore.form.settings.webp_form_data_provider"
- 'Oksydan\Module\IsThemeCore\Form\Settings\WebpType'
- "Webp"
oksydan.module.is_themecore.form.choice_provider.webp_library_choice_provider:
class: Oksydan\Module\IsThemeCore\Form\ChoiceProvider\WebpLibraryChoiceProvider
arguments:
- "@oksydan.module.is_themecore.core.webp.webp_convert_libraries"
oksydan.module.is_themecore.form.choice_provider.list_display_choice_provider:
class: Oksydan\Module\IsThemeCore\Form\ChoiceProvider\ListDisplayChoiceProvider
arguments:
- "@oksydan.module.is_themecore.core.listing_display.theme_list_display"
oksydan.module.is_themecore.form.settings.webp_configuration:
class: Oksydan\Module\IsThemeCore\Form\Settings\WebpConfiguration
arguments:
- "@prestashop.adapter.legacy.configuration"
oksydan.module.is_themecore.form.settings.general_configuration:
class: Oksydan\Module\IsThemeCore\Form\Settings\GeneralConfiguration
arguments:
- "@prestashop.adapter.legacy.configuration"
- "@prestashop.adapter.shop.context"
- "@prestashop.adapter.multistore_feature"

View File

@@ -0,0 +1,39 @@
services:
_defaults:
public: true
oksydan.module.is_themecore.module:
class: Is_themecore
factory: [ 'Module', 'getInstanceByName' ]
public: false
arguments:
- 'is_themecore'
Is_themecore: '@oksydan.module.is_themecore.module'
oksydan.module.is_themecore.core.webp.webp_convert_libraries:
class: Oksydan\Module\IsThemeCore\Core\Webp\WebpConvertLibraries
oksydan.module.is_themecore.core.webp.webp_files_eraser:
class: Oksydan\Module\IsThemeCore\Core\Webp\WebpFilesEraser
oksydan.module.is_themecore.core.listing_display.theme_list_display:
class: Oksydan\Module\IsThemeCore\Core\ListingDisplay\ThemeListDisplay
oksydan.module.is_themecore.core.htaccess.htaccess_generator:
class: Oksydan\Module\IsThemeCore\Core\Htaccess\HtaccessGenerator
arguments:
- "@Is_themecore"
oksydan.module.is_themecore.core.webp.related_image_file_finder:
class: Oksydan\Module\IsThemeCore\Core\Webp\RelatedImageFileFinder
oksydan.module.is_themecore.core.webp.webp_generator:
class: Oksydan\Module\IsThemeCore\Core\Webp\WebpGenerator
arguments:
- "@oksydan.module.is_themecore.core.webp.related_image_file_finder"
Oksydan\Module\IsThemeCore\Core\Partytown\FilesInstallation:
class: Oksydan\Module\IsThemeCore\Core\Partytown\FilesInstallation
arguments:
- "@Is_themecore"

View File

@@ -0,0 +1,81 @@
imports:
- { resource: ../common.yml }
services:
_defaults:
public: true
Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter\StructuredDataBreadcrumbPresenter:
class: 'Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter\StructuredDataBreadcrumbPresenter'
Oksydan\Module\IsThemeCore\Core\StructuredData\Provider\StructuredDataBreadcrumbProvider:
class: 'Oksydan\Module\IsThemeCore\Core\StructuredData\Provider\StructuredDataBreadcrumbProvider'
arguments:
- "@=service('prestashop.adapter.legacy.context').getContext()"
Oksydan\Module\IsThemeCore\Core\StructuredData\BreadcrumbStructuredData:
class: 'Oksydan\Module\IsThemeCore\Core\StructuredData\BreadcrumbStructuredData'
public: true
arguments:
- '@Oksydan\Module\IsThemeCore\Core\StructuredData\Provider\StructuredDataBreadcrumbProvider'
- '@Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter\StructuredDataBreadcrumbPresenter'
Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter\StructuredDataWebsitePresenter:
class: 'Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter\StructuredDataWebsitePresenter'
arguments:
- "@=service('prestashop.adapter.legacy.context').getContext()"
Oksydan\Module\IsThemeCore\Core\StructuredData\Provider\StructuredDataWebsiteProvider:
class: 'Oksydan\Module\IsThemeCore\Core\StructuredData\Provider\StructuredDataWebsiteProvider'
arguments:
- "@=service('prestashop.adapter.legacy.context').getContext()"
Oksydan\Module\IsThemeCore\Core\StructuredData\WebsiteStructuredData:
class: 'Oksydan\Module\IsThemeCore\Core\StructuredData\WebsiteStructuredData'
public: true
arguments:
- '@Oksydan\Module\IsThemeCore\Core\StructuredData\Provider\StructuredDataWebsiteProvider'
- '@Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter\StructuredDataWebsitePresenter'
Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter\StructuredDataShopPresenter:
class: 'Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter\StructuredDataShopPresenter'
arguments:
- "@=service('prestashop.adapter.legacy.context').getContext()"
Oksydan\Module\IsThemeCore\Core\StructuredData\Provider\StructuredDataShopProvider:
class: 'Oksydan\Module\IsThemeCore\Core\StructuredData\Provider\StructuredDataShopProvider'
arguments:
- "@=service('prestashop.adapter.legacy.context').getContext()"
Oksydan\Module\IsThemeCore\Core\StructuredData\ShopStructuredData:
class: 'Oksydan\Module\IsThemeCore\Core\StructuredData\ShopStructuredData'
public: true
arguments:
- '@Oksydan\Module\IsThemeCore\Core\StructuredData\Provider\StructuredDataShopProvider'
- '@Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter\StructuredDataShopPresenter'
Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter\StructuredDataProductPresenter:
class: 'Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter\StructuredDataProductPresenter'
arguments:
- "@=service('prestashop.adapter.legacy.context').getContext()"
Oksydan\Module\IsThemeCore\Core\StructuredData\Provider\StructuredDataProductProvider:
class: 'Oksydan\Module\IsThemeCore\Core\StructuredData\Provider\StructuredDataProductProvider'
arguments:
- "@=service('prestashop.adapter.legacy.context').getContext()"
Oksydan\Module\IsThemeCore\Core\StructuredData\ProductStructuredData:
class: 'Oksydan\Module\IsThemeCore\Core\StructuredData\ProductStructuredData'
public: true
arguments:
- '@Oksydan\Module\IsThemeCore\Core\StructuredData\Provider\StructuredDataProductProvider'
- '@Oksydan\Module\IsThemeCore\Core\StructuredData\Presenter\StructuredDataProductPresenter'
Oksydan\Module\IsThemeCore\Core\Partytown\PartytownScript:
class: Oksydan\Module\IsThemeCore\Core\Partytown\PartytownScript
arguments:
- "@Is_themecore"
Oksydan\Module\IsThemeCore\Core\Partytown\PartytownScriptUriResolver:
class: Oksydan\Module\IsThemeCore\Core\Partytown\PartytownScriptUriResolver
arguments:
- "@=service('prestashop.adapter.legacy.context').getContext()"

View 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;

View File

@@ -0,0 +1,30 @@
is_themecore_module_settings:
path: /is_themecore/settings
methods: [GET]
defaults:
_controller: 'Oksydan\Module\IsThemeCore\Controller\Admin\SettingsController::indexAction'
_legacy_controller: themecoreSettings
is_themecore_module_settings_general_save:
path: /is_themecore/settings/general
methods: [POST, PATCH]
defaults:
_controller: 'Oksydan\Module\IsThemeCore\Controller\Admin\SettingsController::processGeneralFormAction'
_legacy_controller: themecoreSettings
_legacy_link: themecoreSettings:update
is_themecore_module_settings_webp_save:
path: /is_themecore/settings/webp
methods: [POST, PATCH]
defaults:
_controller: 'Oksydan\Module\IsThemeCore\Controller\Admin\SettingsController::processWebpFormAction'
_legacy_controller: themecoreSettings
_legacy_link: themecoreSettings:update
is_themecore_module_settings_webp_erase_all:
path: /is_themecore/settings/webp
methods: [GET]
defaults:
_controller: 'Oksydan\Module\IsThemeCore\Controller\Admin\SettingsController::processWebpEraseImages'
_legacy_controller: themecoreSettings
_legacy_link: themecoreSettings:update

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<module>
<name>is_themecore</name>
<displayName><![CDATA[Theme core module]]></displayName>
<version><![CDATA[4.1.3]]></version>
<description><![CDATA[Required for theme to work.]]></description>
<author><![CDATA[Igor Stępień]]></author>
<tab><![CDATA[others]]></tab>
<is_configurable>1</is_configurable>
<need_instance>1</need_instance>
</module>

View File

@@ -0,0 +1,35 @@
<?php
use Oksydan\Module\IsThemeCore\Core\ListingDisplay\ThemeListDisplay;
class Is_themecoreAjaxThemeModuleFrontController extends ModuleFrontController
{
public $displayType;
public function init()
{
parent::init();
$this->displayType = Tools::getValue('displayType');
}
public function initContent()
{
parent::initContent();
$themeDisplay = new ThemeListDisplay();
if ($this->displayType) {
$themeDisplay->setDisplay($this->displayType);
$this->ajaxRender(json_encode([
'hasError' => false,
'success' => true,
]));
} else {
$this->ajaxRender(json_encode([
'hasError' => true,
'success' => false,
]));
}
}
}

View 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;

View 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;

View 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;

View File

@@ -0,0 +1,212 @@
<?php
declare(strict_types=1);
if (file_exists(dirname(__FILE__) . '/vendor/autoload.php')) {
require_once dirname(__FILE__) . '/vendor/autoload.php';
}
use Oksydan\Module\IsThemeCore\Core\Partytown\FilesInstallation;
use Oksydan\Module\IsThemeCore\Form\Settings\GeneralConfiguration;
use Oksydan\Module\IsThemeCore\Form\Settings\WebpConfiguration;
use Oksydan\Module\IsThemeCore\HookDispatcher;
use PrestaShop\PrestaShop\Adapter\Configuration;
use PrestaShop\PrestaShop\Adapter\SymfonyContainer;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class Is_themecore extends Module
{
protected $hookDispatcher;
/**
* @var array<string, string> Configuration values
*/
public const CONFIGURATION_VALUES = [
GeneralConfiguration::THEMECORE_DISPLAY_LIST => 'grid',
GeneralConfiguration::THEMECORE_EARLY_HINTS => false,
GeneralConfiguration::THEMECORE_PRELOAD_CSS => false,
GeneralConfiguration::THEMECORE_LOAD_PARTY_TOWN => false,
GeneralConfiguration::THEMECORE_DEBUG_PARTY_TOWN => false,
WebpConfiguration::THEMECORE_WEBP_ENABLED => false,
WebpConfiguration::THEMECORE_WEBP_QUALITY => 90,
WebpConfiguration::THEMECORE_WEBP_CONVERTER => null,
WebpConfiguration::THEMECORE_WEBP_SHARPYUV => false,
];
/**
* @var string[] Hooks to register
*/
public const HOOKS = [
'actionOutputHTMLBefore',
'actionDispatcherBefore',
'actionFrontControllerSetMedia',
'displayListingStructuredData',
'displayHeader',
'actionProductSearchAfter',
'actionHtaccessCreate',
'objectShopUrlAddAfter',
'objectShopUrlUpdateAfter',
'objectShopUrlDeleteAfter',
'actionFrontControllerInitBefore',
];
/**
* @var Configuration<string, mixed> Configuration
*/
private $configuration;
public function __construct()
{
$this->name = 'is_themecore';
$this->tab = 'others';
$this->version = '4.1.3';
$this->author = 'Igor Stępień';
$this->ps_versions_compliancy = ['min' => '8.0.0', 'max' => _PS_VERSION_];
$this->bootstrap = true;
parent::__construct();
$this->displayName = $this->trans(
'Theme core module',
[],
'Modules.isthemecore.Admin'
);
$this->description = $this->trans(
'Required for theme to work.',
[],
'Modules.isthemecore.Admin'
);
$this->tabs = [
[
'name' => 'Theme core module settings',
'class_name' => 'themecoreSettings',
'route_name' => 'is_themecore_module_settings',
'parent_class_name' => 'CONFIGURE',
'visible' => false,
'wording' => 'Theme core module settings',
'wording_domain' => 'Modules.isthemecore.Admin',
],
];
$this->configuration = new Configuration();
}
/**
* {@inheritdoc}
*/
public function isUsingNewTranslationSystem(): bool
{
return true;
}
/**
* {@inheritdoc}
*/
public function install(): bool
{
return parent::install()
&& $this->installConfiguration()
&& $this->installPartytown()
&& $this->registerHook(self::HOOKS)
;
}
/**
* Install configuration values
*/
private function installConfiguration(): bool
{
try {
foreach (self::CONFIGURATION_VALUES as $key => $default_value) {
$this->configuration->set($key, $default_value);
}
} catch (Exception $e) {
return false;
}
return true;
}
/**
* Install Partytown
*/
private function installPartytown(): bool
{
$installer = new FilesInstallation($this); // SERVICES NOT AVAILABLE DURING INSTALLATION
try {
$installer->installFiles();
} catch (Exception $e) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
public function uninstall(): bool
{
return parent::uninstall()
&& $this->uninstallConfiguration()
;
}
/**
* Uninstall configuration values
*/
private function uninstallConfiguration(): bool
{
try {
foreach (array_keys(self::CONFIGURATION_VALUES) as $key) {
$this->configuration->remove($key);
}
} catch (Exception $e) {
return false;
}
return true;
}
/**
* Get module configuration page content
*/
public function getContent(): void
{
$container = SymfonyContainer::getInstance();
if ($container != null) {
/** @var UrlGeneratorInterface */
$router = $container->get('router');
header('Location: https://houtendiershop.com'. $router->generate('is_themecore_module_settings'));
exit;
/*Tools::redirectAdmin($router->generate('is_themecore_module_settings'));
die();*/
}
}
public function __call(string $methodName, array $arguments)
{
return $this
->getHookDispatcher()
->dispatch($methodName, $arguments[0] ?? []);
}
protected function getHookDispatcher(): HookDispatcher
{
if (!isset($this->hookDispatcher)) {
if (!class_exists(HookDispatcher::class)) {
require_once dirname(__FILE__) . '/vendor/autoload.php';
}
$this->hookDispatcher = new HookDispatcher($this);
}
return $this->hookDispatcher;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1 @@
(()=>{"use strict";const t=((t,e)=>{const{forward:n=[],...i}={},o=JSON.stringify(i,((t,e)=>("function"==typeof e&&(e=String(e)).startsWith(t+"(")&&(e="function "+e),e)));return["!(function(w,p,f,c){",Object.keys(i).length>0?`c=w[p]=Object.assign(w[p]||{},${o});`:"c=w[p]=w[p]||{};","c[f]=(c[f]||[])",n.length>0?`.concat(${JSON.stringify(n)})`:"","})(window,'partytown','forward');",'/* Partytown 0.8.0 - MIT builder.io */\n!function(t,e,n,i,r,o,a,d,s,c,l,p){function u(){p||(p=1,"/"==(a=(o.lib||"/~partytown/")+(o.debug?"debug/":""))[0]&&(s=e.querySelectorAll(\'script[type="text/partytown"]\'),i!=t?i.dispatchEvent(new CustomEvent("pt1",{detail:t})):(d=setTimeout(f,1e4),e.addEventListener("pt0",w),r?h(1):n.serviceWorker?n.serviceWorker.register(a+(o.swPath||"partytown-sw.js"),{scope:a}).then((function(t){t.active?h():t.installing&&t.installing.addEventListener("statechange",(function(t){"activated"==t.target.state&&h()}))}),console.error):f())))}function h(t){c=e.createElement(t?"script":"iframe"),t||(c.setAttribute("style","display:block;width:0;height:0;border:0;visibility:hidden"),c.setAttribute("aria-hidden",!0)),c.src=a+"partytown-"+(t?"atomics.js?v=0.8.0":"sandbox-sw.html?"+Date.now()),e.querySelector(o.sandboxParent||"body").appendChild(c)}function f(n,r){for(w(),i==t&&(o.forward||[]).map((function(e){delete t[e.split(".")[0]]})),n=0;n<s.length;n++)(r=e.createElement("script")).innerHTML=s[n].innerHTML,e.head.appendChild(r);c&&c.parentNode.removeChild(c)}function w(){clearTimeout(d)}o=t.partytown||{},i==t&&(o.forward||[]).map((function(e){l=t,e.split(".").map((function(e,n,i){l=l[i[n]]=n+1<i.length?"push"==i[n+1]?[]:l[i[n]]||{}:function(){(t._ptf=t._ptf||[]).push(i,arguments)}}))})),"complete"==e.readyState?u():(t.addEventListener("DOMContentLoaded",u),t.addEventListener("load",u))}(window,document,navigator,top,window.crossOriginIsolated);'].join("")})();document.addEventListener("DOMContentLoaded",(()=>{const e=document.createElement("script");e.textContent=t,document.body.append(e)}))})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
const resolves=new Map,swMessageError=(e,s)=>({$msgId$:e.$msgId$,$error$:s}),httpRequestFromWebWorker=e=>new Promise((async s=>{const t=await e.clone().json(),o=await(e=>new Promise((async s=>{const t=[...await self.clients.matchAll()].sort(((e,s)=>e.url>s.url?-1:e.url<s.url?1:0))[0];if(t){const o=[s,setTimeout((()=>{resolves.delete(e.$msgId$),s(swMessageError(e,"Timeout"))}),12e4)];resolves.set(e.$msgId$,o),t.postMessage(e)}else s(swMessageError(e,"NoParty"))})))(t);s(response(JSON.stringify(o),"application/json"))})),response=(e,s)=>new Response(e,{headers:{"content-type":s||"text/html","Cache-Control":"no-store"}});self.oninstall=()=>self.skipWaiting(),self.onactivate=()=>self.clients.claim(),self.onmessage=e=>{const s=e.data,t=resolves.get(s.$msgId$);t&&(resolves.delete(s.$msgId$),clearTimeout(t[1]),t[0](s))},self.onfetch=e=>{const s=e.request,t=new URL(s.url).pathname;t.endsWith("sw.html")?e.respondWith(response('<!DOCTYPE html><html><head><meta charset="utf-8"><script src="./partytown-sandbox-sw.js?v=0.8.0"><\/script></head></html>')):t.endsWith("proxytown")&&e.respondWith(httpRequestFromWebWorker(s))};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
!function(t,e,n,i,r,o,a,s,d,c,l,p){function u(){p||(p=1,"/"==(a=(o.lib||"/~partytown/")+(!1!==o.debug?"debug/":""))[0]?(d=e.querySelectorAll('script[type="text/partytown"]'),i!=t?i.dispatchEvent(new CustomEvent("pt1",{detail:t})):(s=setTimeout(f,1e4),e.addEventListener("pt0",h),r?w(1):n.serviceWorker?n.serviceWorker.register(a+(o.swPath||"partytown-sw.js"),{scope:a}).then((function(t){t.active?w():t.installing?t.installing.addEventListener("statechange",(function(t){"activated"==t.target.state&&w()})):console.warn(t)}),console.error):f())):console.warn('Partytown config.lib url must start with "/"'))}function w(t){c=e.createElement(t?"script":"iframe"),t||(c.setAttribute("style","display:block;width:0;height:0;border:0;visibility:hidden"),c.setAttribute("aria-hidden",!0)),c.src=a+"partytown-"+(t?"atomics.js?v=0.8.0":"sandbox-sw.html?"+Date.now()),e.querySelector(o.sandboxParent||"body").appendChild(c)}function f(n,r){for(console.warn("Partytown script fallback"),h(),i==t&&(o.forward||[]).map((function(e){delete t[e.split(".")[0]]})),n=0;n<d.length;n++)(r=e.createElement("script")).innerHTML=d[n].innerHTML,e.head.appendChild(r);c&&c.parentNode.removeChild(c)}function h(){clearTimeout(s)}o=t.partytown||{},i==t&&(o.forward||[]).map((function(e){l=t,e.split(".").map((function(e,n,i){l=l[i[n]]=n+1<i.length?"push"==i[n+1]?[]:l[i[n]]||{}:function(){(t._ptf=t._ptf||[]).push(i,arguments)}}))})),"complete"==e.readyState?u():(t.addEventListener("DOMContentLoaded",u),t.addEventListener("load",u))}(window,document,navigator,top,window.crossOriginIsolated);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
!function(t,e,n,i,r,o,a,d,s,c,l,p){function u(){p||(p=1,"/"==(a=(o.lib||"/~partytown/")+(o.debug?"debug/":""))[0]&&(s=e.querySelectorAll('script[type="text/partytown"]'),i!=t?i.dispatchEvent(new CustomEvent("pt1",{detail:t})):(d=setTimeout(f,1e4),e.addEventListener("pt0",w),r?h(1):n.serviceWorker?n.serviceWorker.register(a+(o.swPath||"partytown-sw.js"),{scope:a}).then((function(t){t.active?h():t.installing&&t.installing.addEventListener("statechange",(function(t){"activated"==t.target.state&&h()}))}),console.error):f())))}function h(t){c=e.createElement(t?"script":"iframe"),t||(c.setAttribute("style","display:block;width:0;height:0;border:0;visibility:hidden"),c.setAttribute("aria-hidden",!0)),c.src=a+"partytown-"+(t?"atomics.js?v=0.8.0":"sandbox-sw.html?"+Date.now()),e.querySelector(o.sandboxParent||"body").appendChild(c)}function f(n,r){for(w(),i==t&&(o.forward||[]).map((function(e){delete t[e.split(".")[0]]})),n=0;n<s.length;n++)(r=e.createElement("script")).innerHTML=s[n].innerHTML,e.head.appendChild(r);c&&c.parentNode.removeChild(c)}function w(){clearTimeout(d)}o=t.partytown||{},i==t&&(o.forward||[]).map((function(e){l=t,e.split(".").map((function(e,n,i){l=l[i[n]]=n+1<i.length?"push"==i[n+1]?[]:l[i[n]]||{}:function(){(t._ptf=t._ptf||[]).push(i,arguments)}}))})),"complete"==e.readyState?u():(t.addEventListener("DOMContentLoaded",u),t.addEventListener("load",u))}(window,document,navigator,top,window.crossOriginIsolated);

View File

@@ -0,0 +1,183 @@
<?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
{
/**
* @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(),
]);
}
/**
* @param Request $request
*
* @return RedirectResponse
*
* @throws \LogicException
*/
public function processGeneralFormAction(Request $request)
{
return $this->processForm(
$request,
$this->getGeneralFormHandler(),
'General'
);
}
/**
* @param Request $request
*
* @return RedirectResponse
*
* @throws \LogicException
*/
public function processWebpFormAction(Request $request)
{
return $this->processForm(
$request,
$this->getWebpFormHandler(),
'Webp'
);
}
/**
* @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_IMG_DIR_ . 'p/');
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;
}
}

View 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;

View 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;

View 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;

View 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;
}
}

View 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;

View 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';
}
}

View 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;

View 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;
}
}

View 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;

View 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());
}
}
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,303 @@
<?php
namespace Oksydan\Module\IsThemeCore\Core\Smarty;
use Oksydan\Module\IsThemeCore\Core\Webp\WebpPictureGenerator;
use Oksydan\Module\IsThemeCore\Form\Settings\WebpConfiguration;
use PrestaShop\PrestaShop\Core\Util\ColorBrightnessCalculator;
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;
}
/* CUSTOM ADDITIONS BELOW */
public static function isBright($hexColor)
{
$calculator = new ColorBrightnessCalculator();
return $calculator->isBright($hexColor);
}
public static function displaySvgIcon($params){
/*
Helper to display SVG icons stored in themes/falcon/_dev/img/icons/
registered under public_html/modules/is_themecore/src/Hook/Smarty.php
Usage examples in a TPL file:
{svg_icon file='person.svg' fill='red' width='24' height='24'}
{svg_icon file='person.svg'}
*/
// Grab svg under falcon/_dev/img and display an error if not found
if (empty($params['file'])) {
return '';
}
$icon_path = _PS_THEME_DIR_ . '/_dev/img/' . basename($params['file']);
if (!is_file($icon_path)) {
if (defined('_PS_MODE_DEV_') && _PS_MODE_DEV_) {
return 'Invalid SVG path: ' . htmlspecialchars($icon_path);
}
return '';
}
$svg_content = file_get_contents($icon_path);
/* Define an associative array of SVG properties
If the property is a style, addd it in a style tag.
If the property is an attribute, replace it in the svg code.
This reduces the risk of interfering with other attributes in the svg's code that can mess up the whole image. */
$svg_props = array(
'width' => 'style',
'height' => 'style',
'opacity' => 'style',
'fill' => 'attribute',
'stroke' => 'attribute',
);
// Styles
$styles = [];
foreach ($svg_props as $prop => $type) {
if ($type === 'style' && !empty($params[$prop])) {
$value = $params[$prop];
if (in_array($prop, ['width', 'height']) && is_numeric($value)) {
$value .= 'px';
}
$styles[] = $prop . ': ' . $value;
}
}
if (!empty($styles)) {
$style_attr = 'style="' . implode('; ', $styles) . '"';
$svg_content = preg_replace('/(<svg[^>]*?)>/', '$1 ' . $style_attr . '>', $svg_content, 1);
}
// Attributes
foreach ($svg_props as $prop => $type) {
if ($type === 'attribute' && !empty($params[$prop])) {
$value = $params[$prop];
// Use word boundaries to avoid matching partial names like fill-opacity or stroke-width
// Replace ALL occurrences of the attribute
$svg_content = preg_replace(
'/\b' . preg_quote($prop) . '\b\s*=\s*"[^"]*"/',
$prop . '="' . $value . '"',
$svg_content
);
}
}
return $svg_content;
}
public static function displayPriceSplit($price, $show_currency = false) {
/* Helper to split the price into euros and cents to make it more stylish
registered under public_html/modules/is_themecore/src/Hook/Smarty.php
Usage examples in a TPL file:
{price_split price=$product.price}
*/
//Convert to a string and only keep numbers, commas and dots
$currency_name = \Context::getContext()->currency->name;
$currency_symbol = \Context::getContext()->currency->symbol;
$price_string = implode(',', $price);
$price_string = preg_replace('/[^0-9,.]/', '', $price_string);
// Split by comma and wrap each part in a span
$price_parts = explode(',', $price_string);
if (count($price_parts) >= 2) {
echo '<span class="currency" aria-label="' . htmlspecialchars($currency_name) . '">' . ($show_currency ? $currency_symbol : '') . $price_parts[0] . '</span>';
echo '<sup class="cents" aria-label="cents">' . $price_parts[1] . '</sup>';
} else {
foreach ($price_parts as $part) {
echo '<span>' . $part . '</span>';
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Oksydan\Module\IsThemeCore\Core\StructuredData;
class BreadcrumbStructuredData extends AbstractStructuredData implements StructuredDataInterface
{
public function getStructuredDataType(): string
{
return 'breadcrumb';
}
}

View File

@@ -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'],
];
}
}
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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'],
];
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Oksydan\Module\IsThemeCore\Core\StructuredData;
class ProductStructuredData extends AbstractStructuredData implements StructuredDataInterface
{
public function getStructuredDataType(): string
{
return 'product';
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Oksydan\Module\IsThemeCore\Core\StructuredData\Provider;
interface StructuredDataProviderInterface
{
/**
* Provide data
*
* @return array
*/
public function getData();
}

View File

@@ -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');
}
}

View File

@@ -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');
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Oksydan\Module\IsThemeCore\Core\StructuredData;
class ShopStructuredData extends AbstractStructuredData implements StructuredDataInterface
{
public function getStructuredDataType(): string
{
return 'shop';
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Oksydan\Module\IsThemeCore\Core\StructuredData;
class WebsiteStructuredData extends AbstractStructuredData implements StructuredDataInterface
{
public function getStructuredDataType(): string
{
return 'website';
}
}

View File

@@ -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;
}
}

View 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'],
]
);
}
}
}

View 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;
}
}
}
}

View 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 [];
}
}

View 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;
}
}
}
}

View 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(),
],
]);
}
}

View 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;
}
}

View 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;

View 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;

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View 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;

View 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');
}
}

View 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);
}
}

View 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(
$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;
}
}

View 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');
}
}

View 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);
}
}

View 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(
$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,
]
),
]);
}
}

View 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;

View 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;

View 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;
}
}

View 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'),
]);
}
}

View 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;
}
}

Some files were not shown because too many files have changed in this diff Show More