You are here

public function SchedulerManager::unpublish in Scheduler 2.x

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

Unpublish scheduled entities.

Return value

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

Throws

\Drupal\scheduler\Exception\SchedulerEntityTypeNotEnabledException

File

src/SchedulerManager.php, line 425

Class

SchedulerManager
Defines a scheduler manager.

Namespace

Drupal\scheduler

Code

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

    // Select all entities of the types for this plugin that are enabled for
    // scheduled unpublishing and where unpublish_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('unpublish_on')
        ->condition('unpublish_on', $this->time
        ->getRequestTime(), '<=')
        ->condition($plugin
        ->typeFieldName(), $scheduler_enabled_types, 'IN')
        ->sort('unpublish_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 unpublished.
    $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 unpublished.
    $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);

    /** @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 unpublishing, 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, 'unpublish_enable', $this
        ->setting('default_unpublish_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 an unpublish-on value, or
        // it is later than the date we are processing then move to the next.
        $unpublish_on = $entity->unpublish_on->value;
        if (empty($unpublish_on) || $unpublish_on > $this->time
          ->getRequestTime()) {
          continue;
        }

        // Do not process the entity if it still has a publish_on time which
        // is in the past, as this implies that scheduled publishing has been
        // blocked by one of the hook functions we provide, and is still being
        // blocked now that the unpublishing time has been reached.
        $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_UNPUBLISH Scheduler event so that modules can react
        // before the entity is unpublished.
        $this
          ->dispatchSchedulerEvent($entity, 'PRE_UNPUBLISH');

        // Update 'changed' timestamp.
        $entity
          ->setChangedTime($unpublish_on);
        $create_unpublishing_revision = $this
          ->getThirdPartySetting($entity, 'unpublish_revision', $this
          ->setting('default_unpublish_revision'));
        if ($create_unpublishing_revision && $entity
          ->getEntityType()
          ->isRevisionable()) {
          $entity
            ->setNewRevision();

          // Use a core date format to guarantee a time is included.
          $revision_log_message = $this
            ->t('Unpublished by Scheduler. The scheduled unpublishing date was @unpublish_on.', [
            '@unpublish_on' => $this->dateFormatter
              ->format($unpublish_on, 'short'),
          ]);

          // Create the new revision, setting message and revision timestamp.
          $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->unpublish_on->value = NULL;

        // Invoke all implementations of hook_scheduler_unpublish_process()
        // and hook_scheduler_{type}_unpublish_process() to allow other
        // modules to do the "unpublishing" process instead of Scheduler.
        $hook_implementations = $this
          ->getHookImplementations('unpublish_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('Unpublishing failed for %title. Calls to @hook returned a failure code.', $logger_variables);

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

          // The entity was 'unpublished' 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 unpublished.
          $this->logger
            ->notice('@type: scheduled unpublishing of %title.', $logger_variables);
          $entity
            ->setUnpublished();
        }

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

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

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

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