BlockBase.php 11.8 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * Contains \Drupal\Core\Block\BlockBase.
6 7
 */

8
namespace Drupal\Core\Block;
9

10
use Drupal\block\BlockInterface;
11
use Drupal\Core\Access\AccessResult;
12
use Drupal\Core\Form\FormStateInterface;
13
use Drupal\Core\Plugin\ContextAwarePluginBase;
14
use Drupal\Component\Utility\Unicode;
15
use Drupal\Component\Utility\NestedArray;
16
use Drupal\Core\Language\LanguageInterface;
17
use Drupal\Core\Cache\Cache;
18
use Drupal\Core\Session\AccountInterface;
19
use Drupal\Component\Transliteration\TransliterationInterface;
20 21 22 23 24 25 26

/**
 * Defines a base block implementation that most blocks plugins will extend.
 *
 * This abstract class provides the generic block configuration form, default
 * block settings, and handling for general user-defined block visibility
 * settings.
27 28
 *
 * @ingroup block_api
29
 */
30
abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface {
31

32 33 34 35 36 37 38
  /**
   * The transliteration service.
   *
   * @var \Drupal\Component\Transliteration\TransliterationInterface
   */
  protected $transliteration;

39 40 41 42 43 44 45 46 47
  /**
   * {@inheritdoc}
   */
  public function label() {
    if (!empty($this->configuration['label'])) {
      return $this->configuration['label'];
    }

    $definition = $this->getPluginDefinition();
48 49 50
    // Cast the admin label to a string since it is an object.
    // @see \Drupal\Core\StringTranslation\TranslationWrapper
    return (string) $definition['admin_label'];
51 52
  }

53
  /**
54
   * {@inheritdoc}
55
   */
56
  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
57
    parent::__construct($configuration, $plugin_id, $plugin_definition);
58
    $this->setConfiguration($configuration);
59 60 61
  }

  /**
62
   * {@inheritdoc}
63
   */
64
  public function getConfiguration() {
65
    return $this->configuration;
66 67 68
  }

  /**
69 70 71
   * {@inheritdoc}
   */
  public function setConfiguration(array $configuration) {
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
    $this->configuration = NestedArray::mergeDeep(
      $this->baseConfigurationDefaults(),
      $this->defaultConfiguration(),
      $configuration
    );
  }

  /**
   * Returns generic default configuration for block plugins.
   *
   * @return array
   *   An associative array with the default configuration.
   */
  protected function baseConfigurationDefaults() {
    return array(
87
      'id' => $this->getPluginId(),
88
      'label' => '',
89
      'provider' => $this->pluginDefinition['provider'],
90 91 92 93 94 95
      'label_display' => BlockInterface::BLOCK_LABEL_VISIBLE,
      'cache' => array(
        'max_age' => 0,
        'contexts' => array(),
      ),
    );
96 97
  }

98 99 100 101 102 103 104
  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return array();
  }

105 106
  /**
   * {@inheritdoc}
107
   */
108
  public function setConfigurationValue($key, $value) {
109 110 111
    $this->configuration[$key] = $value;
  }

112 113 114 115 116 117 118
  /**
   * {@inheritdoc}
   */
  public function calculateDependencies() {
    return array();
  }

119
  /**
120
   * {@inheritdoc}
121
   */
122
  public function access(AccountInterface $account, $return_as_object = FALSE) {
123 124 125 126
    // @todo Remove self::blockAccess() and force individual plugins to return
    //   their own AccessResult logic. Until that is done in
    //   https://www.drupal.org/node/2375689 the access will be set uncacheable.
    if ($this->blockAccess($account)) {
127
      $access = AccessResult::allowed();
128 129
    }
    else {
130
      $access = AccessResult::forbidden();
131
    }
132 133 134

    $access->setCacheable(FALSE);
    return $return_as_object ? $access : $access->isAllowed();
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
  }

  /**
   * Indicates whether the block should be shown.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The user session for which to check access.
   *
   * @return bool
   *   TRUE if the block should be shown, or FALSE otherwise.
   *
   * @see self::access()
   */
  protected function blockAccess(AccountInterface $account) {
    // By default, the block is visible.
150 151 152 153
    return TRUE;
  }

  /**
154
   * {@inheritdoc}
155 156 157 158 159 160
   *
   * Creates a generic configuration form for all block types. Individual
   * block plugins can add elements to this form by overriding
   * BlockBase::blockForm(). Most block plugins should not override this
   * method unless they need to alter the generic form elements.
   *
161
   * @see \Drupal\Core\Block\BlockBase::blockForm()
162
   */
163
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
164
    $definition = $this->getPluginDefinition();
165
    $form['provider'] = array(
166
      '#type' => 'value',
167
      '#value' => $definition['provider'],
168 169
    );

170 171 172 173 174
    $form['admin_label'] = array(
      '#type' => 'item',
      '#title' => t('Block description'),
      '#markup' => $definition['admin_label'],
    );
175
    $form['label'] = array(
176
      '#type' => 'textfield',
177
      '#title' => $this->t('Title'),
178
      '#maxlength' => 255,
179
      '#default_value' => $this->label(),
180 181 182 183
      '#required' => TRUE,
    );
    $form['label_display'] = array(
      '#type' => 'checkbox',
184
      '#title' => $this->t('Display title'),
185
      '#default_value' => ($this->configuration['label_display'] === BlockInterface::BLOCK_LABEL_VISIBLE),
186
      '#return_value' => BlockInterface::BLOCK_LABEL_VISIBLE,
187
    );
188 189 190
    // Identical options to the ones for page caching.
    // @see \Drupal\system\Form\PerformanceForm::buildForm()
    $period = array(0, 60, 180, 300, 600, 900, 1800, 2700, 3600, 10800, 21600, 32400, 43200, 86400);
191
    $period = array_map(array(\Drupal::service('date.formatter'), 'formatInterval'), array_combine($period, $period));
192 193
    $period[0] = '<' . t('no caching') . '>';
    $period[\Drupal\Core\Cache\Cache::PERMANENT] = t('Forever');
194
    $form['cache'] = array(
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
      '#type' => 'details',
      '#title' => t('Cache settings'),
    );
    $form['cache']['max_age'] = array(
      '#type' => 'select',
      '#title' => t('Maximum age'),
      '#description' => t('The maximum time this block may be cached.'),
      '#default_value' => $this->configuration['cache']['max_age'],
      '#options' => $period,
    );
    $contexts = \Drupal::service("cache_contexts")->getLabels();
    // Blocks are always rendered in a "per theme" cache context. No need to
    // show that option to the end user.
    unset($contexts['cache_context.theme']);
    $form['cache']['contexts'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Vary by context'),
      '#description' => t('The contexts this cached block must be varied by.'),
      '#default_value' => $this->configuration['cache']['contexts'],
      '#options' => $contexts,
      '#states' => array(
        'disabled' => array(
          ':input[name="settings[cache][max_age]"]' => array('value' => (string) 0),
        ),
      ),
220
    );
221 222 223 224 225 226 227 228 229 230 231
    if (count($this->getRequiredCacheContexts()) > 0) {
      // Remove the required cache contexts from the list of contexts a user can
      // choose to modify by: they must always be applied.
      $context_labels = array();
      foreach ($this->getRequiredCacheContexts() as $context) {
        $context_labels[] = $form['cache']['contexts']['#options'][$context];
        unset($form['cache']['contexts']['#options'][$context]);
      }
      $required_context_list = implode(', ', $context_labels);
      $form['cache']['contexts']['#description'] .= ' ' . t('This block is <em>always</em> varied by the following contexts: %required-context-list.', array('%required-context-list' => $required_context_list));
    }
232

233
    // Add plugin-specific settings for this block type.
234
    $form += $this->blockForm($form, $form_state);
235 236 237 238
    return $form;
  }

  /**
239
   * {@inheritdoc}
240
   */
241
  public function blockForm($form, FormStateInterface $form_state) {
242 243 244 245
    return array();
  }

  /**
246
   * {@inheritdoc}
247 248
   *
   * Most block plugins should not override this method. To add validation
249
   * for a specific block type, override BlockBase::blockValidate().
250
   *
251
   * @see \Drupal\Core\Block\BlockBase::blockValidate()
252
   */
253
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
254
    // Remove the admin_label form item element value so it will not persist.
255
    $form_state->unsetValue('admin_label');
256

257
    // Transform the #type = checkboxes value to a numerically indexed array.
258 259
    $contexts = $form_state->getValue(array('cache', 'contexts'));
    $form_state->setValue(array('cache', 'contexts'), array_values(array_filter($contexts)));
260

261 262 263 264
    $this->blockValidate($form, $form_state);
  }

  /**
265
   * {@inheritdoc}
266
   */
267
  public function blockValidate($form, FormStateInterface $form_state) {}
268 269

  /**
270
   * {@inheritdoc}
271 272 273 274
   *
   * Most block plugins should not override this method. To add submission
   * handling for a specific block type, override BlockBase::blockSubmit().
   *
275
   * @see \Drupal\Core\Block\BlockBase::blockSubmit()
276
   */
277
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
278
    // Process the block's submission handling if no errors occurred only.
279
    if (!$form_state->getErrors()) {
280 281 282 283
      $this->configuration['label'] = $form_state->getValue('label');
      $this->configuration['label_display'] = $form_state->getValue('label_display');
      $this->configuration['provider'] = $form_state->getValue('provider');
      $this->configuration['cache'] = $form_state->getValue('cache');
284 285 286 287 288
      $this->blockSubmit($form, $form_state);
    }
  }

  /**
289
   * {@inheritdoc}
290
   */
291
  public function blockSubmit($form, FormStateInterface $form_state) {}
292

293 294 295 296 297 298 299 300 301 302
  /**
   * {@inheritdoc}
   */
  public function getMachineNameSuggestion() {
    $definition = $this->getPluginDefinition();
    $admin_label = $definition['admin_label'];

    // @todo This is basically the same as what is done in
    //   \Drupal\system\MachineNameController::transliterate(), so it might make
    //   sense to provide a common service for the two.
303
    $transliterated = $this->transliteration()->transliterate($admin_label, LanguageInterface::LANGCODE_DEFAULT, '_');
304 305 306 307 308 309 310 311 312 313 314 315

    $replace_pattern = '[^a-z0-9_.]+';

    $transliterated = Unicode::strtolower($transliterated);

    if (isset($replace_pattern)) {
      $transliterated = preg_replace('@' . $replace_pattern . '@', '', $transliterated);
    }

    return $transliterated;
  }

316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
  /**
   * Wraps the transliteration service.
   *
   * @return \Drupal\Component\Transliteration\TransliterationInterface
   */
  protected function transliteration() {
    if (!$this->transliteration) {
      $this->transliteration = \Drupal::transliteration();
    }
    return $this->transliteration;
  }

  /**
   * Sets the transliteration service.
   *
   * @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration
   *   The transliteration service.
   */
  public function setTransliteration(TransliterationInterface $transliteration) {
    $this->transliteration = $transliteration;
  }

338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
  /**
   * Returns the cache contexts required for this block.
   *
   * @return array
   *   The required cache contexts IDs.
   */
  protected function getRequiredCacheContexts() {
    return array();
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheKeys() {
    // Return the required cache contexts, merged with the user-configured cache
    // contexts, if any.
    return array_merge($this->getRequiredCacheContexts(), $this->configuration['cache']['contexts']);
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
    // If a block plugin's output changes, then it must be able to invalidate a
    // cache tag that affects all instances of this block: across themes and
    // across regions.
364
    return array('block_plugin:' . str_replace(':', '__', $this->getPluginID()));
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheMaxAge() {
    return (int)$this->configuration['cache']['max_age'];
  }

  /**
   * {@inheritdoc}
   */
  public function isCacheable() {
    // Similar to the page cache, a block is cacheable if it has a max age.
    // Blocks that should never be cached can override this method to simply
    // return FALSE.
    $max_age = $this->getCacheMaxAge();
    return $max_age === Cache::PERMANENT || $max_age > 0;
  }

385
}