Files
urlredirection/seotricks.php
2025-03-22 13:35:46 +02:00

554 lines
20 KiB
PHP

<?php
use PrestaShop\PrestaShop\Core\Module\WidgetInterface;
use PrestaShop\PrestaShop\Core\Domain\Product\Stock\ValueObject\OutOfStockType;
class SEOTricks extends Module implements WidgetInterface
{
public $merchantReturnPolicyURL = '/uk/blog/proposal-to-conclude-a-contract-3.html';
public function __construct()
{
$this->name = 'seotricks';
$this->tab = 'front_office_features';
$this->version = '1.0.0';
$this->author = 'panariga';
$this->need_instance = 0;
parent::__construct();
$this->displayName = $this->l('SEO Tricks');
$this->description = $this->l('SEO Tricks');
$this->ps_versions_compliancy = array('min' => '1.7', 'max' => _PS_VERSION_);
}
/**
* Register Module hooks
*
* @return bool
*/
public function registerHooks()
{
$hooks = [
'actionDispatcher',
'ActionObjectCategoryUpdateAfter',
'ActionObjectProductUpdateAfter',
'displayProductAdditionalInfo',
// 'moduleRoutes'
];
$res = false;
foreach ($hooks as $hook) {
if (!$res = $this->registerHook($hook)) {
return false;
}
}
return $res;
}
public function install()
{
// Create table (you might want to handle this in a separate function with more sophisticated error checking)
$sql = "CREATE TABLE IF NOT EXISTS `" . _DB_PREFIX_ . "url_redirection` (
`id_url_redirection` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`url` VARCHAR(255) NOT NULL,
`object_name` VARCHAR(64) NOT NULL,
`object_id` INT(10) UNSIGNED NOT NULL,
`date_add` DATETIME DEFAULT NULL,
`date_upd` DATETIME DEFAULT NULL,
PRIMARY KEY (`id_url_redirection`),
UNIQUE KEY `url_unique` (`url`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;";
if (!Db::getInstance()->execute($sql)) {
return false;
}
return parent::install() &&
$this->registerHooks();
}
public function uninstall()
{
if (!parent::uninstall()) {
return false;
}
/* // Drop table
$sql = "DROP TABLE IF EXISTS `" . _DB_PREFIX_ . "url_redirection`";
if (!Db::getInstance()->execute($sql)) {
return false;
} */
return true;
}
public function hookDisplayProductAdditionalInfo($params)
{
echo $this->renderWidget('DisplayProductAdditionalInfo', $params);
}
public function hookactionDispatcher($params)
{
URLRedirect::hookActionDispatcher($params);
}
public function hookActionObjectCategoryUpdateAfter($params)
{
$category = $params['object'];
$idCategory = (int) $category->id;
$languages = Language::getLanguages(false, Context::getContext()->shop->id); // Get all languages for this shop
foreach ($languages as $lang) {
$link = Context::getContext()->link;
$url = $link->getCategoryLink($idCategory, null, $lang['id_lang'], Context::getContext()->shop->id); // Generate new URL
URLRedirect::saveUrl($url, 'category', $idCategory, true); // true for update if exists
}
}
public function hookActionObjectProductUpdateAfter($params)
{
$product = $params['object'];
$id_product = (int)$product->id;
$languages = Language::getLanguages(false, Context::getContext()->shop->id); // get all languages for this shop
foreach ($languages as $lang) {
$link = Context::getContext()->link;
$url = $link->getProductLink($id_product, null, null, null, $lang['id_lang']); // generate new URL
URLRedirect::saveUrl($url, 'product', $id_product, true); // true for update if exists
foreach (Product::getAttributesInformationsByProduct($id_product) as $attrComb) {
$url = $link->getProductLink($id_product, null, null, null, (int) $lang['id_lang'], null, (int) $attrComb['id_attribute']); // generate new URL
URLRedirect::saveUrl($url, 'product', $id_product, true); // true for update if exists
}
// $new_url = trim(str_replace(Context::getContext()->shop->getBaseURL(true, false), '', $url), '/'); // Get the URL part after base URL
}
}
public function generateAndSaveAllUrls()
{
$savedUrls = [
'category' => 0,
'product' => 0,
'cms' => 0,
];
$link = Context::getContext()->link;
// Categories
$categories = Category::getCategories(null, true, false); // all categories
foreach ($categories as $category) {
$categoryId = (int)$category['id_category'];
$languages = Language::getLanguages(true); // Get all available languages
foreach ($languages as $language) {
$categoryLink = $link->getCategoryLink($categoryId, null, $language['id_lang']);
// $url = trim(str_replace(Context::getContext()->shop->getBaseURL(true, false), '', $categoryLink), '/');
if (URLRedirect::saveUrl($categoryLink, 'category', $categoryId, true)) { // update existing
$savedUrls['category']++;
}
}
}
// Products
$products = Product::getProducts(Context::getContext()->language->id, 0, 0, 'id_product', 'ASC'); //Get all products
foreach ($products as $product) {
$productId = (int)$product['id_product'];
$languages = Language::getLanguages(true);
foreach ($languages as $language) {
$productLink = $link->getProductLink($productId, null, null, null, $language['id_lang']);
// $url = trim(str_replace(Context::getContext()->shop->getBaseURL(true, false), '', $productLink), '/');
if (URLRedirect::saveUrl($productLink, 'product', $productId, true)) { // update existing
$savedUrls['product']++;
}
}
}
// CMS Pages
$cmsPages = CMS::getCMSPages(null, true, Context::getContext()->language->id, Context::getContext()->shop->id);
foreach ($cmsPages as $cmsPage) {
$cmsId = (int)$cmsPage['id_cms'];
$languages = Language::getLanguages(true);
foreach ($languages as $language) {
$cmsLink = $link->getCMSLink($cmsId, null, null, $language['id_lang']);
// $url = trim(str_replace(Context::getContext()->shop->getBaseURL(true, false), '', $cmsLink), '/');
if (URLRedirect::saveUrl($cmsLink, 'cms', $cmsId, true)) { // update existing
$savedUrls['cms']++;
}
}
}
return $savedUrls;
}
public function getContent()
{
$output = '';
if (Tools::isSubmit('generate_urls')) {
$savedUrls = $this->generateAndSaveAllUrls();
$output .= $this->displayConfirmation(
$this->l('URLs generated successfully.') . ' ' .
$this->l('Categories:') . ' ' . $savedUrls['category'] . ', ' .
$this->l('Products:') . ' ' . $savedUrls['product'] . ', ' .
$this->l('CMS:') . ' ' . $savedUrls['cms']
);
}
$output .= '
<form action="' . $_SERVER['REQUEST_URI'] . '" method="post">
<button type="submit" name="generate_urls" class="btn btn-default">
<i class="process-icon-refresh"></i> ' . $this->l('Generate All URLs') . '
</button>
</form>
';
return $output;
}
public static function getProductsForTemplate(Product $product)
{
$productsForTemplates = [];
//$productAttributes = Product::getProductAttributesIds($product->id);
$productAttributes = $product->getAttributeCombinations();
/* array(26) {
["id_product_attribute"]=>
int(110)
["id_product"]=>
int(67)
["reference"]=>
string(6) "a56220"
["supplier_reference"]=>
string(0) ""
["ean13"]=>
string(13) "0064992562205"
["isbn"]=>
string(0) ""
["upc"]=>
string(0) ""
["mpn"]=>
string(0) ""
["wholesale_price"]=>
string(11) "1183.860000"
["price"]=>
string(11) "1480.000000"
["ecotax"]=>
string(8) "0.000000"
["weight"]=>
string(8) "2.000000"
["unit_price_impact"]=>
string(8) "0.000000"
["default_on"]=>
int(1)
["minimal_quantity"]=>
int(1)
["low_stock_threshold"]=>
int(0)
["low_stock_alert"]=>
int(0)
["available_date"]=>
string(10) "0000-00-00"
["id_shop"]=>
int(1)
["id_attribute_group"]=>
int(6)
["is_color_group"]=>
int(0)
["group_name"]=>
string(16) "Упаковка"
["attribute_name"]=>
string(6) "2 кг"
["id_attribute"]=>
int(29)
["location"]=>
string(0) ""
["quantity"]=>
int(10)
} */
foreach ($productAttributes as $productAttribute) {
$productForTemplate = new ProductForTemplate();
$productForTemplate->product = $product;
$productForTemplate->combination = new Combination((int) $productAttribute["id_product_attribute"]);
$productForTemplate->name = $productAttribute["attribute_name"];
$productForTemplate->group_name = $productAttribute["group_name"];
$productForTemplate->product = $product;
$productForTemplate->price = $product->getPrice(true, $productForTemplate->combination->id, 2, null, false, true);
$productForTemplate->price_without_reduction = $product->getPrice(true, $productForTemplate->combination->id, 2, null, false, false);
$productForTemplate->quantity = $productAttribute["quantity"];
$productForTemplate->id_attribute_group = $productAttribute["id_attribute_group"];
$productForTemplate->id_attribute = $productAttribute["id_attribute"];
// panariga - update for id attriburte when availiable
$productForTemplate->orderOutOfStock = StockAvailable::outOfStock($product->id);
$productsForTemplates[] = $productForTemplate;
}
return $productsForTemplates;
}
public function renderWidget($hookName, array $params)
{
try {
if (!isset($params['product']) || !$params['product']) {
return '';
}
$product = new Product($params['product']->id, true, $this->context->language->id);
$productsForTemplate = $this->getProductsForTemplate($product);
$productVariants = null;
$commentRepository = $this->get('product_comment_repository');
$averageRating = $commentRepository->getAverageGrade($product->id, (bool) Configuration::get('PRODUCT_COMMENTS_MODERATE'));
$nbComments = $commentRepository->getCommentsNumber($product->id, (bool) Configuration::get('PRODUCT_COMMENTS_MODERATE'));
$aggregateRating = null;
if ($averageRating) {
$aggregateRating = [
"@type" => "AggregateRating",
"ratingValue" => $averageRating,
];
}
if ($nbComments) {
$aggregateRating["reviewCount"] = $nbComments;
}
$aggregateOffer = [
"@type" => "AggregateOffer",
"highPrice" => 0,
"lowPrice" => 0,
"offerCount" => 0,
"priceCurrency" => $this->context->currency->iso_code,
];
foreach ($productsForTemplate as $productForTemplate) {
$url = $this->context->link->getProductLink($product, null, $product->category);
if ($productForTemplate->quantity > 0) {
$availability = "https://schema.org/InStock";
} elseif ($productForTemplate->orderOutOfStock == OutOfStockType::OUT_OF_STOCK_AVAILABLE) {
$availability = "https://schema.org/BackOrder";
} else {
$availability = "https://schema.org/OutOfStock";
}
$productVariantMeta = [
"@type" => "Product",
"sku" => $productForTemplate->combination->reference,
"gtin14" => $productForTemplate->combination->ean13,
"image" => $this->getProductCoverURL($product),
"name" => $product->name . ' ' . $productForTemplate->name,
"description" => strip_tags($product->description),
"weight" => [
"@context" => "https://schema.org",
"@type" => "QuantitativeValue",
"value" => round($productForTemplate->combination->weight, 2),
"unitCode" => "kg"
],
"size" => $productForTemplate->name,
"aggregateRating" => $aggregateRating,
"offers" => [
"@type" => "Offer",
"url" => $url,
"priceCurrency" => $this->context->currency->iso_code,
"price" => $productForTemplate->price,
'priceValidUntil' => date("Y-m-d", strtotime('first day of +2 month')),
"itemCondition" => "https://schema.org/NewCondition",
"availability" => $availability,
// 'button' => $button
"shippingDetails" => $this->generateShippingPolicySchema($productForTemplate->price),
"hasMerchantReturnPolicy" => [
"@context" => "http://schema.org/",
"@type" => "MerchantReturnPolicy",
"@id" => $this->merchantReturnPolicyURL,
"applicableCountry" => "UA",
"returnPolicyCategory" => "https://schema.org/MerchantReturnNotPermitted",
]
],
];
if ($aggregateOffer["highPrice"] == 0) {
$aggregateOffer["highPrice"] = $productVariantMeta["offers"]["price"];
}
if ($aggregateOffer["lowPrice"] == 0) {
$aggregateOffer["lowPrice"] = $productVariantMeta["offers"]["price"];
}
if ($aggregateOffer["highPrice"] < $productVariantMeta["offers"]["price"]) {
$aggregateOffer["highPrice"] = $productVariantMeta["offers"]["price"];
}
if ($aggregateOffer["lowPrice"] > $productVariantMeta["offers"]["price"]) {
$aggregateOffer["lowPrice"] = $productVariantMeta["offers"]["price"];
}
$aggregateOffer["offerCount"] = $aggregateOffer['offerCount'] + 1;
if (($activeDiscount = $this->getActiveDiscount($params['product'])) && (($activeDiscountTime = strtotime($activeDiscount['to'])) > time())) {
$productVariantMeta["offers"]['priceValidUntil'] = date("Y-m-d", $activeDiscountTime);
}
$productVariantsMeta[] = $productVariantMeta;
// $productVariants['canonical'] = $canonical;
}
if (empty($productsForTemplate)) {
return '';
}
$productGroupMeta = [
"@context" => "https://schema.org/",
"@type" => "ProductGroup",
"@id" => $product->link_rewrite,
"name" => $product->name,
"description" => $product->description,
// canonicalrewrite copy getRewriteUrlByLinkRewrite()
"url" => $url,
"aggregateRating" => $aggregateRating,
"brand" => [
"@type" => "Brand",
"name" => $product->manufacturer_name
],
"productGroupID" => 'product-' . $product->id,
"variesBy" => [
"https://schema.org/size"
],
"hasVariant" => $productVariantsMeta,
];
$aggregateOffer["image"] = $productVariantsMeta['0']["image"];
$productAggregateOffer = [
"@context" => "https://schema.org/",
"@type" => "Product",
"name" => $product->name,
"description" => $product->description,
// canonicalrewrite copy getRewriteUrlByLinkRewrite()
"url" => $url,
"offers" => $aggregateOffer,
];
$this->context->smarty->assign([
'productVariants' => $productsForTemplate,
]);
$this->context->smarty->assign([
'productGroupMeta' => $this->recursiveUnsetNull($productGroupMeta),
]);
$this->context->smarty->assign([
'productAggregateOffer' => $this->recursiveUnsetNull($productAggregateOffer),
]);
$templateFile = 'module:' . $this->name . '/views/templates/hook/widget.tpl';
return $this->fetch($templateFile);
} catch (Throwable $e) {
// PrestaShopLogger::addLog($e->getTraceAsString(), 4);
PrestaShopLogger::addLog($e->getMessage(), 4);
PrestaShopLogger::addLog($e->getLine(), 4);
}
return '';
}
public function recursiveUnsetNull($array)
{
foreach ($array as $key => $value) {
if ($value === null || $value === '') {
unset($array[$key]);
} elseif (is_array($value)) {
$array[$key] = $this->recursiveUnsetNull($value);
if (empty($array[$key])) {
unset($array[$key]);
}
}
}
return $array;
}
public function generateShippingPolicySchema($amount)
{
$shippingDetails = [
"@context" => "https://schema.org/",
"@type" => "OfferShippingDetails",
"shippingDestination" => [
"@type" => "DefinedRegion",
"addressCountry" => "UA"
],
"deliveryTime" => [
"@type" => "ShippingDeliveryTime",
"handlingTime" => [
"@type" => "QuantitativeValue",
"minValue" => 0,
"maxValue" => 2,
"unitCode" => "DAY"
],
"transitTime" => [
"@type" => "QuantitativeValue",
"minValue" => 1,
"maxValue" => 2,
"unitCode" => "DAY"
]
]
];
if ($amount >= 1000) {
$shippingDetails["shippingRate"] = [
"@type" => "MonetaryAmount",
"value" => 0,
"currency" => "UAH"
];
} else {
$shippingDetails["shippingRate"] = [
"@type" => "MonetaryAmount",
"value" => 100,
"currency" => "UAH"
];
}
return $shippingDetails;
}
public function getProductCoverURL(Product $product): string
{
$cover = Image::getCover($product->id);
if (!isset($cover['id_image']) || $cover['id_image'] == '') {
return '';
}
return $this->context->link->getImageLink($product->link_rewrite, (int) $cover['id_image'], 'large_default');
}
public function getActiveDiscount($product)
{
return SpecificPrice::getSpecificPrice($product->id, $this->context->shop->id, $this->context->currency->id, $this->context->country->id ?? (int)Configuration::get('PS_COUNTRY_DEFAULT'), $this->context->customer->id_default_group, 1);
}
public function getWidgetVariables($hookName, array $params)
{
return [];
}
}