marketo_ma-8.x-2.x-dev/src/PageAttachment.php
src/PageAttachment.php
<?php
namespace Drupal\marketo_ma;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\marketo_ma\Service\MarketoMaMunchkinInterface;
use Drupal\marketo_ma\Service\MarketoMaServiceInterface;
use Drupal\path_alias\AliasManagerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Page attachment service to attach munchkin tracking and related actions.
*/
class PageAttachment {
/**
* Configuration service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Current user account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Route matching service.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Path matching service.
*
* @var \Drupal\Core\Path\PathMatcherInterface
*/
protected $pathMatcher;
/**
* Munchkin service.
*
* @var \Drupal\marketo_ma\Service\MarketoMaMunchkinInterface
*/
protected $munchkin;
/**
* Temporary storage service.
*
* @var \Drupal\Core\TempStore\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* Optional path alias manager for resolving aliases.
*
* @var \Drupal\path_alias\AliasManagerInterface|null
*/
protected $pathAliasManager;
/**
* Current path.
*
* @var \Drupal\Core\Path\CurrentPathStack
*/
protected $currentPath;
/**
* Request stack service.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* Calculated page match to avoid re-calculation.
*
* @var bool|null
*/
private $pageMatchCache;
/**
* Marketo settings.
*
* @var \Drupal\Core\Config\ImmutableConfig|null
*/
private $config;
/**
* Creates the Marketo MA core service..
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
* The path matcher service.
* @param \Drupal\marketo_ma\Service\MarketoMaMunchkinInterface $munchkin
* The munchkin service.
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory
* Temporary storage service for holding lead data until delivery.
* @param \Drupal\Core\Path\CurrentPathStack $currentPath
* The current path.
* @param \Symfony\Component\HttpFoundation\RequestStack|null $requestStack
* Request stack service.
*/
public function __construct(
ConfigFactoryInterface $config_factory,
AccountInterface $current_user,
PathMatcherInterface $path_matcher,
MarketoMaMunchkinInterface $munchkin,
PrivateTempStoreFactory $temp_store_factory,
CurrentPathStack $currentPath,
RequestStack $requestStack = NULL
) {
$this->configFactory = $config_factory;
$this->currentUser = $current_user;
$this->pathMatcher = $path_matcher;
$this->munchkin = $munchkin;
$this->tempStoreFactory = $temp_store_factory;
$this->currentPath = $currentPath;
$this->requestStack = $requestStack;
}
/**
* Set the alias manager is path_alias is installed.
*
* @param \Drupal\path_alias\AliasManagerInterface $aliasManager
* The path alias manager services.
*/
public function setAliasManager(AliasManagerInterface $aliasManager) {
$this->pathAliasManager = $aliasManager;
}
/**
* The current tracking method.
*
* \Drupal\marketo_ma\Service\MarketoMaMunchkinInterface::MARKETO_TRACKING_*
*
* @return string
* On of the defined tracking methods.
*/
private function trackingMethod() {
return $this->config()->get('tracking_method');
}
/**
* Helper for loading marketo settings out of config.
*
* @return \Drupal\Core\Config\ImmutableConfig
* The current marketo settings.
*/
private function config() {
// Load config if not already loaded.
if (!isset($this->config)) {
$this->config = $this->configFactory->get(MarketoMaServiceInterface::MARKETO_MA_CONFIG_NAME);
}
return $this->config;
}
/**
* Handles `hook_page_attachments` for the marketo_ma module.
*/
public function pageAttachments(&$page) {
// Check whether we should track via the Munchkin.
if ($this->shouldTrackCurrentRequest()) {
// Add marketo ma to the page..
if (!empty($this->munchkin->getAccountId())) {
// Add the library and settings for tracking the page.
$page['#attached']['library'][] = 'marketo_ma/marketo-ma';
$page['#attached']['drupalSettings']['marketo_ma'] = [
'track' => TRUE,
'key' => $this->munchkin->getAccountId(),
'initParams' => $this->munchkin->getInitParams(),
'library' => $this->munchkin->getLibrary(),
];
}
// Get the Lead data from temporary user storage.
$lead = $this->getUserData();
// Check for the munchkin option and that the munchkin api is configured.
if ($lead
&& $this->trackingMethod() == MarketoMaServiceInterface::TRACKING_METHOD_MUNCHKIN
&& $this->munchkin->isConfigured()
&& !empty($lead->getEmail())
) {
// Set drupalSettings so JS will do the lead association.
$page['#attached']['drupalSettings']['marketo_ma']['actions'][] = $this->munchkin->getAction(
MarketoMaMunchkinInterface::ACTION_ASSOCIATE_LEAD,
$lead
);
// Set the associated flag so we are not associating on every request.
$this->resetUserData();
}
}
}
/**
* Checks if the request should be tracked.
*/
public function shouldTrackCurrentRequest() {
return $this->checkPageVisibility() && $this->checkRoleVisibility($this->currentUser);
}
/**
* Sets temporary user data for this session.
*
* @param \Drupal\marketo_ma\Lead $lead
* The marketo lead.
*
* @return $this
* The current object for chaining.
*/
public function setUserData(Lead $lead) {
// Make sure we have a real user before trying to access user data.
if ($this->sessionAvailable()) {
$this->temporaryStorage()->set('user_data', $lead);
}
return $this;
}
/**
* Gets temporary user data for the current session.
*
* @return \Drupal\marketo_ma\Lead|null
* The temporary user data.
*/
public function getUserData() {
return $this->temporaryStorage()->get('user_data');
}
/**
* Resets (deletes) the temporary user data for the current session.
*
* @return $this
* The current object for chaining.
*/
public function resetUserData() {
// Make sure we have a real user before trying to access user data.
if ($this->sessionAvailable()) {
$this->temporaryStorage()->delete('user_data');
}
return $this;
}
/**
* Determines whether the current session contains any temporary user data.
*
* @return bool
* If there is lead data to be delivered.
*/
public function hasUserData() {
return !empty($this->getUserData());
}
/**
* Check whether user data is available for the current user.
*
* @return bool
* If there is a session available.
*/
protected function sessionAvailable() {
return ($this->currentUser->id() || $this->requestStack->getCurrentRequest()->hasSession());
}
/**
* Gets the private temporary storage for the marketo_ma module.
*
* @return \Drupal\Core\TempStore\PrivateTempStore
* Temporary storage for holding lead data until its able to be delivered.
*/
protected function temporaryStorage() {
return $this->tempStoreFactory->get('marketo_ma');
}
/**
* Tracking visibility check for pages.
*
* Based on visibility setting this function returns TRUE if JS code should
* be added to the current page and otherwise FALSE.
*
* @todo it would be nice if this used condition plugins.
* @see \Drupal\block\BlockAccessControlHandler
*
* @return bool
* True if the page should be tracked by visibility rules.
*/
protected function checkPageVisibility() {
// Cache visibility result if function is called more than once.
if (!isset($this->pageMatchCache)) {
$config = $this->config();
$visibility_request_path_mode = $config->get('tracking.request_path.mode');
$visibility_request_path_pages = $config->get('tracking.request_path.pages');
// Match path if necessary.
if (!empty($visibility_request_path_pages)) {
if ($visibility_request_path_mode < 2) {
// Convert path to lowercase. This allows comparison of the same path
// with different case. Ex: /Page, /page, /PAGE.
$pages = mb_strtolower($visibility_request_path_pages);
// Compare the lowercase path alias (if any) and internal path.
$path_alias = $path = $this->currentPath->getPath();
if (isset($this->pathAliasManager)) {
$path_alias = mb_strtolower($this->pathAliasManager->getAliasByPath($path));
}
$match = $this->pathMatcher->matchPath(
$path_alias,
$pages
) || (($path != $path_alias) && $this->pathMatcher->matchPath($path, $pages));
// When $visibility_request_path_mode has a value of 0, the tracking
// code is displayed on all pages except those listed in $pages. When
// set to 1, it is displayed only on those pages listed in $pages.
$this->pageMatchCache = !($visibility_request_path_mode xor $match);
}
else {
$this->pageMatchCache = FALSE;
}
}
else {
$this->pageMatchCache = TRUE;
}
}
return $this->pageMatchCache;
}
/**
* Tracking visibility check for user roles.
*
* Based on visibility setting this function returns TRUE if JS code should
* be added for the current role and otherwise FALSE.
*
* @param \Drupal\Core\Session\AccountInterface $account
* A user object containing an array of roles to check.
*
* @return bool
* TRUE if JS code should be added for the current role and otherwise FALSE.
*/
protected function checkRoleVisibility(AccountInterface $account) {
$enabled = $visibility_user_role_mode = $this->config()->get('tracking.user_role.mode');
$visibility_user_role_roles = $this->config()->get('tracking.user_role.roles');
if (count($visibility_user_role_roles) > 0) {
// One or more roles are selected.
foreach (array_values($account->getRoles()) as $user_role) {
// Is the current user a member of one of these roles?
if (in_array($user_role, $visibility_user_role_roles)) {
// Current user is a member of a role that should be tracked/excluded
// from tracking.
$enabled = !$visibility_user_role_mode;
break;
}
}
}
else {
// No role is selected, therefore all roles should be tracked.
$enabled = TRUE;
}
return $enabled;
}
}
