I finally had time to make a custom module for that: ctool_views_extender
In your MODULE.module:
<?php
use Drupal\views\Plugin\views\display\Block as CoreBlock;
use Drupal\MODULE\Plugin\Display\Block;
/**
* Implements hook_views_plugins_display_alter().
*/
function MODULE_views_plugins_display_alter(&$displays) {
if (!empty($displays['block']['class'])) {
$displays['block']['class'] = Block::class;
}
}
In src/Plugin/Display/Block.php:
<?php
/**
* @file
* Contains \Drupal\MODULE\Plugin\Display\Block.
*/
namespace Drupal\MODULE\Plugin\Display;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\Block\ViewsBlock;
use Drupal\ctools_views\Plugin\Display\Block as CtoolsBlock;
/**
* Provides a Block display plugin that allows for greater control over Views
* block settings.
*/
class Block extends CtoolsBlock {
/**
* {@inheritdoc}
*/
public function optionsSummary(&$categories, &$options) {
parent::optionsSummary($categories, $options);
$filtered_allow = array_filter($this->getOption('allow'));
$filter_options = [
'background' => $this->t('Background')
];
$filter_intersect = array_intersect_key($filter_options, $filtered_allow);
$options['allow'] = array(
'category' => 'block',
'title' => $this->t('Allow settings'),
'value' => empty($filtered_allow) ? $this->t('None') : implode(', ', $filter_intersect),
);
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$options = $form['allow']['#options'];
$options['background'] = $this->t('Background');
$form['allow']['#options'] = $options;
}
/**
* {@inheritdoc}
*/
public function blockForm(ViewsBlock $block, array &$form, FormStateInterface $form_state) {
$form = parent::blockForm($block, $form, $form_state);
$allow_settings = array_filter($this->getOption('allow'));
$block_configuration = $block->getConfiguration();
if(!empty($allow_settings['background'])){
$form['override']['background'] = array(
'#type' => 'managed_file',
'#title' => $this->t('Background'),
'#description' => $this->t('Enter background image'),
'#default_value' => isset($block_configuration['background'])?$block_configuration['background']:'',
'#upload_location' => 'public://backgrounds'
);
}
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit(ViewsBlock $block, $form, FormStateInterface $form_state) {
// Set default value for items_per_page if left blank.
if (empty($form_state->getValue(array('override', 'items_per_page')))) {
$form_state->setValue(array('override', 'items_per_page'), "none");
}
parent::blockSubmit($block, $form, $form_state);
$configuration = $block->getConfiguration();
$allow_settings = array_filter($this->getOption('allow'));
if (!empty($allow_settings['background'])) {
$background = $form_state->getValue(['override', 'background']);
$configuration['background'] = $background;
// fix bug in Drupal which does not put the image as permanent
$fid = $form_state->getValue(['override', 'background'])[0];
/** @var \Drupal\file\Entity\File $file */
$file = \Drupal\file\Entity\File::load($fid);
/** @var \Drupal\file\FileUsage\DatabaseFileUsageBackend $file_usage */
$file_usage = \Drupal::service('file.usage');
$file_usage->add($file, 'ctools_views', 'image', 1);
}
$block->setConfiguration($configuration);
}
}
Then all you need to do is output it in a twig template. I use another module that I made to simplify things (get_url_from_fid on github or How to get path of image from file id on SE)
{% set background = '' %}
{% if configuration.background.0 %}
{% set background = 'background-image: url(' ~ get_url_from_fid(configuration.background.0) ~ ')' %}
{% endif %}
<div{{ attributes.addClass(classes) }} style="{{ background }}">
Cheers!