Unverified Commit 2819baaa authored by Alex Pott's avatar Alex Pott
Browse files

Issue #1685492 by eclipsegc, sun, dawehner, Crell, nicxvan, berdir, longwave,...

Issue #1685492 by eclipsegc, sun, dawehner, Crell, nicxvan, berdir, longwave, dcam, alexpott: Convert theme engines into services
parent 391e99db
Loading
Loading
Loading
Loading
Loading
+0 −6
Original line number Diff line number Diff line
@@ -10607,12 +10607,6 @@
	'count' => 1,
	'path' => __DIR__ . '/lib/Drupal/Core/Theme/ThemeManager.php',
];
$ignoreErrors[] = [
	'message' => '#^Variable \\$theme_engine in isset\\(\\) always exists and is not nullable\\.$#',
	'identifier' => 'isset.variable',
	'count' => 1,
	'path' => __DIR__ . '/lib/Drupal/Core/Theme/ThemeManager.php',
];
$ignoreErrors[] = [
	'message' => '#^Method Drupal\\\\Core\\\\Theme\\\\ThemeManagerInterface\\:\\:alter\\(\\) has no return type specified\\.$#',
	'identifier' => 'missingType.return',
+6 −2
Original line number Diff line number Diff line
@@ -1600,13 +1600,17 @@ services:
  Drupal\Core\Datetime\DateFormatterInterface: '@date.formatter'
  theme.manager:
    class: Drupal\Core\Theme\ThemeManager
    arguments: ['%app.root%', '@theme.negotiator', '@theme.initialization', '@module_handler', '@callable_resolver', '@keyvalue', '@cache.bootstrap']
    autowire: true
    calls:
      - [setThemeRegistry, ['@theme.registry']]
  Drupal\Core\Theme\ThemeManagerInterface: '@theme.manager'
  Drupal\Core\Template\TwigThemeEngine:
    autowire: true
    tags:
      - { name: theme_engine, engine_name: twig }
  theme.initialization:
    class: Drupal\Core\Theme\ThemeInitialization
    arguments: ['%app.root%', '@theme_handler', '@cache.bootstrap', '@module_handler']
    autowire: true
  Drupal\Core\Theme\ThemeInitializationInterface: '@theme.initialization'
  theme.registry:
    class: Drupal\Core\Theme\Registry
+127 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Core\Template;

use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Utility\Html;
use Drupal\Core\Render\Markup;
use Drupal\Core\Theme\ThemeEngineInterface;
use Twig\Error\RuntimeError;

/**
 * Twig theme engine.
 */
class TwigThemeEngine implements ThemeEngineInterface {

  protected const string EXTENSION = '.html.twig';

  public function __construct(
    protected TwigEnvironment $twig,
  ) {}

  /**
   * {@inheritdoc}
   */
  public function theme(array $existing, string $type, string $theme, string $path): array {
    return drupal_find_theme_templates($existing, self::EXTENSION, $path);
  }

  /**
   * {@inheritdoc}
   */
  public function renderTemplate(string $template_file, array $variables): string|MarkupInterface {
    $template_file .= self::EXTENSION;
    try {
      $rendered_markup = $this->twig->load($template_file)->render($variables);
    }
    catch (RuntimeError $e) {
      // In case there is a previous exception, re-throw the previous exception,
      // so that the original exception is shown, rather than
      // \Twig\Template::displayWithErrorHandling()'s exception.
      $previous_exception = $e->getPrevious();
      if ($previous_exception) {
        throw $previous_exception;
      }
      throw $e;
    }
    if ($this->twig->isDebug()) {
      $output = [
        'debug_prefix' => '',
        'debug_info' => '',
        'rendered_markup' => $rendered_markup,
        'debug_suffix' => '',
      ];

      $output['debug_prefix'] .= "\n\n<!-- THEME DEBUG -->";
      $output['debug_prefix'] .= "\n<!-- THEME HOOK: '" . Html::escape($variables['theme_hook_original']) . "' -->";
      // If there are theme suggestions, reverse the array so more specific
      // suggestions are shown first.
      if (!empty($variables['theme_hook_suggestions'])) {
        $variables['theme_hook_suggestions'] = array_reverse($variables['theme_hook_suggestions']);
      }
      // Add debug output for directly called suggestions like
      // '#theme' => 'comment__node__article'.
      if (str_contains($variables['theme_hook_original'], '__')) {
        $derived_suggestions[] = $hook = $variables['theme_hook_original'];
        while ($pos = strrpos($hook, '__')) {
          $hook = substr($hook, 0, $pos);
          $derived_suggestions[] = $hook;
        }
        // Get the value of the base hook (last derived suggestion) and append
        // it to the end of all theme suggestions.
        $base_hook = array_pop($derived_suggestions);
        $variables['theme_hook_suggestions'] = array_merge($derived_suggestions, $variables['theme_hook_suggestions']);
        $variables['theme_hook_suggestions'][] = $base_hook;
      }
      if (!empty($variables['theme_hook_suggestions'])) {
        $current_template = basename($template_file);
        $suggestions = $variables['theme_hook_suggestions'];
        // Only add the original theme hook if it wasn't a directly called
        // suggestion.
        if (!str_contains($variables['theme_hook_original'], '__')) {
          $suggestions[] = $variables['theme_hook_original'];
        }
        $invalid_suggestions = [];
        $base_hook = $base_hook ?? $variables['theme_hook_original'];
        foreach ($suggestions as $key => &$suggestion) {
          // Valid suggestions are $base_hook, $base_hook__*, and contain no
          // hyphens.
          if (($suggestion !== $base_hook && !str_starts_with($suggestion, $base_hook . '__')) || str_contains($suggestion, '-')) {
            $invalid_suggestions[] = $suggestion;
            unset($suggestions[$key]);
            continue;
          }
          $template = strtr($suggestion, '_', '-') . self::EXTENSION;
          $prefix = ($template == $current_template) ? '✅' : '▪️';

          // Add deprecated hint.
          if (isset($variables['theme_hook_suggestions__DEPRECATED'][$suggestion])) {
            $template .= ' (deprecated)';
          }
          $suggestion = $prefix . ' ' . $template;
        }
        $output['debug_info'] .= "\n<!-- FILE NAME SUGGESTIONS:\n   " . Html::escape(implode("\n   ", $suggestions)) . "\n-->";

        if (!empty($invalid_suggestions)) {
          $output['debug_info'] .= "\n<!-- INVALID FILE NAME SUGGESTIONS:";
          $output['debug_info'] .= "\n   See https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Render!theme.api.php/function/hook_theme_suggestions_alter";
          $output['debug_info'] .= "\n   " . Html::escape(implode("\n   ", $invalid_suggestions));
          $output['debug_info'] .= "\n-->";
        }
      }
      // Check if the template_file belongs to a custom theme.
      $template_override_status_output = "BEGIN OUTPUT";
      $template_override_suffix_output = "END OUTPUT";
      if (str_starts_with($template_file, $variables['directory'])) {
        $template_override_status_output = "💡 BEGIN CUSTOM TEMPLATE OUTPUT";
        $template_override_suffix_output = "END CUSTOM TEMPLATE OUTPUT";
      }
      $output['debug_info']   .= "\n<!-- " . $template_override_status_output . " from '" . Html::escape($template_file) . "' -->\n";
      $output['debug_suffix'] .= "\n<!-- " . $template_override_suffix_output . " from '" . Html::escape($template_file) . "' -->\n\n";
      // This output has already been rendered and is therefore considered safe.
      return Markup::create(implode('', $output));
    }
    return Markup::create($rendered_markup);
  }

}
+1 −0
Original line number Diff line number Diff line
@@ -162,6 +162,7 @@ public function getEngine() {
   * @see \Drupal\Core\Extension\ThemeExtensionList::doList()
   */
  public function getOwner() {
    @trigger_error(__METHOD__ . '() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. Theme engines are now tagged services instead of extensions. See https://www.drupal.org/node/3547356', E_USER_DEPRECATED);
    return $this->owner;
  }

+12 −6
Original line number Diff line number Diff line
@@ -573,8 +573,12 @@ protected function processExtension(array &$cache, $name, $type, $theme, $path)
      $result = $this->themeManager->invoke($name, 'theme', $args);
    }
    else {
      // @todo Simplify in Drupal 12 in https://www.drupal.org/project/drupal/issues/3555931
      $function = $name . '_theme';
      if (function_exists($function)) {
      if (in_array($type, ['theme_engine', 'base_theme_engine'], TRUE) && $theme_engine = $this->themeManager->getThemeEngine($name)) {
        $function = [$theme_engine, 'theme'];
      }
      if (is_callable($function)) {
        $result = $function(... $args);
      }
    }
@@ -667,11 +671,13 @@ protected function processExtension(array &$cache, $name, $type, $theme, $path)
            $prefixes[] = $theme;

            foreach ($prefixes as $prefix) {
              if (function_exists($prefix . '_preprocess')) {
                $info['preprocess functions'][] = $prefix . '_preprocess';
              foreach ([$prefix . '_preprocess', $prefix . '_preprocess_' . $hook] as $function) {
                if (function_exists($function)) {
                  if ($prefix !== $theme) {
                    @trigger_error(sprintf('Providing %s() is deprecated in drupal:11.3.0 and is removed from drupal:12.0.0. There is no replacement. See https://www.drupal.org/node/3547356', $function), E_USER_DEPRECATED);
                  }
                  $info['preprocess functions'][] = $function;
                }
              if (function_exists($prefix . '_preprocess_' . $hook)) {
                $info['preprocess functions'][] = $prefix . '_preprocess_' . $hook;
              }
            }
          }
Loading