You are here

public function SchedulerManager::publish in Scheduler 8

Same name and namespace in other branches
  1. 2.x src/SchedulerManager.php \Drupal\scheduler\SchedulerManager::publish()

Publish scheduled nodes.

Return value

bool TRUE if any node has been published, FALSE otherwise.

Throws

\Drupal\scheduler\Exception\SchedulerMissingDateException

\Drupal\scheduler\Exception\SchedulerNodeTypeNotEnabledException

File

src/SchedulerManager.php, line 131

Class

SchedulerManager
Defines a scheduler manager.

Namespace

Drupal\scheduler

Code

public function publish() {
  $result = FALSE;
  $action = 'publish';

  // Select all nodes of the types that are enabled for scheduled publishing
  // and where publish_on is less than or equal to the current time.
  $nids = [];
  $scheduler_enabled_types = array_keys(_scheduler_get_scheduler_enabled_node_types($action));
  if (!empty($scheduler_enabled_types)) {
    $query = $this->entityTypeManager
      ->getStorage('node')
      ->getQuery()
      ->exists('publish_on')
      ->condition('publish_on', $this->time
      ->getRequestTime(), '<=')
      ->condition('type', $scheduler_enabled_types, 'IN')
      ->latestRevision()
      ->sort('publish_on')
      ->sort('nid');

    // Disable access checks for this query.
    // @see https://www.drupal.org/node/2700209
    $query
      ->accessCheck(FALSE);
    $nids = $query
      ->execute();
  }

  // Allow other modules to add to the list of nodes to be published.
  $nids = array_unique(array_merge($nids, $this
    ->nidList($action)));

  // Allow other modules to alter the list of nodes to be published.
  $this->moduleHandler
    ->alter('scheduler_nid_list', $nids, $action);

  // In 8.x the entity translations are all associated with one node id
  // unlike 7.x where each translation was a separate node. This means that
  // the list of node ids returned above may have some translations that need
  // processing now and others that do not.

  /** @var \Drupal\node\NodeInterface[] $nodes */
  $nodes = $this
    ->loadNodes($nids);
  foreach ($nodes as $node_multilingual) {

    // The API calls could return nodes of types which are not enabled for
    // scheduled publishing, so do not process these. This check can be done
    // once, here, as the setting will be the same for all translations.
    if (!$node_multilingual->type->entity
      ->getThirdPartySetting('scheduler', 'publish_enable', $this
      ->setting('default_publish_enable'))) {
      throw new SchedulerNodeTypeNotEnabledException(sprintf("Node %d '%s' will not be published because node type '%s' is not enabled for scheduled publishing", $node_multilingual
        ->id(), $node_multilingual
        ->getTitle(), node_get_type_label($node_multilingual)));
    }
    $languages = $node_multilingual
      ->getTranslationLanguages();
    foreach ($languages as $language) {

      // The object returned by getTranslation() behaves the same as a $node.
      $node = $node_multilingual
        ->getTranslation($language
        ->getId());

      // If the current translation does not have a publish on value, or it is
      // later than the date we are processing then move on to the next.
      $publish_on = $node->publish_on->value;
      if (empty($publish_on) || $publish_on > $this->time
        ->getRequestTime()) {
        continue;
      }

      // Check that other modules allow the action on this node.
      if (!$this
        ->isAllowed($node, $action)) {
        continue;
      }

      // $node->setChangedTime($publish_on) will fail badly if an API call has
      // removed the date. Trap this as an exception here and give a
      // meaningful message.
      // @todo This will now never be thrown due to the empty(publish_on)
      // check above to cater for translations. Remove this exception?
      if (empty($node->publish_on->value)) {
        $field_definitions = $this->entityTypeManager
          ->getFieldDefinitions('node', $node
          ->getType());
        $field = (string) $field_definitions['publish_on']
          ->getLabel();
        throw new SchedulerMissingDateException(sprintf("Node %d '%s' will not be published because field '%s' has no value", $node
          ->id(), $node
          ->getTitle(), $field));
      }

      // Trigger the PRE_PUBLISH event so that modules can react before the
      // node is published.
      $event = new SchedulerEvent($node);
      $this
        ->dispatch($event, SchedulerEvents::PRE_PUBLISH);
      $node = $event
        ->getNode();

      // Update 'changed' timestamp.
      $node
        ->setChangedTime($publish_on);
      $old_creation_date = $node
        ->getCreatedTime();
      $msg_extra = '';

      // If required, set the created date to match published date.
      if ($node->type->entity
        ->getThirdPartySetting('scheduler', 'publish_touch', $this
        ->setting('default_publish_touch')) || $node
        ->getCreatedTime() > $publish_on && $node->type->entity
        ->getThirdPartySetting('scheduler', 'publish_past_date_created', $this
        ->setting('default_publish_past_date_created'))) {
        $node
          ->setCreatedTime($publish_on);
        $msg_extra = $this
          ->t('The previous creation date was @old_creation_date, now updated to match the publishing date.', [
          '@old_creation_date' => $this->dateFormatter
            ->format($old_creation_date, 'short'),
        ]);
      }
      $create_publishing_revision = $node->type->entity
        ->getThirdPartySetting('scheduler', 'publish_revision', $this
        ->setting('default_publish_revision'));
      if ($create_publishing_revision) {
        $node
          ->setNewRevision();

        // Use a core date format to guarantee a time is included.
        $revision_log_message = rtrim($this
          ->t('Published by Scheduler. The scheduled publishing date was @publish_on.', [
          '@publish_on' => $this->dateFormatter
            ->format($publish_on, 'short'),
        ]) . ' ' . $msg_extra);
        $node
          ->setRevisionLogMessage($revision_log_message)
          ->setRevisionCreationTime($this->time
          ->getRequestTime());
      }

      // Unset publish_on so the node will not get rescheduled by subsequent
      // calls to $node->save().
      $node->publish_on->value = NULL;

      // Invoke all implementations of hook_scheduler_publish_action() to
      // allow other modules to do the "publishing" process instead of
      // Scheduler.
      $hook = 'scheduler_publish_action';
      $processed = FALSE;
      $failed = FALSE;
      foreach ($this->moduleHandler
        ->getImplementations($hook) as $module) {
        $function = $module . '_' . $hook;
        $return = $function($node);
        $processed = $processed || $return === 1;
        $failed = $failed || $return === -1;
      }

      // Log the fact that a scheduled publication is about to take place.
      $view_link = $node
        ->toLink($this
        ->t('View node'));
      $node_type = $this->entityTypeManager
        ->getStorage('node_type')
        ->load($node
        ->bundle());
      $node_type_link = $node_type
        ->toLink($this
        ->t('@label settings', [
        '@label' => $node_type
          ->label(),
      ]), 'edit-form');
      $logger_variables = [
        '@type' => $node_type
          ->label(),
        '%title' => $node
          ->getTitle(),
        'link' => $node_type_link
          ->toString() . ' ' . $view_link
          ->toString(),
        '@hook' => 'hook_' . $hook,
      ];
      if ($failed) {

        // At least one hook function returned a failure or exception, so stop
        // processing this node and move on to the next one.
        $this->logger
          ->warning('Publishing failed for %title. Calls to @hook returned a failure code.', $logger_variables);
        continue;
      }
      elseif ($processed) {

        // The node had 'publishing' processed by a module implementing the
        // hook, so no need to do anything more, apart from log this result.
        $this->logger
          ->notice('@type: scheduled processing of %title completed by calls to @hook.', $logger_variables);
      }
      else {

        // None of the above hook calls processed the node and there were no
        // errors detected so set the node to published.
        $this->logger
          ->notice('@type: scheduled publishing of %title.', $logger_variables);
        $node
          ->setPublished();
      }

      // Invoke the event to tell Rules that Scheduler has published the node.
      if ($this->moduleHandler
        ->moduleExists('scheduler_rules_integration')) {
        _scheduler_rules_integration_dispatch_cron_event($node, 'publish');
      }

      // Trigger the PUBLISH event so that modules can react after the node is
      // published.
      $event = new SchedulerEvent($node);
      $this
        ->dispatch($event, SchedulerEvents::PUBLISH);

      // Use the standard actions system to publish and save the node.
      $node = $event
        ->getNode();
      $action_id = 'node_publish_action';
      if ($this->moduleHandler
        ->moduleExists('workbench_moderation_actions')) {

        // workbench_moderation_actions module uses a custom action instead.
        $action_id = 'state_change__node__published';
      }
      $this->entityTypeManager
        ->getStorage('action')
        ->load($action_id)
        ->getPlugin()
        ->execute($node);
      $result = TRUE;
    }
  }
  return $result;
}