diff --git a/classes/PrettyURL.php b/classes/PrettyURL.php new file mode 100644 index 0000000..05cb282 --- /dev/null +++ b/classes/PrettyURL.php @@ -0,0 +1,183 @@ +language->id; + $idShop = (int) Context::getContext()->shop->id; + $parsedURL = self::parseURL($uri, $idLang, $idShop); + + if (empty($parsedURL['controller'])) { + return $fallbackController; // No route to add if controller is not determined + } + $_GET['id_product'] = $parsedURL['id_product']; + $_GET['id_product_attribute'] = $parsedURL['id_product_attribute']; + $_GET['id_category'] = $parsedURL['id_category']; + + return $parsedURL['controller']; + } + + public static function parseURL(string $url, int $idLang, int $idShop): array + { + $path = urldecode(parse_url($url, PHP_URL_PATH)); + $result = ['id_product' => null, 'id_category' => null, 'id_product_attribute' => null, 'path' => $path, 'rewrite' => null, 'rule' => null, 'controller' => null]; + + if (!$path) { + return $result; + } + + $parts = explode('/', trim($path, '/')); + // $result['path'] = $parts; + + $lastPart = end($parts); + + // Check if last part is a product link rewrite + $productId = self::getProductIdByLinkRewrite($lastPart, $idLang, $idShop); + if ($productId !== null) { + $result['id_product'] = $productId; + $result['rule'] = '{categories}/{rewrite}'; + $result['rewrite'] = $lastPart; + $result['controller'] = 'product'; + return $result; + } + + // Check if last part is a category link rewrite + $categoryId = self::getCategoryIdByLinkRewrite($parts, $idLang, $idShop); + if ($categoryId !== null) { + $result['id_category'] = $categoryId; + $result['rule'] = '{rewrite}'; + $result['rewrite'] = $lastPart; + $result['controller'] = 'category'; + return $result; + } + + return $result; + } + + + /** + * Simulates a function to get product ID by link rewrite. + * Replace with your actual implementation. + */ + public static function getProductIdByLinkRewrite(string $linkRewrite, int $idLang, int $idShop): ?int + { + + $sql = new DbQuery(); + $sql->select('pl.id_product'); + $sql->from('product_lang', 'pl'); + $sql->where('pl.link_rewrite = "' . pSQL($linkRewrite) . '"'); + $sql->where('pl.id_lang = ' . $idLang); + $sql->where('pl.id_shop = ' . $idShop); + + $productId = Db::getInstance()->getValue($sql); + + if ($productId) { + return (int) $productId; + } else { + return null; + } + } + + + + + + + +/* public static function getCategoryIdByLinkRewrite(string $linkRewrite, int $idLang, int $idShop): ?int + { + + $sql = new DbQuery(); + $sql->select('cl.id_category'); + $sql->from('category_lang', 'cl'); + $sql->where('cl.link_rewrite = "' . pSQL($linkRewrite) . '"'); + $sql->where('cl.id_lang = ' . $idLang); + $sql->where('cl.id_shop = ' . $idShop); + + $categoryId = Db::getInstance()->getValue($sql); + + if ($categoryId) { + return (int) $categoryId; + } else { + return null; + } + } + */ + + + + + + ////////////// + + public static function getCategoryIdByLinkRewrite(array $parts, int $idLang, int $idShop): ?int + { + $linkRewrite = end($parts); + $sql = new DbQuery(); + $sql->select('cl.id_category'); + $sql->from('category_lang', 'cl'); + $sql->where('cl.link_rewrite = "' . pSQL($linkRewrite) . '"'); + $sql->where('cl.id_lang = ' . $idLang); + $sql->where('cl.id_shop = ' . $idShop); + + $categoryIds = Db::getInstance()->executeS($sql); + + if (!$categoryIds) { + return null; + } + + if (count($categoryIds) === 1) { + return (int) $categoryIds[0]['id_category']; + } + + // Multiple categories with the same link_rewrite, resolve by parent categories + return self::resolveCategoryIdByParentCategories($categoryIds, $parts, $idLang, $idShop); + } + + private static function resolveCategoryIdByParentCategories(array $categoryIds, array $parts, int $idLang, int $idShop): ?int + { + // Remove the last part (current category link_rewrite) + array_pop($parts); + + $idCategory = null; + foreach ($categoryIds as $categoryData) { + $categoryId = (int) $categoryData['id_category']; + if (self::checkCategoryPath($categoryId, $parts, $idLang, $idShop)) { + $idCategory = $categoryId; + break; + } + } + + return $idCategory; + } + + private static function checkCategoryPath(int $categoryId, array $parts, int $idLang, int $idShop): bool + { + $currentCategoryId = $categoryId; + $parts = array_reverse($parts); // Reverse parts to iterate from parent to child + + foreach ($parts as $linkRewrite) { + $sql = new DbQuery(); + $sql->select('c.id_parent'); + $sql->from('category', 'c'); + $sql->leftJoin('category_lang', 'cl', 'c.id_category = cl.id_category AND cl.id_lang = ' . (int)$idLang . ' AND cl.id_shop = '.(int)$idShop); + $sql->where('c.id_category = ' . (int)$currentCategoryId); + $sql->where('cl.link_rewrite = "' . pSQL($linkRewrite) . '"'); + + + $parentId = (int) Db::getInstance()->getValue($sql); + + if (!$parentId) { + return false; + } + + $currentCategoryId = $parentId; // Move to the parent category + } + + // If we reached the top of the path without failing, it's a match + return true; + } +} diff --git a/classes/ProductForTemplate.php b/classes/ProductForTemplate.php new file mode 100644 index 0000000..9ee91e7 --- /dev/null +++ b/classes/ProductForTemplate.php @@ -0,0 +1,15 @@ + 'url_redirection', + 'primary' => 'id_url_redirection', + 'multilang' => false, + 'fields' => [ + 'url' => ['type' => self::TYPE_STRING, 'validate' => 'isString', 'required' => true, 'size' => 255], + 'object_name' => ['type' => self::TYPE_STRING, 'validate' => 'isString', 'required' => true, 'size' => 64], + 'object_id' => ['type' => self::TYPE_INT, 'validate' => 'isInt', 'required' => true], + 'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => false], + 'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate', 'required' => false], + ], + ]; + + /** + * @param string $url + * @param string $object_name + * @param int $object_id + * @param bool $updateExisting + * + * @return bool + */ + public static function saveUrl(string $url, string $object_name, int $object_id, bool $updateExisting = false): bool + { + // $url = trim($url, '/'); // Remove leading/trailing slashes + $url = trim(str_replace(Context::getContext()->shop->getBaseURL(true, false), '', $url), '/'); + + if (empty($url) || empty($object_name) || $object_id <= 0) { + return false; + } + + $redirection = URLRedirect::getByUrl($url); + if ($redirection->id) { + if (!$updateExisting) { + return false; // Do not update if we shouldn't. + } + + // Update existing entry + $redirection->object_name = $object_name; + $redirection->object_id = $object_id; + $redirection->date_upd = date('Y-m-d H:i:s'); + return $redirection->update(); + } + + + + $redirection->url = $url; + $redirection->object_name = $object_name; + $redirection->object_id = $object_id; + $redirection->date_add = date('Y-m-d H:i:s'); + + return $redirection->add(); + } + + /** + * Get redirection by URL. + * + * @param string $url + * @return URLRedirect + */ + public static function getByUrl(string $url): URLRedirect + { + $url = trim(urldecode($url), '/'); + $sql = new DbQuery(); + $sql->select('*'); + $sql->from(self::$definition['table']); + $sql->where('url = "' . pSQL($url) . '"'); + $object = new self(); + if ($result = Db::getInstance()->getRow($sql)) { + + $object->id = (int) $result['id_url_redirection']; + $object->url = $result['url']; + $object->object_name = $result['object_name']; + $object->object_id = (int) $result['object_id']; + $object->date_add = $result['date_add']; + $object->date_upd = $result['date_upd']; + } + + return $object; + } + public static function extractPath($requestUri) + { + // Remove query string if present + $uri = parse_url($requestUri, PHP_URL_PATH); + $segments = explode('/', trim($uri, '/')); + + // If the first segment is 2 letters, ignore it + if (isset($segments[0]) && preg_match('/^[a-zA-Z]{2}$/', $segments[0])) { + array_shift($segments); + } + + return strtolower(implode('/', $segments)); + } + /** + * This method hooks into the dispatcher to check if the current URL + * is a 404 and if there's a redirection available for it. + * This hook needs to be called `hookActionDispatcherBefore` in a module. + * + * @param array $params + * @return void + */ + public static function hookActionDispatcher(array $params): void + { + if (!defined('_PS_ADMIN_DIR_') && $params['controller_class'] === 'PageNotFoundController') { + $url = trim(str_replace(Context::getContext()->shop->getBaseURL(true, false), '', $_SERVER['REQUEST_URI']), '/'); + + if (!empty($url)) { + + $redirection = URLRedirect::getByUrl($url); + if ($redirection->id) { + $targetUrl = ''; + + switch ($redirection->object_name) { + case 'category': + $targetUrl = Context::getContext()->link->getCategoryLink($redirection->object_id); + break; + case 'product': + $targetUrl = Context::getContext()->link->getProductLink($redirection->object_id); + break; + case 'cms': + $targetUrl = Context::getContext()->link->getCMSLink($redirection->object_id); + break; + // Add more cases for other object types (cms_category, supplier, manufacturer etc.) + default: + // Log the invalid object_name or maybe remove the redirection. + PrestaShopLogger::addLog( + 'Invalid object_name in URLRedirect table: ' . $redirection->object_name . ' URL: ' . $url, + 3, // Severity: WARNING + 0, // Error Code + 'URLRedirect', // Object Class + $redirection->id, // Object ID + true + ); + return; // Don't redirect if the object name is invalid. + } + + if (!empty($targetUrl)) { + Tools::redirect($targetUrl); + } + } + } + } + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..bd096a9 --- /dev/null +++ b/composer.json @@ -0,0 +1,29 @@ +{ + "name": "panariga/seotricks", + "description": "seotricks", + "authors": [ + { + "name": "panariga", + "email": "panariga@gmail.com" + } + ], + "require": { + "php": ">=8.0.0" + }, + "autoload": { + "psr-4": { + "\\": "src/" + }, + "classmap": [ + "classes/" + ], + "exclude-from-classmap": [] + }, + "config": { + "preferred-install": "dist", + "prepend-autoloader": false + }, + "type": "prestashop-module", + "author": "panariga", + "license": "MIT" +} \ No newline at end of file diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..8ca9a0e --- /dev/null +++ b/composer.lock @@ -0,0 +1,20 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "c831f345a274722d1e92b83bf2e33280", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.0.0" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/config_uk.xml b/config_uk.xml index 03abeba..89c665c 100644 --- a/config_uk.xml +++ b/config_uk.xml @@ -1,9 +1,9 @@ - urlredirection - + seotricks + - + 1 diff --git a/seotricks.php b/seotricks.php new file mode 100644 index 0000000..0742aff --- /dev/null +++ b/seotricks.php @@ -0,0 +1,553 @@ +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 .= ' +
+ +
+ '; + + 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 []; + } +} diff --git a/views/templates/hook/widget.tpl b/views/templates/hook/widget.tpl new file mode 100644 index 0000000..6e8181d --- /dev/null +++ b/views/templates/hook/widget.tpl @@ -0,0 +1,12 @@ + + +{if $productGroupMeta} + +{/if} +{if $productAggregateOffer} + +{/if} \ No newline at end of file