Commit 3281e4aa authored by Majid Ali Khan's avatar Majid Ali Khan Committed by Vladimir Roudakov
Browse files

Issue #3273208 by majid.ali, RockHard76, xamount, jukka792, VladimirAus: Cache...

Issue #3273208 by majid.ali, RockHard76, xamount, jukka792, VladimirAus: Cache external addtoany JS library and update the local cache with cron
parent af20f592
Loading
Loading
Loading
Loading
+85 −34
Original line number Diff line number Diff line
@@ -5,14 +5,14 @@
 * Handle AddToAny integration.
 */

use Drupal\Core\Entity\EntityInterface;
use Drupal\addtoany\Form\AddToAnySettingsForm;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\Component\Utility\UrlHelper;
use Drupal\node\Entity\Node;
use Drupal\addtoany\Form\AddToAnySettingsForm;

/**
 *  Implements hook_help().
@@ -37,7 +37,6 @@ function addtoany_help($route_name, RouteMatchInterface $route_match){
  }
}


/**
 * Implements hook_theme().
 */
@@ -122,7 +121,8 @@ function addtoany_entity_extra_field_info() {
    $entityTypeId = $type->id();
    $isAllowed = $config->get("entities.{$entityTypeId}");
    if ($isAllowed) {
      $bundles = Drupal::service('entity_type.bundle.info')->getBundleInfo($entityTypeId);
      $bundles = Drupal::service('entity_type.bundle.info')
        ->getBundleInfo($entityTypeId);
      foreach ($bundles as $bundle => $bundle_data) {
        $extra[$entityTypeId][$bundle]['display']['addtoany'] = [
          'label' => t('AddToAny'),
@@ -152,7 +152,8 @@ function addtoany_entity_view(array &$build, EntityInterface $entity, EntityView
    if ($isAllowed) {
      $data = addtoany_create_entity_data($entity);
      $build['addtoany'] = [
        '#addtoany_html' => \Drupal::token()->replace($data['addtoany_html'], ['node' => $entity]),
        '#addtoany_html' => \Drupal::token()
          ->replace($data['addtoany_html'], ['node' => $entity]),
        '#link_url' => $data['link_url'],
        '#link_title' => $data['link_title'],
        '#button_setting' => $data['button_setting'],
@@ -188,9 +189,14 @@ function addtoany_page_attachments(&$page) {
    }
  }

  //@TODO: Make path configurable.
  $version = \Drupal::state()->get('addtoany.scripts_version');
  $addtoany_lib_path = \Drupal::service('file_url_generator')
    ->generateAbsoluteString('public://js/addtoany/menu/' . $version);
  $javascript_header = 'window.a2a_config=window.a2a_config||{};'
    . 'a2a_config.callbacks=[];a2a_config.overlays=[];'
    . 'a2a_config.templates={};'
    . 'a2a_config.static_server= ' . json_encode($addtoany_lib_path, JSON_UNESCAPED_SLASHES) . ';'
    . $additional_js;

  // Add AddToAny initial JS config.
@@ -249,7 +255,8 @@ function addtoany_page_attachments(&$page) {
 *   The array with url, title, additional_html that will be send to Twig.
 */
function addtoany_create_entity_data($node, $config = NULL) {
  $url = isset($node) ? $node->toUrl('canonical', ['absolute' => TRUE])->toString() : NULL;
  $url = isset($node) ? $node->toUrl('canonical', ['absolute' => TRUE])
    ->toString() : NULL;
  $title = isset($node) ? $node->label() : NULL;
  return addtoany_create_data($url, $title, $config);
}
@@ -287,7 +294,9 @@ function addtoany_create_data($url = NULL, $title = NULL, $config = NULL) {
      $url = (is_null($url) || $url === $front_url . '/node') ? $front_url : $url;
    }
    // Use the current path if the URL is still NULL.
    $url = (is_null($url)) ? Url::fromRoute('<current>')->setAbsolute()->toString() : $url;
    $url = (is_null($url)) ? Url::fromRoute('<current>')
      ->setAbsolute()
      ->toString() : $url;
  }
  else {
    // Sanitize and encode URL for HTML output.
@@ -346,3 +355,45 @@ function addtoany_create_data($url = NULL, $title = NULL, $config = NULL) {

  return $info;
}

/**
 * Implements hook_library_info_alter().
 */
function addtoany_library_info_alter(&$libraries, $extension) {
  // Get the remote addtoany library and download it locally to the website.
  if ($extension == 'addtoany' && isset($libraries['addtoany']['js']['https://static.addtoany.com/menu/page.js'])) {
    if (\Drupal::config('addtoany.settings')
        ->get('library_location') == 'local') {
      $version = \Drupal::state()->get('addtoany.scripts_version');
      $file = 'public://js/addtoany/menu/' . $version . 'page.js';
      if (is_file($file)) {
        $fileUrl = \Drupal::service('file_url_generator')
          ->generateString($file);
        unset($libraries['addtoany']['js']['https://static.addtoany.com/menu/page.js']);
        $libraries['addtoany']['js'][$fileUrl] = [];
      }

    }
  }
}

/**
 * Implements hook_cache_flush().
 */
function addtoany_cache_flush() {
  // Delete all the old versions of the scripts.
  \Drupal::service('addtoany.update')->cleanup();
}

/**
 * Implements hook_cron().
 */
function addtoany_cron() {
  $version = \Drupal::state()->get('addtoany.scripts_version');
  $latest_version = \Drupal::service('addtoany.update')->getScripts();
  // Do not update if latest version is already there.
  if ($latest_version['version'] !== $version) {
    // Update addtoany scripts locally.
    \Drupal::service('addtoany.update')->updateLatestScripts();
  }
}

addtoany.services.yml

0 → 100644
+4 −0
Original line number Diff line number Diff line
services:
  addtoany.update:
    class: Drupal\addtoany\UpdateScripts
    arguments: [ '@file_system', '@file_url_generator', '@logger.factory', '@state' ]
+92 −68
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
 * Configure AddToAny settings for this site.
 */
class AddToAnySettingsForm extends ConfigFormBase {

  /**
   * Drupal\Core\Extension\ModuleHandler definition.
   *
@@ -77,15 +78,6 @@ class AddToAnySettingsForm extends ConfigFormBase {
    return 'addtoany_settings_form';
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return [
      'addtoany.settings',
    ];
  }

  /**
   * {@inheritdoc}
   */
@@ -100,6 +92,11 @@ class AddToAnySettingsForm extends ConfigFormBase {

    $button_img = '<img src="' . $base_path . $this->moduleExtensionList->getPath('addtoany') . '/images/%s" width="%d" height="%d"%s />';

    $library_options = [
      'remote' => $this->t('Remote'),
      'local' => $this->t('Local'),
    ];

    $button_options = [
      'default' => sprintf($button_img, 'a2a_32_32.svg', 32, 32, ' class="addtoany-round-icon"'),
      'custom' => $this->t('Custom button'),
@@ -191,6 +188,15 @@ class AddToAnySettingsForm extends ConfigFormBase {
      '#title' => $this->t('Additional options'),
      '#open' => ($js_value !== '' || $css_value !== '') ? TRUE : FALSE,
    ];

    $form['addtoany_additional_settings']['addtoany_library_location'] = [
      '#type' => 'radios',
      '#title' => $this->t('Location of the library'),
      '#description' => $this->t('The way the library is stored, if local than refreshed on cache clear'),
      '#default_value' => $addtoany_settings->get('library_location') ?? 'remote',
      '#options' => $library_options,
    ];

    $form['addtoany_additional_settings']['addtoany_additional_js'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Additional JavaScript'),
@@ -237,8 +243,14 @@ class AddToAnySettingsForm extends ConfigFormBase {

    // Whitelist the entity IDs that let us link to each bundle's Manage Display page.
    $linkableEntities = [
      'block_content', 'comment', 'commerce_product', 'commerce_store',
      'contact_message', 'media', 'node', 'paragraph',
      'block_content',
      'comment',
      'commerce_product',
      'commerce_store',
      'contact_message',
      'media',
      'node',
      'paragraph',
    ];

    foreach ($entities as $entity) {
@@ -274,7 +286,7 @@ class AddToAnySettingsForm extends ConfigFormBase {
        '#title' => $this->t('@entity', ['@entity' => $entity->getLabel()]),
        '#default_value' => $addtoany_settings->get("entities.{$entityId}"),
        '#description' => $description,
        '#attributes' => ['class' => ['addtoany-entity-checkbox']]
        '#attributes' => ['class' => ['addtoany-entity-checkbox']],
      ];
    }

@@ -287,6 +299,24 @@ class AddToAnySettingsForm extends ConfigFormBase {
    return parent::buildForm($form, $form_state);
  }

  /**
   * Get all available content entities in the environment.
   *
   * @return array
   */
  public static function getContentEntities() {
    $content_entity_types = [];
    $entity_type_definitions = \Drupal::entityTypeManager()->getDefinitions();
    /* @var $definition EntityTypeInterface */
    foreach ($entity_type_definitions as $definition) {
      if ($definition instanceof ContentEntityType) {
        $content_entity_types[] = $definition;
      }
    }

    return $content_entity_types;
  }

  /**
   * {@inheritdoc}
   */
@@ -299,7 +329,8 @@ class AddToAnySettingsForm extends ConfigFormBase {
      ->set('buttons_size', $values['addtoany_buttons_size'])
      ->set('custom_universal_button', $values['addtoany_custom_universal_button'])
      ->set('universal_button', $values['addtoany_universal_button'])
      ->set('universal_button_placement', $values['addtoany_universal_button_placement']);
      ->set('universal_button_placement', $values['addtoany_universal_button_placement'])
      ->set('library_location', $values['addtoany_library_location']);

    foreach (self::getContentEntities() as $entity) {
      $entityId = $entity->id();
@@ -313,19 +344,12 @@ class AddToAnySettingsForm extends ConfigFormBase {
  }

  /**
   * Get all available content entities in the environment.
   * @return array
   * {@inheritdoc}
   */
  public static function getContentEntities() {
    $content_entity_types = [];
    $entity_type_definitions = \Drupal::entityTypeManager()->getDefinitions();
    /* @var $definition EntityTypeInterface */
    foreach ($entity_type_definitions as $definition) {
      if ($definition instanceof ContentEntityType) {
        $content_entity_types[] = $definition;
      }
  protected function getEditableConfigNames() {
    return [
      'addtoany.settings',
    ];
  }

    return $content_entity_types;
  }
}

src/UpdateScripts.php

0 → 100644
+169 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\addtoany;

use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\File\FileSystemInterface;

/**
 * Class UpdateScripts
 */
class UpdateScripts {

  /**
   * Path to contract files.
   */
  const scriptsPath = 'public://js/addtoany/menu/';

  /**
   * List of the latest scripts to download.
   */
  const latestScripts = 'https://www.addtoany.com/ext/updater/files_list/';

  /**
   * The file system service.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

  /**
   * The file URL generator.
   *
   * @var \Drupal\Core\File\FileUrlGeneratorInterface
   */
  protected $fileUrlGenerator;

  /**
   * Logger channel.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $logger;

  /**
   * The state service.
   *
   * @var \Drupal\Core\State\StateInterface
   */
  protected $state;

  /**
   * Constructs a UpdateScripts object
   *
   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
   * @param \Drupal\Core\File\FileUrlGeneratorInterface $fileUrlGenerator
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger
   * @param \Drupal\Core\State\StateInterface $state
   *
   */

  public function __construct(FileSystemInterface $fileSystem, FileUrlGeneratorInterface $fileUrlGenerator, LoggerChannelFactoryInterface $logger, StateInterface $state) {
    $this->fileSystem = $fileSystem;
    $this->logger = $logger->get('addtoany');
    $this->fileUrlGenerator = $fileUrlGenerator;
    $this->state = $state;
  }

  /**
   * Download the latest scripts.
   *
   * @see https://www.addtoany.com/ext/updater/files_list
   * @return false|void
   */
  public function updateLatestScripts() {
    try {
      // List of files that will be downloaded with version.
      $files_to_download = $this->getScripts();

      // Create directory for output
      $directory = self::scriptsPath . $files_to_download['version'];
      // Now we only need the files that will be downloaded.
      $latest_version = $files_to_download['version'];
      unset($files_to_download['version']);
      // Empty the directory before putting new content.
      //$this->fileSystem->deleteRecursive($directory);
      if (!$this->fileSystem->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS)) {
        throw new \RuntimeException("Failed to create the directory: " . $directory);
      }

      foreach ($files_to_download as $file_to_download) {
        $content = file_get_contents($file_to_download);
        $name = basename($file_to_download);
        $file = $directory . '/' . $name;
        // Remove hardcoded external link in page.js that downloads information document..
        if ($name == 'page.js') {
          preg_match('#https?://[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s])(.html)[-A-Za-z0-9+&@\#/%?=~_|!:,.;]+[-A-Za-z0-9+&@\#/%=~_|])#', $content, $match);
          $content = str_replace($match[0], '', $content);
        }
        file_put_contents($file, $content);
      }
      $this->state->set('addtoany.scripts_version', $latest_version);
    }
    catch (\Exception $e) {
      $this->logger->error(
        $e->getMessage() . "\n Failed to update addtoany scripts\n",
      );
      return FALSE;
    }
  }

  /**
   * Get a list of the latest script files
   * and use the hash in the file names for versioning.
   *
   * @return array|false
   */
  public function getScripts() {
    try {
      $files_to_download = file_get_contents(self::latestScripts);
      $files_to_download = explode("\r\n", $files_to_download, 20);
      // Take the first file and extract
      // the hash in the name and use it for versioning.
      $first_file = reset($files_to_download);
      $file_name_parts = explode('.', basename($first_file));
      $version_hash = $file_name_parts[1];
      if (empty($version_hash)) {
        throw new \RuntimeException("Failed to get the hash from js file name: " . basename($first_file));
      }
      $files_to_download['version'] = $version_hash;
      return $files_to_download;
    }
    catch (\Exception $e) {
      $this->logger->error(
        $e->getMessage() . "\n Failed to get the scripts\n",
      );
      return FALSE;
    }
  }

  /**
   * Remove all the versions of the scripts
   * except the latest version.
   *
   * @return void
   * @see getScripts() to check how versioning is done.
   */
  public function cleanup() {
    try {
      $all_script_versions = $this->fileSystem->scanDirectory(self::scriptsPath, '/.*/', ['recurse' => FALSE]);
      $latest_version = $this->state->get('addtoany.scripts_version');
      if (isset($all_script_versions)) {
        foreach ($all_script_versions as $script_version) {
          if ($script_version->name === $latest_version) {
            continue;
          }
          $this->fileSystem->deleteRecursive($script_version->uri);
        }
      }
    }
    catch (\Exception $e) {
      $this->logger->error(
        $e->getMessage() . "\n Failed to cleanup old scripts\n",
      );
    }
  }

}