first commit

This commit is contained in:
2024-11-05 12:22:50 +01:00
commit e5682a3912
19641 changed files with 2948548 additions and 0 deletions

View File

@@ -0,0 +1,200 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PrestashopFacebook\Provider;
use Controller;
use GuzzleHttp\Psr7\Request;
use PrestaShop\Module\PrestashopFacebook\Adapter\ConfigurationAdapter;
use PrestaShop\Module\PrestashopFacebook\API\ResponseListener;
use PrestaShop\Module\PrestashopFacebook\Config\Config;
use PrestaShop\Module\PrestashopFacebook\Exception\AccessTokenException;
use PrestaShop\Module\PrestashopFacebook\Factory\ApiClientFactoryInterface;
class AccessTokenProvider
{
/**
* @var ConfigurationAdapter
*/
private $configurationAdapter;
/**
* @var Controller
*/
private $controller;
/**
* @var ApiClientFactoryInterface
*/
private $psApiClientFactory;
/**
* @var ResponseListener
*/
private $responseListener;
/**
* @var string
*/
private $userAccessToken;
/**
* @var string|null
*/
private $systemAccessToken;
public function __construct(
ConfigurationAdapter $configurationAdapter,
ResponseListener $responseListener,
$controller,
ApiClientFactoryInterface $psApiClientFactory
) {
$this->configurationAdapter = $configurationAdapter;
$this->responseListener = $responseListener;
$this->controller = $controller;
$this->psApiClientFactory = $psApiClientFactory;
}
/**
* @return string
*/
public function getUserAccessToken()
{
if (!$this->userAccessToken) {
$this->getOrRefreshTokens();
}
return $this->userAccessToken;
}
/**
* @return string|null
*/
public function getSystemAccessToken()
{
if (!$this->systemAccessToken) {
$this->getOrRefreshTokens();
}
return $this->systemAccessToken;
}
/**
* Load data from configuration table and request from API them if something is missing
*/
private function getOrRefreshTokens()
{
$this->userAccessToken = $this->configurationAdapter->get(Config::PS_FACEBOOK_USER_ACCESS_TOKEN);
$this->systemAccessToken = $this->configurationAdapter->get(Config::PS_FACEBOOK_SYSTEM_ACCESS_TOKEN);
$tokenExpirationDate = $this->configurationAdapter->get(Config::PS_FACEBOOK_USER_ACCESS_TOKEN_EXPIRATION_DATE);
$currentTimestamp = time();
if ((!$this->systemAccessToken
|| !$tokenExpirationDate
|| ($tokenExpirationDate - $currentTimestamp <= 86400))
&& isset($this->controller->controller_type)
&& $this->controller->controller_type === 'moduleadmin'
&& $this->userAccessToken
) {
$this->refreshTokens();
}
}
/**
* Exchange existing tokens with new ones, then store them in the DB + make them available in this class
*
* @return void
*/
public function refreshTokens()
{
$externalBusinessId = $this->configurationAdapter->get(Config::PS_FACEBOOK_EXTERNAL_BUSINESS_ID);
$accessToken = $this->configurationAdapter->get(Config::PS_FACEBOOK_USER_ACCESS_TOKEN);
$managerId = $this->configurationAdapter->get(Config::PS_FACEBOOK_BUSINESS_MANAGER_ID);
if (!$managerId) {
// Force as null, otherwise it gets a falsy value in the API request
$managerId = null;
}
$response = $this->responseListener->handleResponse(
$this->psApiClientFactory->createClient()->sendRequest(
new Request(
'POST',
'/account/' . $externalBusinessId . '/exchange_tokens',
[],
json_encode([
'userAccessToken' => $accessToken,
'businessManagerId' => $managerId,
])
)
),
[
'exceptionClass' => AccessTokenException::class,
]
);
if (!$response->isSuccessful()) {
return;
}
$responseContent = $response->getBody();
if (isset($responseContent['longLived']['access_token'])) {
$tokenExpiresIn = time() + (70 * 365 * 24 * 3600); // never expires
$this->userAccessToken = $responseContent['longLived']['access_token'];
$this->configurationAdapter->updateValue(Config::PS_FACEBOOK_USER_ACCESS_TOKEN, $this->userAccessToken);
$this->configurationAdapter->updateValue(Config::PS_FACEBOOK_USER_ACCESS_TOKEN_EXPIRATION_DATE, $tokenExpiresIn);
}
if (isset($responseContent['system']['access_token'])) {
$this->systemAccessToken = $responseContent['system']['access_token'];
$this->configurationAdapter->updateValue(Config::PS_FACEBOOK_SYSTEM_ACCESS_TOKEN, $this->systemAccessToken);
}
}
/**
* Exchange existing tokens with new ones, then store them in the DB + make them available in this class
*
* @return array|null
*/
public function retrieveTokens()
{
$externalBusinessId = $this->configurationAdapter->get(Config::PS_FACEBOOK_EXTERNAL_BUSINESS_ID);
$response = $this->responseListener->handleResponse(
$this->psApiClientFactory->createClient()->sendRequest(
new Request(
'GET',
'/account/' . $externalBusinessId . '/app_tokens'
)
),
[
'exceptionClass' => AccessTokenException::class,
]
);
if (!$response->isSuccessful()) {
return null;
}
return $response->getBody();
}
}

View File

@@ -0,0 +1,553 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PrestashopFacebook\Provider;
use Cart;
use Context;
use Order;
use PrestaShop\Module\PrestashopFacebook\Adapter\ConfigurationAdapter;
use PrestaShop\Module\PrestashopFacebook\Adapter\ToolsAdapter;
use PrestaShop\Module\PrestashopFacebook\Repository\GoogleCategoryRepository;
use PrestaShop\Module\PrestashopFacebook\Repository\ProductRepository;
use PrestaShop\Module\Ps_facebook\Utility\CustomerInformationUtility;
use PrestaShop\Module\Ps_facebook\Utility\ProductCatalogUtility;
use PrestaShopException;
use Product;
use Ps_facebook;
class EventDataProvider
{
public const PRODUCT_TYPE = 'product';
public const CATEGORY_TYPE = 'product_group';
/**
* @var Context
*/
private $context;
private $locale;
/**
* @var ToolsAdapter
*/
private $toolsAdapter;
/**
* @var ConfigurationAdapter
*/
private $configurationAdapter;
/**
* @var ProductRepository
*/
private $productRepository;
/**
* @var ps_facebook
*/
private $module;
/**
* @var ProductAvailabilityProviderInterface
*/
private $availabilityProvider;
/**
* @var GoogleCategoryRepository
*/
private $googleCategoryRepository;
/**
* @var GoogleCategoryProvider
*/
private $googleCategoryProvider;
public function __construct(
ToolsAdapter $toolsAdapter,
ConfigurationAdapter $configurationAdapter,
ProductRepository $productRepository,
Context $context,
ps_facebook $module,
ProductAvailabilityProviderInterface $availabilityProvider,
GoogleCategoryRepository $googleCategoryRepository,
GoogleCategoryProvider $googleCategoryProvider
) {
$this->toolsAdapter = $toolsAdapter;
$this->context = $context;
$this->locale = \Tools::strtoupper($this->context->language->iso_code);
$this->configurationAdapter = $configurationAdapter;
$this->productRepository = $productRepository;
$this->module = $module;
$this->availabilityProvider = $availabilityProvider;
$this->googleCategoryRepository = $googleCategoryRepository;
$this->googleCategoryProvider = $googleCategoryProvider;
}
public function generateEventData($name, array $params)
{
switch ($name) {
case 'hookDisplayHeader':
if (true === \Tools::isSubmit('submitCustomizedData')) {
return $this->getCustomEventData();
}
if ($this->context->controller instanceof \ProductControllerCore) {
return $this->getProductPageData();
}
if ($this->context->controller instanceof \CategoryControllerCore) {
return $this->getCategoryPageData();
}
if ($this->context->controller instanceof \CmsControllerCore) {
return $this->getCMSPageData();
}
break;
case 'hookActionSearch':
return $this->getSearchEventData($params);
case 'hookActionObjectCustomerMessageAddAfter':
return $this->getContactEventData();
case 'hookDisplayOrderConfirmation':
return $this->getOrderConfirmationEvent($params);
case 'InitiateCheckout':
return $this->getInitiateCheckoutEvent();
case 'hookActionCartSave':
return $this->getAddToCartEventData();
case 'hookActionNewsletterRegistrationAfter':
return $this->getShopSubscriptionEvent($params);
case 'hookActionCustomerAccountAdd':
return $this->getCompleteRegistrationEventData();
case 'customizeProduct':
return $this->getCustomisationEventData($params);
case 'hookActionFacebookCallPixel':
return $this->getCustomEvent($params);
}
return false;
}
private function getProductPageData()
{
$type = 'ViewContent';
/** @var \ProductControllerCore $controller */
$controller = $this->context->controller;
$product = $controller->getTemplateVarProduct();
$fbProductId = ProductCatalogUtility::makeProductId(
$product['id_product'],
$product['id_product_attribute']
);
$productUrl = $this->context->link->getProductLink($product['id']);
$categoryPath = $this->googleCategoryProvider->getCategoryPaths(
$product['id_category_default'],
$this->context->language->id,
$this->context->shop->id
);
$content = [
'id' => $fbProductId,
'title' => \Tools::replaceAccentedChars($product['name']),
'category' => $categoryPath['category_path'],
'item_price' => $product['price_tax_exc'],
'brand' => (new \Manufacturer($product['id_manufacturer']))->name,
];
$customData = [
'currency' => $this->getCurrency(),
'content_ids' => [$fbProductId],
'contents' => [$content],
'content_type' => self::PRODUCT_TYPE,
'value' => $product['price_tax_exc'],
];
$category = $this->googleCategoryRepository->getGoogleCategoryIdByCategoryId(
$product['id_category_default'],
$this->context->shop->id
) ?: '';
$this->context->smarty->assign(
[
'retailer_item_id' => $fbProductId,
'product_availability' => $this->availabilityProvider->getProductAvailability(
(int) $product['id_product'],
(int) $product['id_product_attribute']
),
'item_group_id' => $category,
]
);
return [
'custom_data' => $customData,
'event_source_url' => $productUrl,
] + $this->getCommonData($type);
}
private function getCategoryPageData()
{
$type = 'ViewCategory';
/** @var \CategoryControllerCore $controller */
$controller = $this->context->controller;
$category = $controller->getCategory();
$page = $this->toolsAdapter->getValue('page');
$resultsPerPage = $this->configurationAdapter->get('PS_PRODUCTS_PER_PAGE');
$prods = $category->getProducts($this->context->language->id, $page, $resultsPerPage);
$categoryUrl = $this->context->link->getCategoryLink($category->id);
$breadcrumbs = $controller->getBreadcrumbLinks();
$breadcrumb = implode(' > ', array_column($breadcrumbs['links'], 'title'));
$contentIds = [];
if ($prods) {
foreach ($prods as $product) {
$contentIds[] = ProductCatalogUtility::makeProductId(
$product['id_product'],
$product['id_product_attribute']
);
}
}
$customData = [
'content_name' => \Tools::replaceAccentedChars($category->name) . ' ' . $this->locale,
'content_category' => \Tools::replaceAccentedChars($breadcrumb),
'content_type' => self::CATEGORY_TYPE,
'content_ids' => $contentIds ?: null,
];
return [
'custom_data' => $customData,
'event_source_url' => $categoryUrl,
] + $this->getCommonData($type);
}
private function getCMSPageData()
{
$type = 'ViewCMS';
$cms = new \CMS((int) $this->toolsAdapter->getValue('id_cms'), $this->context->language->id);
/** @var \CmsControllerCore $controller */
$controller = $this->context->controller;
$breadcrumbs = $controller->getBreadcrumbLinks();
$breadcrumb = implode(' > ', array_column($breadcrumbs['links'], 'title'));
$customData = [
'content_name' => \Tools::replaceAccentedChars($cms->meta_title) . ' ' . $this->locale,
'content_category' => \Tools::replaceAccentedChars($breadcrumb),
'content_type' => self::PRODUCT_TYPE,
];
return [
'custom_data' => $customData,
] + $this->getCommonData($type);
}
private function getAddToCartEventData()
{
$action = $this->toolsAdapter->getValue('action');
$quantity = $this->toolsAdapter->getValue('qty');
$idProduct = $this->toolsAdapter->getValue('id_product');
$op = $this->toolsAdapter->getValue('op');
$isDelete = $this->toolsAdapter->getValue('delete');
$idProductAttribute = $this->toolsAdapter->getValue('id_product_attribute');
$attributeGroups = $this->toolsAdapter->getValue('group');
if ($attributeGroups) {
try {
$idProductAttribute = $this->productRepository->getIdProductAttributeByIdAttributes(
$idProduct,
$attributeGroups
);
} catch (PrestaShopException $e) {
return false;
}
}
if ($action !== 'update') {
return false;
}
$type = 'AddToCart';
if ($op) {
$type = $op === 'up' ? 'IncreaseProductQuantityInCart' : 'DecreaseProductQuantityInCart';
} elseif ($isDelete) {
//todo: when removing product from cart this hook gets called twice
$type = 'RemoveProductFromCart';
$quantity = null;
}
$productName = Product::getProductName($idProduct, $idProductAttribute);
$customData = [
'content_name' => pSQL($productName),
'content_type' => self::PRODUCT_TYPE,
'content_ids' => [
ProductCatalogUtility::makeProductId(
$idProduct,
$idProductAttribute
),
],
'num_items' => pSQL($quantity),
];
return [
'custom_data' => $customData,
] + $this->getCommonData($type);
}
private function getCompleteRegistrationEventData()
{
$type = 'CompleteRegistration';
$customData = [
'content_name' => 'authentication',
];
return [
'custom_data' => $customData,
] + $this->getCommonData($type);
}
private function getContactEventData()
{
return $this->getCommonData('Contact');
}
private function getCustomisationEventData($params)
{
$type = 'CombinationProduct';
$idLang = (int) $this->context->language->id;
$productId = $this->toolsAdapter->getValue('id_product');
$attributeIds = $params['attributeIds'];
$customData = $this->getCustomAttributeData($productId, $idLang, $attributeIds);
return [
'custom_data' => $customData,
] + $this->getCommonData($type);
}
private function getCustomEventData()
{
return $this->getCommonData('CustomizeProduct');
}
private function getSearchEventData($params)
{
$searchQuery = $params['searched_query'];
$quantity = $params['total'];
$type = 'Search';
$customData = [
'content_name' => 'searchQuery',
'search_string' => $searchQuery,
];
if ($quantity) {
$customData['num_items'] = $quantity;
}
return [
'custom_data' => $customData,
] + $this->getCommonData($type);
}
private function getOrderConfirmationEvent($params)
{
/** @var Order $order */
$order = $this->module->psVersionIs17 ? $params['order'] : $params['objOrder'];
$productList = [];
foreach ($order->getCartProducts() as $product) {
$productList[] = ProductCatalogUtility::makeProductId(
$product['id_product'],
$product['id_product_attribute']
);
}
$type = 'Purchase';
$customData = [
'content_name' => 'purchased',
'order_id' => $order->id,
'currency' => $this->getCurrency(),
'content_ids' => $productList,
'content_type' => self::PRODUCT_TYPE,
'value' => (float) ($order->total_paid_tax_excl),
];
return [
'custom_data' => $customData,
] + $this->getCommonData($type);
}
private function getInitiateCheckoutEvent()
{
$type = 'InitiateCheckout';
$cart = $this->context->cart;
$contents = $this->getProductContent($cart);
$customData = [
'contents' => $contents,
'content_type' => 'product',
'currency' => $this->getCurrency(),
'value' => $cart->getOrderTotal(false),
];
return [
'custom_data' => $customData,
] + $this->getCommonData($type);
}
private function getShopSubscriptionEvent($params)
{
$type = 'Subscribe';
$customData = [
'content_name' => pSQL($params['email']),
];
return [
'custom_data' => $customData,
] + $this->getCommonData($type);
}
/**
* @param Cart $cart
*
* @return array
*/
private function getProductContent(Cart $cart)
{
$contents = [];
foreach ($cart->getProducts() as $product) {
$categoryPath = $this->googleCategoryProvider->getCategoryPaths(
$product['id_category_default'],
$this->context->language->id,
$this->context->shop->id
);
$content = [
'id' => ProductCatalogUtility::makeProductId($product['id_product'], $product['id_product_attribute']),
'quantity' => $product['quantity'],
'item_price' => $product['price'],
'title' => \Tools::replaceAccentedChars($product['name']),
'brand' => (new \Manufacturer($product['id_manufacturer']))->name,
'category' => $categoryPath['category_path'],
];
$contents[] = $content;
}
return $contents;
}
/**
* @param array $params
*
* @return array|null
*/
private function getCustomEvent($params)
{
if (!isset($params['eventName']) || !isset($params['module'])) {
return null;
}
$type = pSQL($params['eventName']);
$customData = [
'custom_properties' => [
'module' => pSQL($params['module']),
],
];
if (isset($params['id_product'])) {
$fbProductId = ProductCatalogUtility::makeProductId(
$params['id_product'],
isset($params['id_product_attribute']) ? $params['id_product_attribute'] : 0
);
$customData['content_ids']['module'] = $fbProductId;
}
return [
'custom_data' => $customData,
] + $this->getCommonData($type);
}
/**
* @param int $productId
* @param int $idLang
* @param int[] $attributeIds
*
* @return array
*
* @throws \PrestaShopException
*/
private function getCustomAttributeData($productId, $idLang, $attributeIds)
{
$attributes = [];
foreach ($attributeIds as $attributeId) {
$attributes[] = (new \AttributeCore($attributeId, $idLang))->name;
}
try {
$idProductAttribute = $this->productRepository->getIdProductAttributeByIdAttributes(
$productId,
$attributeIds
);
} catch (PrestaShopException $e) {
$idProductAttribute = 0;
}
return [
'content_type' => self::PRODUCT_TYPE,
'content_ids' => [
ProductCatalogUtility::makeProductId($productId, $idProductAttribute),
],
'custom_properties' => [
'custom_attributes' => $attributes,
],
];
}
/**
* Generate the array with data that are used for all events
*
* @see https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events
*
* @param string $eventType
*/
private function getCommonData($eventType)
{
$time = time();
return [
'event_type' => $eventType,
'event_time' => $time,
'user' => CustomerInformationUtility::getCustomerInformationForPixel($this->context->customer),
'eventID' => uniqid($eventType . '_' . $time . '_', true),
];
}
private function getCurrency()
{
return \Tools::strtolower($this->context->currency->iso_code);
}
}

View File

@@ -0,0 +1,92 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PrestashopFacebook\Provider;
use PrestaShop\Module\PrestashopFacebook\Adapter\ConfigurationAdapter;
use PrestaShop\Module\PrestashopFacebook\API\Client\FacebookClient;
use PrestaShop\Module\PrestashopFacebook\Config\Config;
use PrestaShop\Module\PrestashopFacebook\DTO\ContextPsFacebook;
use PrestaShop\Module\PrestashopFacebook\DTO\Object\Catalog;
class FacebookDataProvider
{
/**
* @var FacebookClient
*/
protected $facebookClient;
/**
* @var ConfigurationAdapter
*/
private $configurationAdapter;
public function __construct(FacebookClient $facebookClient, ConfigurationAdapter $configurationAdapter)
{
$this->facebookClient = $facebookClient;
$this->configurationAdapter = $configurationAdapter;
}
/**
* https://github.com/facebookarchive/php-graph-sdk
* https://developers.facebook.com/docs/graph-api/changelog/version8.0
**
* @param array $fbe
*
* @return ContextPsFacebook|null
*/
public function getContext(array $fbe)
{
if (isset($fbe['error']) || !$this->facebookClient->hasAccessToken()) {
return null;
}
$externalBusinessId = $this->configurationAdapter->get(Config::PS_FACEBOOK_EXTERNAL_BUSINESS_ID);
$hasFbeFeatures = (bool) $this->facebookClient->getFbeFeatures($externalBusinessId);
if (!$hasFbeFeatures) {
return null;
}
$user = $this->facebookClient->getUserEmail();
$businessManager = $this->facebookClient->getBusinessManager($fbe['business_manager_id']);
$pixel = $this->facebookClient->getPixel($fbe['ad_account_id'], $fbe['pixel_id']);
$pages = $this->facebookClient->getPage($fbe['pages']);
$ad = $this->facebookClient->getAd($fbe['ad_account_id']);
$productSyncStarted = (bool) $this->configurationAdapter->get(Config::PS_FACEBOOK_PRODUCT_SYNC_FIRST_START);
$categoryMatchingStarted = false; // TODO : must be true only if all parent categories are matched !
$catalog = new Catalog($fbe['catalog_id'], $productSyncStarted, $categoryMatchingStarted);
return new ContextPsFacebook(
$user,
$businessManager,
$pixel,
$pages,
$ad,
$catalog
);
}
public function getProductsInCatalogCount()
{
$catalogId = $this->configurationAdapter->get(Config::PS_FACEBOOK_CATALOG_ID);
return $this->facebookClient->getProductsInCatalogCount($catalogId);
}
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PrestashopFacebook\Provider;
use PrestaShop\Module\PrestashopFacebook\Adapter\ConfigurationAdapter;
use PrestaShop\Module\PrestashopFacebook\Config\Config;
class FbeDataProvider
{
/**
* @var ConfigurationAdapter
*/
private $configurationAdapter;
public function __construct(ConfigurationAdapter $configurationAdapter)
{
$this->configurationAdapter = $configurationAdapter;
}
/**
* @return array
*/
public function getFbeData()
{
return [
'pixel_id' => $this->configurationAdapter->get(Config::PS_PIXEL_ID),
'profiles' => $this->configurationAdapter->get(Config::PS_FACEBOOK_PROFILES),
'pages' => [
$this->configurationAdapter->get(Config::PS_FACEBOOK_PAGES),
],
'business_manager_id' => $this->configurationAdapter->get(Config::PS_FACEBOOK_BUSINESS_MANAGER_ID),
'catalog_id' => $this->configurationAdapter->get(Config::PS_FACEBOOK_CATALOG_ID),
'ad_account_id' => $this->configurationAdapter->get(Config::PS_FACEBOOK_AD_ACCOUNT_ID),
];
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PrestashopFacebook\Provider;
use PrestaShop\Module\PrestashopFacebook\Adapter\ConfigurationAdapter;
use PrestaShop\Module\PrestashopFacebook\API\Client\FacebookClient;
use PrestaShop\Module\PrestashopFacebook\Config\Config;
class FbeFeatureDataProvider
{
/**
* @var FacebookClient
*/
private $facebookClient;
/**
* @var ConfigurationAdapter
*/
private $configurationAdapter;
public function __construct(FacebookClient $facebookClient, ConfigurationAdapter $configurationAdapter)
{
$this->facebookClient = $facebookClient;
$this->configurationAdapter = $configurationAdapter;
}
public function getFbeFeatures()
{
if (!$this->facebookClient->hasAccessToken()) {
return false;
}
$externalBusinessId = $this->configurationAdapter->get(Config::PS_FACEBOOK_EXTERNAL_BUSINESS_ID);
$features = $this->facebookClient->getFbeFeatures($externalBusinessId);
$unavailableFeatures = [];
$productsSynced = $this->configurationAdapter->get(Config::PS_FACEBOOK_PRODUCT_SYNC_ON);
$features = array_filter($features, function ($key) {
return in_array($key, Config::AVAILABLE_FBE_FEATURES);
}, ARRAY_FILTER_USE_KEY);
foreach ($features as $featureName => $feature) {
if ($feature['enabled'] || $this->configurationAdapter->get(Config::FBE_FEATURE_CONFIGURATION . $featureName) !== false) {
$this->configurationAdapter->updateValue(Config::FBE_FEATURE_CONFIGURATION . $featureName, json_encode($feature));
}
}
$enabledFeatures = array_filter($features, function ($featureName) {
return $this->configurationAdapter->get(Config::FBE_FEATURE_CONFIGURATION . $featureName) !== false;
}, ARRAY_FILTER_USE_KEY);
$disabledFeatures = array_filter($features, function ($featureName) {
return $this->configurationAdapter->get(Config::FBE_FEATURE_CONFIGURATION . $featureName) === false;
}, ARRAY_FILTER_USE_KEY);
if (!$productsSynced) {
$unavailableFeatures = array_filter($features, function ($key) use ($enabledFeatures) {
return in_array($key, Config::FBE_FEATURES_REQUIRING_PRODUCT_SYNC)
&& in_array($key, $enabledFeatures);
}, ARRAY_FILTER_USE_KEY);
}
return [
'enabledFeatures' => $enabledFeatures,
'disabledFeatures' => $disabledFeatures,
'unavailableFeatures' => $unavailableFeatures,
];
}
}

View File

@@ -0,0 +1,161 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PrestashopFacebook\Provider;
use Category;
use PrestaShop\Module\PrestashopFacebook\Config\Config;
use PrestaShop\Module\PrestashopFacebook\Repository\GoogleCategoryRepository;
class GoogleCategoryProvider implements GoogleCategoryProviderInterface
{
/**
* @var GoogleCategoryRepository
*/
private $googleCategoryRepository;
public function __construct(
GoogleCategoryRepository $googleCategoryRepository
) {
$this->googleCategoryRepository = $googleCategoryRepository;
}
/**
* @param int $categoryId
* @param int $shopId
*
* @return array|null
*/
public function getGoogleCategory($categoryId, $shopId)
{
$categoryMatch = $this->googleCategoryRepository->getCategoryMatchByCategoryId($categoryId, $shopId);
if (!is_array($categoryMatch)) {
return null;
}
return $categoryMatch;
}
/**
* @param int $categoryId
* @param int $langId
* @param int $shopId
* @param int $page
*
* @return array|null
*/
public function getGoogleCategoryChildren($categoryId, $langId, $shopId, $page = 1)
{
if ($page < 1) {
$page = 1;
}
$googleCategories = $this->googleCategoryRepository->getFilteredCategories(
$categoryId,
$langId,
Config::CATEGORIES_PER_PAGE * ($page - 1),
Config::CATEGORIES_PER_PAGE,
$shopId
);
if (!is_array($googleCategories)) {
return null;
}
return $googleCategories;
}
/**
* @param int $shopId
*
* @return array
*/
public function getInformationAboutCategoryMatches($shopId)
{
$numberOfMatchedCategories = $this->googleCategoryRepository->getNumberOfMatchedCategories($shopId);
$totalCategories = $this->googleCategoryRepository->getNumberOfTotalCategories($shopId);
return [
'matched' => $numberOfMatchedCategories,
'total' => $totalCategories,
];
}
/**
* @param array $categoryIds
* @param int $shopId
*
* @return array|null
*
* @throws \PrestaShopDatabaseException
*/
public function getGoogleCategories(array $categoryIds, $shopId)
{
$categoryMatch = $this->googleCategoryRepository->getCategoryMatchesByCategoryIds($categoryIds, $shopId);
if (!is_array($categoryMatch)) {
return null;
}
return $categoryMatch;
}
public function getCategoryPaths($topCategoryId, $langId, $shopId)
{
if ((int) $topCategoryId === 0) {
return [
'category_path' => '',
'category_id_path' => '',
];
}
$categoryId = $topCategoryId;
$categories = [];
try {
$categoriesWithParentsInfo = $this->googleCategoryRepository->getCategoriesWithParentInfo($langId, $shopId);
} catch (\PrestaShopDatabaseException $e) {
return [
'category_path' => '',
'category_id_path' => '',
];
}
$homeCategory = Category::getTopCategory()->id;
$categoryExists = true;
while ((int) $categoryId != $homeCategory && $categoryExists) {
$categoryExists = false;
foreach ($categoriesWithParentsInfo as $category) {
if ($category['id_category'] == $categoryId) {
$categories[] = $category;
$categoryId = $category['id_parent'];
$categoryExists = true;
break;
}
}
}
$categories = array_reverse($categories);
return [
'category_path' => implode(' > ', array_map(function ($category) {
return $category['name'];
}, $categories)),
'category_id_path' => implode(' > ', array_map(function ($category) {
return $category['id_category'];
}, $categories)),
];
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PrestashopFacebook\Provider;
interface GoogleCategoryProviderInterface
{
/**
* @param int $categoryId
* @param int $shopId
*
* @return array|null
*/
public function getGoogleCategory($categoryId, $shopId);
/**
* @param int $categoryId
* @param int $langId
* @param int $shopId
* @param int $page
*
* @return array|null
*/
public function getGoogleCategoryChildren($categoryId, $langId, $shopId, $page);
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PrestashopFacebook\Provider;
use PrestaShop\Module\PrestashopFacebook\Repository\ShopRepository;
use PrestaShop\Module\Ps_facebook\Tracker\Segment;
use Shop;
class MultishopDataProvider
{
/**
* @var ShopRepository
*/
private $shopRepository;
/**
* @var Segment
*/
private $segment;
public function __construct(
ShopRepository $shopRepository,
Segment $segment
) {
$this->shopRepository = $shopRepository;
$this->segment = $segment;
}
/**
* It appeared that PS Account is currently incompatible with multistore feature.
* While a new major version is prepared, we display a message if the merchant
* onboarded one other shop.
*
* To revent this, we check if a shop is already onboarded and
* warn the merchant accordingly.
*
* @param Shop $currentShop
*
* @return bool
*/
public function isCurrentShopInConflict(Shop $currentShop)
{
$configurationData = $this->shopRepository->getShopDomainsAndConfiguration();
foreach ($configurationData as $shopData) {
if ((int) $shopData['id_shop'] === (int) $currentShop->id) {
continue;
}
if (empty($shopData['acces_token_value'])) {
continue;
}
$this->segment->setMessage('Error: Warn about multistore incompatibility with PS Account');
$this->segment->track();
return true;
}
return false;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PrestashopFacebook\Provider;
class PrevalidationScanDataProvider
{
/**
* @var PrevalidationScanCacheProvider
*/
protected $preValidationCacheProvider;
public function __construct(PrevalidationScanCacheProvider $preValidationCacheProvider)
{
$this->preValidationCacheProvider = $preValidationCacheProvider;
}
/**
* @return array|null
*/
public function getPrevalidationScanSummary($shopId)
{
return json_decode($this->preValidationCacheProvider->get(
PrevalidationScanCacheProvider::CACHE_KEY_SUMMARY . $shopId
));
}
/**
* @param int $page
* @param int $shopId
*
* @return array
*/
public function getPageOfPrevalidationScan($shopId, $page)
{
$cacheContent = json_decode($this->preValidationCacheProvider->get(
PrevalidationScanCacheProvider::CACHE_KEY_PAGE . $shopId . '_' . $page
));
if (empty($cacheContent)) {
return [];
}
return $cacheContent;
}
}

View File

@@ -0,0 +1,93 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PrestashopFacebook\Provider;
use Ps_facebook;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
class PrevalidationScanCacheProvider
{
public const CACHE_KEY_SUMMARY = 'summary_';
public const CACHE_KEY_PAGE = 'page_';
/**
* @var string
*/
protected $cachePath;
/**
* @var Filesystem
*/
protected $filesystem;
/**
* @param string $cachePath
*/
public function __construct(Ps_facebook $module, $cachePath)
{
$this->cachePath = $cachePath . '/' . $module->name . '/';
$this->filesystem = new Filesystem();
}
/**
* @param string $cacheKey
*/
public function get($cacheKey)
{
if (!file_exists($this->getCacheFilePath($cacheKey))) {
return null;
}
return file_get_contents($this->getCacheFilePath($cacheKey));
}
/**
* @param string $cacheKey
* @param string $content
*/
public function set($cacheKey, $content)
{
$this->filesystem->dumpFile($this->getCacheFilePath($cacheKey), $content);
}
public function clear()
{
$this->filesystem->mkdir($this->cachePath);
$finder = Finder::create();
$files = $finder->files()->in($this->cachePath)->name('*.json');
foreach ($files as $file) {
$this->filesystem->remove($file);
}
}
/**
* @param string $cacheKey
*
* @return string
*/
private function getCacheFilePath($cacheKey)
{
return $this->cachePath . $cacheKey . '.json';
}
}

View File

@@ -0,0 +1,58 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PrestashopFacebook\Provider;
use FacebookAds\Object\Values\ProductItemAvailabilityValues;
use Product;
use StockAvailable;
class ProductAvailabilityProvider implements ProductAvailabilityProviderInterface
{
/**
* @param int $productId
* @param int $productAttributeId
*
* @return string
*
* @throws \PrestaShopDatabaseException
* @throws \PrestaShopException
*/
public function getProductAvailability($productId, $productAttributeId)
{
$product = new Product($productId);
if ((int) StockAvailable::getQuantityAvailableByProduct($productId, $productAttributeId)) {
return ProductItemAvailabilityValues::IN_STOCK;
}
switch ($product->out_of_stock) {
case 1:
return ProductItemAvailabilityValues::AVAILABLE_FOR_ORDER;
case 2:
$isAvailable = Product::isAvailableWhenOutOfStock($product->out_of_stock);
return $isAvailable ? ProductItemAvailabilityValues::AVAILABLE_FOR_ORDER : ProductItemAvailabilityValues::OUT_OF_STOCK;
case 0:
default:
return ProductItemAvailabilityValues::OUT_OF_STOCK;
}
}
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PrestashopFacebook\Provider;
interface ProductAvailabilityProviderInterface
{
/**
* @param int $productId
*
* @return string
*/
public function getProductAvailability($productId, $productAttributeId);
}

View File

@@ -0,0 +1,107 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
namespace PrestaShop\Module\PrestashopFacebook\Provider;
use GuzzleHttp\Psr7\Request;
use PrestaShop\Module\PrestashopFacebook\Adapter\ConfigurationAdapter;
use PrestaShop\Module\PrestashopFacebook\API\ResponseListener;
use PrestaShop\Module\PrestashopFacebook\Config\Config;
use PrestaShop\Module\PrestashopFacebook\Exception\FacebookAccountException;
use PrestaShop\Module\PrestashopFacebook\Factory\ApiClientFactoryInterface;
use Psr\Http\Client\ClientInterface;
class ProductSyncReportProvider
{
/**
* @var ConfigurationAdapter
*/
private $configurationAdapter;
/**
* @var ClientInterface
*/
private $psApiClient;
/**
* @var ResponseListener
*/
private $responseListener;
public function __construct(
ConfigurationAdapter $configurationAdapter,
ApiClientFactoryInterface $psApiClientFactory,
ResponseListener $responseListener
) {
$this->configurationAdapter = $configurationAdapter;
$this->responseListener = $responseListener;
$this->psApiClient = $psApiClientFactory->createClient();
}
public function getProductSyncReport()
{
$businessId = $this->configurationAdapter->get(Config::PS_FACEBOOK_EXTERNAL_BUSINESS_ID);
$response = $this->responseListener->handleResponse(
$this->psApiClient->sendRequest(
new Request(
'GET',
"/account/{$businessId}/reporting"
)
),
[
'exceptionClass' => FacebookAccountException::class,
]
);
$responseContent = $response->getBody();
if (!$response->isSuccessful()) {
$responseContent = [];
}
return $this->fixMissingValues($responseContent);
}
private function fixMissingValues($response)
{
if (!isset($response['errors'])) {
$response['errors'] = [];
}
if (!isset($response['lastFinishedSyncStartedAt'])) {
$response['lastFinishedSyncStartedAt'] = 0;
}
$response['errors'] = array_filter($response['errors'], [$this, 'filterErrorsWithoutMessage']);
return $response;
}
/**
* Hotfix as the Nest API should not return products without message
*
* @return bool
*/
private function filterErrorsWithoutMessage(array $productInError)
{
return !empty($productInError);
}
}

View File

@@ -0,0 +1,28 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
header('Location: ../');
exit;