Minor improvement for ImportService::importFromUrl

Created on 13 August 2025, about 2 months ago

Problem/Motivation

I was trying to sync entities, and didn't get anything. While debuging this I saw $json['data'] was empty.

The reason was that the request got access denied on hook_entity_access()

Proposed resolution

- Add a comment to poor people like me, to give them a hint quicker
- Replace isset with !empty()

📌 Task
Status

Active

Version

3.0

Component

Code

Created by

🇮🇱Israel amitaibu Israel

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @amitaibu
  • Merge request !125Improve docs on ImportService::importFromUrl → (Closed) created by amitaibu
  • Pipeline finished with Failed
    about 2 months ago
    Total: 133s
    #572222
  • 🇬🇧United Kingdom joachim

    Thanks for the patch!

    Could you rebase onto 4.0.x please? CI isn't working on the 3 branch.

    Also, BTW, I assume from you hitting importFromUrl() that you're using a queue to do your imports? Could you tell me a bit about your setup for that please?

  • 🇮🇱Israel amitaibu Israel

    > Could you rebase onto 4.0.x please?

    Sure.

    Context: We have a Central site and a few Satellite sites. On the Central site we're using the Translation system. So 1 Language == 1 Satellite site.

    On the central site, upon hook_node_update(), I call this logic, which will auto-subscribe/ sync my updated node on the satellite sites.

    
    namespace Drupal\general_entity_sync_server;
    
    use Drupal\Core\Config\ConfigFactoryInterface;
    use Drupal\Core\Database\Connection;
    use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
    use Drupal\Core\Entity\EntityTypeManagerInterface;
    use Drupal\Core\Logger\LoggerChannelFactoryInterface;
    use Drupal\Core\Messenger\MessengerInterface;
    use Drupal\Core\StringTranslation\StringTranslationTrait;
    use Drupal\entity_share_websub_hub\Subscription;
    use Drupal\node\NodeInterface;
    use GuzzleHttp\Client;
    use Symfony\Component\DependencyInjection\ContainerInterface;
    use Symfony\Component\HttpFoundation\Response;
    
    /**
     * Defines a class for implementing hooks.
     *
     * @internal
     */
    final class GeneralEntitySyncHooks implements ContainerInjectionInterface {
    
      use StringTranslationTrait;
    
      /**
       * GeneralSsoServerHooks constructor.
       */
      public function __construct(
        private readonly EntityTypeManagerInterface $entityTypeManager,
        private readonly Client $httpClient,
        private readonly LoggerChannelFactoryInterface $loggerChannelFactory,
        private readonly MessengerInterface $messenger,
        private readonly ConfigFactoryInterface $configFactory,
        private readonly Connection $database,
        private readonly Subscription $webHubSubscriptionManager,
      ) {}
    
      /**
       * {@inheritdoc}
       */
      public static function create(ContainerInterface $container) {
        return new static(
          $container->get('entity_type.manager'),
          $container->get('http_client'),
          $container->get('logger.factory'),
          $container->get('messenger'),
          $container->get('config.factory'),
          $container->get('database'),
          $container->get('entity_share_websub_hub.subscription'),
        );
      }
    
      /**
       * Notify Satellite sites for subscription of new translations.
       *
       * We call this on node update hook and not insert hook because insert hook
       * is not invoked when a new translation is created.
       *
       * @param \Drupal\node\NodeInterface $node
       *   Node.
       */
      public function notifySatelliteSites(NodeInterface $node) {
        // Get node details.
        $entity_type_id = $node->getEntityTypeId();
        $node_type = $node->getType();
        $langcode = $node->language()->getId();
        $uuid = $node->uuid();
        $title = $node->getTitle();
    
        // Get existing subscriptions.
        $subscriptions = $this->webHubSubscriptionManager->checkSubscriptions($node);
        $subscribed_channels = [];
        if (!empty($subscriptions)) {
          // Batch query to get all subscribed channel IDs at once.
          $results = $this->database->select('entity_share_websub_hub_subscription', 't')
            ->fields('t', ['channel_id'])
            ->condition('t.sid', $subscriptions, 'IN')
            ->execute()
            ->fetchCol();
    
          // Use array_flip for O(1) lookups.
          $subscribed_channels = array_flip($results);
        }
    
        /** @var \Drupal\entity_share_server\Entity\ChannelInterface[] $channels */
        $channels = $this->entityTypeManager->getStorage('channel')->loadMultiple();
    
        foreach ($channels as $channel) {
          // Skip if the entity doesn't match entity type and bundle configured to
          // the channel.
          if ($entity_type_id !== $channel->get('channel_entity_type') || $node_type !== $channel->get('channel_bundle')) {
            continue;
          }
    
          // Skip if the channel language doesn't match the current node's language.
          $channel_langcode = $channel->get('channel_langcode');
          if ($langcode !== $channel_langcode) {
            continue;
          }
    
          $channel_id = $channel->id();
    
          // Skip if already subscribed.
          if (isset($subscribed_channels[$channel_id])) {
            continue;
          }
    
          // Prep data for POST request to satellite sites.
          $satellite_site_url = $this->configFactory->get('general_entity_sync_server.settings')->get("channel_to_window_sites_mapping.$channel_langcode");
          $data = [
            'remote_id' => 'tgeneral_central',
            'channel_id' => $channel_id,
            'uuid' => $uuid,
          ];
    
          try {
            $response = $this->httpClient->post($satellite_site_url . "/entity-websub/auto-subscribe", [
              'json' => $data,
            ]);
            if ($response->getStatusCode() == Response::HTTP_OK) {
              // Display success message to users.
              $this->messenger->addMessage($this->t('@bundle content @title also synced to @site_id', [
                '@bundle' => $node_type,
                '@title' => $title,
                '@site_id' => $channel_id,
              ]));
            }
          }
          catch (\Exception $e) {
            // Log error for other status codes which will throw exception.
            $this->loggerChannelFactory->get('general_entity_sync_server')->error($this->t('Cannot sync @bundle content @title to @site_id. Sync failed with error: @error', [
              '@bundle' => $node->bundle(),
              '@title' => $node->getTitle(),
              '@site_id' => $channel_id,
              '@error' => $e->getMessage(),
            ]));
          }
        }
      }
    
    }
    

    Happy to follow a different path if you think there is one better

  • Pipeline finished with Failed
    about 2 months ago
    Total: 130s
    #572285
  • Pipeline finished with Failed
    about 2 months ago
    Total: 139s
    #572288
  • Pipeline finished with Failed
    about 2 months ago
    Total: 138s
    #572290
  • Merge request !126Improve docs on ImportService::importFromUrl → (Open) created by amitaibu
  • Pipeline finished with Success
    about 2 months ago
    Total: 678s
    #572293
  • 🇬🇧United Kingdom joachim

    Ah, so your content origin site posts to a special endpoint on the content client site, which tells it to do an automated entity share pull from the requesting content origin site. Using the Entity Share queue system.

    Interesting! Also, the same architecture I used *years* ago with Transport module, lol!

    Have you had any issues with 🐛 pulled dependencies don't participate in the batch, risk of PHP execution timeouts Active ?

    I'm going to do some major restructuring of the central engine of the pull process for that.

  • 🇮🇱Israel amitaibu Israel

    > Have you had any issues with #3539265: pulled dependencies don't participate in the batch, risk of PHP execution timeouts?

    Not so far :)

    Setting back to Needs review, as MR is now against the 4.x version

Production build 0.71.5 2024