BlockForm.php 12.9 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * Contains \Drupal\block\BlockForm.
6 7 8 9
 */

namespace Drupal\block;

10
use Drupal\Core\Cache\Cache;
11
use Drupal\Core\Entity\EntityForm;
12
use Drupal\Core\Entity\EntityManagerInterface;
13
use Drupal\Core\Language\Language;
14 15
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\language\ConfigurableLanguageManagerInterface;
16
use Symfony\Component\DependencyInjection\ContainerInterface;
17 18

/**
19
 * Provides form for block instance forms.
20
 */
21
class BlockForm extends EntityForm {
22

23 24 25 26 27 28 29
  /**
   * The block entity.
   *
   * @var \Drupal\block\BlockInterface
   */
  protected $entity;

30
  /**
31
   * The block storage.
32
   *
33
   * @var \Drupal\Core\Entity\EntityStorageInterface
34
   */
35
  protected $storage;
36

37 38 39
  /**
   * The language manager.
   *
40
   * @var \Drupal\Core\Language\LanguageManagerInterface
41 42 43
   */
  protected $languageManager;

44
  /**
45
   * Constructs a BlockForm object.
46
   *
47
   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
48
   *   The entity manager.
49
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
50
   *   The language manager.
51
   */
52
  public function __construct(EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
53
    $this->storage = $entity_manager->getStorage('block');
54
    $this->languageManager = $language_manager;
55 56 57 58 59
  }

  /**
   * {@inheritdoc}
   */
60
  public static function create(ContainerInterface $container) {
61
    return new static(
62
      $container->get('entity.manager'),
63
      $container->get('language_manager')
64 65
    );
  }
66 67

  /**
68
   * {@inheritdoc}
69
   */
70
  public function form(array $form, array &$form_state) {
71
    $entity = $this->entity;
72 73 74

    // Store theme settings in $form_state for use below.
    if (!$theme = $entity->get('theme')) {
75
      $theme = $this->config('system.theme')->get('default');
76 77 78
    }
    $form_state['block_theme'] = $theme;

79
    $form['#tree'] = TRUE;
80
    $form['settings'] = $entity->getPlugin()->buildConfigurationForm(array(), $form_state);
81

82
    // If creating a new block, calculate a safe default machine name.
83
    $form['id'] = array(
84 85
      '#type' => 'machine_name',
      '#maxlength' => 64,
86 87
      '#description' => $this->t('A unique name for this block instance. Must be alpha-numeric and underscore separated.'),
      '#default_value' => !$entity->isNew() ? $entity->id() : $this->getUniqueMachineName($entity),
88
      '#machine_name' => array(
89
        'exists' => '\Drupal\block\Entity\Block::load',
90 91 92 93 94 95 96 97 98 99
        'replace_pattern' => '[^a-z0-9_.]+',
        'source' => array('settings', 'label'),
      ),
      '#required' => TRUE,
      '#disabled' => !$entity->isNew(),
    );

    // Visibility settings.
    $form['visibility'] = array(
      '#type' => 'vertical_tabs',
100
      '#title' => $this->t('Visibility settings'),
101
      '#attached' => array(
102 103 104
        'library' => array(
          'block/drupal.block',
        ),
105 106 107 108 109 110 111 112 113
      ),
      '#tree' => TRUE,
      '#weight' => 10,
      '#parents' => array('visibility'),
    );

    // Per-path visibility.
    $form['visibility']['path'] = array(
      '#type' => 'details',
114
      '#title' => $this->t('Pages'),
115 116 117 118 119 120 121 122
      '#group' => 'visibility',
      '#weight' => 0,
    );

    // @todo remove this access check and inject it in some other way. In fact
    //   this entire visibility settings section probably needs a separate user
    //   interface in the near future.
    $visibility = $entity->get('visibility');
123
    $access = $this->currentUser()->hasPermission('use PHP for settings');
124 125 126 127 128 129 130 131 132 133 134 135
    if (!empty($visibility['path']['visibility']) && $visibility['path']['visibility'] == BLOCK_VISIBILITY_PHP && !$access) {
      $form['visibility']['path']['visibility'] = array(
        '#type' => 'value',
        '#value' => BLOCK_VISIBILITY_PHP,
      );
      $form['visibility']['path']['pages'] = array(
        '#type' => 'value',
        '#value' => !empty($visibility['path']['pages']) ? $visibility['path']['pages'] : '',
      );
    }
    else {
      $options = array(
136 137
        BLOCK_VISIBILITY_NOTLISTED => $this->t('All pages except those listed'),
        BLOCK_VISIBILITY_LISTED => $this->t('Only the listed pages'),
138
      );
139
      $description = $this->t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %user for the current user's page and %user-wildcard for every user page. %front is the front page.", array('%user' => 'user', '%user-wildcard' => 'user/*', '%front' => '<front>'));
140 141 142

      $form['visibility']['path']['visibility'] = array(
        '#type' => 'radios',
143
        '#title' => $this->t('Show block on specific pages'),
144 145 146 147 148
        '#options' => $options,
        '#default_value' => !empty($visibility['path']['visibility']) ? $visibility['path']['visibility'] : BLOCK_VISIBILITY_NOTLISTED,
      );
      $form['visibility']['path']['pages'] = array(
        '#type' => 'textarea',
149
        '#title' => '<span class="visually-hidden">' . $this->t('Pages') . '</span>',
150 151 152 153 154 155
        '#default_value' => !empty($visibility['path']['pages']) ? $visibility['path']['pages'] : '',
        '#description' => $description,
      );
    }

    // Configure the block visibility per language.
156 157
    if ($this->languageManager->isMultilingual() && $this->languageManager instanceof ConfigurableLanguageManagerInterface) {
      $language_types = $this->languageManager->getLanguageTypes();
158 159

      // Fetch languages.
160
      $languages = $this->languageManager->getLanguages(Language::STATE_ALL);
161
      $langcodes_options = array();
162 163 164
      foreach ($languages as $language) {
        // @todo $language->name is not wrapped with t(), it should be replaced
        //   by CMI translation implementation.
165
        $langcodes_options[$language->id] = $language->name;
166 167 168
      }
      $form['visibility']['language'] = array(
        '#type' => 'details',
169
        '#title' => $this->t('Languages'),
170 171 172 173 174
        '#group' => 'visibility',
        '#weight' => 5,
      );
      // If there are multiple configurable language types, let the user pick
      // which one should be applied to this visibility setting. This way users
175
      // can limit blocks by interface language or content language for example.
176
      $info = $this->languageManager->getDefinedLanguageTypesInfo();
177
      $language_type_options = array();
178 179
      foreach ($language_types as $type_key) {
        $language_type_options[$type_key] = $info[$type_key]['name'];
180 181 182
      }
      $form['visibility']['language']['language_type'] = array(
        '#type' => 'radios',
183
        '#title' => $this->t('Language type'),
184
        '#options' => $language_type_options,
185
        '#default_value' => !empty($visibility['language']['language_type']) ? $visibility['language']['language_type'] : reset($language_types),
186 187 188 189
        '#access' => count($language_type_options) > 1,
      );
      $form['visibility']['language']['langcodes'] = array(
        '#type' => 'checkboxes',
190
        '#title' => $this->t('Show this block only for specific languages'),
191 192
        '#default_value' => !empty($visibility['language']['langcodes']) ? $visibility['language']['langcodes'] : array(),
        '#options' => $langcodes_options,
193
        '#description' => $this->t('Show this block only for the selected language(s). If you select no languages, the block will be visible in all languages.'),
194 195 196 197 198 199 200
      );
    }

    // Per-role visibility.
    $role_options = array_map('check_plain', user_role_names());
    $form['visibility']['role'] = array(
      '#type' => 'details',
201
      '#title' => $this->t('Roles'),
202 203 204 205 206
      '#group' => 'visibility',
      '#weight' => 10,
    );
    $form['visibility']['role']['roles'] = array(
      '#type' => 'checkboxes',
207
      '#title' => $this->t('Show block for specific roles'),
208 209
      '#default_value' => !empty($visibility['role']['roles']) ? $visibility['role']['roles'] : array(),
      '#options' => $role_options,
210
      '#description' => $this->t('Show this block only for the selected role(s). If you select no roles, the block will be visible to all users.'),
211 212
    );

213
    // Theme settings.
214
    if ($entity->get('theme')) {
215 216
      $form['theme'] = array(
        '#type' => 'value',
217
        '#value' => $theme,
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
      );
    }
    else {
      $theme_options = array();
      foreach (list_themes() as $theme_name => $theme_info) {
        if (!empty($theme_info->status)) {
          $theme_options[$theme_name] = $theme_info->info['name'];
        }
      }
      $form['theme'] = array(
        '#type' => 'select',
        '#options' => $theme_options,
        '#title' => t('Theme'),
        '#default_value' => $theme,
        '#ajax' => array(
          'callback' => array($this, 'themeSwitch'),
          'wrapper' => 'edit-block-region-wrapper',
        ),
      );
    }
238

239 240 241
    // Region settings.
    $form['region'] = array(
      '#type' => 'select',
242 243
      '#title' => $this->t('Region'),
      '#description' => $this->t('Select the region where this block should be displayed.'),
244
      '#default_value' => $entity->get('region'),
245
      '#empty_value' => BlockInterface::BLOCK_REGION_NONE,
246 247 248
      '#options' => system_region_list($theme, REGIONS_VISIBLE),
      '#prefix' => '<div id="edit-block-region-wrapper">',
      '#suffix' => '</div>',
249
    );
250 251 252
    $form['#attached']['css'] = array(
      drupal_get_path('module', 'block') . '/css/block.admin.css',
    );
253
    return $form;
254 255
  }

256 257 258 259 260 261 262 263
  /**
   * Handles switching the available regions based on the selected theme.
   */
  public function themeSwitch($form, &$form_state) {
    $form['region']['#options'] = system_region_list($form_state['values']['theme'], REGIONS_VISIBLE);
    return $form['region'];
  }

264
  /**
265
   * {@inheritdoc}
266 267 268
   */
  protected function actions(array $form, array &$form_state) {
    $actions = parent::actions($form, $form_state);
269
    $actions['submit']['#value'] = $this->t('Save block');
270 271 272 273
    return $actions;
  }

  /**
274
   * {@inheritdoc}
275 276 277 278
   */
  public function validate(array $form, array &$form_state) {
    parent::validate($form, $form_state);

279
    // Remove empty lines from the role visibility list.
280 281 282 283 284 285 286
    $form_state['values']['visibility']['role']['roles'] = array_filter($form_state['values']['visibility']['role']['roles']);
    // The Block Entity form puts all block plugin form elements in the
    // settings form element, so just pass that to the block for validation.
    $settings = array(
      'values' => &$form_state['values']['settings']
    );
    // Call the plugin validate handler.
287
    $this->entity->getPlugin()->validateConfigurationForm($form, $settings);
288 289 290
  }

  /**
291
   * {@inheritdoc}
292 293 294 295
   */
  public function submit(array $form, array &$form_state) {
    parent::submit($form, $form_state);

296
    $entity = $this->entity;
297 298
    // The Block Entity form puts all block plugin form elements in the
    // settings form element, so just pass that to the block for submission.
299
    // @todo Find a way to avoid this manipulation.
300
    $settings = array(
301 302
      'values' => &$form_state['values']['settings'],
      'errors' => $form_state['errors'],
303
    );
304

305
    // Call the plugin submit handler.
306
    $entity->getPlugin()->submitConfigurationForm($form, $settings);
307 308 309

    // Save the settings of the plugin.
    $entity->save();
310

311
    drupal_set_message($this->t('The block configuration has been saved.'));
312 313
    // Invalidate the content cache and redirect to the block listing,
    // because we need to remove cached block contents for each cache backend.
314
    Cache::invalidateTags(array('content' => TRUE));
315 316 317 318 319 320 321 322 323
    $form_state['redirect_route'] = array(
      'route_name' => 'block.admin_display_theme',
      'route_parameters' => array(
        'theme' => $form_state['values']['theme'],
      ),
      'options' => array(
        'query' => array('block-placement' => drupal_html_class($this->entity->id()))
      ),
    );
324 325
  }

326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
  /**
   * {@inheritdoc}
   */
  public function buildEntity(array $form, array &$form_state) {
    $entity = parent::buildEntity($form, $form_state);

    // visibility__active_tab is Form API state and not configuration.
    // @todo Fix vertical tabs.
    $visibility = $entity->get('visibility');
    unset($visibility['visibility__active_tab']);
    $entity->set('visibility', $visibility);

    return $entity;
  }

341 342 343 344 345 346 347 348 349 350 351 352 353
  /**
   * Generates a unique machine name for a block.
   *
   * @param \Drupal\block\BlockInterface $block
   *   The block entity.
   *
   * @return string
   *   Returns the unique name.
   */
  public function getUniqueMachineName(BlockInterface $block) {
    $suggestion = $block->getPlugin()->getMachineNameSuggestion();

    // Get all the blocks which starts with the suggested machine name.
354
    $query = $this->storage->getQuery();
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
    $query->condition('id', $suggestion, 'CONTAINS');
    $block_ids = $query->execute();

    $block_ids = array_map(function ($block_id) {
      $parts = explode('.', $block_id);
      return end($parts);
    }, $block_ids);

    // Iterate through potential IDs until we get a new one. E.g.
    // 'plugin', 'plugin_2', 'plugin_3'...
    $count = 1;
    $machine_default = $suggestion;
    while (in_array($machine_default, $block_ids)) {
      $machine_default = $suggestion . '_' . ++$count;
    }
    return $machine_default;
  }

373
}