Issue #1479454 by Hugo Wetterberg, galooph, dawehner, andypost, marcingy, heyrocker...
[project/drupal.git] / core / modules / block / lib / Drupal / block / BlockBase.php
1 <?php
2
3 /**
4 * @file
5 * Contains \Drupal\block\BlockBase.
6 */
7
8 namespace Drupal\block;
9
10 use Drupal\Component\Plugin\PluginBase;
11 use Drupal\block\Plugin\Core\Entity\Block;
12 use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
13
14 /**
15 * Defines a base block implementation that most blocks plugins will extend.
16 *
17 * This abstract class provides the generic block configuration form, default
18 * block settings, and handling for general user-defined block visibility
19 * settings.
20 */
21 abstract class BlockBase extends PluginBase implements BlockInterface {
22
23 /**
24 * The entity using this plugin.
25 *
26 * @var \Drupal\block\Plugin\Core\Entity\Block
27 */
28 protected $entity;
29
30 /**
31 * Overrides \Drupal\Component\Plugin\PluginBase::__construct().
32 */
33 public function __construct(array $configuration, $plugin_id, DiscoveryInterface $discovery, Block $entity) {
34 parent::__construct($configuration, $plugin_id, $discovery);
35
36 $this->entity = $entity;
37 }
38
39 /**
40 * Returns plugin-specific settings for the block.
41 *
42 * Block plugins only need to override this method if they override the
43 * defaults provided in BlockBase::settings().
44 *
45 * @return array
46 * An array of block-specific settings to override the defaults provided in
47 * BlockBase::settings().
48 *
49 * @see \Drupal\block\BlockBase::settings().
50 */
51 public function settings() {
52 return array();
53 }
54
55 /**
56 * Returns the configuration data for the block plugin.
57 *
58 * @return array
59 * The plugin configuration array from PluginBase::$configuration.
60 *
61 * @todo This doesn't belong here. Move this into a new base class in
62 * http://drupal.org/node/1764380.
63 * @todo This does not return a config object, so the name is confusing.
64 *
65 * @see \Drupal\Component\Plugin\PluginBase::$configuration
66 */
67 public function getConfig() {
68 if (empty($this->configuration)) {
69 // If the plugin configuration is not already set, initialize it with the
70 // default settings for the block plugin.
71 $this->configuration = $this->settings();
72
73 // @todo This loads the default subject. Is this the right place to do so?
74 $definition = $this->getDefinition();
75 if (isset($definition['subject'])) {
76 $this->configuration += array('subject' => $definition['subject']);
77 }
78 }
79 // Ensure that the default cache mode is set.
80 $this->configuration += array('cache' => DRUPAL_NO_CACHE);
81 return $this->configuration;
82 }
83
84 /**
85 * Sets a particular value in the block settings.
86 *
87 * @param string $key
88 * The key of PluginBase::$configuration to set.
89 * @param mixed $value
90 * The value to set for the provided key.
91 *
92 * @todo This doesn't belong here. Move this into a new base class in
93 * http://drupal.org/node/1764380.
94 * @todo This does not set a value in config(), so the name is confusing.
95 *
96 * @see \Drupal\Component\Plugin\PluginBase::$configuration
97 */
98 public function setConfig($key, $value) {
99 $this->configuration[$key] = $value;
100 }
101
102 /**
103 * Indicates whether block-specific criteria allow access to the block.
104 *
105 * Blocks with access restrictions that should always be applied,
106 * regardless of user-configured settings, should implement this method
107 * with that access control logic.
108 *
109 * @return bool
110 * FALSE to deny access to the block, or TRUE to allow
111 * BlockBase::access() to make the access determination.
112 *
113 * @see \Drupal\block\BlockBase::access()
114 */
115 public function blockAccess() {
116 // By default, the block is visible unless user-configured rules indicate
117 // that it should be hidden.
118 return TRUE;
119 }
120
121 /**
122 * Implements \Drupal\block\BlockInterface::access().
123 *
124 * Adds the user-configured per-role, per-path, and per-language visibility
125 * settings to all blocks, and invokes hook_block_access().
126 *
127 * Most plugins should not override this method unless they need to remove
128 * the user-defined access restrictions. To add specific access
129 * restrictions for a particular block type, override
130 * BlockBase::blockAccess() instead.
131 *
132 * @see hook_block_access()
133 * @see \Drupal\block\BlockBase::blockAccess()
134 */
135 public function access() {
136 // If the block-specific access restrictions indicate the block is not
137 // accessible, always deny access.
138 if (!$this->blockAccess()) {
139 return FALSE;
140 }
141
142 // Otherwise, check for other access restrictions.
143 global $user;
144
145 // Deny access to disabled blocks.
146 if (!$this->entity->get('status')) {
147 return FALSE;
148 }
149
150 // User role access handling.
151 // If a block has no roles associated, it is displayed for every role.
152 // For blocks with roles associated, if none of the user's roles matches
153 // the settings from this block, access is denied.
154 $visibility = $this->entity->get('visibility');
155 if (!empty($visibility['role']['roles']) && !array_intersect(array_filter($visibility['role']['roles']), array_keys($user->roles))) {
156 // No match.
157 return FALSE;
158 }
159
160 // Page path handling.
161 // Limited visibility blocks must list at least one page.
162 if (!empty($visibility['path']['visibility']) && $visibility['path']['visibility'] == BLOCK_VISIBILITY_LISTED && empty($visibility['path']['pages'])) {
163 return FALSE;
164 }
165
166 // Match path if necessary.
167 if (!empty($visibility['path']['pages'])) {
168 // Assume there are no matches until one is found.
169 $page_match = FALSE;
170
171 // Convert path to lowercase. This allows comparison of the same path
172 // with different case. Ex: /Page, /page, /PAGE.
173 $pages = drupal_strtolower($visibility['path']['pages']);
174 if ($visibility['path']['visibility'] < BLOCK_VISIBILITY_PHP) {
175 // Compare the lowercase path alias (if any) and internal path.
176 $path = current_path();
177 $path_alias = drupal_strtolower(drupal_container()->get('path.alias_manager')->getPathAlias($path));
178 $page_match = drupal_match_path($path_alias, $pages) || (($path != $path_alias) && drupal_match_path($path, $pages));
179 // When $block->visibility has a value of 0
180 // (BLOCK_VISIBILITY_NOTLISTED), the block is displayed on all pages
181 // except those listed in $block->pages. When set to 1
182 // (BLOCK_VISIBILITY_LISTED), it is displayed only on those pages
183 // listed in $block->pages.
184 $page_match = !($visibility['path']['visibility'] xor $page_match);
185 }
186 elseif (module_exists('php')) {
187 $page_match = php_eval($visibility['path']['pages']);
188 }
189
190 // If there are page visibility restrictions and this page does not
191 // match, deny access.
192 if (!$page_match) {
193 return FALSE;
194 }
195 }
196
197 // Language visibility settings.
198 if (!empty($visibility['language']['langcodes']) && array_filter($visibility['language']['langcodes'])) {
199 if (empty($visibility['language']['langcodes'][language($visibility['language']['language_type'])->langcode])) {
200 return FALSE;
201 }
202 }
203
204 // Check other modules for block access rules.
205 foreach (module_implements('block_access') as $module) {
206 if (module_invoke($module, 'block_access', $this->entity) === FALSE) {
207 return FALSE;
208 }
209 }
210
211 // If nothing denied access to the block, it is accessible.
212 return TRUE;
213 }
214
215 /**
216 * Implements \Drupal\block\BlockInterface::form().
217 *
218 * Creates a generic configuration form for all block types. Individual
219 * block plugins can add elements to this form by overriding
220 * BlockBase::blockForm(). Most block plugins should not override this
221 * method unless they need to alter the generic form elements.
222 *
223 * @see \Drupal\block\BlockBase::blockForm()
224 */
225 public function form($form, &$form_state) {
226 $entity = $form_state['entity'];
227 $definition = $this->getDefinition();
228 $form['id'] = array(
229 '#type' => 'value',
230 '#value' => $entity->id(),
231 );
232 $form['module'] = array(
233 '#type' => 'value',
234 '#value' => $definition['module'],
235 );
236
237 $form['label'] = array(
238 '#type' => 'textfield',
239 '#title' => t('Block title'),
240 '#maxlength' => 255,
241 '#default_value' => !$entity->isNew() ? $entity->label() : $definition['subject'],
242 );
243 $form['machine_name'] = array(
244 '#type' => 'textfield',
245 '#title' => t('Block machine name'),
246 '#maxlength' => 64,
247 '#description' => t('A unique name to save this block configuration. Must be alpha-numeric and be underscore separated.'),
248 '#default_value' => $entity->id(),
249 '#required' => TRUE,
250 '#disabled' => !$entity->isNew(),
251 );
252
253 // Region settings.
254 $form['region'] = array(
255 '#type' => 'select',
256 '#title' => t('Region'),
257 '#description' => t('Select the region where this block should be displayed.'),
258 '#default_value' => $entity->get('region'),
259 '#empty_value' => BLOCK_REGION_NONE,
260 '#options' => system_region_list($entity->get('theme'), REGIONS_VISIBLE),
261 );
262
263 // Visibility settings.
264 $form['visibility'] = array(
265 '#type' => 'vertical_tabs',
266 '#title' => t('Visibility settings'),
267 '#attached' => array(
268 'js' => array(drupal_get_path('module', 'block') . '/block.js'),
269 ),
270 '#tree' => TRUE,
271 '#weight' => 10,
272 );
273
274 // Per-path visibility.
275 $form['visibility']['path'] = array(
276 '#type' => 'details',
277 '#title' => t('Pages'),
278 '#collapsed' => TRUE,
279 '#group' => 'visibility',
280 '#weight' => 0,
281 );
282
283 // @todo remove this access check and inject it in some other way. In fact
284 // this entire visibility settings section probably needs a separate user
285 // interface in the near future.
286 $visibility = $entity->get('visibility');
287 $access = user_access('use PHP for settings');
288 if (!empty($visibility['path']['visibility']) && $visibility['path']['visibility'] == BLOCK_VISIBILITY_PHP && !$access) {
289 $form['visibility']['path']['visibility'] = array(
290 '#type' => 'value',
291 '#value' => BLOCK_VISIBILITY_PHP,
292 );
293 $form['visibility']['path']['pages'] = array(
294 '#type' => 'value',
295 '#value' => !empty($visibility['path']['pages']) ? $visibility['path']['pages'] : '',
296 );
297 }
298 else {
299 $options = array(
300 BLOCK_VISIBILITY_NOTLISTED => t('All pages except those listed'),
301 BLOCK_VISIBILITY_LISTED => t('Only the listed pages'),
302 );
303 $description = 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>'));
304
305 if (module_exists('php') && $access) {
306 $options += array(BLOCK_VISIBILITY_PHP => t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'));
307 $title = t('Pages or PHP code');
308 $description .= ' ' . t('If the PHP option is chosen, enter PHP code between %php. Note that executing incorrect PHP code can break your Drupal site.', array('%php' => '<?php ?>'));
309 }
310 else {
311 $title = t('Pages');
312 }
313 $form['visibility']['path']['visibility'] = array(
314 '#type' => 'radios',
315 '#title' => t('Show block on specific pages'),
316 '#options' => $options,
317 '#default_value' => !empty($visibility['path']['visibility']) ? $visibility['path']['visibility'] : BLOCK_VISIBILITY_NOTLISTED,
318 );
319 $form['visibility']['path']['pages'] = array(
320 '#type' => 'textarea',
321 '#title' => '<span class="element-invisible">' . $title . '</span>',
322 '#default_value' => !empty($visibility['path']['pages']) ? $visibility['path']['pages'] : '',
323 '#description' => $description,
324 );
325 }
326
327 // Configure the block visibility per language.
328 if (module_exists('language') && language_multilingual()) {
329 $configurable_language_types = language_types_get_configurable();
330
331 // Fetch languages.
332 $languages = language_list(LANGUAGE_ALL);
333 foreach ($languages as $language) {
334 // @todo $language->name is not wrapped with t(), it should be replaced
335 // by CMI translation implementation.
336 $langcodes_options[$language->langcode] = $language->name;
337 }
338 $form['visibility']['language'] = array(
339 '#type' => 'details',
340 '#title' => t('Languages'),
341 '#collapsed' => TRUE,
342 '#group' => 'visibility',
343 '#weight' => 5,
344 );
345 // If there are multiple configurable language types, let the user pick
346 // which one should be applied to this visibility setting. This way users
347 // can limit blocks by interface language or content language for exmaple.
348 $language_types = language_types_info();
349 $language_type_options = array();
350 foreach ($configurable_language_types as $type_key) {
351 $language_type_options[$type_key] = $language_types[$type_key]['name'];
352 }
353 $form['visibility']['language']['language_type'] = array(
354 '#type' => 'radios',
355 '#title' => t('Language type'),
356 '#options' => $language_type_options,
357 '#default_value' => !empty($visibility['language']['language_type']) ? $visibility['language']['language_type'] : $configurable_language_types[0],
358 '#access' => count($language_type_options) > 1,
359 );
360 $form['visibility']['language']['langcodes'] = array(
361 '#type' => 'checkboxes',
362 '#title' => t('Show this block only for specific languages'),
363 '#default_value' => !empty($visibility['language']['langcodes']) ? $visibility['language']['langcodes'] : array(),
364 '#options' => $langcodes_options,
365 '#description' => t('Show this block only for the selected language(s). If you select no languages, the block will be visibile in all languages.'),
366 );
367 }
368
369 // Per-role visibility.
370 $role_options = array_map('check_plain', user_role_names());
371 $form['visibility']['role'] = array(
372 '#type' => 'details',
373 '#title' => t('Roles'),
374 '#collapsed' => TRUE,
375 '#group' => 'visibility',
376 '#weight' => 10,
377 );
378 $form['visibility']['role']['roles'] = array(
379 '#type' => 'checkboxes',
380 '#title' => t('Show block for specific roles'),
381 '#default_value' => !empty($visibility['role']['roles']) ? $visibility['role']['roles'] : array(),
382 '#options' => $role_options,
383 '#description' => t('Show this block only for the selected role(s). If you select no roles, the block will be visible to all users.'),
384 );
385
386 // Add plugin-specific settings for this block type.
387 $form['settings'] = $this->blockForm(array(), $form_state);
388 return $form;
389 }
390
391 /**
392 * Returns the configuration form elements specific to this block plugin.
393 *
394 * Blocks that need to add form elements to the normal block configuration
395 * form should implement this method.
396 *
397 * @param array $form
398 * The form definition array for the block configuration form.
399 * @param array $form_state
400 * An array containing the current state of the configuration form.
401 *
402 * @return array $form
403 * The renderable form array representing the entire configuration form.
404 *
405 * @see \Drupal\block\BlockBase::form()
406 */
407 public function blockForm($form, &$form_state) {
408 return array();
409 }
410
411 /**
412 * Implements \Drupal\block\BlockInterface::validate().
413 *
414 * Most block plugins should not override this method. To add validation
415 * for a specific block type, override BlockBase::blockValdiate().
416 *
417 * @todo Add inline documentation to this method.
418 *
419 * @see \Drupal\block\BlockBase::blockValidate()
420 */
421 public function validate($form, &$form_state) {
422 if (empty($form['machine_name']['#disabled'])) {
423 if (preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['machine_name'])) {
424 form_set_error('machine_name', t('Block name must be alphanumeric or underscores only.'));
425 }
426 }
427 else {
428 $config_id = explode('.', $form_state['values']['machine_name']);
429 $form_state['values']['machine_name'] = array_pop($config_id);
430 }
431 $form_state['values']['visibility']['role']['roles'] = array_filter($form_state['values']['visibility']['role']['roles']);
432 if ($form_state['entity']->isNew()) {
433 form_set_value($form['id'], $form_state['entity']->get('theme') . '.' . $form_state['values']['machine_name'], $form_state);
434 }
435 $this->blockValidate($form, $form_state);
436 }
437
438 /**
439 * Adds block type-specific validation for the block form.
440 *
441 * Note that this method takes the form structure and form state arrays for
442 * the full block configuration form as arguments, not just the elements
443 * defined in BlockBase::blockForm().
444 *
445 * @param array $form
446 * The form definition array for the full block configuration form.
447 * @param array $form_state
448 * An array containing the current state of the configuration form.
449 *
450 * @see \Drupal\block\BlockBase::blockForm()
451 * @see \Drupal\block\BlockBase::blockSubmit()
452 * @see \Drupal\block\BlockBase::validate()
453 */
454 public function blockValidate($form, &$form_state) {}
455
456 /**
457 * Implements \Drupal\block\BlockInterface::submit().
458 *
459 * Most block plugins should not override this method. To add submission
460 * handling for a specific block type, override BlockBase::blockSubmit().
461 *
462 * @todo Add inline documentation to this method.
463 *
464 * @see \Drupal\block\BlockBase::blockSubmit()
465 */
466 public function submit($form, &$form_state) {
467 if (!form_get_errors()) {
468 $this->blockSubmit($form, $form_state);
469
470 drupal_set_message(t('The block configuration has been saved.'));
471 cache_invalidate_tags(array('content' => TRUE));
472 $form_state['redirect'] = 'admin/structure/block/list/block_plugin_ui:' . $form_state['entity']->get('theme');
473 }
474 }
475
476 /**
477 * Adds block type-specific submission handling for the block form.
478 *
479 * Note that this method takes the form structure and form state arrays for
480 * the full block configuration form as arguments, not just the elements
481 * defined in BlockBase::blockForm().
482 *
483 * @param array $form
484 * The form definition array for the full block configuration form.
485 * @param array $form_state
486 * An array containing the current state of the configuration form.
487 *
488 * @see \Drupal\block\BlockBase::blockForm()
489 * @see \Drupal\block\BlockBase::blockValidate()
490 * @see \Drupal\block\BlockBase::submit()
491 */
492 public function blockSubmit($form, &$form_state) {}
493
494 }