You are here

public function SchedulerManager::publish in Scheduler 2.x

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

Publish scheduled entities.

Return value

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

Throws

\Drupal\scheduler\Exception\SchedulerEntityTypeNotEnabledException

File

src/SchedulerManager.php, line 231

Class

SchedulerManager
Defines a scheduler manager.

Namespace

Drupal\scheduler

Code

public function publish() {
  $result = FALSE;
  $process = 'publish';
  $plugins = $this
    ->getPlugins();
  foreach ($plugins as $entityTypeId => $plugin) {

    // Select all entities of the types for this plugin that are enabled for
    // scheduled publishing and where publish_on is less than or equal to the
    // current time.
    $ids = [];
    $scheduler_enabled_types = $this
      ->getEnabledTypes($entityTypeId, $process);
    if (!empty($scheduler_enabled_types)) {
      $query = $this->entityTypeManager
        ->getStorage($entityTypeId)
        ->getQuery()
        ->exists('publish_on')
        ->condition('publish_on', $this->time
        ->getRequestTime(), '<=')
        ->condition($plugin
        ->typeFieldName(), $scheduler_enabled_types, 'IN')
        ->sort('publish_on');

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

      // If the entity type is revisionable then make sure we look for the
      // latest revision. This is important for moderated entities.
      if ($this->entityTypeManager
        ->getDefinition($entityTypeId)
        ->isRevisionable()) {
        $query
          ->latestRevision();
      }
      $ids = $query
        ->execute();
    }

    // Allow other modules to add to the list of entities to be published.
    $hook_implementations = $this
      ->getHookImplementations('list', $entityTypeId);
    foreach ($hook_implementations as $function) {

      // Cast each hook result as array, to protect from bad implementations.
      $ids = array_merge($ids, (array) $function($process, $entityTypeId));
    }

    // Allow other modules to alter the list of entities to be published.
    $hook_implementations = $this
      ->getHookImplementations('list_alter', $entityTypeId);
    foreach ($hook_implementations as $function) {
      $function($ids, $process, $entityTypeId);
    }

    // Finally ensure that there are no duplicates in the list of ids.
    $ids = array_unique($ids);

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

    /** @var \Drupal\Core\Entity\EntityInterface[] $entities */
    $entities = $this
      ->loadEntities($ids, $entityTypeId);
    foreach ($entities as $entity_multilingual) {

      // The API calls could return entities of types which are not enabled
      // for scheduled publishing, so do not process these. This check can be
      // done once as the setting will be the same for all translations.
      if (!$this
        ->getThirdPartySetting($entity_multilingual, 'publish_enable', $this
        ->setting('default_publish_enable'))) {
        $this
          ->throwSchedulerException($entity_multilingual, 'SchedulerEntityTypeNotEnabledException', $process);
      }
      $languages = $entity_multilingual
        ->getTranslationLanguages();
      foreach ($languages as $language) {

        // The object returned by getTranslation() is a normal $entity.
        $entity = $entity_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 = $entity->publish_on->value;
        if (empty($publish_on) || $publish_on > $this->time
          ->getRequestTime()) {
          continue;
        }

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

        // Trigger the PRE_PUBLISH Scheduler event so that modules can react
        // before the entity is published.
        $this
          ->dispatchSchedulerEvent($entity, 'PRE_PUBLISH');

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

        // If required, set the created date to match published date.
        if ($this
          ->getThirdPartySetting($entity, 'publish_touch', $this
          ->setting('default_publish_touch')) || $entity
          ->getCreatedTime() > $publish_on && $this
          ->getThirdPartySetting($entity, 'publish_past_date_created', $this
          ->setting('default_publish_past_date_created'))) {
          $entity
            ->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 = $this
          ->getThirdPartySetting($entity, 'publish_revision', $this
          ->setting('default_publish_revision'));
        if ($create_publishing_revision && $entity
          ->getEntityType()
          ->isRevisionable()) {
          $entity
            ->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);
          $entity
            ->setRevisionLogMessage($revision_log_message)
            ->setRevisionCreationTime($this->time
            ->getRequestTime());
        }

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

        // Invoke all implementations of hook_scheduler_publish_process() and
        // hook_scheduler_{type}_publish_process() to allow other modules to
        // do the "publishing" process instead of Scheduler.
        $hook_implementations = $this
          ->getHookImplementations('publish_process', $entity);
        $processed = FALSE;
        $failed = FALSE;
        foreach ($hook_implementations as $function) {
          $return = $function($entity);
          $processed = $processed || $return === 1;
          $failed = $failed || $return === -1;
        }

        // Create a set of variables for use in the log message.
        $entity_type = $this->entityTypeManager
          ->getStorage($entityTypeId . '_type')
          ->load($entity
          ->bundle());
        $view_link = $entity
          ->toLink($this
          ->t('View @type', [
          '@type' => strtolower($entity_type
            ->label()),
        ]));
        $entity_type_link = $entity_type
          ->toLink($this
          ->t('@label settings', [
          '@label' => $entity_type
            ->label(),
        ]), 'edit-form');
        $logger_variables = [
          '@type' => $entity_type
            ->label(),
          '%title' => $entity
            ->label(),
          '@hook' => implode(', ', $hook_implementations),
          'link' => $view_link
            ->toString() . ' ' . $entity_type_link
            ->toString(),
        ];
        if ($failed) {

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

          // Restore the publish_on date to allow another attempt next time.
          $entity->publish_on->value = $publish_on;
          $entity
            ->save();
          continue;
        }
        elseif ($processed) {

          // The entity was 'published' by a module implementing the hook, so
          // we only need to 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 entity and there were
          // no errors detected so set the entity to published.
          $this->logger
            ->notice('@type: scheduled publishing of %title.', $logger_variables);
          $entity
            ->setPublished();
        }

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

        // Trigger the PUBLISH Scheduler event so that modules can react after
        // the entity is published.
        $this
          ->dispatchSchedulerEvent($entity, 'PUBLISH');

        // Use the standard actions system to publish and save the entity.
        $action_id = $plugin
          ->publishAction();
        if ($this->moduleHandler
          ->moduleExists('workbench_moderation_actions')) {

          // workbench_moderation_actions module uses a custom action instead.
          $action_id = 'state_change__' . $entityTypeId . '__published';
        }
        if (!($loaded_action = $this->entityTypeManager
          ->getStorage('action')
          ->load($action_id))) {
          $this
            ->missingAction($action_id, $process);
        }
        $loaded_action
          ->getPlugin()
          ->execute($entity);
        $result = TRUE;
      }
    }
  }
  return $result;
}