* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) */ if (!defined('_PS_VERSION_')) { exit; } class Gsitemap extends Module { const HOOK_ADD_URLS = 'gSitemapAppendUrls'; /** * @var bool */ public $cron = false; /** * @var array */ protected $sql_checks = []; /** * @var array */ protected $type_array = []; /** * @var array */ protected $disallow_controllers = [ 'addresses', 'address', 'authentication', 'cart', 'discount', 'footer', 'get-file', 'header', 'history', 'identity', 'images.inc', 'init', 'my-account', 'order', 'order-slip', 'order-detail', 'order-follow', 'order-return', 'order-confirmation', 'pagination', 'password', 'pdf-invoice', 'pdf-order-return', 'pdf-order-slip', 'product-sort', 'registration', 'search', 'statistics', 'attachment', 'guest-tracking', ]; public function __construct() { $this->name = 'gsitemap'; $this->tab = 'checkout'; $this->version = '4.4.0'; $this->author = 'PrestaShop'; $this->need_instance = 0; $this->bootstrap = true; parent::__construct(); $this->displayName = $this->trans('Google sitemap', [], 'Modules.Gsitemap.Admin'); $this->description = $this->trans('Generate your Google sitemap file and keep it up to date with this module.', [], 'Modules.Gsitemap.Admin'); $this->ps_versions_compliancy = [ 'min' => '1.7.1.0', 'max' => _PS_VERSION_, ]; $this->confirmUninstall = $this->trans('Are you sure you want to uninstall this module?', [], 'Admin.Notifications.Warning'); $this->type_array = [ 'home', 'meta', 'product', 'category', 'manufacturer', 'cms', 'module', ]; $metas = Db::getInstance()->ExecuteS('SELECT * FROM `' . _DB_PREFIX_ . 'meta` ORDER BY `id_meta` ASC'); $disabled_metas = explode(',', Configuration::get('GSITEMAP_DISABLE_LINKS')); foreach ($metas as $meta) { if (in_array($meta['id_meta'], $disabled_metas)) { if (($key = array_search($meta['page'], $this->type_array)) !== false) { unset($this->type_array[$key]); } } } } /** * Google sitemap installation process: * * Step 1 - Pre-set Configuration option values * Step 2 - Install the Addon and create a database table to store sitemap files name by shop * * @return bool Installation result */ public function install() { foreach ([ 'GSITEMAP_PRIORITY_HOME' => 1.0, 'GSITEMAP_PRIORITY_PRODUCT' => 0.9, 'GSITEMAP_PRIORITY_CATEGORY' => 0.8, 'GSITEMAP_PRIORITY_MANUFACTURER' => 0.7, 'GSITEMAP_PRIORITY_CMS' => 0.7, 'GSITEMAP_FREQUENCY' => 'weekly', 'GSITEMAP_LAST_EXPORT' => false, 'GSITEMAP_DISABLE_LINKS' => '', ] as $key => $val) { if (!Configuration::updateValue($key, $val)) { return false; } } return parent::install() && Db::getInstance()->Execute('CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'gsitemap_sitemap` (`link` varchar(255) DEFAULT NULL, `id_shop` int(11) DEFAULT 0) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8;') && $this->installHook(); } /** * Check if the hook is present in the system or add it * * @return bool */ protected function installHook() { $hook = new Hook(Hook::getIdByName(self::HOOK_ADD_URLS)); if (Validate::isLoadedObject($hook)) { return true; } $hook = new Hook(); $hook->name = self::HOOK_ADD_URLS; $hook->title = 'GSitemap Append URLs'; $hook->description = 'This hook allows a module to add URLs to a generated sitemap'; $hook->position = true; return $hook->save(); } /** * Google sitemap uninstallation process: * * Step 1 - Remove Configuration option values from database * Step 2 - Remove the database containing the generated sitemap files names * Step 3 - Uninstallation of the Addon itself * * @return bool Uninstallation result */ public function uninstall() { foreach ([ 'GSITEMAP_PRIORITY_HOME', 'GSITEMAP_PRIORITY_PRODUCT', 'GSITEMAP_PRIORITY_CATEGORY', 'GSITEMAP_PRIORITY_MANUFACTURER', 'GSITEMAP_PRIORITY_CMS', 'GSITEMAP_FREQUENCY', 'GSITEMAP_LAST_EXPORT', 'GSITEMAP_DISABLE_LINKS', ] as $configurationKey) { if (!Configuration::deleteByName($configurationKey)) { return false; } } return parent::uninstall() && $this->removeSitemap(); } /** * Delete all the generated sitemap files and drop the addon table. * * @return bool */ public function removeSitemap() { $links = Db::getInstance()->ExecuteS('SELECT * FROM `' . _DB_PREFIX_ . 'gsitemap_sitemap`'); if ($links) { foreach ($links as $link) { if (!@unlink($this->normalizeDirectory(_PS_ROOT_DIR_) . $link['link'])) { return false; } } } if (!Db::getInstance()->Execute('DROP TABLE `' . _DB_PREFIX_ . 'gsitemap_sitemap`')) { return false; } return true; } public function getContent() { /* Store the posted parameters and generate a new Google sitemap files for the current Shop */ if (Tools::isSubmit('SubmitGsitemap')) { Configuration::updateValue('GSITEMAP_FREQUENCY', pSQL(Tools::getValue('gsitemap_frequency'))); $meta = ''; if (Tools::getValue('gsitemap_meta')) { $meta .= implode(', ', Tools::getValue('gsitemap_meta')); } Configuration::updateValue('GSITEMAP_DISABLE_LINKS', $meta); $this->emptySitemap(); $this->createSitemap(); /* If no posted form and the variable [continue] is found in the HTTP request variable keep creating sitemap */ } elseif (Tools::getValue('continue')) { $this->createSitemap(); } /* Empty the Shop domain cache */ if (method_exists('ShopUrl', 'resetMainDomainCache')) { ShopUrl::resetMainDomainCache(); } /* Get Meta pages and remove index page it's managed elsewhere (@see $this->getHomeLink()) */ /* We also remove all pages that are blocked in core robots.txt file */ $store_metas = array_filter(Meta::getMetasByIdLang( (int) $this->context->cookie->id_lang), function ($meta) { return $meta['page'] != 'index' && !in_array($meta['page'], $this->disallow_controllers); } ); $store_url = $this->context->link->getBaseLink(); $this->context->smarty->assign([ 'gsitemap_form' => './index.php?controller=AdminModules&configure=gsitemap&token=' . Tools::getAdminTokenLite('AdminModules') . '&tab_module=' . $this->tab . '&module_name=gsitemap', 'gsitemap_cron' => $this->context->link->getModuleLink( 'gsitemap', 'cron', [ 'token' => Tools::substr(Tools::hash('gsitemap/cron'), 0, 10), 'id_shop' => $this->context->shop->id, ] ), 'gsitemap_feed_exists' => file_exists($this->normalizeDirectory(_PS_ROOT_DIR_) . 'index_sitemap.xml'), 'gsitemap_last_export' => Configuration::get('GSITEMAP_LAST_EXPORT'), 'gsitemap_frequency' => Configuration::get('GSITEMAP_FREQUENCY'), 'gsitemap_store_url' => $store_url, 'gsitemap_links' => Db::getInstance()->ExecuteS('SELECT * FROM `' . _DB_PREFIX_ . 'gsitemap_sitemap` WHERE id_shop = ' . (int) $this->context->shop->id), 'store_metas' => $store_metas, 'gsitemap_disable_metas' => explode(',', Configuration::get('GSITEMAP_DISABLE_LINKS')), 'gsitemap_customer_limit' => [ 'max_exec_time' => (int) ini_get('max_execution_time'), 'memory_limit' => (int) ini_get('memory_limit'), ], 'prestashop_ssl' => Configuration::get('PS_SSL_ENABLED'), 'shop' => $this->context->shop, ]); return $this->display(__FILE__, 'views/templates/admin/configuration.tpl'); } /** * Delete all the generated sitemap files from the files system and the database. * * @param int $id_shop * * @return bool */ public function emptySitemap($id_shop = 0) { if (!isset($this->context)) { $this->context = new Context(); } if ($id_shop != 0) { $this->context->shop = new Shop((int) $id_shop); } $links = Db::getInstance()->ExecuteS('SELECT * FROM `' . _DB_PREFIX_ . 'gsitemap_sitemap` WHERE id_shop = ' . (int) $this->context->shop->id); if ($links) { foreach ($links as $link) { @unlink($this->normalizeDirectory(_PS_ROOT_DIR_) . $link['link']); } return Db::getInstance()->Execute('DELETE FROM `' . _DB_PREFIX_ . 'gsitemap_sitemap` WHERE id_shop = ' . (int) $this->context->shop->id); } return true; } /** * @param array $link_sitemap contain all the links for the Google sitemap file to be generated * @param array $new_link contain the link elements * @param string $lang language of link to add * @param int $index index of the current Google sitemap file * @param int $i count of elements added to sitemap main array * @param int $id_obj identifier of the object of the link to be added to the Gogle sitemap file * * @return bool */ public function addLinkToSitemap(&$link_sitemap, $new_link, $lang, &$index, &$i, $id_obj) { if ($i <= 25000 && memory_get_usage() < 100000000) { $link_sitemap[] = $new_link; ++$i; return true; } else { $this->recursiveSitemapCreator($link_sitemap, $lang, $index); if ($index % 20 == 0 && !$this->cron) { $this->context->smarty->assign([ 'gsitemap_number' => (int) $index, 'gsitemap_refresh_page' => $this->context->link->getAdminLink('AdminModules', true, [], [ 'tab_module' => $this->tab, 'module_name' => $this->name, 'continue' => 1, 'type' => $new_link['type'], 'lang' => $lang, 'index' => $index, 'id' => (int) $id_obj, 'id_shop' => $this->context->shop->id, ]), ]); return false; } elseif ($index % 20 == 0 && $this->cron) { header('Refresh: 5; url=http' . (Configuration::get('PS_SSL_ENABLED') ? 's' : '') . '://' . Tools::getShopDomain(false, true) . __PS_BASE_URI__ . 'modules/gsitemap/gsitemap-cron.php?continue=1&token=' . Tools::substr(Tools::hash('gsitemap/cron'), 0, 10) . '&type=' . $new_link['type'] . '&lang=' . $lang . '&index=' . $index . '&id=' . (int) $id_obj . '&id_shop=' . $this->context->shop->id); exit(); } else { if ($this->cron) { Tools::redirect($this->context->link->getModuleLink( 'gsitemap', 'cron', [ 'continue' => '1', 'token' => Tools::substr(Tools::hash('gsitemap/cron'), 0, 10), 'type' => $new_link['type'], 'lang' => $lang, 'index' => $index, 'id' => (int) $id_obj, 'id_shop' => $this->context->shop->id, ] )); } else { Tools::redirectAdmin($this->context->link->getAdminLink('AdminModules', true, [], [ 'tab_module' => $this->tab, 'module_name' => $this->name, 'configure' => $this->name, 'continue' => 1, 'type' => $new_link['type'], 'lang' => $lang, 'index' => $index, 'id' => (int) $id_obj, 'id_shop' => $this->context->shop->id, ])); } exit(); } } } /** * Hydrate $link_sitemap with home link * * @param array $link_sitemap contain all the links for the Google sitemap file to be generated * @param array $lang language of link to add * @param int $index index of the current Google sitemap file * @param int $i count of elements added to sitemap main array * * @return bool */ protected function getHomeLink(&$link_sitemap, $lang, &$index, &$i) { $link = new Link(); return $this->addLinkToSitemap($link_sitemap, [ 'type' => 'home', 'page' => 'home', 'link' => $link->getPageLink('index', null, $lang['id_lang']), 'image' => false, ], $lang['iso_code'], $index, $i, -1); } /** * Hydrate $link_sitemap with meta link * * @param array $link_sitemap contain all the links for the Google sitemap file to be generated * @param array $lang language of link to add * @param int $index index of the current Google sitemap file * @param int $i count of elements added to sitemap main array * @param int $id_meta meta object identifier * * @return bool */ protected function getMetaLink(&$link_sitemap, $lang, &$index, &$i, $id_meta = 0) { if (method_exists('ShopUrl', 'resetMainDomainCache')) { ShopUrl::resetMainDomainCache(); } $link = new Link(); $metas = Db::getInstance()->ExecuteS('SELECT * FROM `' . _DB_PREFIX_ . 'meta` WHERE `configurable` > 0 AND `id_meta` >= ' . (int) $id_meta . ' AND page <> \'index\' ORDER BY `id_meta` ASC'); foreach ($metas as $meta) { // Check if this meta is not in the list of blocked controllers in core robots.txt if (in_array($meta['page'], $this->disallow_controllers)) { continue; } $url = ''; if (!in_array($meta['id_meta'], explode(',', Configuration::get('GSITEMAP_DISABLE_LINKS')))) { $url = $link->getPageLink($meta['page'], null, $lang['id_lang']); if (!$this->addLinkToSitemap($link_sitemap, [ 'type' => 'meta', 'page' => $meta['page'], 'link' => $url, 'image' => false, ], $lang['iso_code'], $index, $i, $meta['id_meta'])) { return false; } } } return true; } /** * Hydrate $link_sitemap with products link * * @param array $link_sitemap contain all the links for the Google sitemap file to be generated * @param array $lang language of link to add * @param int $index index of the current Google sitemap file * @param int $i count of elements added to sitemap main array * @param int $id_product product object identifier * * @return bool */ protected function getProductLink(&$link_sitemap, $lang, &$index, &$i, $id_product = 0) { $link = new Link(); if (method_exists('ShopUrl', 'resetMainDomainCache')) { ShopUrl::resetMainDomainCache(); } /* * If group feature is enabled, we will show only publicly accessible categories in the sitemap. * In the core, if there is at least one category of the product publicly accessible, the product is accessible. * So, we do a subselect where we try to find at least one category accessible, then we inner join it to the product table * and we are left with only accessible products. */ if (Group::isFeatureActive() && !empty(Configuration::get('PS_UNIDENTIFIED_GROUP'))) { $group_join = ' INNER JOIN (SELECT DISTINCT cp.`id_product` FROM `' . _DB_PREFIX_ . 'category_product` cp INNER JOIN `' . _DB_PREFIX_ . 'category_group` ctg ON (ctg.`id_category` = cp.`id_category`) WHERE ctg.`id_group` = ' . (int) Configuration::get('PS_UNIDENTIFIED_GROUP') . ' AND cp.`id_product` >= ' . (int) $id_product . ' ) g ON ps.`id_product` = g.`id_product`'; } else { $group_join = ' '; } // Get product IDs $products_id = Db::getInstance()->ExecuteS('SELECT ps.`id_product` FROM `' . _DB_PREFIX_ . 'product_shop` ps' . $group_join . ' WHERE ps.`id_product` >= ' . (int) $id_product . ' AND ps.`active` = 1 AND ps.`visibility` != \'none\' AND ps.`id_shop`=' . $this->context->shop->id . ' ORDER BY ps.`id_product` ASC'); // Process each category and add it to list of links that will be further "converted" to XML and added to the sitemap foreach ($products_id as $product_id) { $product = new Product((int) $product_id['id_product'], false, (int) $lang['id_lang']); $url = $link->getProductLink($product, $product->link_rewrite, htmlspecialchars(strip_tags($product->category)), $product->ean13, (int) $lang['id_lang'], (int) $this->context->shop->id, 0); $images_product = []; foreach ($product->getImages((int) $lang['id_lang']) as $id_image) { if (isset($id_image['id_image'])) { $image_link = $this->context->link->getImageLink($product->link_rewrite, (string) $id_image['id_image'], ImageType::getFormattedName('large')); $image_link = (!in_array(rtrim(Context::getContext()->shop->virtual_uri, '/'), explode('/', $image_link))) ? str_replace([ 'https', Context::getContext()->shop->domain . Context::getContext()->shop->physical_uri, ], [ 'http', Context::getContext()->shop->domain . Context::getContext()->shop->physical_uri . Context::getContext()->shop->virtual_uri, ], $image_link) : $image_link; $images_product[] = [ 'title_img' => htmlspecialchars(strip_tags($product->name)), 'caption' => htmlspecialchars(strip_tags($product->meta_description)), 'link' => $image_link, ]; } unset($image_link); } if (!$this->addLinkToSitemap($link_sitemap, [ 'type' => 'product', 'page' => 'product', 'lastmod' => $product->date_upd, 'link' => $url, 'images' => $images_product, ], $lang['iso_code'], $index, $i, $product_id['id_product'])) { return false; } } return true; } /** * Hydrate $link_sitemap with categories link * * @param array $link_sitemap contain all the links for the Google sitemap file to be generated * @param array $lang language of link to add * @param int $index index of the current Google sitemap file * @param int $i count of elements added to sitemap main array * @param int $id_category category object identifier * * @return bool */ protected function getCategoryLink(&$link_sitemap, $lang, &$index, &$i, $id_category = 0) { $link = new Link(); if (method_exists('ShopUrl', 'resetMainDomainCache')) { ShopUrl::resetMainDomainCache(); } // If group feature is enabled, we will show only publicly accessible categories in the sitemap if (Group::isFeatureActive() && !empty(Configuration::get('PS_UNIDENTIFIED_GROUP'))) { $group_join = ' INNER JOIN `' . _DB_PREFIX_ . 'category_group` cg ON c.`id_category` = cg.`id_category` AND cg.`id_group` = ' . (int) Configuration::get('PS_UNIDENTIFIED_GROUP'); } else { $group_join = ' '; } // Get category IDs $categories_id = Db::getInstance()->ExecuteS('SELECT c.id_category FROM `' . _DB_PREFIX_ . 'category` c INNER JOIN `' . _DB_PREFIX_ . 'category_shop` cs ON c.`id_category` = cs.`id_category`' . $group_join . ' WHERE c.`id_category` >= ' . (int) $id_category . ' AND c.`active` = 1 AND c.`id_category` != ' . (int) Configuration::get('PS_ROOT_CATEGORY') . ' AND c.id_category != ' . (int) Configuration::get('PS_HOME_CATEGORY') . ' AND c.id_parent > 0 AND c.`id_category` > 0 AND cs.`id_shop` = ' . (int) $this->context->shop->id . ' ORDER BY c.`id_category` ASC'); // Process each category and add it to list of links that will be further "converted" to XML and added to the sitemap foreach ($categories_id as $category_id) { $category = new Category((int) $category_id['id_category'], (int) $lang['id_lang']); $url = $link->getCategoryLink($category, urlencode($category->link_rewrite), (int) $lang['id_lang']); $image_category = []; if ($category->id_image) { $image_link = $this->context->link->getCatImageLink($category->link_rewrite, (int) $category->id_image, ImageType::getFormattedName('category')); $image_link = (!in_array(rtrim(Context::getContext()->shop->virtual_uri, '/'), explode('/', $image_link))) ? str_replace([ 'https', Context::getContext()->shop->domain . Context::getContext()->shop->physical_uri, ], [ 'http', Context::getContext()->shop->domain . Context::getContext()->shop->physical_uri . Context::getContext()->shop->virtual_uri, ], $image_link) : $image_link; $image_category = [ 'title_img' => htmlspecialchars(strip_tags($category->name)), 'caption' => Tools::substr(htmlspecialchars(strip_tags($category->description)), 0, 350), 'link' => $image_link, ]; } if (!$this->addLinkToSitemap($link_sitemap, [ 'type' => 'category', 'page' => 'category', 'lastmod' => $category->date_upd, 'link' => $url, 'image' => $image_category, ], $lang['iso_code'], $index, $i, (int) $category_id['id_category'])) { return false; } unset($image_link); } return true; } /** * return the link elements for the manufacturer object * * @param array $link_sitemap contain all the links for the Google Sitemap file to be generated * @param array $lang language of link to add * @param int $index index of the current Google Sitemap file * @param int $i count of elements added to sitemap main array * @param int $id_manufacturer manufacturer object identifier * * @return bool */ protected function getManufacturerLink(&$link_sitemap, $lang, &$index, &$i, $id_manufacturer = 0) { $link = new Link(); if (method_exists('ShopUrl', 'resetMainDomainCache')) { ShopUrl::resetMainDomainCache(); } // Get manufacturers IDs $manufacturers_id = Db::getInstance()->ExecuteS('SELECT m.`id_manufacturer` FROM `' . _DB_PREFIX_ . 'manufacturer` m INNER JOIN `' . _DB_PREFIX_ . 'manufacturer_lang` ml on m.`id_manufacturer` = ml.`id_manufacturer`' . ' INNER JOIN `' . _DB_PREFIX_ . 'manufacturer_shop` ms ON m.`id_manufacturer` = ms.`id_manufacturer`' . ' WHERE m.`active` = 1 AND m.`id_manufacturer` >= ' . (int) $id_manufacturer . ' AND ms.`id_shop` = ' . (int) $this->context->shop->id . ' AND ml.`id_lang` = ' . (int) $lang['id_lang'] . ' ORDER BY m.`id_manufacturer` ASC' ); // Process each manufacturer and add it to list of links that will be further "converted" to XML and added to the sitemap foreach ($manufacturers_id as $manufacturer_id) { $manufacturer = new Manufacturer((int) $manufacturer_id['id_manufacturer'], $lang['id_lang']); $url = $link->getManufacturerLink($manufacturer, urlencode($manufacturer->link_rewrite), $lang['id_lang']); $image_link = $this->context->link->getManufacturerImageLink((int) $manufacturer->id, ImageType::getFormattedName('medium')); $image_link = (!in_array(rtrim(Context::getContext()->shop->virtual_uri, '/'), explode('/', $image_link))) ? str_replace([ 'https', Context::getContext()->shop->domain . Context::getContext()->shop->physical_uri, ], [ 'http', Context::getContext()->shop->domain . Context::getContext()->shop->physical_uri . Context::getContext()->shop->virtual_uri, ], $image_link) : $image_link; $manufacturer_image = [ 'title_img' => htmlspecialchars(strip_tags($manufacturer->name)), 'caption' => htmlspecialchars(strip_tags($manufacturer->short_description)), 'link' => $image_link, ]; if (!$this->addLinkToSitemap($link_sitemap, [ 'type' => 'manufacturer', 'page' => 'manufacturer', 'lastmod' => $manufacturer->date_upd, 'link' => $url, 'image' => $manufacturer_image, ], $lang['iso_code'], $index, $i, $manufacturer_id['id_manufacturer'])) { return false; } unset($image_link); } return true; } /** * return the link elements for the CMS object * * @param array $link_sitemap contain all the links for the Google sitemap file to be generated * @param array $lang the language of link to add * @param int $index the index of the current Google sitemap file * @param int $i the count of elements added to sitemap main array * @param int $id_cms the CMS object identifier * * @return bool */ protected function getCmsLink(&$link_sitemap, $lang, &$index, &$i, $id_cms = 0) { $link = new Link(); if (method_exists('ShopUrl', 'resetMainDomainCache')) { ShopUrl::resetMainDomainCache(); } $cmss_id = Db::getInstance()->ExecuteS('SELECT c.`id_cms` FROM `' . _DB_PREFIX_ . 'cms` c INNER JOIN `' . _DB_PREFIX_ . 'cms_lang` cl ON c.`id_cms` = cl.`id_cms` ' . 'INNER JOIN `' . _DB_PREFIX_ . 'cms_shop` cs ON c.`id_cms` = cs.`id_cms` ' . 'INNER JOIN `' . _DB_PREFIX_ . 'cms_category` cc ON c.id_cms_category = cc.id_cms_category AND cc.active = 1 WHERE c.`active` =1 AND c.`indexation` =1 AND c.`id_cms` >= ' . (int) $id_cms . ' AND cs.id_shop = ' . (int) $this->context->shop->id . ' AND cl.`id_lang` = ' . (int) $lang['id_lang'] . ' GROUP BY c.`id_cms` ORDER BY c.`id_cms` ASC'); if (is_array($cmss_id)) { foreach ($cmss_id as $cms_id) { $cms = new CMS((int) $cms_id['id_cms'], $lang['id_lang']); $cms->link_rewrite = urlencode((is_array($cms->link_rewrite) ? $cms->link_rewrite[(int) $lang['id_lang']] : $cms->link_rewrite)); $url = $link->getCMSLink($cms, null, null, $lang['id_lang']); if (!$this->addLinkToSitemap($link_sitemap, [ 'type' => 'cms', 'page' => 'cms', 'link' => $url, 'image' => false, ], $lang['iso_code'], $index, $i, $cms_id['id_cms'])) { return false; } } } return true; } /** * Returns link elements generated by modules subscribes to hook gsitemap::HOOK_ADD_URLS * * The hook expects modules to return a vector of associative arrays each of them being acceptable by * the gsitemap::_addLinkToSitemap() second attribute (minus the 'type' index). * The 'type' index is automatically set to 'module' (not sure here, should we be safe or trust modules?). * * @param array $link_sitemap by ref. accumulator for all the links for the Google sitemap file to be generated * @param array $lang the language being processed * @param int $index the index of the current Google sitemap file * @param int $i the count of elements added to sitemap main array * @param int $num_link restart at link number #$num_link * * @return bool */ protected function getModuleLink(&$link_sitemap, $lang, &$index, &$i, $num_link = 0) { /** @var array|string $modules_links */ $modules_links = Hook::exec(self::HOOK_ADD_URLS, [ 'lang' => $lang, ], null, true); if (empty($modules_links) || !is_array($modules_links)) { return true; } $links = []; foreach ($modules_links as $module_links) { $links = array_merge($links, $module_links); } foreach ($links as $n => $link) { if ($num_link > $n) { continue; } $link['type'] = 'module'; if (!$this->addLinkToSitemap($link_sitemap, $link, $lang['iso_code'], $index, $i, $n)) { return false; } } return true; } /** * Create the Google sitemap by Shop * * @param int $id_shop Shop identifier * * @return bool */ public function createSitemap($id_shop = 0) { if (@fopen($this->normalizeDirectory(_PS_ROOT_DIR_) . '/test.txt', 'wb') == false) { $this->context->controller->errors[] = $this->trans('An error occured while trying to check your file permissions. Please adjust your permissions to allow PrestaShop to write a file in your root directory.', [], 'Modules.Gsitemap.Admin'); return false; } else { @unlink($this->normalizeDirectory(_PS_ROOT_DIR_) . 'test.txt'); } if ($id_shop != 0) { $this->context->shop = new Shop((int) $id_shop); } $type = Tools::getValue('type') ? Tools::getValue('type') : ''; $languages = Language::getLanguages(true, $this->context->shop->id); $lang_stop = Tools::getValue('lang') ? true : false; $id_obj = Tools::getValue('id') ? (int) Tools::getValue('id') : 0; foreach ($languages as $lang) { $i = 0; $index = (Tools::getValue('index') && Tools::getValue('lang') == $lang['iso_code']) ? (int) Tools::getValue('index') : 0; if ($lang_stop && $lang['iso_code'] != Tools::getValue('lang')) { continue; } elseif ($lang_stop && $lang['iso_code'] == Tools::getValue('lang')) { $lang_stop = false; } $link_sitemap = []; foreach ($this->type_array as $type_val) { if ($type == '' || $type == $type_val) { $function = 'get' . Tools::ucfirst($type_val) . 'Link'; if (!$this->$function($link_sitemap, $lang, $index, $i, $id_obj)) { return false; } $type = ''; $id_obj = 0; } } $this->recursiveSitemapCreator($link_sitemap, $lang['iso_code'], $index); $index = 0; } $this->createIndexSitemap(); Configuration::updateValue('GSITEMAP_LAST_EXPORT', date('r')); Tools::file_get_contents('https://www.google.com/webmasters/sitemaps/ping?sitemap=' . urlencode($this->context->link->getBaseLink() . $this->context->shop->physical_uri . $this->context->shop->virtual_uri . $this->context->shop->id)); if ($this->cron) { exit(); } Tools::redirectAdmin('index.php?controller=AdminModules&configure=gsitemap&token=' . Tools::getAdminTokenLite('AdminModules') . '&tab_module=' . $this->tab . '&module_name=gsitemap&validation'); exit(); } /** * Store the generated sitemap file to the database * * @param string $sitemap the name of the generated Google sitemap file * * @return bool */ protected function saveSitemapLink($sitemap) { if ($sitemap) { return Db::getInstance()->Execute('INSERT INTO `' . _DB_PREFIX_ . 'gsitemap_sitemap` (`link`, id_shop) VALUES (\'' . pSQL($sitemap) . '\', ' . (int) $this->context->shop->id . ')'); } return false; } /** * @param array $link_sitemap contain all the links for the Google sitemap file to be generated * @param string $lang the language of link to add * @param int $index the index of the current Google sitemap file * * @return bool */ protected function recursiveSitemapCreator($link_sitemap, $lang, &$index) { if (!count($link_sitemap)) { return false; } $sitemap_link = $this->context->shop->id . '_' . $lang . '_' . $index . '_sitemap.xml'; $write_fd = fopen($this->normalizeDirectory(_PS_ROOT_DIR_) . $sitemap_link, 'wb'); fwrite($write_fd, '' . PHP_EOL . '' . PHP_EOL); foreach ($link_sitemap as $file) { fwrite($write_fd, '' . PHP_EOL); $lastmod = (isset($file['lastmod']) && !empty($file['lastmod'])) ? date('c', strtotime($file['lastmod'])) : null; $this->addSitemapNode($write_fd, htmlspecialchars(strip_tags($file['link'])), $this->getPriorityPage($file['page']), Configuration::get('GSITEMAP_FREQUENCY'), $lastmod); $images = []; if (isset($file['image']) && $file['image']) { $images[] = $file['image']; } if (isset($file['images']) && $file['images']) { $images = array_merge($images, $file['images']); } foreach ($images as $image) { $this->addSitemapNodeImage($write_fd, htmlspecialchars(strip_tags($image['link'])), isset($image['title_img']) ? htmlspecialchars(str_replace([ "\r\n", "\r", "\n", ], '', $this->removeControlCharacters(strip_tags($image['title_img'])))) : '', isset($image['caption']) ? htmlspecialchars(str_replace([ "\r\n", "\r", "\n", ], '', strip_tags($image['caption']))) : ''); } fwrite($write_fd, '' . PHP_EOL); } fwrite($write_fd, '' . PHP_EOL); fclose($write_fd); $this->saveSitemapLink($sitemap_link); ++$index; return true; } /** * Returns the priority value set in the configuration parameters. * Falls back to 0.1 for things that don't have priority. * * @param string $page * * @return float|string|bool */ protected function getPriorityPage($page) { return Configuration::get('GSITEMAP_PRIORITY_' . Tools::strtoupper($page)) ? Configuration::get('GSITEMAP_PRIORITY_' . Tools::strtoupper($page)) : 0.1; } /** * Add a new line to the sitemap file * * @param resource $fd file system object resource * @param string $loc string the URL of the object page * @param string $priority * @param string $change_freq * @param string $last_mod the last modification date/time as a timestamp */ protected function addSitemapNode($fd, $loc, $priority, $change_freq, $last_mod = null) { fwrite( $fd, '' . (Configuration::get('PS_REWRITING_SETTINGS') ? '' : $loc) . '' . PHP_EOL . ($last_mod ? '' . date('c', strtotime($last_mod)) . '' : '') . PHP_EOL . '' . $change_freq . '' . PHP_EOL . '' . number_format((float) $priority, 1, '.', '') . '' . PHP_EOL ); } protected function addSitemapNodeImage($fd, $link, $title, $caption) { fwrite($fd, '' . PHP_EOL . '' . (Configuration::get('PS_REWRITING_SETTINGS') ? '' : $link) . '' . PHP_EOL . '' . PHP_EOL . '' . PHP_EOL . '' . PHP_EOL); } /** * Create the index file for all generated sitemaps * * @return bool */ protected function createIndexSitemap() { $sitemaps = Db::getInstance()->ExecuteS('SELECT `link` FROM `' . _DB_PREFIX_ . 'gsitemap_sitemap` WHERE id_shop = ' . $this->context->shop->id); if (!$sitemaps) { return false; } $xml = ''; $xml_feed = new SimpleXMLElement($xml); foreach ($sitemaps as $link) { $sitemap = $xml_feed->addChild('sitemap'); $sitemap->addChild('loc', $this->context->link->getBaseLink() . $link['link']); $sitemap->addChild('lastmod', date('c')); } file_put_contents($this->normalizeDirectory(_PS_ROOT_DIR_) . $this->context->shop->id . '_index_sitemap.xml', $xml_feed->asXML()); return true; } protected function normalizeDirectory($directory) { $last = $directory[Tools::strlen($directory) - 1]; if (in_array($last, [ '/', '\\', ])) { $directory[Tools::strlen($directory) - 1] = DIRECTORY_SEPARATOR; return $directory; } $directory .= DIRECTORY_SEPARATOR; return $directory; } protected function removeControlCharacters($text) { $text = (string) preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $text); $text = (string) preg_replace('!\s+!', ' ', $text); return $text; } }