class ThemeSettingsForm

Same name and namespace in other branches
  1. 9 core/modules/system/src/Form/ThemeSettingsForm.php \Drupal\system\Form\ThemeSettingsForm
  2. 8.9.x core/modules/system/src/Form/ThemeSettingsForm.php \Drupal\system\Form\ThemeSettingsForm
  3. 10 core/modules/system/src/Form/ThemeSettingsForm.php \Drupal\system\Form\ThemeSettingsForm

Displays theme configuration for entire site and individual themes.

@internal

Hierarchy

Expanded class hierarchy of ThemeSettingsForm

1 string reference to 'ThemeSettingsForm'
system.routing.yml in core/modules/system/system.routing.yml
core/modules/system/system.routing.yml

File

core/modules/system/src/Form/ThemeSettingsForm.php, line 27

Namespace

Drupal\system\Form
View source
class ThemeSettingsForm extends ConfigFormBase {
    
    /**
     * 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\Mime\MimeTypeGuesserInterface
     */
    protected $mimeTypeGuesser;
    
    /**
     * An array of configuration names that should be editable.
     *
     * @var array
     */
    protected $editableConfig = [];
    
    /**
     * The theme manager.
     *
     * @var \Drupal\Core\Theme\ThemeManagerInterface
     */
    protected $themeManager;
    
    /**
     * The file system.
     *
     * @var \Drupal\Core\File\FileSystemInterface
     */
    protected $fileSystem;
    
    /**
     * Constructs a ThemeSettingsForm object.
     *
     * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
     *   The factory for configuration objects.
     * @param \Drupal\Core\Config\TypedConfigManagerInterface $typedConfigManager
     *   The typed config manager.
     * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
     *   The module handler instance to use.
     * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
     *   The theme handler.
     * @param \Symfony\Component\Mime\MimeTypeGuesserInterface $mime_type_guesser
     *   The MIME type guesser instance to use.
     * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
     *   The theme manager.
     * @param \Drupal\Core\File\FileSystemInterface $file_system
     *   The file system.
     */
    public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, $mime_type_guesser, ThemeManagerInterface $theme_manager, FileSystemInterface $file_system) {
        parent::__construct($config_factory, $typedConfigManager);
        $this->moduleHandler = $module_handler;
        $this->themeHandler = $theme_handler;
        $this->mimeTypeGuesser = $mime_type_guesser;
        $this->themeManager = $theme_manager;
        $this->fileSystem = $file_system;
    }
    
    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container) {
        return new static($container->get('config.factory'), $container->get('config.typed'), $container->get('module_handler'), $container->get('theme_handler'), $container->get('file.mime_type.guesser'), $container->get('theme.manager'), $container->get('file_system'));
    }
    
    /**
     * {@inheritdoc}
     */
    public function getFormId() {
        return 'system_theme_settings';
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getEditableConfigNames() {
        return $this->editableConfig;
    }
    
    /**
     * {@inheritdoc}
     *
     * @param array $form
     *   An associative array containing the structure of the form.
     * @param \Drupal\Core\Form\FormStateInterface $form_state
     *   The current state of the form.
     * @param string $theme
     *   The theme name.
     */
    public function buildForm(array $form, FormStateInterface $form_state, $theme = '') {
        $form = parent::buildForm($form, $form_state);
        $themes = $this->themeHandler
            ->listInfo();
        // Default settings are defined in theme_get_setting() in includes/theme.inc
        if ($theme) {
            if (!$this->themeHandler
                ->hasUi($theme)) {
                throw new NotFoundHttpException();
            }
            $var = 'theme_' . $theme . '_settings';
            $config_key = $theme . '.settings';
            $themes = $this->themeHandler
                ->listInfo();
            $features = $themes[$theme]->info['features'];
        }
        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,
        ];
        $form['var'] = [
            '#type' => 'hidden',
            '#value' => $var,
        ];
        $form['config_key'] = [
            '#type' => 'hidden',
            '#value' => $config_key,
        ];
        // Toggle settings
        $toggles = [
            'node_user_picture' => $this->t('User pictures in posts'),
            'comment_user_picture' => $this->t('User pictures in comments'),
            'comment_user_verification' => $this->t('User verification status in comments'),
            'favicon' => $this->t('Shortcut icon'),
        ];
        // Some features are not always available
        $disabled = [];
        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'] = [
            '#type' => 'details',
            '#title' => $this->t('Page element display'),
            '#open' => TRUE,
        ];
        foreach ($toggles as $name => $title) {
            if (!$theme || in_array($name, $features)) {
                $form['theme_settings']['toggle_' . $name] = [
                    '#type' => 'checkbox',
                    '#title' => $title,
                    '#default_value' => theme_get_setting('features.' . $name, $theme),
                ];
                // Disable checkboxes for features not supported in the current configuration.
                if (isset($disabled['toggle_' . $name])) {
                    $form['theme_settings']['toggle_' . $name]['#disabled'] = TRUE;
                }
            }
        }
        if (!Element::children($form['theme_settings'])) {
            // 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')) {
            $form['logo'] = [
                '#type' => 'details',
                '#title' => $this->t('Logo image'),
                '#open' => TRUE,
            ];
            $form['logo']['default_logo'] = [
                '#type' => 'checkbox',
                '#title' => $this->t('Use the logo supplied by the theme'),
                '#default_value' => theme_get_setting('logo.use_default', $theme),
                '#tree' => FALSE,
            ];
            $form['logo']['settings'] = [
                '#type' => 'container',
                '#states' => [
                    // Hide the logo settings when using the default logo.
'invisible' => [
                        'input[name="default_logo"]' => [
                            'checked' => TRUE,
                        ],
                    ],
                ],
            ];
            $form['logo']['settings']['logo_path'] = [
                '#type' => 'textfield',
                '#title' => $this->t('Path to custom logo'),
                '#default_value' => theme_get_setting('logo.path', $theme),
            ];
            $form['logo']['settings']['logo_upload'] = [
                '#type' => 'file',
                '#title' => $this->t('Upload logo image'),
                '#description' => $this->t("If you don't have direct file access to the server, use this field to upload your logo."),
                '#upload_validators' => [
                    'FileExtension' => [
                        'extensions' => 'png gif jpg jpeg apng svg',
                    ],
                ],
            ];
        }
        if ((!$theme || in_array('favicon', $features)) && $this->moduleHandler
            ->moduleExists('file')) {
            $form['favicon'] = [
                '#type' => 'details',
                '#title' => $this->t('Favicon'),
                '#open' => TRUE,
                '#description' => $this->t("Your shortcut icon, or favicon, is displayed in the address bar and bookmarks of most browsers."),
                '#states' => [
                    // Hide the shortcut icon settings fieldset when shortcut icon display
                    // is disabled.
'invisible' => [
                        'input[name="toggle_favicon"]' => [
                            'checked' => FALSE,
                        ],
                    ],
                ],
            ];
            $form['favicon']['default_favicon'] = [
                '#type' => 'checkbox',
                '#title' => $this->t('Use the favicon supplied by the theme'),
                '#default_value' => theme_get_setting('favicon.use_default', $theme),
            ];
            $form['favicon']['settings'] = [
                '#type' => 'container',
                '#states' => [
                    // Hide the favicon settings when using the default favicon.
'invisible' => [
                        'input[name="default_favicon"]' => [
                            'checked' => TRUE,
                        ],
                    ],
                ],
            ];
            $form['favicon']['settings']['favicon_path'] = [
                '#type' => 'textfield',
                '#title' => $this->t('Path to custom icon'),
                '#default_value' => theme_get_setting('favicon.path', $theme),
            ];
            $form['favicon']['settings']['favicon_upload'] = [
                '#type' => 'file',
                '#title' => $this->t('Upload favicon image'),
                '#description' => $this->t("If you don't have direct file access to the server, use this field to upload your shortcut icon."),
                '#upload_validators' => [
                    'FileExtension' => [
                        'extensions' => 'ico png gif jpg jpeg apng svg webp',
                    ],
                ],
            ];
        }
        // Inject human-friendly values and form element descriptions for logo and
        // favicon.
        foreach ([
            'logo' => 'logo.svg',
            'favicon' => 'favicon.ico',
        ] as $type => $default) {
            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 (StreamWrapperManager::getScheme($original_path) == 'public') {
                    $friendly_path = StreamWrapperManager::getTarget($original_path);
                    $element['#default_value'] = $friendly_path;
                }
                // Prepare local file path for description.
                if ($original_path && isset($friendly_path)) {
                    $local_file = strtr($original_path, [
                        'public:/' => PublicStream::basePath(),
                    ]);
                }
                elseif ($theme) {
                    $local_file = $this->themeHandler
                        ->getTheme($theme)
                        ->getPath() . '/' . $default;
                }
                else {
                    $local_file = $this->themeManager
                        ->getActiveTheme()
                        ->getPath() . '/' . $default;
                }
                $element['#description'] = $this->t('Examples: <code>@implicit-public-file</code> (for a file in the public filesystem), <code>@explicit-file</code>, or <code>@local-file</code>.', [
                    '@implicit-public-file' => $friendly_path ?? $default,
                    '@explicit-file' => StreamWrapperManager::getScheme($original_path) !== FALSE ? $original_path : 'public://' . $default,
                    '@local-file' => $local_file,
                ]);
            }
        }
        if ($theme) {
            // Call engine-specific settings.
            $function = $themes[$theme]->prefix . '_engine_settings';
            if (function_exists($function)) {
                $form['engine_specific'] = [
                    '#type' => 'details',
                    '#title' => $this->t('Theme-engine-specific settings'),
                    '#open' => TRUE,
                    '#description' => $this->t('These settings only exist for the themes based on the %engine theme engine.', [
                        '%engine' => $themes[$theme]->prefix,
                    ]),
                ];
                $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;
            }
            else {
                $theme_keys = [
                    $theme,
                ];
            }
            // 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 = $this->themeManager
                ->getActiveTheme();
            $default_theme = $default_active_theme->getName();
            
            /** @var \Drupal\Core\Theme\ThemeInitialization $theme_initialization */
            $theme_initialization = \Drupal::service('theme.initialization');
            $this->themeManager
                ->setActiveTheme($theme_initialization->getActiveThemeByName($theme));
            // Process the theme and all its base themes.
            foreach ($theme_keys as $theme) {
                // Include the theme-settings.php file.
                $theme_path = $this->themeHandler
                    ->getTheme($theme)
                    ->getPath();
                $theme_settings_file = $theme_path . '/theme-settings.php';
                $theme_file = $theme_path . '/' . $theme . '.theme';
                $filenames = [
                    $theme_settings_file,
                    $theme_file,
                ];
                foreach ($filenames as $filename) {
                    if (file_exists($filename)) {
                        require_once $filename;
                        // The file must be required for the cached form too.
                        $files = $form_state->getBuildInfo()['files'];
                        if (!in_array($filename, $files)) {
                            $files[] = $filename;
                        }
                        $form_state->addBuildInfo('files', $files);
                    }
                }
                // 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)) {
                $this->themeManager
                    ->setActiveTheme($default_active_theme);
            }
            else {
                $this->themeManager
                    ->resetActiveTheme();
            }
        }
        return $form;
    }
    
    /**
     * {@inheritdoc}
     */
    public function validateForm(array &$form, FormStateInterface $form_state) {
        parent::validateForm($form, $form_state);
        if ($this->moduleHandler
            ->moduleExists('file')) {
            // Check for a new uploaded logo.
            if (isset($form['logo'])) {
                $file = _file_save_upload_from_form($form['logo']['settings']['logo_upload'], $form_state, 0);
                if ($file) {
                    // Put the temporary file in form_values so we can save it on submit.
                    $form_state->setValue('logo_upload', $file);
                }
            }
            // Check for a new uploaded favicon.
            if (isset($form['favicon'])) {
                $file = _file_save_upload_from_form($form['favicon']['settings']['favicon_upload'], $form_state, 0);
                if ($file) {
                    // Put the temporary file in form_values so we can save it on submit.
                    $form_state->setValue('favicon_upload', $file);
                }
            }
            // When intending to use the default logo, unset the logo_path.
            if ($form_state->getValue('default_logo')) {
                $form_state->unsetValue('logo_path');
            }
            // When intending to use the default favicon, unset the favicon_path.
            if ($form_state->getValue('default_favicon')) {
                $form_state->unsetValue('favicon_path');
            }
            // If the user provided a path for a logo or favicon file, make sure a file
            // exists at that path.
            if ($form_state->getValue('logo_path')) {
                $path = $this->validatePath($form_state->getValue('logo_path'));
                if (!$path) {
                    $form_state->setErrorByName('logo_path', $this->t('The custom logo path is invalid.'));
                }
            }
            if ($form_state->getValue('favicon_path')) {
                $path = $this->validatePath($form_state->getValue('favicon_path'));
                if (!$path) {
                    $form_state->setErrorByName('favicon_path', $this->t('The custom favicon path is invalid.'));
                }
            }
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function submitForm(array &$form, FormStateInterface $form_state) {
        parent::submitForm($form, $form_state);
        $config_key = $form_state->getValue('config_key');
        $this->editableConfig = [
            $config_key,
        ];
        $config = $this->config($config_key);
        // Exclude unnecessary elements before saving.
        $form_state->cleanValues();
        $form_state->unsetValue('var');
        $form_state->unsetValue('config_key');
        $values = $form_state->getValues();
        // 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.
        $default_scheme = $this->config('system.file')
            ->get('default_scheme');
        try {
            if (!empty($values['logo_upload'])) {
                $filename = $this->fileSystem
                    ->copy($values['logo_upload']->getFileUri(), $default_scheme . '://');
                $values['default_logo'] = 0;
                $values['logo_path'] = $filename;
            }
        } catch (FileException $e) {
            // Ignore.
        }
        try {
            if (!empty($values['favicon_upload'])) {
                $filename = $this->fileSystem
                    ->copy($values['favicon_upload']->getFileUri(), $default_scheme . '://');
                $values['default_favicon'] = 0;
                $values['favicon_path'] = $filename;
                $values['toggle_favicon'] = 1;
            }
        } catch (FileException $e) {
            // Ignore.
        }
        unset($values['logo_upload']);
        unset($values['favicon_upload']);
        // 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
                ->guessMimeType($values['favicon_path']);
        }
        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 ($this->fileSystem
            ->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 (StreamWrapperManager::getScheme($path) === FALSE) {
            $path = 'public://' . $path;
        }
        if (is_file($path)) {
            return $path;
        }
        return FALSE;
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
ConfigFormBase::CONFIG_KEY_TO_FORM_ELEMENT_MAP protected constant The $form_state key which stores a map of config keys to form elements.
ConfigFormBase::copyFormValuesToConfig private static function Copies form values to Config keys.
ConfigFormBase::doStoreConfigMap protected function Helper method for #after_build callback ::storeConfigKeyToFormElementMap().
ConfigFormBase::formatMultipleViolationsMessage protected function Formats multiple violation messages associated with a single form element. 1
ConfigFormBase::loadDefaultValuesFromConfig public function Process callback to recursively load default values from #config_target.
ConfigFormBase::storeConfigKeyToFormElementMap public function #after_build callback which stores a map of element names to config keys.
ConfigFormBase::typedConfigManager protected function Returns the typed config manager service.
ConfigFormBaseTrait::config protected function Retrieves a configuration object.
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
FormBase::$configFactory protected property The config factory. 2
FormBase::$requestStack protected property The request stack. 1
FormBase::$routeMatch protected property The route match.
FormBase::configFactory protected function Gets the config factory for this form. 2
FormBase::container private function Returns the service container.
FormBase::currentUser protected function Gets the current user. 2
FormBase::getRequest protected function Gets the request object.
FormBase::getRouteMatch protected function Gets the route match.
FormBase::logger protected function Gets the logger for a specific channel.
FormBase::redirect protected function Returns a redirect response object for the specified route.
FormBase::resetConfigFactory public function Resets the configuration factory.
FormBase::setConfigFactory public function Sets the config factory for this form.
FormBase::setRequestStack public function Sets the request stack object to use.
LoggerChannelTrait::$loggerFactory protected property The logger channel factory service.
LoggerChannelTrait::getLogger protected function Gets the logger for a specific channel.
LoggerChannelTrait::setLoggerFactory public function Injects the logger channel factory.
MessengerTrait::$messenger protected property The messenger. 16
MessengerTrait::messenger public function Gets the messenger. 16
MessengerTrait::setMessenger public function Sets the messenger.
RedirectDestinationTrait::$redirectDestination protected property The redirect destination service. 2
RedirectDestinationTrait::getDestinationArray protected function Prepares a &#039;destination&#039; URL query parameter for use with \Drupal\Core\Url.
RedirectDestinationTrait::getRedirectDestination protected function Returns the redirect destination service.
RedirectDestinationTrait::setRedirectDestination public function Sets the redirect destination service.
StringTranslationTrait::$stringTranslation protected property The string translation service. 3
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
ThemeSettingsForm::$editableConfig protected property An array of configuration names that should be editable.
ThemeSettingsForm::$fileSystem protected property The file system.
ThemeSettingsForm::$mimeTypeGuesser protected property The MIME type guesser.
ThemeSettingsForm::$moduleHandler protected property The module handler.
ThemeSettingsForm::$themeHandler protected property The theme handler.
ThemeSettingsForm::$themeManager protected property The theme manager.
ThemeSettingsForm::buildForm public function Overrides ConfigFormBase::buildForm
ThemeSettingsForm::create public static function Instantiates a new instance of this class. Overrides ConfigFormBase::create
ThemeSettingsForm::getEditableConfigNames protected function Gets the configuration names that will be editable. Overrides ConfigFormBaseTrait::getEditableConfigNames
ThemeSettingsForm::getFormId public function Returns a unique string identifying the form. Overrides FormInterface::getFormId
ThemeSettingsForm::submitForm public function Form submission handler. Overrides ConfigFormBase::submitForm
ThemeSettingsForm::validateForm public function Form validation handler. Overrides ConfigFormBase::validateForm
ThemeSettingsForm::validatePath protected function Helper function for the system_theme_settings form.
ThemeSettingsForm::__construct public function Constructs a ThemeSettingsForm object. Overrides ConfigFormBase::__construct

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.