diff --git a/core/modules/system/system.tokens.inc b/core/modules/system/system.tokens.inc index c55e106..4abc27e 100644 --- a/core/modules/system/system.tokens.inc +++ b/core/modules/system/system.tokens.inc @@ -19,6 +19,16 @@ function system_token_info() { 'name' => t("Site information"), 'description' => t("Tokens for site-wide settings and other global information."), ]; + $types['site-logo'] = [ + 'name' => t("Site logo"), + 'description' => t("Tokens related to the site logo."), + 'needs-data' => 'site', + ]; + $types['site-logo-properties'] = [ + 'name' => t("Site logo properties"), + 'description' => t("Tokens for site logo properties."), + 'needs-data' => 'site-logo', + ]; $types['date'] = [ 'name' => t("Dates"), 'description' => t("Tokens related to times and dates."), @@ -53,6 +63,42 @@ function system_token_info() { /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */ $date_formatter = \Drupal::service('date.formatter'); + // Chained tokens for site-wide settings and other global information. + $site['logo'] = [ + 'name' => t('Logo'), + 'description' => t('Tokens related to the site logo.'), + 'type' => 'site-logo', + ]; + + // Logo related tokens. + $site_logo['active-theme'] = [ + 'name' => t('Active theme'), + 'description' => t('The logo of the active theme. Pay attention when using this from the administrative interface, as the theme might be different.'), + 'type' => 'site-logo-properties', + ]; + $site_logo['default-theme'] = [ + 'name' => t('Default theme'), + 'description' => t('The logo that is configured for the default theme.'), + 'type' => 'site-logo-properties', + ]; + // Obtain a list of installed themes and make tokens from them. + $themes = \Drupal::service('theme_handler')->listInfo(); + foreach ($themes as $theme => $info) { + if (empty($info->info['hidden'])) { + $site_logo['theme-' . $theme] = [ + 'name' => t('@theme', ['@theme' => $info->info['name']]), + 'description' => t('The logo for the %theme theme.', ['%theme' => $info->info['name']]), + 'type' => 'site-logo-properties', + ]; + } + } + + // Theme specific logo tokens. + $site_logo_properties['url'] = [ + 'name' => t('URL'), + 'description' => t('The URL of the logo.'), + ]; + // Date related tokens. $date['short'] = [ 'name' => t("Short format"), @@ -83,6 +129,8 @@ function system_token_info() { 'types' => $types, 'tokens' => [ 'site' => $site, + 'site-logo' => $site_logo, + 'site-logo-properties' => $site_logo_properties, 'date' => $date, ], ]; @@ -151,10 +199,65 @@ function system_tokens($type, $tokens, array $data, array $options, BubbleableMe $bubbleable_metadata->addCacheableDependency($result); $replacements[$original] = $result->getGeneratedUrl(); break; + + // Default values for the chained tokens handled below. + case 'logo': + $replacements[$original] = _system_tokens_get_actual_logo('active-theme', $bubbleable_metadata, TRUE); + break; } } + + // Detect chained tokens ([site:logo:?]). + if ($logo_tokens = $token_service->findWithPrefix($tokens, 'logo')) { + $replacements += $token_service->generate('site-logo', $logo_tokens, [], $options, $bubbleable_metadata); + } } + elseif ($type == 'site-logo') { + foreach ($tokens as $name => $original) { + switch ($name) { + case 'active-theme': + case 'default-theme': + $replacements[$original] = _system_tokens_get_actual_logo($name, $bubbleable_metadata, TRUE); + break; + } + } + if ($logo_tokens = $token_service->findWithPrefix($tokens, 'active-theme')) { + $replacements += $token_service->generate('site-logo-properties', $logo_tokens, ['theme' => 'active-theme'], $options, $bubbleable_metadata); + } + if ($logo_tokens = $token_service->findWithPrefix($tokens, 'default-theme')) { + $replacements += $token_service->generate('site-logo-properties', $logo_tokens, ['theme' => 'default-theme'], $options, $bubbleable_metadata); + } + + // List installed themes. + $themes = \Drupal::service('theme_handler')->listInfo(); + $installed_themes = array_keys($themes); + + // Detect direct tokens ([site:logo:theme-bartik]). + foreach ($tokens as $name => $original) { + // Strip 'theme-' to get the theme name, but do not strip 'active-theme'. + $name = (stripos($name, 'theme-') === 0) ? substr($name, 6) : $name; + if (in_array($name, $installed_themes)) { + $replacements[$original] = _system_tokens_get_actual_logo($name, $bubbleable_metadata, TRUE); + } + } + + // Detect chained tokens ([site:logo:theme-bartik:?]). + foreach ($installed_themes as $installed_theme) { + if ($created_tokens = $token_service->findWithPrefix($tokens, 'theme-' . $installed_theme)) { + $replacements += $token_service->generate('site-logo-properties', $created_tokens, ['theme' => $installed_theme], $options, $bubbleable_metadata); + } + } + } + elseif ($type == 'site-logo-properties' && !empty($data['theme'])) { + foreach ($tokens as $name => $original) { + switch ($name) { + case 'url': + $replacements[$original] = _system_tokens_get_actual_logo($data['theme'], $bubbleable_metadata); + break; + } + } + } elseif ($type == 'date') { if (empty($data['date'])) { $date = REQUEST_TIME; @@ -196,3 +299,57 @@ function system_tokens($type, $tokens, array $data, array $options, BubbleableMe return $replacements; } + +/** + * Obtains the logo that is configured for the provided theme. + * + * If there is no logo for the requested theme, this function will return the + * logo for the default theme instead. + * + * @param $theme + * The theme to load the logo for. + * @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata + * The bubbleable metadata to alter in order to cache the token. + * @param bool $render + * (Optional) If this is set to TRUE, this function will return a rendered + * logo. It will return an URL to the logo otherwise. + * + * @return string + * The rendered logo, or an url to the image file. + */ +function _system_tokens_get_actual_logo($theme, BubbleableMetadata &$bubbleable_metadata, $render = FALSE) { + // Retrieve configurations. + $system_theme_config = \Drupal::config('system.theme'); + $default_theme = $system_theme_config->get('default'); + + if ($theme == 'default-theme') { + $theme = $default_theme; + $bubbleable_metadata->addCacheableDependency($system_theme_config); + } + elseif ($theme == 'active-theme') { + $theme = \Drupal::service('theme.manager')->getActiveTheme()->getName(); + $bubbleable_metadata->addCacheContexts(['theme']); + } + + $global_config = \Drupal::config('system.theme.global'); + $theme_config = \Drupal::config($theme . '.settings'); + $bubbleable_metadata->addCacheableDependency($global_config); + $bubbleable_metadata->addCacheableDependency($theme_config); + + // If there is no logo, we use the default theme as fallback. + $logo_path = theme_get_setting('logo.url', $theme) ?: theme_get_setting('logo.url', $default_theme); + // @todo: We might be able to use the relative $logo_path variable once + // https://www.drupal.org/project/drupal/issues/2704597 is complete. Currently + // this is required to make the tokens work in e-mails. + $logo_url = Url::fromUserInput($logo_path, ['absolute' => TRUE])->toString(); + + if ($render) { + $build = [ + '#theme' => 'image', + '#uri' => $logo_url, + ]; + return \Drupal::service('renderer')->renderPlain($build); + } + + return $logo_url; +} diff --git a/core/modules/system/tests/src/Kernel/Token/TokenReplaceKernelTest.php b/core/modules/system/tests/src/Kernel/Token/TokenReplaceKernelTest.php index 8f71253..1d909db 100644 --- a/core/modules/system/tests/src/Kernel/Token/TokenReplaceKernelTest.php +++ b/core/modules/system/tests/src/Kernel/Token/TokenReplaceKernelTest.php @@ -2,11 +2,11 @@ namespace Drupal\Tests\system\Kernel\Token; -use Drupal\Core\Url; use Drupal\Component\Render\FormattableMarkup; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Xss; use Drupal\Core\Render\BubbleableMetadata; +use Drupal\Core\Url; /** * Generates text using placeholders for dummy content to check token @@ -129,6 +129,188 @@ public function testSystemSiteTokenReplacement() { } /** + * Tests the logo tokens for the active theme. + */ + public function testSystemSiteLogoActiveThemeTokenReplacement() { + $subsequent_tests = $this->systemSiteLogoTokensData(); + + // Prepare instances that we need only once. + $theme_initialization = \Drupal::service('theme.initialization'); + $theme_manager = \Drupal::service('theme.manager'); + $renderer = \Drupal::service('renderer'); + + foreach ($subsequent_tests as $testdata) { + list ($theme, $config) = $testdata; + $theme_config = $config[$theme]; + + // Set the theme as active theme. + $active_theme = $theme_initialization->initTheme($theme); + $theme_manager->setActiveTheme($active_theme); + + // Prepare the render array for the expected logo. + $build = [ + '#theme' => 'image', + '#uri' => Url::fromUserInput($theme_config->get('logo.path'), ['absolute' => TRUE])->toString(), + ]; + $logo = $renderer->renderPlain($build); + + $tests['[site:logo]'] = + $tests['[site:logo:active-theme]'] = $logo; + $tests['[site:logo:active-theme:url]'] = Url::fromUserInput($theme_config->get('logo.path'), ['absolute' => TRUE])->toString(); + + $metadata_tests['[site:logo]'] = + $metadata_tests['[site:logo:active-theme]'] = + $metadata_tests['[site:logo:active-theme:url]'] = BubbleableMetadata::createFromObject($config['global']) + ->addCacheableDependency($theme_config) + ->addCacheContexts(['theme']); + + // Test to make sure that we generated something for each token. + $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.'); + + foreach ($tests as $input => $expected) { + $bubbleable_metadata = new BubbleableMetadata(); + $output = $this->tokenService->replace($input, [], [], $bubbleable_metadata); + $this->assertEquals($expected, $output, new FormattableMarkup('System site information token %token replaced.', ['%token' => $input])); + $this->assertEquals($metadata_tests[$input], $bubbleable_metadata, new FormattableMarkup('Asserting metadata for token %token.', ['%token' => $input])); + } + } + } + + /** + * Tests the logo tokens for the default theme. + */ + public function testSystemSiteLogoDefaultThemeTokenReplacement() { + $subsequent_tests = $this->systemSiteLogoTokensData(); + + // Prepare instances that we need only once. + $system_theme_config = $this->config('system.theme'); + $renderer = \Drupal::service('renderer'); + + foreach ($subsequent_tests as $testdata) { + list ($theme, $config) = $testdata; + $theme_config = $config[$theme]; + + // Set the default theme. + $system_theme_config + ->set('default', $theme) + ->save(); + + // Prepare the render array for the expected logo. + $build = [ + '#theme' => 'image', + '#uri' => Url::fromUserInput($theme_config->get('logo.path'), ['absolute' => TRUE])->toString(), + ]; + $logo = $renderer->renderPlain($build); + + // Generate and test tokens. + $tests['[site:logo:default-theme]'] = $logo; + $tests['[site:logo:default-theme:url]'] = Url::fromUserInput($theme_config->get('logo.path'), ['absolute' => TRUE])->toString(); + + $metadata_tests['[site:logo:default-theme]'] = + $metadata_tests['[site:logo:default-theme:url]'] = BubbleableMetadata::createFromObject($config['global']) + ->addCacheableDependency($theme_config) + ->addCacheableDependency($system_theme_config); + + // Test to make sure that we generated something for each token. + $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.'); + + foreach ($tests as $input => $expected) { + $bubbleable_metadata = new BubbleableMetadata(); + $output = $this->tokenService->replace($input, [], [], $bubbleable_metadata); + $this->assertEquals($expected, $output, new FormattableMarkup('System site information token %token replaced.', ['%token' => $input])); + $this->assertEquals($metadata_tests[$input], $bubbleable_metadata, new FormattableMarkup('Asserting metadata for token %token.', ['%token' => $input])); + } + } + } + + /** + * Tests the logo tokens for the individual themes. + */ + public function testSystemSiteLogoThemeTokenReplacement() { + $subsequent_tests = $this->systemSiteLogoTokensData(); + + // Prepare instances that we need only once. + $renderer = \Drupal::service('renderer'); + + foreach ($subsequent_tests as $testdata) { + list ($theme, $config) = $testdata; + $theme_config = $config[$theme]; + + // Prepare the render array for the expected logo. + $build = [ + '#theme' => 'image', + '#uri' => Url::fromUserInput($theme_config->get('logo.path'), ['absolute' => TRUE])->toString(), + ]; + $logo = $renderer->renderPlain($build); + + // Generate and test tokens. + $tests['[site:logo:theme-' . $theme . ']'] = $logo; + $tests['[site:logo:theme-' . $theme . ':url]'] = Url::fromUserInput($theme_config->get('logo.path'), ['absolute' => TRUE])->toString(); + $tests['[site:logo:theme-' . $theme . ':foo]'] = '[site:logo:theme-' . $theme . ':foo]'; + $tests['[site:logo:theme-not-enabled-theme]'] = '[site:logo:theme-not-enabled-theme]'; + $tests['[site:logo:theme-not-enabled-theme:url]'] = '[site:logo:theme-not-enabled-theme:url]'; + $tests['[site:logo:theme-not-enabled-theme:foo]'] = '[site:logo:theme-not-enabled-theme:foo]'; + + $metadata_tests['[site:logo:theme-' . $theme . ']'] = BubbleableMetadata::createFromObject($config['global']) + ->addCacheableDependency($theme_config); + $metadata_tests['[site:logo:theme-' . $theme . ':url]'] = BubbleableMetadata::createFromObject($config['global']) + ->addCacheableDependency($theme_config); + $metadata_tests['[site:logo:theme-' . $theme . ':foo]'] = new BubbleableMetadata(); + $metadata_tests['[site:logo:theme-not-enabled-theme]'] = new BubbleableMetadata(); + $metadata_tests['[site:logo:theme-not-enabled-theme:url]'] = new BubbleableMetadata(); + $metadata_tests['[site:logo:theme-not-enabled-theme:foo]'] = new BubbleableMetadata(); + + // Test to make sure that we generated something for each token. + $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.'); + + foreach ($tests as $input => $expected) { + $bubbleable_metadata = new BubbleableMetadata(); + $output = $this->tokenService->replace($input, [], [], $bubbleable_metadata); + $this->assertEquals($expected, $output, new FormattableMarkup('System site information token %token replaced.', ['%token' => $input])); + $this->assertEquals($metadata_tests[$input], $bubbleable_metadata, new FormattableMarkup('Asserting metadata for token %token.', ['%token' => $input])); + } + } + } + + /** + * Prepares configurations and returns themes and configurations to test. + * + * @return array + * An array of theme and configuration pairs. + */ + public function systemSiteLogoTokensData() { + // Install Bartik and Seven. + \Drupal::service('theme_installer')->install(['bartik']); + \Drupal::service('theme_installer')->install(['seven']); + + // Set the global configuration. + $config['global'] = $this->config('system.theme.global'); + $config['global'] + ->set('logo.path', '/path/to/global_logo.svg') + ->set('logo.use_default', FALSE) + ->save(); + + // Set the Seven configuration. + $config['bartik'] = $this->config('bartik.settings'); + $config['bartik'] + ->set('logo.path', '/path/to/bartik_logo.svg') + ->set('logo.use_default', FALSE) + ->save(); + + // Set the Seven configuration. + $config['seven'] = $this->config('seven.settings'); + $config['seven'] + ->set('logo.path', '/path/to/seven_logo.svg') + ->set('logo.use_default', FALSE) + ->save(); + + return [ + ['bartik', $config], + ['seven', $config], + ]; + } + + /** * Tests the generation of all system date tokens. */ public function testSystemDateTokenReplacement() {