554 lines
20 KiB
PHP
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 [];
|
|
}
|
|
}
|