Skip to content

Commit

Permalink
Merge pull request #48 from CuBoulder/external-services-refactor
Browse files Browse the repository at this point in the history
Third-party services update (v2.7)
  • Loading branch information
jcsparks authored Feb 15, 2024
2 parents 5ef1637 + ea024ee commit b46bbec
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 50 deletions.
26 changes: 18 additions & 8 deletions src/Controller/ExternalServiceIncludeListBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\ucb_site_configuration\SiteConfiguration;
use Symfony\Component\DependencyInjection\ContainerInterface;

Expand All @@ -14,6 +14,13 @@
*/
class ExternalServiceIncludeListBuilder extends ConfigEntityListBuilder {

/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;

/**
* The site configuration service defined in this module.
*
Expand All @@ -26,13 +33,14 @@ class ExternalServiceIncludeListBuilder extends ConfigEntityListBuilder {
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity storage class.
* @param \Drupal\ucb_site_configuration\SiteConfiguration $service
* The site configuration service defined in this module.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, SiteConfiguration $service) {
parent::__construct($entity_type, $storage);
public function __construct(EntityTypeInterface $entity_type, EntityTypeManagerInterface $entity_type_manager, SiteConfiguration $service) {
parent::__construct($entity_type, $entity_type_manager->getStorage($entity_type->id()));
$this->entityTypeManager = $entity_type_manager;
$this->service = $service;
}

Expand All @@ -41,9 +49,9 @@ public function __construct(EntityTypeInterface $entity_type, EntityStorageInter
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity_type.manager')->getStorage($entity_type->id()),
$container->get('ucb_site_configuration')
$entity_type,
$container->get('entity_type.manager'),
$container->get('ucb_site_configuration')
);
}

Expand All @@ -65,9 +73,11 @@ public function buildHeader() {
*/
public function buildRow(EntityInterface $entity) {
$externalServiceConfiguration = $this->service->getConfiguration()->get('external_services')[$entity->getServiceName()];
$selectedCount = count($entity->getNodes());
$totalCount = $entity->isSitewide() ? $this->entityTypeManager->getStorage('node')->getQuery()->count()->accessCheck(FALSE)->execute() - $selectedCount : $selectedCount;
$row['service_name'] = $externalServiceConfiguration['label'] ?? $entity->getServiceName();
$row['label'] = $entity->label();
$row['included_on'] = $entity->isSitewide() ? $this->t('All pages') : $this->formatPlural(count($entity->getNodes()), '1 page', '@count pages');
$row['included_on'] = $this->formatPlural($totalCount, '1 page', '@count pages');
return $row + parent::buildRow($entity);
}

Expand Down
12 changes: 3 additions & 9 deletions src/Form/ExternalServiceIncludeEntityForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ public function form(array $form, FormStateInterface $form_state) {
'#type' => 'radios',
'#title' => $this->t('Include this service on'),
'#options' => [
$this->t('Specific content'),
$this->t('All content on this site'),
$this->t('Specified content only'),
$this->t('All content on this site, excluding specified content'),
],
'#default_value' => $entity->isSitewide() ? 1 : 0,
'#required' => TRUE,
Expand All @@ -116,7 +116,7 @@ public function form(array $form, FormStateInterface $form_state) {
'#type' => 'entity_autocomplete',
'#target_type' => 'node',
'#title' => $this->t('Content'),
'#description' => $this->t('Specify content to include this service on. Multiple entries may be seperated by commas.'),
'#description' => $this->t('Specify content to include or exclude (optional). Multiple entries may be seperated by commas.'),
'#default_value' => $entity->getNodes(),
'#tags' => TRUE,
],
Expand All @@ -127,12 +127,6 @@ public function form(array $form, FormStateInterface $form_state) {
'#description' => $this->t('If enabled, all users will be able to add or remove this service for content they can edit, including when creating new content. A user with permission to administer third-party services will always be able to add or remove this service, regardless if enabled.'),
'#default_value' => $entity->isContentEditingEnabled(),
'#tags' => TRUE,
'#states' => [
'visible' => [':input[name="sitewide"]' => ['value' => 0]],
],
],
'#states' => [
'visible' => [':input[name="sitewide"]' => ['value' => 0]],
],
];
return $form + parent::form($form, $form_state);
Expand Down
113 changes: 90 additions & 23 deletions src/SiteConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Drupal\ucb_site_configuration;

use Drupal\Component\Serialization\Json;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityTypeRepositoryInterface;
Expand Down Expand Up @@ -406,39 +407,105 @@ public function attachPeopleListConfiguration(array &$variables) {
}

/**
* Attaches external service includes.
* Attaches all applicable third-party service to a page.
*
* This function is meant to be called from `hook_preprocess`. Variables can
* be referenced from the template using `service_servicename_includes`.
* This function is meant to be called from `hook_page_attachments`.
*
* @param array &$variables
* The array to add the external service includes to.
* @param array &$attachments
* The attachments array to add the third-party services to.
* @param \Drupal\node\NodeInterface|null $node
* A node to match includes that are for specific content. If null, only
* sitewide includes will be attached.
*/
public function attachExternalServiceIncludes(array &$variables, NodeInterface $node = NULL) {
public function attachExternalServiceIncludes(array &$attachments, NodeInterface $node = NULL) {
$storage = $this->entityTypeManager->getStorage($this->entityTypeRepository->getEntityTypeFromClass(ExternalServiceInclude::class));
$query = $storage->getQuery('OR')->condition('sitewide', TRUE);
$query = $storage->getQuery('OR');
$exclusiveGroup = $query->andConditionGroup()->condition('sitewide', TRUE);
if ($node) {
$query->condition('nodes.*', $node->id());
$inclusiveGroup = $query->andConditionGroup()->condition('sitewide', FALSE);
$inclusiveGroup->condition('nodes.*', $node->id());
$exclusiveGroup->condition($query->orConditionGroup()->condition('nodes.0', operator: 'IS NULL')->condition('nodes.*', [$node->id()], 'NOT IN'));
$query->condition($inclusiveGroup);
}
$query->condition($exclusiveGroup);
$results = $query->execute();
/** @var \Drupal\ucb_site_configuration\Entity\ExternalServiceIncludeInterface[] */
$externalServiceIncludeEntities = $storage->loadMultiple($results);
$externalServiceIncludeArrays = [];
foreach ($externalServiceIncludeEntities as $externalServiceInclude) {
$externalServiceName = $externalServiceInclude->getServiceName();
$externalServiceIncludeArrays[$externalServiceName][] = [
'id' => $externalServiceInclude->id(),
'label' => $externalServiceInclude->label(),
'service_name' => $externalServiceName,
'service_settings' => $externalServiceInclude->getServiceSettings(),
'sitewide' => $externalServiceInclude->isSitewide(),
];
$externalServiceEnabled = count($results) > 0;
if ($externalServiceEnabled) {
/** @var \Drupal\ucb_site_configuration\Entity\ExternalServiceIncludeInterface[] */
$externalServiceIncludeEntities = $storage->loadMultiple($results);
foreach ($externalServiceIncludeEntities as $externalServiceInclude) {
$this->attachExternalServiceInclude($attachments, $externalServiceInclude);
}
}
foreach ($externalServiceIncludeArrays as $externalServiceName => $externalServiceIncludeArray) {
$variables['service_' . $externalServiceName . '_includes'] = $externalServiceIncludeArray;
}

/**
* Attaches a single third-party service to a page.
*
* @param array &$attachments
* The attachments array to add the third-party service to.
* @param \Drupal\ucb_site_configuration\Entity\ExternalServiceIncludeInterface $externalServiceInclude
* The third-party service to attach.
*/
public function attachExternalServiceInclude(array &$attachments, $externalServiceInclude) {
$externalServiceId = $externalServiceInclude->id();
$externalServiceName = $externalServiceInclude->getServiceName();
$externalServiceSettings = $externalServiceInclude->getServiceSettings();
switch ($externalServiceName) {
case 'livechat':
$serviceConfiguration = [
'license' => $externalServiceSettings['license_id'],
];
$attachments['#attached']['library'][] = 'ucb_site_configuration/service-livechat';
$attachments['#attached']['html_head'][] = [
[
'#type' => 'html_tag',
'#tag' => 'script',
'#value' => 'window.__lc=' . Json::encode($serviceConfiguration) . ';',
'#attributes' => [
'type' => 'text/javascript',
],
],
'service-livechat-' . $externalServiceId,
];
break;

case 'mainstay':
$serviceConfiguration = [
'botToken' => $externalServiceSettings['bot_token'],
'collegeId' => $externalServiceSettings['college_id'],
];
$attachments['#attached']['library'][] = 'ucb_site_configuration/service-mainstay';
$attachments['#attached']['html_head'][] = [
[
'#type' => 'html_tag',
'#tag' => 'script',
'#value' => 'window.admitHubBot=' . Json::encode($serviceConfiguration) . ';',
'#attributes' => [
'type' => 'text/javascript',
],
],
'service-mainstay-' . $externalServiceId,
];
break;

case 'statuspage':
$attachments['#attached']['html_head'][] = [
[
'#type' => 'html_tag',
'#tag' => 'script',
'#attributes' => [
'type' => 'text/javascript',
'src' => 'https://' . $externalServiceSettings['page_id'] . '.statuspage.io/embed/script.js',
'async' => '',
'defer' => '',
],
],
'service-statuspage-' . $externalServiceId,
];
break;

default:
}
}

Expand All @@ -451,7 +518,7 @@ public function attachExternalServiceIncludes(array &$variables, NodeInterface $
*/
public function getContentAccessibleExternalServiceIncludes() {
$storage = $this->entityTypeManager->getStorage($this->entityTypeRepository->getEntityTypeFromClass(ExternalServiceInclude::class));
$query = $storage->getQuery('AND')->condition('sitewide', FALSE);
$query = $storage->getQuery();
if (!$this->user->hasPermission('administer ucb external services')) {
$query->condition('content_editing_enabled', TRUE);
}
Expand Down
2 changes: 1 addition & 1 deletion ucb_site_configuration.info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CU Boulder Site Configuration
description: 'Allows CU Boulder site administrators to configure site-specific settings.'
core_version_requirement: ^9 || ^10
type: module
version: '2.6.4'
version: '2.7'
package: CU Boulder
dependencies:
- block
Expand Down
20 changes: 20 additions & 0 deletions ucb_site_configuration.libraries.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
service-livechat:
version: 1.x
js:
https://cdn.livechatinc.com/tracking.js:
type: external
attributes:
async: true
defer: true

service-mainstay:
version: 1.x
css:
theme:
https://webbot.mainstay.com/static/css/webchat.css: { type: external }
js:
https://webbot.mainstay.com/static/js/webchat.js:
type: external
attributes:
async: true
defer: true
34 changes: 25 additions & 9 deletions ucb_site_configuration.module
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,29 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\node\NodeInterface;

/**
* Enables external service includes to be referenced from the page template.
* Enables site general settings to be referenced from the page template.
*
* Implements hook_preprocess_HOOK().
*/
function ucb_site_configuration_preprocess_page(array &$variables) {
/** @var \Drupal\ucb_site_configuration\SiteConfiguration */
$service = \Drupal::service('ucb_site_configuration');
$service->attachSiteInformation($variables);
$variables['#cache']['tags'][] = 'config:ucb_site_configuration.configuration';
$variables['#cache']['tags'][] = 'config:ucb_site_configuration.settings';
}

/**
* Attaches third-party services to nodes.
*
* Implements hook_page_attachments().
*/
function ucb_site_configuration_page_attachments(array &$variables) {
/** @var \Drupal\ucb_site_configuration\SiteConfiguration */
$service = \Drupal::service('ucb_site_configuration');
if (($node = \Drupal::routeMatch()->getParameter('node')) && $node instanceof NodeInterface) {
$service->attachExternalServiceIncludes($variables, $node);
}
else {
$service->attachExternalServiceIncludes($variables);
}
$variables['#cache']['tags'][] = 'config:ucb_site_configuration.configuration';
$variables['#cache']['tags'][] = 'config:ucb_site_configuration.settings';
}

/**
Expand Down Expand Up @@ -72,7 +79,12 @@ function ucb_site_configuration_form_node_form_alter(array &$form, FormStateInte
foreach ($contentAccessibleExternalServiceIncludes as $externalServiceInclude) {
$includeId = $externalServiceInclude->id();
$contentServicesFieldAllowedOptions[$includeId] = $externalServiceInclude->label();
if (in_array($node->id(), $externalServiceInclude->getNodeIds())) {
$isSelected = in_array($node->id(), $externalServiceInclude->getNodeIds());
// Inverts the selection for a site-wide include.
if ($externalServiceInclude->isSitewide()) {
$isSelected = !$isSelected;
}
if ($isSelected) {
$contentServicesFieldDefaultOptions[] = $includeId;
}
}
Expand Down Expand Up @@ -115,14 +127,18 @@ function ucb_site_configuration_form_node_form_submit(array &$form, FormStateInt
$nodeIds = $externalServiceInclude->getNodeIds();
$index = array_search($node->id(), $nodeIds);
$isSelected = (bool) $contentServicesFieldSelectedIds[$includeId];
// Add the node to the include.
// Inverts the selection for a site-wide include.
if ($externalServiceInclude->isSitewide()) {
$isSelected = !$isSelected;
}
if ($isSelected && $index === FALSE) {
// Adds the node to the include.
$nodeIds[] = $node->id();
$externalServiceInclude->set('nodes', $nodeIds);
$externalServiceInclude->save();
// Remove the node from the include.
}
elseif (!$isSelected && $index !== FALSE) {
// Removes the node from the include.
unset($nodeIds[$index]);
$externalServiceInclude->set('nodes', $nodeIds);
$externalServiceInclude->save();
Expand Down

0 comments on commit b46bbec

Please sign in to comment.