g2-8.x-1.x-dev/src/WOTD.php

src/WOTD.php
<?php

declare(strict_types=1);

namespace Drupal\g2;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\State\StateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\Core\Utility\Error;
use Drupal\g2\Exception\RandomException;
use Drupal\node\NodeInterface;
use Drupal\views\ViewExecutable;
use Psr\Log\LoggerInterface;

/**
 * Class WOTD provides data for the WOTD block and API.
 *
 * @phpstan-consistent-constructor
 */
class WOTD {

  use StringTranslationTrait;

  /**
   * The format of the last WOTD timestamp in state.
   */
  const DATE_STORAGE_FORMAT = DATE_RFC3339;

  /**
   * The format under which dates are compared for WOTD rotation.
   */
  const DATE_COMPARISON_FORMAT = 'Y-m-d';

  /**
   * The Epoch in RFC3339 format.
   */
  const EPOCH_RFC3339 = '1970-01-01T00:00:00+00:00';

  /**
   * RX_TITLE matches the optional nid in an autocomplete entry like "foo (24)".
   */
  const RX_TITLE = '/\((\d+)\)\s*$/';

  /**
   * The core database service.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected Connection $db;

  /**
   * The config.factory service.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected ConfigFactoryInterface $config;

  /**
   * The entity_type.manager service.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected EntityTypeManagerInterface $etm;

  /**
   * The logger.channel.g2 service.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected LoggerInterface $logger;

  /**
   * The g2.random service.
   *
   * @var \Drupal\g2\Random
   */
  protected Random $random;

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

  /**
   * The datetime.time service.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  protected TimeInterface $time;

  /**
   * Random constructor.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config
   *   The config.factory service.
   * @param \Drupal\Core\Database\Connection $db
   *   The database service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $etm
   *   The entity_type.manager server.
   * @param \Psr\Log\LoggerInterface $logger
   *   The logger.channel.g2 service.
   * @param \Drupal\g2\Random $random
   *   The g2.random service.
   * @param \Drupal\Core\State\StateInterface $state
   *   The state service.
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   The datetime.time service.
   */
  public function __construct(
    ConfigFactoryInterface $config,
    Connection $db,
    EntityTypeManagerInterface $etm,
    LoggerInterface $logger,
    Random $random,
    StateInterface $state,
    TimeInterface $time,
  ) {
    $this->config = $config;
    $this->db = $db;
    $this->etm = $etm;
    $this->logger = $logger;
    $this->random = $random;
    $this->state = $state;
    $this->time = $time;
  }

  /**
   * Helper function to read configuration.
   *
   * @return bool
   *   Is services.wotd.auth_change true ?
   */
  public function isAutoChangeEnabled(): bool {
    return (bool) $this->config
      ->get(G2::CONFIG_NAME)
      ->get(G2::VARWOTDAUTOCHANGE);
  }

  /**
   * Apply WOTD auto change if the current day change since it was last changed.
   */
  public function autoChange(): void {
    $mixedDate = $this->state->get(G2::VARWOTDDATE);
    if (!is_scalar($mixedDate)) {
      $mixedDate = static::EPOCH_RFC3339;
    }
    $previousStored = (string) $mixedDate ?: static::EPOCH_RFC3339;
    $previousDTI = \DateTimeImmutable::createFromFormat(static::DATE_STORAGE_FORMAT, $previousStored);
    assert($previousDTI instanceof \DateTimeImmutable);
    $todayDTI = \DateTimeImmutable::createFromFormat("U",
      (string) $this->time->getRequestTime());
    assert($todayDTI instanceof \DateTimeImmutable);
    $previous = $previousDTI->format(static::DATE_COMPARISON_FORMAT);
    $today = $todayDTI->format(static::DATE_COMPARISON_FORMAT);
    if ($today !== $previous) {
      $todayStored = $todayDTI->format(static::DATE_STORAGE_FORMAT);
      try {
        $random = $this->random->get();
        $this->logger->info('WOTD auto-changed to "@title" (@nid)"', [
          '@title' => $random->label(),
          '@nid' => $random->id(),
          'link' => $random->toLink($this->t("view"))->toString(),
        ]);
        $this->config
          ->getEditable(G2::CONFIG_NAME)
          ->set(G2::VARWOTDENTRY, (int) $random->id())
          ->save();
        $this->state->set(G2::VARWOTDDATE, $todayStored);
      }
      catch (RandomException $e) {
        $link = Link::createFromRoute($this->t("check G2 entries"),
          G2::ROUTE_ADMIN_CONTENT,
          [],
          ['query' => ['type' => G2::BUNDLE]],
        )->toString();
        $this->logger->error(Error::DEFAULT_ERROR_MESSAGE,
          ['link' => $link] + Error::decodeException($e));
      }
    }
    G2::invalidateWotdView();
  }

  /**
   * Implements hook_cron().
   */
  public function cron(): void {
    if ($this->isAutoChangeEnabled()) {
      $this->autoChange();
    }
  }

  /**
   * Get the WOTD.
   *
   * @return \Drupal\node\NodeInterface|null
   *   The daily WOTD node, or NULL if none is set.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function get(): ?NodeInterface {
    $mixedNid = $this->config
      ->get(G2::CONFIG_NAME)
      ->get(G2::VARWOTDENTRY);
    if (empty($mixedNid)) {
      return NULL;
    }
    assert(is_numeric($mixedNid));
    $nid = (int) $mixedNid;

    return $this->etm
      ->getStorage(G2::TYPE)
      ->load($nid);
  }

  /**
   * Return the title of the node completed by its nid in parentheses.
   *
   * This is meant to be used on input fields, not on displays, because it is
   * not escaped.
   *
   * @param \Drupal\node\NodeInterface|null $node
   *   The node to label.
   *
   * @return string
   *   The title like "Some title (62)".
   */
  public static function numberedTitleInput(?NodeInterface $node): string {
    if (empty($node) || $node->id() === 0) {
      return '';
    }

    // !title: we don't filter since this is input, not output,
    // and can contain normally escaped characters, to accommodate
    // entries like "<", "C#" or "AT&T"
    $title = strtr('!title (@nid)',
      ['!title' => $node->label(), '@nid' => $node->id()]
    );
    return $title;
  }

  /**
   * Implements hook_preprocess_views_view_rss().
   *
   * Provide missing channel elements.
   *
   * @param mixed[] $variables
   *   See hook_preprocess_HOOK for views_view_rss.
   */
  public function preprocessViewsViewRss(array &$variables): void {
    $route = $this->config
      ->get(G2::CONFIG_NAME)
      ->get(G2::VARMAINROUTE);
    assert(is_string($route));
    $mail = $this->config
      ->get('system.site')
      ->get('mail');

    $variables['link'] = Url::fromRoute($route)->setAbsolute()->toString();
    // Get the major Drupal version.
    [$coreVersion] = explode('.', \Drupal::VERSION);
    $generator = $this->t('Glossary 2 module for Drupal @version :url', [
      '@version' => $coreVersion,
      ':url' => 'https://www.drupal.org/project/g2',
    ]);
    $variables['channel_elements'][] = [
      '#type' => 'html_tag',
      '#tag' => 'generator',
      '#value' => $generator,
    ];
    $variables['channel_elements'][] = [
      '#type' => 'html_tag',
      '#tag' => 'managingEditor',
      '#value' => $mail,
    ];
  }

  /**
   * Implements hook_views_pre_render().
   *
   * Override WOTD created time with the latest rotation time.
   */
  public function viewsPreRender(ViewExecutable $view): void {
    if (empty($view->result)) {
      return;
    }
    /** @var \Drupal\node\Entity\Node $node */
    $node = &$view->result[0]->_entity;
    $createdDate = $this->state->get(G2::VARWOTDDATE) ?: time();
    assert(is_numeric($createdDate));
    $created = \DateTimeImmutable::createFromFormat(WOTD::DATE_STORAGE_FORMAT, (string) $createdDate);
    assert($created instanceof \DateTimeImmutable);
    $node->setCreatedTime($created->getTimestamp());
  }

  /**
   * Return the nodes matching an autocomplete title pattern like "foo (62)".
   *
   * @param string $title
   *   The pattern to match.
   *
   * @return \Drupal\node\NodeInterface[]
   *   The matching nodes.
   */
  public function matchesFromTitle(string $title): array {
    [$qTitle, $qNid] = [$title, 0];
    // Capture the optional parenthesized nid and remove it if found.
    $ok = (bool) preg_match(static::RX_TITLE, $title, $matches, PREG_OFFSET_CAPTURE, 0);
    if ($ok && count($matches) === 2) {
      $qNid = (int) $matches[1][0];
      // We matched a non-captures opening parenthese, so exclude it.
      $qTitle = trim(mb_substr($title, 0, $matches[1][1] - 1));
    }
    $storage = $this->etm
      ->getStorage(G2::TYPE);
    if ($qNid !== 0) {
      $nids = [$qNid];
    }
    else {
      $nids = $storage
        ->getQuery()
        ->condition('title', "$qTitle%", 'LIKE')
        ->condition('type', G2::BUNDLE)
        ->condition('status', NodeInterface::PUBLISHED)
        ->accessCheck()
        ->execute();
      $nids = array_map('intval', $nids);
    }
    /** @var \Drupal\node\NodeInterface[] $nodes */
    $nodes = $storage->loadMultiple($nids);
    return $nodes;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc