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