Newer
Older
Alex Pott
committed
<?php
/**
* @file
* Contains \Drupal\system\Form\ThemeSettingsForm.
*/
namespace Drupal\system\Form;
use Drupal\Core\Extension\ThemeHandlerInterface;
Dries Buytaert
committed
use Drupal\Core\Form\FormStateInterface;
Angie Byron
committed
use Drupal\Core\Render\Element;
use Drupal\Core\StreamWrapper\PublicStream;
Alex Pott
committed
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface;
Alex Pott
committed
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Drupal\Core\Cache\Cache;
Alex Pott
committed
use Drupal\Core\Config\ConfigFactoryInterface;
Alex Pott
committed
use Drupal\Core\Extension\ModuleHandlerInterface;
Angie Byron
committed
use Drupal\Core\Form\ConfigFormBase;
Alex Pott
committed
/**
* Displays theme configuration for entire site and individual themes.
*/
Angie Byron
committed
class ThemeSettingsForm extends ConfigFormBase {
Alex Pott
committed
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* The MIME type guesser.
*
* @var \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface
*/
protected $mimeTypeGuesser;
/**
* An array of configuration names that should be editable.
*
* @var array
*/
protected $editableConfig = [];
Alex Pott
committed
/**
* Constructs a ThemeSettingsForm object.
*
Alex Pott
committed
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
Alex Pott
committed
* The factory for configuration objects.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
Alex Pott
committed
* The module handler instance to use.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
* @param \Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface $mime_type_guesser
* The MIME type guesser instance to use.
Alex Pott
committed
*/
public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, MimeTypeGuesserInterface $mime_type_guesser) {
parent::__construct($config_factory);
Alex Pott
committed
$this->moduleHandler = $module_handler;
$this->themeHandler = $theme_handler;
$this->mimeTypeGuesser = $mime_type_guesser;
Alex Pott
committed
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('module_handler'),
$container->get('theme_handler'),
$container->get('file.mime_type.guesser')
Alex Pott
committed
);
}
/**
* {@inheritdoc}
*/
Alex Pott
committed
public function getFormId() {
Alex Pott
committed
return 'system_theme_settings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return $this->editableConfig;
}
Alex Pott
committed
/**
* {@inheritdoc}
*
* @param string $theme
Alex Pott
committed
* The theme name.
*/
Dries Buytaert
committed
public function buildForm(array $form, FormStateInterface $form_state, $theme = '') {
Alex Pott
committed
$form = parent::buildForm($form, $form_state);
$themes = $this->themeHandler->listInfo();
Alex Pott
committed
catch
committed
// Deny access if the theme is not installed or not found.
if (!empty($theme) && (empty($themes[$theme]) || !$themes[$theme]->status)) {
Alex Pott
committed
throw new NotFoundHttpException();
}
// Default settings are defined in theme_get_setting() in includes/theme.inc
if ($theme) {
$var = 'theme_' . $theme . '_settings';
$config_key = $theme . '.settings';
$themes = $this->themeHandler->listInfo();
$features = $themes[$theme]->info['features'];
Alex Pott
committed
}
else {
$var = 'theme_settings';
$config_key = 'system.theme.global';
}
// @todo this is pretty meaningless since we're using theme_get_settings
// which means overrides can bleed into active config here. Will be fixed
// by https://www.drupal.org/node/2402467.
$this->editableConfig = [$config_key];
Alex Pott
committed
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
$form['var'] = array(
'#type' => 'hidden',
'#value' => $var
);
$form['config_key'] = array(
'#type' => 'hidden',
'#value' => $config_key
);
// Toggle settings
$toggles = array(
'logo' => t('Logo'),
'name' => t('Site name'),
'slogan' => t('Site slogan'),
'node_user_picture' => t('User pictures in posts'),
'comment_user_picture' => t('User pictures in comments'),
'comment_user_verification' => t('User verification status in comments'),
'favicon' => t('Shortcut icon'),
);
// Some features are not always available
$disabled = array();
if (!user_picture_enabled()) {
$disabled['toggle_node_user_picture'] = TRUE;
$disabled['toggle_comment_user_picture'] = TRUE;
}
if (!$this->moduleHandler->moduleExists('comment')) {
$disabled['toggle_comment_user_picture'] = TRUE;
$disabled['toggle_comment_user_verification'] = TRUE;
}
$form['theme_settings'] = array(
'#type' => 'details',
'#title' => t('Toggle display'),
Angie Byron
committed
'#open' => TRUE,
Alex Pott
committed
'#description' => t('Enable or disable the display of certain page elements.'),
);
foreach ($toggles as $name => $title) {
if ((!$theme) || in_array($name, $features)) {
$form['theme_settings']['toggle_' . $name] = array('#type' => 'checkbox', '#title' => $title, '#default_value' => theme_get_setting('features.' . $name, $theme));
Alex Pott
committed
// Disable checkboxes for features not supported in the current configuration.
if (isset($disabled['toggle_' . $name])) {
$form['theme_settings']['toggle_' . $name]['#disabled'] = TRUE;
}
}
}
Angie Byron
committed
if (!Element::children($form['theme_settings'])) {
Alex Pott
committed
// If there is no element in the theme settings details then do not show
// it -- but keep it in the form if another module wants to alter.
$form['theme_settings']['#access'] = FALSE;
}
// Logo settings, only available when file.module is enabled.
if ((!$theme) || in_array('logo', $features) && $this->moduleHandler->moduleExists('file')) {
Alex Pott
committed
$form['logo'] = array(
'#type' => 'details',
'#title' => t('Logo image settings'),
Angie Byron
committed
'#open' => TRUE,
Alex Pott
committed
'#states' => array(
// Hide the logo image settings fieldset when logo display is disabled.
'invisible' => array(
'input[name="toggle_logo"]' => array('checked' => FALSE),
),
),
);
$form['logo']['default_logo'] = array(
'#type' => 'checkbox',
'#title' => t('Use the default logo supplied by the theme'),
'#default_value' => theme_get_setting('logo.use_default', $theme),
Alex Pott
committed
'#tree' => FALSE,
);
$form['logo']['settings'] = array(
'#type' => 'container',
'#states' => array(
// Hide the logo settings when using the default logo.
'invisible' => array(
'input[name="default_logo"]' => array('checked' => TRUE),
),
),
);
$form['logo']['settings']['logo_path'] = array(
'#type' => 'textfield',
'#title' => t('Path to custom logo'),
'#default_value' => theme_get_setting('logo.path', $theme),
Alex Pott
committed
);
$form['logo']['settings']['logo_upload'] = array(
'#type' => 'file',
'#title' => t('Upload logo image'),
'#maxlength' => 40,
'#description' => t("If you don't have direct file access to the server, use this field to upload your logo.")
);
}
Dries Buytaert
committed
if (((!$theme) || in_array('favicon', $features)) && $this->moduleHandler->moduleExists('file')) {
Alex Pott
committed
$form['favicon'] = array(
'#type' => 'details',
'#title' => t('Shortcut icon settings'),
Angie Byron
committed
'#open' => TRUE,
Alex Pott
committed
'#description' => t("Your shortcut icon, or 'favicon', is displayed in the address bar and bookmarks of most browsers."),
'#states' => array(
// Hide the shortcut icon settings fieldset when shortcut icon display
// is disabled.
'invisible' => array(
'input[name="toggle_favicon"]' => array('checked' => FALSE),
),
),
);
$form['favicon']['default_favicon'] = array(
'#type' => 'checkbox',
'#title' => t('Use the default shortcut icon supplied by the theme'),
'#default_value' => theme_get_setting('favicon.use_default', $theme),
Alex Pott
committed
);
$form['favicon']['settings'] = array(
'#type' => 'container',
'#states' => array(
// Hide the favicon settings when using the default favicon.
'invisible' => array(
'input[name="default_favicon"]' => array('checked' => TRUE),
),
),
);
$form['favicon']['settings']['favicon_path'] = array(
'#type' => 'textfield',
'#title' => t('Path to custom icon'),
'#default_value' => theme_get_setting('favicon.path', $theme),
Alex Pott
committed
);
$form['favicon']['settings']['favicon_upload'] = array(
'#type' => 'file',
'#title' => t('Upload icon image'),
'#description' => t("If you don't have direct file access to the server, use this field to upload your shortcut icon.")
);
}
// Inject human-friendly values and form element descriptions for logo and
// favicon.
foreach (array('logo' => 'logo.svg', 'favicon' => 'favicon.ico') as $type => $default) {
Alex Pott
committed
if (isset($form[$type]['settings'][$type . '_path'])) {
$element = &$form[$type]['settings'][$type . '_path'];
// If path is a public:// URI, display the path relative to the files
// directory; stream wrappers are not end-user friendly.
$original_path = $element['#default_value'];
$friendly_path = NULL;
if (file_uri_scheme($original_path) == 'public') {
$friendly_path = file_uri_target($original_path);
$element['#default_value'] = $friendly_path;
}
// Prepare local file path for description.
if ($original_path && isset($friendly_path)) {
$local_file = strtr($original_path, array('public:/' => PublicStream::basePath()));
Alex Pott
committed
}
elseif ($theme) {
$local_file = drupal_get_path('theme', $theme) . '/' . $default;
Alex Pott
committed
}
else {
$local_file = \Drupal::theme()->getActiveTheme()->getPath() . '/' . $default;
Alex Pott
committed
}
$element['#description'] = t('Examples: <code>@implicit-public-file</code> (for a file in the public filesystem), <code>@explicit-file</code>, or <code>@local-file</code>.', array(
'@implicit-public-file' => isset($friendly_path) ? $friendly_path : $default,
'@explicit-file' => file_uri_scheme($original_path) !== FALSE ? $original_path : 'public://' . $default,
'@local-file' => $local_file,
));
}
}
if ($theme) {
Alex Pott
committed
// Call engine-specific settings.
$function = $themes[$theme]->prefix . '_engine_settings';
Alex Pott
committed
if (function_exists($function)) {
$form['engine_specific'] = array(
'#type' => 'details',
'#title' => t('Theme-engine-specific settings'),
Angie Byron
committed
'#open' => TRUE,
'#description' => t('These settings only exist for the themes based on the %engine theme engine.', array('%engine' => $themes[$theme]->prefix)),
Alex Pott
committed
);
$function($form, $form_state);
}
// Create a list which includes the current theme and all its base themes.
if (isset($themes[$theme]->base_themes)) {
$theme_keys = array_keys($themes[$theme]->base_themes);
$theme_keys[] = $theme;
Alex Pott
committed
}
else {
$theme_keys = array($theme);
Alex Pott
committed
}
// Save the name of the current theme (if any), so that we can temporarily
// override the current theme and allow theme_get_setting() to work
// without having to pass the theme name to it.
$default_active_theme = \Drupal::theme()->getActiveTheme();
$default_theme = $default_active_theme->getName();
/** @var \Drupal\Core\Theme\ThemeInitialization $theme_initialization */
$theme_initialization = \Drupal::service('theme.initialization');
\Drupal::theme()->setActiveTheme($theme_initialization->getActiveThemeByName($theme));
Alex Pott
committed
// Process the theme and all its base themes.
foreach ($theme_keys as $theme) {
// Include the theme-settings.php file.
$filename = DRUPAL_ROOT . '/' . $themes[$theme]->getPath() . '/theme-settings.php';
Alex Pott
committed
if (file_exists($filename)) {
require_once $filename;
}
// Call theme-specific settings.
$function = $theme . '_form_system_theme_settings_alter';
if (function_exists($function)) {
$function($form, $form_state);
}
}
// Restore the original current theme.
if (isset($default_theme)) {
\Drupal::theme()->setActiveTheme($default_active_theme);
Alex Pott
committed
}
else {
\Drupal::theme()->resetActiveTheme();
Alex Pott
committed
}
}
return $form;
}
/**
* {@inheritdoc}
*/
Dries Buytaert
committed
public function validateForm(array &$form, FormStateInterface $form_state) {
Alex Pott
committed
parent::validateForm($form, $form_state);
if ($this->moduleHandler->moduleExists('file')) {
// Handle file uploads.
$validators = array('file_validate_is_image' => array());
// Check for a new uploaded logo.
$file = file_save_upload('logo_upload', $validators, FALSE, 0);
Alex Pott
committed
if (isset($file)) {
// File upload was attempted.
if ($file) {
// Put the temporary file in form_values so we can save it on submit.
Alex Pott
committed
$form_state->setValue('logo_upload', $file);
Alex Pott
committed
}
else {
// File upload failed.
$form_state->setErrorByName('logo_upload', $this->t('The logo could not be uploaded.'));
Alex Pott
committed
}
}
$validators = array('file_validate_extensions' => array('ico png gif jpg jpeg apng svg'));
// Check for a new uploaded favicon.
$file = file_save_upload('favicon_upload', $validators, FALSE, 0);
Alex Pott
committed
if (isset($file)) {
// File upload was attempted.
if ($file) {
// Put the temporary file in form_values so we can save it on submit.
Alex Pott
committed
$form_state->setValue('favicon_upload', $file);
Alex Pott
committed
}
else {
// File upload failed.
$form_state->setErrorByName('favicon_upload', $this->t('The favicon could not be uploaded.'));
Alex Pott
committed
}
}
// If the user provided a path for a logo or favicon file, make sure a file
// exists at that path.
Alex Pott
committed
if ($form_state->getValue('logo_path')) {
$path = $this->validatePath($form_state->getValue('logo_path'));
Alex Pott
committed
if (!$path) {
$form_state->setErrorByName('logo_path', $this->t('The custom logo path is invalid.'));
Alex Pott
committed
}
}
Alex Pott
committed
if ($form_state->getValue('favicon_path')) {
$path = $this->validatePath($form_state->getValue('favicon_path'));
Alex Pott
committed
if (!$path) {
$form_state->setErrorByName('favicon_path', $this->t('The custom favicon path is invalid.'));
Alex Pott
committed
}
}
}
}
/**
* {@inheritdoc}
*/
Dries Buytaert
committed
public function submitForm(array &$form, FormStateInterface $form_state) {
Alex Pott
committed
parent::submitForm($form, $form_state);
$config_key = $form_state->getValue('config_key');
$this->editableConfig = [$config_key];
$config = $this->config($config_key);
Alex Pott
committed
// Exclude unnecessary elements before saving.
$form_state->cleanValues();
Alex Pott
committed
$form_state->unsetValue('var');
$form_state->unsetValue('config_key');
Alex Pott
committed
Alex Pott
committed
$values = $form_state->getValues();
Alex Pott
committed
// If the user uploaded a new logo or favicon, save it to a permanent location
// and use it in place of the default theme-provided file.
if ($this->moduleHandler->moduleExists('file')) {
if ($file = $values['logo_upload']) {
$filename = file_unmanaged_copy($file->getFileUri());
$values['default_logo'] = 0;
$values['logo_path'] = $filename;
$values['toggle_logo'] = 1;
}
if ($file = $values['favicon_upload']) {
$filename = file_unmanaged_copy($file->getFileUri());
$values['default_favicon'] = 0;
$values['favicon_path'] = $filename;
$values['toggle_favicon'] = 1;
}
Dries Buytaert
committed
unset($values['logo_upload']);
unset($values['favicon_upload']);
Alex Pott
committed
// If the user entered a path relative to the system files directory for
// a logo or favicon, store a public:// URI so the theme system can handle it.
if (!empty($values['logo_path'])) {
$values['logo_path'] = $this->validatePath($values['logo_path']);
}
if (!empty($values['favicon_path'])) {
$values['favicon_path'] = $this->validatePath($values['favicon_path']);
}
if (empty($values['default_favicon']) && !empty($values['favicon_path'])) {
$values['favicon_mimetype'] = $this->mimeTypeGuesser->guess($values['favicon_path']);
Alex Pott
committed
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
}
}
theme_settings_convert_to_config($values, $config)->save();
}
/**
* Helper function for the system_theme_settings form.
*
* Attempts to validate normal system paths, paths relative to the public files
* directory, or stream wrapper URIs. If the given path is any of the above,
* returns a valid path or URI that the theme system can display.
*
* @param string $path
* A path relative to the Drupal root or to the public files directory, or
* a stream wrapper URI.
* @return mixed
* A valid path that can be displayed through the theme system, or FALSE if
* the path could not be validated.
*/
protected function validatePath($path) {
// Absolute local file paths are invalid.
if (drupal_realpath($path) == $path) {
return FALSE;
}
// A path relative to the Drupal root or a fully qualified URI is valid.
if (is_file($path)) {
return $path;
}
// Prepend 'public://' for relative file paths within public filesystem.
if (file_uri_scheme($path) === FALSE) {
$path = 'public://' . $path;
}
if (is_file($path)) {
return $path;
}
return FALSE;
}
}