Helper/FBEHelper.php (402 lines of code) (raw):
<?php
/**
* Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved
*/
namespace Facebook\BusinessExtension\Helper;
use Facebook\BusinessExtension\Logger\Logger;
use Facebook\BusinessExtension\Model\ConfigFactory;
use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\App\Helper\Context;
use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\HTTP\Client\Curl;
use Magento\Framework\Module\ModuleListInterface;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\UrlInterface;
use FacebookAds\Object\ServerSide\AdsPixelSettings;
use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Model\StoreManagerInterface;
class FBEHelper extends AbstractHelper
{
const MAIN_WEBSITE_STORE = 'Main Website Store';
const MAIN_STORE = 'Main Store';
const MAIN_WEBSITE = 'Main Website';
const FB_GRAPH_BASE_URL = "https://graph.facebook.com/";
const DELETE_SUCCESS_MESSAGE = "You have successfully deleted Facebook Business Extension.
The pixel installed on your website is now deleted.";
const DELETE_FAILURE_MESSAGE = "There was a problem deleting the connection.
Please try again.";
const CURRENT_API_VERSION = "v9.0";
const MODULE_NAME = "Facebook_BusinessExtension";
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
protected $objectManager;
/**
* @var StoreManagerInterface
*/
protected $storeManager;
/**
* @var ConfigFactory
*/
protected $configFactory;
/**
* @var Logger
*/
protected $logger;
/**
* @var DirectoryList
*/
protected $directoryList;
/**
* @var Curl
*/
protected $curl;
/**
* @var ResourceConnection
*/
protected $resourceConnection;
/**
* @var ModuleListInterface
*/
protected $moduleList;
/**
* FBEHelper constructor
*
* @param Context $context
* @param ObjectManagerInterface $objectManager
* @param ConfigFactory $configFactory
* @param Logger $logger
* @param DirectoryList $directorylist
* @param StoreManagerInterface $storeManager
* @param Curl $curl
* @param ResourceConnection $resourceConnection
* @param ModuleListInterface $moduleList
*/
public function __construct(
Context $context,
ObjectManagerInterface $objectManager,
ConfigFactory $configFactory,
Logger $logger,
DirectoryList $directorylist,
StoreManagerInterface $storeManager,
Curl $curl,
ResourceConnection $resourceConnection,
ModuleListInterface $moduleList
) {
parent::__construct($context);
$this->objectManager = $objectManager;
$this->storeManager = $storeManager;
$this->configFactory = $configFactory;
$this->logger = $logger;
$this->directoryList = $directorylist;
$this->curl = $curl;
$this->resourceConnection = $resourceConnection;
$this->moduleList = $moduleList;
}
public function getPixelID()
{
return $this->getConfigValue('fbpixel/id');
}
public function getAccessToken()
{
return $this->getConfigValue('fbaccess/token');
}
/**
* @return mixed
*/
public function getMagentoVersion()
{
return $this->objectManager->get(ProductMetadataInterface::class)->getVersion();
}
/**
* @return mixed
*/
public function getPluginVersion()
{
return $this->moduleList->getOne(self::MODULE_NAME)['setup_version'];
}
/**
* @return string
*/
public function getSource()
{
return 'magento2';
}
/**
* @return string
*/
public function getPartnerAgent($with_magento_version = false)
{
return sprintf(
'%s-%s-%s',
$this->getSource(),
$with_magento_version ? $this->getMagentoVersion() : '0.0.0',
$this->getPluginVersion()
);
}
/**
* @param $partialURL
* @return mixed
*/
public function getUrl($partialURL)
{
$urlInterface = $this->getObject(\Magento\Backend\Model\UrlInterface::class);
return $urlInterface->getUrl($partialURL);
}
/**
* @return mixed
*/
public function getBaseUrlMedia()
{
return $this->getStore()->getBaseUrl(
UrlInterface::URL_TYPE_MEDIA,
$this->maybeUseHTTPS()
);
}
/**
* @return bool
*/
private function maybeUseHTTPS()
{
return isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on';
}
/**
* @param $fullClassName
* @param array $arguments
* @return mixed
*/
public function createObject($fullClassName, array $arguments = [])
{
return $this->objectManager->create($fullClassName, $arguments);
}
/**
* @param $fullClassName
* @return mixed
*/
public function getObject($fullClassName)
{
return $this->objectManager->get($fullClassName);
}
/**
* @param $id
* @return bool
*/
public static function isValidFBID($id)
{
return preg_match("/^\d{1,20}$/", $id) === 1;
}
/**
* @return StoreInterface
*/
public function getStore()
{
return $this->storeManager->getDefaultStoreView();
}
/**
* @return mixed
*/
public function getBaseUrl()
{
// Use this function to get a base url respect to host protocol
return $this->getStore()->getBaseUrl(
UrlInterface::URL_TYPE_WEB,
$this->maybeUseHTTPS()
);
}
/**
* @param $configKey
* @param $configValue
*/
public function saveConfig($configKey, $configValue)
{
try {
$configRow = $this->configFactory->create()->load($configKey);
if ($configRow->getData('config_key')) {
$configRow->setData('config_value', $configValue);
$configRow->setData('update_time', time());
} else {
$t = time();
$configRow->setData('config_key', $configKey);
$configRow->setData('config_value', $configValue);
$configRow->setData('creation_time', $t);
$configRow->setData('update_time', $t);
}
$configRow->save();
} catch (\Exception $e) {
$this->logException($e);
}
}
/**
* @param $configKey
*/
public function deleteConfig($configKey)
{
try {
$configRow = $this->configFactory->create()->load($configKey);
$configRow->delete();
} catch (\Exception $e) {
$this->logException($e);
}
}
/**
* @param $configKey
* @return mixed|null
*/
public function getConfigValue($configKey)
{
try {
$configRow = $this->configFactory->create()->load($configKey);
} catch (\Exception $e) {
$this->logException($e);
return null;
}
return $configRow ? $configRow->getConfigValue() : null;
}
/**
* @param $requestParams
* @param null $accessToken
* @return string|null
*/
public function makeHttpRequest($requestParams, $accessToken = null)
{
$response = null;
if ($accessToken == null) {
$accessToken = $this->getConfigValue('fbaccess/token');
}
try {
$url = $this->getCatalogBatchAPI($accessToken);
$params = [
'access_token' => $accessToken,
'requests' => json_encode($requestParams),
'item_type' => 'PRODUCT_ITEM',
];
$this->curl->post($url, $params);
$response = $this->curl->getBody();
} catch (\Exception $e) {
$this->logException($e);
}
return $response;
}
/**
* @return mixed|string|null
*/
public function getFBEExternalBusinessId()
{
$stored_external_id = $this->getConfigValue('fbe/external/id');
if ($stored_external_id) {
return $stored_external_id;
}
$storeId = $this->getStore()->getId();
return uniqid('fbe_magento_' . $storeId . '_');
}
/**
* @return array|false|int|string|null
*/
public function getStoreName()
{
$frontendName = $this->getStore()->getFrontendName();
if ($frontendName !== 'Default') {
return $frontendName;
}
$defaultStoreName = $this->getStore()->getGroup()->getName();
$escapeStrings = ['\r', '\n', ' ', '\t'];
$defaultStoreName =
trim(str_replace($escapeStrings, ' ', $defaultStoreName));
if (!$defaultStoreName) {
$defaultStoreName = $this->getStore()->getName();
$defaultStoreName =
trim(str_replace($escapeStrings, ' ', $defaultStoreName));
}
if ($defaultStoreName && $defaultStoreName !== self::MAIN_WEBSITE_STORE
&& $defaultStoreName !== self::MAIN_STORE
&& $defaultStoreName !== self::MAIN_WEBSITE) {
return $defaultStoreName;
}
return parse_url(self::getBaseUrl(), PHP_URL_HOST);
}
/**
* @param $info
*/
public function log($info)
{
$this->logger->info($info);
}
/**
* @param \Exception $e
*/
public function logException(\Exception $e)
{
$this->logger->error($e->getMessage());
$this->logger->error($e->getTraceAsString());
$this->logger->error($e);
}
/**
* @return string|void|null
*/
public function getAPIVersion()
{
$accessToken = $this->getAccessToken();
if ($accessToken == null) {
$this->log("can't find access token, won't get api update version ");
return;
}
$api_version = null;
try {
$configRow = $this->configFactory->create()->load('fb/api/version');
$api_version = $configRow ? $configRow->getConfigValue() : null;
//$this->log("Current api version : ".$api_version);
$versionLastUpdate = $configRow ? $configRow->getUpdateTime() : null;
//$this->log("Version last update: ".$versionLastUpdate);
$is_updated_version = $this->isUpdatedVersion($versionLastUpdate);
if ($api_version && $is_updated_version) {
//$this->log("Returning the version already stored in db : ".$api_version);
return $api_version;
}
$this->curl->addHeader("Authorization", "Bearer " . $accessToken);
$this->curl->get(self::FB_GRAPH_BASE_URL . 'api_version');
//$this->log("The API call: ".self::FB_GRAPH_BASE_URL.'api_version');
$response = $this->curl->getBody();
//$this->log("The API reponse : ".json_encode($response));
$decodeResponse = json_decode($response);
$api_version = $decodeResponse->api_version;
//$this->log("The version fetched via API call: ".$api_version);
$this->saveConfig('fb/api/version', $api_version);
} catch (\Exception $e) {
$this->log("Failed to fetch latest api version with error " . $e->getMessage());
}
return $api_version ? $api_version : self::CURRENT_API_VERSION;
}
/*
* TODO decide which ids we want to return for commerce feature
* This function queries FBE assets and other commerce related assets. We have stored most of them during FBE setup,
* such as BM, Pixel, catalog, profiles, ad_account_id. We might want to store or query ig_profiles,
* commerce_merchant_settings_id, pages in the future.
* API dev doc https://developers.facebook.com/docs/marketing-api/fbe/fbe2/guides/get-features
* Here is one example response, we would expect commerce_merchant_settings_id as well in commerce flow
* {"data":[{"business_manager_id":"12345","onsite_eligible":false,"pixel_id":"12333","profiles":["112","111"],
* "ad_account_id":"111","catalog_id":"111","pages":["111"],"instagram_profiles":["111"]}]}
* usage: $_bm = $_assets['business_manager_ids'];
*/
public function queryFBEInstalls($external_business_id = null)
{
if ($external_business_id == null) {
$external_business_id = $this->getFBEExternalBusinessId();
}
$accessToken = $this->getAccessToken();
$urlSuffix = "/fbe_business/fbe_installs?fbe_external_business_id=" . $external_business_id;
$url = $this::FB_GRAPH_BASE_URL . $this->getAPIVersion() . $urlSuffix;
$this->log($url);
try {
$this->curl->addHeader("Authorization", "Bearer " . $accessToken);
$this->curl->get($url);
$response = $this->curl->getBody();
$this->log("The FBE Install reponse : " . json_encode($response));
$decodeResponse = json_decode($response, true);
$assets = $decodeResponse['data'][0];
} catch (\Exception $e) {
$this->log("Failed to query FBEInstalls" . $e->getMessage());
}
}
/**
* @param $pixelId
* @param $pixelEvent
*/
public function logPixelEvent($pixelId, $pixelEvent)
{
$this->log($pixelEvent . " event fired for Pixel id : " . $pixelId);
}
/**
* @return array
*/
public function deleteConfigKeys()
{
$response = [];
$response['success'] = false;
try {
$connection = $this->resourceConnection->getConnection();
$facebook_config = $this->resourceConnection->getTableName('facebook_business_extension_config');
$sql = "DELETE FROM $facebook_config WHERE config_key NOT LIKE 'permanent%' ";
$connection->query($sql);
$response['success'] = true;
$response['message'] = self::DELETE_SUCCESS_MESSAGE;
} catch (\Exception $e) {
$this->log($e->getMessage());
$response['error_message'] = self::DELETE_FAILURE_MESSAGE;
}
return $response;
}
/**
* @param $versionLastUpdate
* @return bool|null
*/
public function isUpdatedVersion($versionLastUpdate)
{
if (!$versionLastUpdate) {
return null;
}
$monthsSinceLastUpdate = 3;
try {
$datetime1 = new \DateTime($versionLastUpdate);
$datetime2 = new \DateTime();
$interval = date_diff($datetime1, $datetime2);
$interval_vars = get_object_vars($interval);
$monthsSinceLastUpdate = $interval_vars['m'];
$this->log("Months since last update : " . $monthsSinceLastUpdate);
} catch (\Exception $e) {
$this->log($e->getMessage());
}
// Since the previous version is valid for 3 months,
// I will check to see for the gap to be only 2 months to be safe.
return $monthsSinceLastUpdate <= 2;
}
/**
* @param $accessToken
* @return string
*/
public function getCatalogBatchAPI($accessToken)
{
$catalogId = $this->getConfigValue('fbe/catalog/id');
$external_business_id = $this->getFBEExternalBusinessId();
if ($catalogId != null) {
$catalog_path = "/" . $catalogId . "/items_batch";
} else {
$catalog_path = "/fbe_catalog/batch?fbe_external_business_id=" .
$external_business_id;
}
$catalogBatchApi = self::FB_GRAPH_BASE_URL .
$this->getAPIVersion($accessToken) .
$catalog_path;
$this->log("Catalog Batch API - " . $catalogBatchApi);
return $catalogBatchApi;
}
/**
* @return mixed
*/
public function getStoreCurrencyCode()
{
return $this->getStore()->getCurrentCurrencyCode();
}
/**
* @return string
*/
public function isFBEInstalled()
{
$isFbeInstalled = $this->getConfigValue('fbe/installed');
if ($isFbeInstalled) {
return 'true';
}
return 'false';
}
/**
* @param $pixelId
* @return mixed
*/
private function fetchAAMSettings($pixelId)
{
return AdsPixelSettings::buildFromPixelId($pixelId);
}
/**
* @return AdsPixelSettings|null
*/
public function getAAMSettings()
{
$settingsAsString = $this->getConfigValue('fbpixel/aam_settings');
if ($settingsAsString) {
$settingsAsArray = json_decode($settingsAsString, true);
if ($settingsAsArray) {
$settings = new AdsPixelSettings();
$settings->setPixelId($settingsAsArray['pixelId']);
$settings->setEnableAutomaticMatching($settingsAsArray['enableAutomaticMatching']);
$settings->setEnabledAutomaticMatchingFields($settingsAsArray['enabledAutomaticMatchingFields']);
return $settings;
}
}
return null;
}
/**
* @param $settings
* @return false|string
*/
private function saveAAMSettings($settings)
{
$settingsAsArray = [
'enableAutomaticMatching' => $settings->getEnableAutomaticMatching(),
'enabledAutomaticMatchingFields' => $settings->getEnabledAutomaticMatchingFields(),
'pixelId' => $settings->getPixelId(),
];
$settingsAsString = json_encode($settingsAsArray);
$this->saveConfig('fbpixel/aam_settings', $settingsAsString);
return $settingsAsString;
}
/**
* @param $pixelId
* @return false|string|null
*/
public function fetchAndSaveAAMSettings($pixelId)
{
$settings = $this->fetchAAMSettings($pixelId);
if ($settings) {
return $this->saveAAMSettings($settings);
}
return null;
}
/**
* Generates a map of the form : 4 => "Root > Mens > Shoes"
*
* @return array
*/
public function generateCategoryNameMap()
{
$categories = $this->getObject(CategoryCollection::class)
->addAttributeToSelect('name')
->addAttributeToSelect('path')
->addAttributeToSelect('is_active')
->addAttributeToFilter('is_active', 1);
$name = [];
$breadcrumb = [];
foreach ($categories as $category) {
$entityId = $category->getId();
$name[$entityId] = $category->getName();
$breadcrumb[$entityId] = $category->getPath();
}
// Converts the product category paths to human readable form.
// e.g. "1/2/3" => "Root > Mens > Shoes"
foreach ($name as $id => $value) {
$breadcrumb[$id] = implode(" > ", array_filter(array_map(
function ($innerId) use (&$name) {
return isset($name[$innerId]) ? $name[$innerId] : null;
},
explode("/", $breadcrumb[$id])
)));
}
return $breadcrumb;
}
}