acquia_perz-4.0.0-rc1/src/Service/Context/PageContext.php
src/Service/Context/PageContext.php
<?php
namespace Drupal\acquia_perz\Service\Context;
use Drupal\acquia_connector\Subscription;
use Drupal\acquia_perz\PerzHelper;
use Drupal\acquia_perz\Service\Helper\SettingsHelper;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Controller\TitleResolverInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\node\NodeInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Route;
/**
* Page Context for Acquia Perz.
*/
class PageContext extends BaseContext {
/**
* Engagement score's default value.
*/
const ENGAGEMENT_SCORE_DEFAULT = 1;
/**
* Lift JavaScript file name.
*/
const LIFT_JS_FILENAME = 'lift.js';
/**
* Personalization Api version.
*/
const API_VERSION = 'v3';
/**
* Field mappings.
*
* @var array
*/
private $fieldMappings;
/**
* Udf Person mappings.
*
* @var array
*/
private $udfPersonMappings;
/**
* Udf Event mappings.
*
* @var array
*/
private $udfEventMappings;
/**
* Udf Touch mappings.
*
* @var array
*/
private $udfTouchMappings;
/**
* Taxonomy term storage.
*
* @var \Drupal\taxonomy\TermStorageInterface
*/
private $taxonomyTermStorage;
/**
* The Site ID.
*
* @var string
*/
protected $siteID;
/**
* Page context, with default value.
*
* @var array
*/
protected $htmlHeadContexts = [
'content_title' => 'Untitled',
'content_type' => 'page',
'page_type' => 'content page',
'language' => '',
'content_section' => '',
'content_keywords' => '',
'post_id' => '',
'content_uuid' => '',
'published_date' => '',
'persona' => '',
'engagement_score' => self::ENGAGEMENT_SCORE_DEFAULT,
];
/**
* Page context, with default value.
*
* @var array
*/
protected $subscription = [];
/**
* Assets URL.
*
* @var string
*/
private $assetsUrl;
/**
* Constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* Entity type manager.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
* @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
* The title resolver.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\acquia_connector\Subscription $subscription
* Acquia Subscription.
*/
public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, RequestStack $request_stack, RouteMatchInterface $route_match, TitleResolverInterface $title_resolver, LanguageManagerInterface $language_manager, Subscription $subscription) {
$this->siteID = PerzHelper::getSiteId();
// Get all our settings.
$settings = $config_factory->get('acquia_perz.settings');
$this->assetsUrl = $settings->get('api.assets_url');
// Ensure the subscription is active and perz data is stored there.
$subscription_data = $subscription->getSubscription();
if (isset($subscription_data['acquia_perz'])) {
$this->subscription = $subscription_data['acquia_perz'];
$this->setContextCredential($this->subscription);
}
// Set mapping information.
$this->fieldMappings = $settings->get('field_mappings');
$this->udfPersonMappings = $settings->get('udf_person_mappings') ?: [];
$this->udfEventMappings = $settings->get('udf_touch_mappings') ?: [];
$this->udfTouchMappings = $settings->get('udf_event_mappings') ?: [];
// Set advanced configuration.
$this->setContextAdvanced($settings->get('advanced'));
// Set taxonomyTermStorage.
$this->taxonomyTermStorage = $entity_type_manager->getStorage('taxonomy_term');
// Set page context.
$request = $request_stack->getCurrentRequest();
$route = $route_match->getRouteObject();
// Set node context.
$this->setContextByNode($request);
// Set base data (title + language)
$this->setBaseData($request, $route, $title_resolver, $language_manager);
}
/**
* Set page context by Node.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*/
private function setContextByNode(Request $request): void {
// If not a request to node, do nothing.
if (!$request->attributes->has('node')) {
return;
}
$node = $request->attributes->get('node');
if (empty($node) || !$node instanceof NodeInterface) {
return;
}
// Otherwise, set page context by node.
$this->setNodeData($node);
$this->setFields($node);
}
/**
* Set page context - title.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
* @param \Symfony\Component\Routing\Route $route
* The route object.
* @param \Drupal\Core\Controller\TitleResolverInterface $titleResolver
* The title resolver.
* @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
* The language manager.
*/
private function setBaseData(Request $request, Route $route, TitleResolverInterface $titleResolver, LanguageManagerInterface $languageManager): void {
// Set language code
// After investigation, there is no use case where the methods
// 'getCurrentLanguage' and 'getId' would not exist within LanguageManager.
// Drupal 8 will ALWAYS set a language code and would not be null,
// therefore no checks are required.
$this->htmlHeadContexts['language'] = $languageManager->getCurrentLanguage()->getId();
// Get title.
$title = $titleResolver->getTitle($request, $route);
// Set title.
// If markup, strip tags and convert to string.
if (is_array($title) && isset($title['#markup']) && isset($title['#allowed_tags'])) {
$allowed_tags = empty($title['#allowed_tags']) ? '' : '<' . implode('><', $title['#allowed_tags']) . '>';
$title = strip_tags($title['#markup'], $allowed_tags);
}
// If still an array or empty, leave title at "Untitled".
if (is_array($title) || empty($title)) {
return;
}
// Otherwise set title.
$this->htmlHeadContexts['content_title'] = $title;
}
/**
* Set page context - credential.
*
* @param array $credential_settings
* Credential settings array.
*/
private function setContextCredential(array $credential_settings): void {
$this->htmlHeadContexts['account_id'] = $credential_settings['account_id'];
$this->htmlHeadContexts['site_id'] = $this->siteID;
$this->htmlHeadContexts['liftAssetsURL'] = $this->assetsUrl;
$this->htmlHeadContexts['liftDecisionAPIURL'] = $credential_settings['endpoint'];
}
/**
* Set page context - advanced.
*
* @param array $advanced_settings
* Advanced settings array.
*/
private function setContextAdvanced(array $advanced_settings): void {
$bootstrap_mode = $advanced_settings['bootstrap_mode'] ?? 'auto';
$replacement_mode = $advanced_settings['content_replacement_mode'];
if (SettingsHelper::isValidBootstrapMode($bootstrap_mode)) {
$this->htmlHeadContexts['bootstrapMode'] = $bootstrap_mode;
}
if (SettingsHelper::isValidContentReplacementMode($replacement_mode)) {
$this->htmlHeadContexts['contentReplacementMode'] = $replacement_mode;
}
if (!empty($advanced_settings['content_origins'])) {
$content_origins = array_map('trim', explode("\n", trim($advanced_settings['content_origins'])));
$content_origins = implode(',', $content_origins);
if ($content_origins) {
$this->htmlHeadContexts['content_origins'] = $content_origins;
}
}
$this->htmlHeadContexts['apiVersion'] = self::API_VERSION;
}
/**
* Set Node data.
*
* @param \Drupal\node\NodeInterface $node
* Node.
*/
private function setNodeData(NodeInterface $node): void {
$this->htmlHeadContexts['content_type'] = $node->getType();
$this->htmlHeadContexts['content_title'] = $node->getTitle();
$this->htmlHeadContexts['published_date'] = $node->getCreatedTime();
$this->htmlHeadContexts['post_id'] = $node->id();
$this->htmlHeadContexts['content_uuid'] = $node->uuid();
$this->htmlHeadContexts['page_type'] = 'node page';
}
/**
* Set fields.
*
* @param \Drupal\node\NodeInterface $node
* Node.
*/
private function setFields(NodeInterface $node): void {
$available_field_vocabulary_names = $this->getAvailableFieldVocabularyNames($node);
$vocabulary_term_names = $this->getVocabularyTermNames($node);
// Find Field Term names.
foreach ($available_field_vocabulary_names as $page_context_name => $vocabulary_names) {
$field_term_names = $this->getFieldTermNames($vocabulary_names, $vocabulary_term_names);
// Only set when the value is a populated array
// Empty arrays return as false in PHP.
if (!empty($field_term_names)) {
$this->htmlHeadContexts[$page_context_name] = implode(',', $field_term_names);
}
}
}
/**
* Get available Fields and their vocabulary names within the node.
*
* @param \Drupal\node\NodeInterface $node
* Node.
*
* @return array
* Node's available Fields' Vocabularies names.
*/
private function getAvailableFieldVocabularyNames(NodeInterface $node): array {
$available_field_vocabulary_names = [];
$available_field_vocabulary_fields = [];
// Regular field mapping.
foreach ($this->fieldMappings as $page_context_name => $field_name) {
if (!isset($node->{$field_name})) {
continue;
}
// Add this field to the list of fields to parse with their corresponding
// page context name;.
if (!isset($available_field_vocabulary_fields[$field_name])) {
$available_field_vocabulary_fields[$field_name] = [];
}
$available_field_vocabulary_fields[$field_name][] = $page_context_name;
}
// The following 3 mappings have all the same structure with different array
// id's so we can merge them without conflict.
$udf_mappings = array_merge($this->udfPersonMappings, $this->udfTouchMappings, $this->udfEventMappings);
foreach ($udf_mappings as $page_context_name => $properties) {
if (!isset($node->{$properties['value']})) {
continue;
}
// Add this field to the list of fields to parse with their corresponding
// page context name;.
if (!isset($available_field_vocabulary_fields[$properties['value']])) {
$available_field_vocabulary_fields[$properties['value']] = [];
}
$available_field_vocabulary_fields[$properties['value']][] = $page_context_name;
}
foreach ($available_field_vocabulary_fields as $field_name => $page_contexts) {
$field_handler_settings = $node->{$field_name}->getSetting('handler_settings');
$vocabulary_names = array_key_exists('target_bundles', $field_handler_settings) ? $field_handler_settings['target_bundles'] : [];
foreach ($page_contexts as $page_context_name) {
$available_field_vocabulary_names[$page_context_name] = $vocabulary_names;
}
}
return $available_field_vocabulary_names;
}
/**
* Get Vocabularies' Term names.
*
* @param \Drupal\node\NodeInterface $node
* Node.
*
* @return array
* Vocabularies' Term names.
*/
private function getVocabularyTermNames(NodeInterface $node): array {
// Find the node's terms.
$terms = $this->taxonomyTermStorage->getNodeTerms([$node->id()]);
$node_terms = $terms[$node->id()] ?? [];
// Find the term names.
$vocabulary_term_names = [];
foreach ($node_terms as $term) {
$vocabulary_id = $term->bundle();
$term_name = $term->getName();
$vocabulary_term_names[$vocabulary_id][] = $term_name;
}
return $vocabulary_term_names;
}
/**
* Get Field's Term names.
*
* @param array $vocabulary_names
* Vocabulary names.
* @param array $vocabulary_term_names
* Vocabulary Term names.
*
* @return array
* Field Term names.
*/
private function getFieldTermNames(array $vocabulary_names, array $vocabulary_term_names): array {
$field_term_names = [];
foreach ($vocabulary_names as $vocabulary_name) {
if (!isset($vocabulary_term_names[$vocabulary_name])) {
continue;
}
$field_term_names = array_merge($field_term_names, $vocabulary_term_names[$vocabulary_name]);
}
return array_unique($field_term_names);
}
/**
* Get the render array for a JavaScript tag.
*
* @return array
* The render array
*/
private function getJavaScriptTagRenderArray(): array {
return [
[
'#tag' => 'script',
'#attributes' => [
'src' => $this->assetsUrl . '/' . self::LIFT_JS_FILENAME,
'async' => TRUE,
],
],
'acquia_lift_javascript',
];
}
/**
* {@inheritdoc}
*/
protected function populateHtmlHead(?array &$htmlHead): void {
parent::populateHtmlHead($htmlHead);
// Attach Lift's JavaScript.
$htmlHead[] = $this->getJavaScriptTagRenderArray();
}
}
