contextly-8.x-2.1/src/ContextlyBaseService.php
src/ContextlyBaseService.php
<?php
namespace Drupal\contextly;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Drupal\Core\Url;
use Drupal\contextly\Contextly\ContextlyDrupalKit;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\Entity\EntityInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\node\NodeInterface;
use Drupal\node\Entity\NodeType;
/**
* The ContextlyBaseService class.
*/
class ContextlyBaseService implements ContextlyBaseServiceInterface {
use StringTranslationTrait;
public function __construct(
protected readonly ContainerInterface $container,
) {
}
/**
* {@inheritdoc}
*/
public function settingsValidateApiKey(string $key): array {
// Parse string key into an array.
$key = explode('-', $key, 2);
if (count($key) !== 2) {
return [FALSE, $this->t('API key is incorrect.')];
}
// Convert it into associative array for later use.
$key = array_combine(['appID', 'appSecret'], $key);
try {
// Check API key. Create API client with isolated session.
$settings = ContextlyDrupalKit::getDefaultSettings();
foreach ($key as $name => $value) {
$settings->{$name} = $value;
}
/** @var \Drupal\contextly\Contextly\ContextlyDrupalKit $kit */
$kit = new ContextlyDrupalKit($settings);
//$session = $kit->newApiSessionIsolated();
$api = $kit->newApi();
// Failed authorization should throw an exception at this point.
$api->testCredentials();
return [TRUE, $key];
}
catch (\Exception $e) {
return [FALSE, $this->t('Test API request failed.')];
}
}
/**
* {@inheritdoc}
*/
public function settingsCpTokenValue(string $type): string {
return 'contextly/cp/' . $type;
}
/**
* {@inheritdoc}
*/
public function settingsSetApiKeyTokenValue() {
return "contextly/set-api-key";
}
/**
* {@inheritdoc}
*/
public function settingsCpTourRedirect() {
/** @var \Symfony\Component\HttpFoundation\Request $request */
$request = $this->container->get('request_stack')->getMainRequest();
if ($request) {
// Cleanup destination in any case.
$request->query->remove('destination');
$token = $request->query->get('token');
if (empty($token)) {
throw new NotFoundHttpException();
}
/** @var \Drupal\Core\Access\CsrfTokenGenerator $token_generator */
$token_generator = $this->container->get('csrf_token');
$token_value = $this->settingsCpTokenValue('tour');
if (!$token_generator->validate($token, $token_value)) {
$this->container->get('messenger')
->addWarning($this->t('Something went wrong. Please try again.'));
return new RedirectResponse(Url::fromUri('/admin/config/content/contextly'));
}
return $this->settingsUrl('/tour');
}
}
/**
* {@inheritdoc}
*/
public function settingsSetApiKeyRedirect() {
/** @var \Symfony\Component\HttpFoundation\Request $request */
$request = $this->container->get('request_stack')->getMainRequest();
if ($request) {
// Cleanup destination in any case.
$request->query->remove('destination');
$token = $request->query->get('token');
$api_key = $request->query->get('api_key');
if (empty($token) || empty($api_key)) {
throw new NotFoundHttpException();
}
/** @var \Drupal\Core\Access\CsrfTokenGenerator $token_generator */
$token_generator = $this->container->get('csrf_token');
$token_value = $this->settingsSetApiKeyTokenValue();
if (!$token_generator->validate($token, $token_value)) {
$this->container->get('messenger')
->addWarning($this->t('Something went wrong. Please try again.'));
return new RedirectResponse(Url::fromRoute('contextly.contextly_admin_form'));
}
[$success, $result] = $this->settingsValidateApiKey($api_key);
if ($success) {
$this->container->get('state')->set('api_key', $result);
$this->settingsResetSharedToken();
$this->container->get('messenger')
->addStatus($this->t('Your API key has been successfully updated.'));
}
else {
$this->container->get('messenger')->addError($result);
}
return new RedirectResponse(Url::fromRoute('contextly.contextly_admin_form'));
}
}
/**
* {@inheritdoc}
*/
public function settingsUrl($type) {
global $base_url;
$site_name = $this->container->get('config.factory')
->get('system.site')->get('name');
$token_value = $this->settingsSetApiKeyTokenValue();
/** @var \Drupal\Core\Access\CsrfTokenGenerator $token_generator */
$token_generator = $this->container->get('csrf_token');
$token = $token_generator->get($token_value);
$options = [
'query' => [
'token' => $token,
],
'absolute' => TRUE,
];
$url = Url::fromRoute('contextly.admin_controller_set_api_key', [], $options);
$query = [
'type' => $type,
'blog_url' => $base_url,
'blog_title' => $site_name,
'cms_settings_page' => $url->toString(),
];
/** @var \Drupal\contextly\Contextly\ContextlyDrupalKit $kit */
$kit = ContextlyDrupalKit::getInstance();
// Try to get the auth token and use direct login.
try {
$token = $kit->newApi()->getAccessToken();
$query += [
'contextly_access_token' => $token,
];
}
catch (\Exception $e) {
// Just silently fail.
}
$path = $this->contextlyServerUrl('cp') . 'cms-redirect';
$url = Url::fromUri($path, ['query' => $query])->toString();
$redirect = new TrustedRedirectResponse($url);
return $redirect->send();
}
/**
* {@inheritdoc}
*/
public function contextlyServerUrl($server_type,
$secure = TRUE): string {
$scheme = $secure ? 'https:' : 'http:';
$server_mode = $this->container->get('config.factory')
->get('contextly.settings')->get('server_mode') ?: 'dev';
$servers = $this->contextlyServers($scheme);
if (!empty($servers[$server_mode][$server_type])) {
return $servers[$server_mode][$server_type];
}
return '';
}
/**
* {@inheritdoc}
*/
public function contextlyServers(string $scheme): array {
return [
'dev' => [
'main' => "$scheme//dev.contextly.com/",
'cp' => "$scheme//dev.contextly.com/",
'api' => "$scheme//devrest.contextly.com/",
],
'live' => [
'main' => "$scheme//contextly.com/",
'cp' => "$scheme//contextly.com/",
'api' => "$scheme//rest.contextly.com/",
],
];
}
/**
* {@inheritdoc}
*/
public function settingsResetSharedToken() {
/** @var \Drupal\contextly\Contextly\ContextlyDrupalKit $kit */
$kit = ContextlyDrupalKit::getInstance();
$kit->newApiSession()->removeSharedToken();
}
/**
* {@inheritdoc}
*/
public function nodeContextlyIsDisabled(NodeInterface $node): bool {
if ($this->isNodeTypeEnabled($node->bundle())) {
return !empty($node->contextly_disabled->value);
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getApiKey(): array {
$config = $this->container->get('config.factory')
->get('contextly.settings');
$key = explode('-', $config->get('api_key'));
if (count($key) !== 2) {
return [];
}
// Convert it into associative array for later use.
return array_combine(['appID', 'appSecret'], $key);
}
/**
* {@inheritdoc}
*/
public function isApiKeySet(): bool {
return !empty($this->getApiKey());
}
/**
* {@inheritdoc}
*/
public function isNodeTypeEnabled(string $type_name): bool {
$config = $this->container->get('config.factory')
->get('contextly.contextly_node_types_admin');
if ($config->get('contextly_all_node_types')) {
return TRUE;
}
$enabled_types = $config->get('contextly_node_types');
if (!is_array($enabled_types)) {
return FALSE;
}
return in_array($type_name, $enabled_types, TRUE);
}
/**
* {@inheritdoc}
*/
public function saveNodeRevisionSettings(NodeInterface $node) {
$fields = [
'disabled' => (int) $this->nodeContextlyIsDisabled($node),
];
$key = [
'nid' => $node->id(),
'vid' => $node->getRevisionId(),
];
$this->container->get('database')->merge('contextly_node_settings')
->key($key)
->fields($fields)
->execute();
}
/**
* {@inheritdoc}
*/
public function watchdogException(\Exception $exception,
string $message = NULL) {
$details = (string) $exception;
if (isset($message)) {
$details = "{$message}\n\n{$details}";
}
$details = nl2br($details);
$this->container->get('logger.factory')->get('contextly')->error($details);
}
/**
* {@inheritdoc}
*/
public function nodeChanged(NodeInterface $node) {
if ($this->isApiKeySet() && $this->isNodeTypeEnabled($node->bundle())) {
/** @var \Drupal\contextly\Contextly\ContextlyDrupalKit $kit */
$kit = ContextlyDrupalKit::getInstance();
try {
$kit->newDrupalNodeEditor()->putNode($node);
}
catch (\ContextlyKitException $e) {
$this->watchdogException($e, 'Unable to send the node to the Contextly');
if ($this->container->get('current_user')->hasPermission('manage contextly links')) {
$this->container->get('messenger')
->addError('Unable to send the node to the Contextly. See log for details.');
}
}
}
}
/**
* {@inheritdoc}
*/
public function removeNodeRevisionSettings(NodeInterface $node) {
$this->container->get('database')->delete('contextly_node_settings')
->condition('vid', $node->vid)
->execute();
}
/**
* {@inheritdoc}
*/
public function getEnabledTypes() {
$config = $this->container->get('config.factory')
->get('contextly.contextly_node_types_admin');
// $all = $config->get('contextly_all_node_types');
if ($config->get('contextly_all_node_types')) {
return array_keys(NodeType::loadMultiple());
}
else {
return $config->get('contextly_node_types');
}
}
/**
* {@inheritdoc}
*/
public function getNodeTypeFields(string $node_type,
array $field_types) {
$fields = [];
$instances = $this->container->get('entity_field.manager')
->getFieldDefinitions('node', $node_type);
foreach ($instances as $field_definition) {
if ($field_definition instanceof FieldConfig &&
(in_array($field_definition->getType(), $field_types, TRUE) ||
in_array($field_definition->getSetting('handler'), $field_types, TRUE))) {
$fields[$field_definition->getName()] = $field_definition->getLabel();
}
}
return $fields;
}
/**
* {@inheritdoc}
*/
public function settingsGetAvailableFields(array $field_types) {
$enabled_types = $this->getEnabledTypes();
$available_fields = [];
foreach ($enabled_types as $node_type) {
$node_type_fields = $this->getNodeTypeFields($node_type, $field_types);
if (!empty($node_type_fields)) {
$available_fields[$node_type] = $node_type_fields;
}
}
return $available_fields;
}
/**
* {@inheritdoc}
*/
public function nodeWidgetsSettings(NodeInterface $node): array {
$api_key = $this->getApiKey();
$app_id = '';
if (isset($api_key['appID'])) {
$app_id = $api_key['appID'];
}
/** @var \Drupal\contextly\Contextly\ContextlyDrupalKit $kit */
$kit = ContextlyDrupalKit::getInstance();
$isHttps = $kit->isHttps();
return [
'contextlyWidgets' => [
'version' => CONTEXTLY_CLIENT_VERSION,
'appId' => $app_id,
'apiServerURL' => $this->contextlyServerUrl('api', $isHttps),
'mainServerURL' => $this->contextlyServerUrl('main', $isHttps),
'renderLinkWidgets' => empty($node->contextly_disabled),
],
];
}
/**
* {@inheritdoc}
*/
public function contextlyNodeView(array &$build,
EntityInterface $entity,
EntityViewDisplayInterface $display,
$view_mode) {
if (!$this->isApiKeySet()) {
// API key is not set.
return;
}
if (!$this->isNodeTypeEnabled($entity->bundle())) {
// Node bundle is not enable to contextly view.
return;
}
// Show widget on the node view page only.
if ($view_mode !== 'full' || !node_is_page($entity)) {
return;
}
$build['contextly_widget'] = [
'#weight' => 0,
];
$build['contextly_widget']['#attached'] = [
'library' => [
'contextly/widget',
'contextly/node-view',
],
'drupalSettings' => $this->nodeWidgetsSettings($entity),
];
$build['storyline_subscribe'] = [
'#theme_wrappers' => ['container'],
'#attributes' => [
'id' => 'ctx-sl-subscribe',
'class' => ['ctx-clearfix'],
],
];
$build['module'] = [
'#theme_wrappers' => ['container'],
'#attributes' => [
'id' => 'ctx-module',
'class' => ['ctx-module-container', 'ctx-clearfix'],
],
];
/** @var \Drupal\contextly\Contextly\ContextlyDrupalKit $kit */
$kit = ContextlyDrupalKit::getInstance();
// Add Contextly meta-data to the post at this point.
$metadata = $kit->newDrupalNodeData()->getMetadata();
$metadata_tag = [
'#type' => 'html_tag',
'#tag' => 'meta',
'#attributes' => [
'name' => 'contextly-page',
'id' => 'contextly-page',
'content' => json_encode($metadata),
],
];
$build['#attached']['html_head'][] = [$metadata_tag, 'contextly_metadata'];
}
/**
* {@inheritdoc}
*/
public function formatDate($timestamp): string {
$system_date = $this->container->get('config.factory')->get('system.date');
$timezone = $system_date
->get('timezone_default') ?: date_default_timezone_get();
return \Drupal::service('date.formatter')
->format($timestamp, 'custom', 'Y-m-d H:i:s', $timezone);
}
/**
* {@inheritdoc}
*/
public function getSettings(NodeInterface $node): array {
global $base_url;
return [
'contextlyEditor' => [
'token' => $this->container->get('csrf_token')
->get('contextly/node-edit/' . $node->id()),
'nid' => $node->id(),
'baseUrl' => $base_url . '/',
],
];
}
}
