block.module 22 KB
Newer Older
Dries's avatar
   
Dries committed
1
<?php
2
// $Id$
Dries's avatar
 
Dries committed
3

Dries's avatar
   
Dries committed
4
5
6
7
8
/**
 * @file
 * Controls the boxes that are displayed around the main content.
 */

9
10
11
/**
 * Denotes that a block is not enabled in any region and should not be shown.
 */
12
13
define('BLOCK_REGION_NONE', -1);

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
 * Constants defining cache granularity for blocks.
 *
 * Modules specify the caching patterns for their blocks using binary
 * combinations of these constants in their hook_block(op 'list'):
 *   $block[delta]['cache'] = BLOCK_CACHE_PER_ROLE | BLOCK_CACHE_PER_PAGE;
 * BLOCK_CACHE_PER_ROLE is used as a default when no caching pattern is
 * specified.
 *
 * The block cache is cleared in cache_clear_all(), and uses the same clearing
 * policy than page cache (node, comment, user, taxonomy added or updated...).
 * Blocks requiring more fine-grained clearing might consider disabling the
 * built-in block cache (BLOCK_NO_CACHE) and roll their own.
 *
 * Note that user 1 is excluded from block caching.
 */

/**
 * The block should not get cached. This setting should be used:
 * - for simple blocks (notably those that do not perform any db query),
 * where querying the db cache would be more expensive than directly generating
 * the content.
 * - for blocks that change too frequently.
 */
define('BLOCK_NO_CACHE', -1);

/**
41
42
43
 * The block can change depending on the roles the user viewing the page belongs to.
 * This is the default setting, used when the block does not specify anything.
 */
44
45
46
define('BLOCK_CACHE_PER_ROLE', 0x0001);

/**
47
48
49
50
 * The block can change depending on the user viewing the page.
 * This setting can be resource-consuming for sites with large number of users,
 * and thus should only be used when BLOCK_CACHE_PER_ROLE is not sufficient.
 */
51
52
53
define('BLOCK_CACHE_PER_USER', 0x0002);

/**
54
55
 * The block can change depending on the page being viewed.
 */
56
57
58
59
60
61
62
define('BLOCK_CACHE_PER_PAGE', 0x0004);

/**
 * The block is the same for every user on every page where it is visible.
 */
define('BLOCK_CACHE_GLOBAL', 0x0008);

Dries's avatar
   
Dries committed
63
64
65
/**
 * Implementation of hook_help().
 */
66
67
function block_help($path, $arg) {
  switch ($path) {
Dries's avatar
   
Dries committed
68
    case 'admin/help#block':
69
70
71
72
73
74
75
76
77
78
79
      $output = '<p>' . t('Blocks are boxes of content rendered into an area, or region, of a web page. The default theme Garland, for example, implements the regions "left sidebar", "right sidebar", "content", "header", and "footer", and a block may appear in any one of these areas. The <a href="@blocks">blocks administration page</a> provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions.', array('@blocks' => url('admin/build/block'))) . '</p>';
      $output .= '<p>' . t('Although blocks are usually generated automatically by modules (like the <em>User login</em> block, for example), administrators can also define custom blocks. Custom blocks have a title, description, and body. The body of the block can be as long as necessary, and can contain content supported by any available <a href="@input-format">input format</a>.', array('@input-format' => url('admin/settings/filters'))) . '</p>';
      $output .= '<p>' . t('When working with blocks, remember that:') . '</p>';
      $output .= '<ul><li>' . t('since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis.') . '</li>';
      $output .= '<li>' . t('disabled blocks, or blocks not in a region, are never shown.') . '</li>';
      $output .= '<li>' . t('blocks can be configured to be visible only on certain pages.') . '</li>';
      $output .= '<li>' . t('blocks can be configured to be visible only when specific conditions are true.') . '</li>';
      $output .= '<li>' . t('blocks can be configured to be visible only for certain user roles.') . '</li>';
      $output .= '<li>' . t('when allowed by an administrator, specific blocks may be enabled or disabled on a per-user basis using the <em>My account</em> page.') . '</li>';
      $output .= '<li>' . t('some dynamic blocks, such as those generated by modules, will be displayed only on certain pages.') . '</li></ul>';
      $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@block">Block module</a>.', array('@block' => 'http://drupal.org/handbook/modules/block/')) . '</p>';
80
      return $output;
81
    case 'admin/build/block':
82
83
      $output = '<p>' . t('This page provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions. To change the region or order of a block, grab a drag-and-drop handle under the <em>Block</em> column and drag the block to a new location in the list. (Grab a handle by clicking and holding the mouse while hovering over a handle icon.) Since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis. Remember that your changes will not be saved until you click the <em>Save blocks</em> button at the bottom of the page.') . '</p>';
      $output .= '<p>' . t('Click the <em>configure</em> link next to each block to configure its specific title and visibility settings. Use the <a href="@add-block">add block page</a> to create a custom block.', array('@add-block' => url('admin/build/block/add'))) . '</p>';
84
      return $output;
85
    case 'admin/build/block/add':
86
      return '<p>' . t('Use this page to create a new custom block. New blocks are disabled by default, and must be moved to a region on the <a href="@blocks">blocks administration page</a> to be visible.', array('@blocks' => url('admin/build/block'))) . '</p>';
Dries's avatar
   
Dries committed
87
  }
Dries's avatar
   
Dries committed
88
89
}

90
/**
91
 * Implementation of hook_theme().
92
93
94
 */
function block_theme() {
  return array(
95
96
    'block_admin_display_form' => array(
      'template' => 'block-admin-display-form',
97
      'file' => 'block.admin.inc',
98
99
100
101
102
      'arguments' => array('form' => NULL),
    ),
  );
}

Dries's avatar
   
Dries committed
103
104
105
/**
 * Implementation of hook_perm().
 */
Dries's avatar
   
Dries committed
106
function block_perm() {
107
108
109
110
  return array(
    'administer blocks' => t('Select which blocks are displayed, and arrange them on the page.'),
    'use PHP for block visibility' => t('Enter PHP code in the field for block visibility settings. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
  );
Dries's avatar
   
Dries committed
111
112
}

Dries's avatar
   
Dries committed
113
/**
Dries's avatar
   
Dries committed
114
 * Implementation of hook_menu().
Dries's avatar
   
Dries committed
115
 */
116
117
function block_menu() {
  $items['admin/build/block'] = array(
118
119
    'title' => 'Blocks',
    'description' => 'Configure what block content appears in your site\'s sidebars and other regions.',
120
    'page callback' => 'block_admin_display',
121
122
123
    'access arguments' => array('administer blocks'),
  );
  $items['admin/build/block/list'] = array(
124
    'title' => 'List',
125
126
127
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
128
  $items['admin/build/block/list/js'] = array(
129
    'title' => 'JavaScript List Form',
130
    'page callback' => 'block_admin_display_js',
131
    'access arguments' => array('administer blocks'),
132
133
    'type' => MENU_CALLBACK,
  );
134
  $items['admin/build/block/configure'] = array(
135
    'title' => 'Configure block',
136
    'page callback' => 'drupal_get_form',
137
    'page arguments' => array('block_admin_configure'),
138
    'access arguments' => array('administer blocks'),
139
140
141
    'type' => MENU_CALLBACK,
  );
  $items['admin/build/block/delete'] = array(
142
    'title' => 'Delete block',
143
    'page callback' => 'drupal_get_form',
144
    'page arguments' => array('block_box_delete'),
145
    'access arguments' => array('administer blocks'),
146
147
148
    'type' => MENU_CALLBACK,
  );
  $items['admin/build/block/add'] = array(
149
    'title' => 'Add block',
150
    'page callback' => 'drupal_get_form',
151
    'page arguments' => array('block_add_block_form'),
152
    'access arguments' => array('administer blocks'),
153
154
155
156
    'type' => MENU_LOCAL_TASK,
  );
  $default = variable_get('theme_default', 'garland');
  foreach (list_themes() as $key => $theme) {
157
    $items['admin/build/block/list/' . $key] = array(
158
159
160
161
162
163
164
      'title' => check_plain($theme->info['name']),
      'page arguments' => array($key),
      'type' => $key == $default ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
      'weight' => $key == $default ? -10 : 0,
      'access callback' => '_block_themes_access',
      'access arguments' => array($theme),
    );
Dries's avatar
   
Dries committed
165
  }
Dries's avatar
   
Dries committed
166
  return $items;
Dries's avatar
   
Dries committed
167
168
}

169
/**
170
 * Menu item access callback - only admin or enabled themes can be accessed.
171
172
173
174
175
 */
function _block_themes_access($theme) {
  return user_access('administer blocks') && ($theme->status || $theme->name == variable_get('admin_theme', '0'));
}

Dries's avatar
   
Dries committed
176
177
178
179
180
/**
 * Implementation of hook_block().
 *
 * Generates the administrator-defined blocks for display.
 */
181
182
183
function block_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
184
185
      $blocks = array();

186
      $result = db_query('SELECT bid, info FROM {boxes} ORDER BY info');
187
      while ($block = db_fetch_object($result)) {
188
        $blocks[$block->bid]['info'] = $block->info;
189
190
        // Not worth caching.
        $blocks[$block->bid]['cache'] = BLOCK_NO_CACHE;
191
192
193
194
      }
      return $blocks;

    case 'configure':
195
196
197
198
      $box = array('format' => FILTER_FORMAT_DEFAULT);
      if ($delta) {
        $box = block_box_get($delta);
      }
199
200
201
202
203
204
205
206
207
208
      if (filter_access($box['format'])) {
        return block_box_form($box);
      }
      break;

    case 'save':
      block_box_save($edit, $delta);
      break;

    case 'view':
209
      $block = db_fetch_object(db_query('SELECT body, format FROM {boxes} WHERE bid = %d', $delta));
210
      $data['content'] = check_markup($block->body, $block->format, FALSE);
211
      return $data;
212
213
214
  }
}

Dries's avatar
   
Dries committed
215
/**
Dries's avatar
   
Dries committed
216
 * Update the 'blocks' DB table with the blocks currently exported by modules.
Dries's avatar
   
Dries committed
217
 *
Dries's avatar
   
Dries committed
218
 * @return
219
 *   Blocks currently exported by modules.
Dries's avatar
   
Dries committed
220
 */
221
function _block_rehash() {
222
223
  global $theme_key;

224
  init_theme();
225
226

  $result = db_query("SELECT * FROM {blocks} WHERE theme = '%s'", $theme_key);
227
228
229
  $old_blocks = array();
  while ($old_block = db_fetch_array($result)) {
    $old_blocks[$old_block['module']][$old_block['delta']] = $old_block;
Dries's avatar
   
Dries committed
230
231
  }

232
  $blocks = array();
233
234
  // Valid region names for the theme.
  $regions = system_region_list($theme_key);
Dries's avatar
   
Dries committed
235
236

  foreach (module_list() as $module) {
Dries's avatar
   
Dries committed
237
    $module_blocks = module_invoke($module, 'block', 'list');
Dries's avatar
   
Dries committed
238
239
    if ($module_blocks) {
      foreach ($module_blocks as $delta => $block) {
240
241
242
243
244
        if (empty($old_blocks[$module][$delta])) {
          // If it's a new block, add identifiers.
          $block['module'] = $module;
          $block['delta']  = $delta;
          $block['theme']  = $theme_key;
245
246
247
248
249
250
          if (!isset($block['pages'])) {
            // {block}.pages is type 'text', so it cannot have a
            // default value, and not null, so we need to provide
            // value if the module did not.
            $block['pages']  = '';
          }
251
252
          // Add defaults and save it into the database.
          drupal_write_record('blocks', $block);
253
254
255
          // Set region to none if not enabled.
          $block['region'] = $block['status'] ? $block['region'] : BLOCK_REGION_NONE;
          // Add to the list of blocks we return.
256
          $blocks[] = $block;
Dries's avatar
   
Dries committed
257
258
        }
        else {
259
260
261
262
263
264
          // If it's an existing block, database settings should overwrite
          // the code. But aside from 'info' everything that's definable in
          // code is stored in the database and we do not store 'info', so we
          // do not need to update the database here.
          // Add 'info' to this block.
          $old_blocks[$module][$delta]['info'] = $block['info'];
265
266
267
268
269
270
271
272
273
          // If the region name does not exist, disable the block and assign it to none.
          if (!empty($old_blocks[$module][$delta]['region']) && !isset($regions[$old_blocks[$module][$delta]['region']])) {
            drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $old_blocks[$module][$delta]['info'], '%region' => $old_blocks[$module][$delta]['region'])), 'warning');
            $old_blocks[$module][$delta]['status'] = 0;
            $old_blocks[$module][$delta]['region'] = BLOCK_REGION_NONE;
          }
          else {
            $old_blocks[$module][$delta]['region'] = $old_blocks[$module][$delta]['status'] ? $old_blocks[$module][$delta]['region'] : BLOCK_REGION_NONE;
          }
274
          // Add this block to the list of blocks we return.
275
276
277
          $blocks[] = $old_blocks[$module][$delta];
          // Remove this block from the list of blocks to be deleted.
          unset($old_blocks[$module][$delta]);
Dries's avatar
   
Dries committed
278
279
280
281
282
        }
      }
    }
  }

283
284
285
286
287
  // Remove blocks that are no longer defined by the code from the database.
  foreach ($old_blocks as $module => $old_module_blocks) {
    foreach ($old_module_blocks as $delta => $block) {
      db_query("DELETE FROM {blocks} WHERE module = '%s' AND delta = '%s' AND theme = '%s'", $module, $delta, $theme_key);
    }
288
  }
Dries's avatar
   
Dries committed
289
290
  return $blocks;
}
Dries's avatar
   
Dries committed
291

292
function block_box_get($bid) {
293
  return db_fetch_array(db_query("SELECT bx.*, bl.title FROM {boxes} bx INNER JOIN {blocks} bl ON bx.bid = bl.delta WHERE bl.module = 'block' AND bx.bid = %d", $bid));
Dries's avatar
   
Dries committed
294
295
}

296
297
298
/**
 * Define the custom block form.
 */
299
function block_box_form($edit = array()) {
300
301
302
303
  $edit += array(
    'info' => '',
    'body' => '',
  );
304
305
306
  $form['info'] = array(
    '#type' => 'textfield',
    '#title' => t('Block description'),
307
    '#default_value' => $edit['info'],
308
    '#maxlength' => 64,
309
    '#description' => t('A brief description of your block. Used on the <a href="@overview">block overview page</a>.', array('@overview' => url('admin/build/block'))),
310
311
312
    '#required' => TRUE,
    '#weight' => -19,
  );
313
314
  $form['body_field']['#weight'] = -17;
  $form['body_field']['body'] = array(
315
316
    '#type' => 'textarea',
    '#title' => t('Block body'),
317
    '#default_value' => $edit['body'],
318
319
320
321
    '#rows' => 15,
    '#description' => t('The content of the block as shown to the user.'),
    '#weight' => -17,
  );
322
323
324
  if (!isset($edit['format'])) {
    $edit['format'] = FILTER_FORMAT_DEFAULT;
  }
325
  $form['body_field']['format'] = filter_form($edit['format'], -16);
326

327
  return $form;
328
329
}

330
function block_box_save($edit, $delta) {
331
332
  if (!filter_access($edit['format'])) {
    $edit['format'] = FILTER_FORMAT_DEFAULT;
Dries's avatar
   
Dries committed
333
334
  }

335
336
  db_query("UPDATE {boxes} SET body = '%s', info = '%s', format = %d WHERE bid = %d", $edit['body'], $edit['info'], $edit['format'], $delta);

337
  return TRUE;
338
339
}

Dries's avatar
   
Dries committed
340
341
342
343
344
345
/**
 * Implementation of hook_user().
 *
 * Allow users to decide which custom blocks to display when they visit
 * the site.
 */
346
function block_user($type, $edit, &$account, $category = NULL) {
347
  switch ($type) {
348
    case 'form':
349
      if ($category == 'account') {
350
        $rids = array_keys($account->roles);
351
        $result = db_query("SELECT DISTINCT b.* FROM {blocks} b LEFT JOIN {blocks_roles} r ON b.module = r.module AND b.delta = r.delta WHERE b.status = 1 AND b.custom != 0 AND (r.rid IN (" . db_placeholders($rids) . ") OR r.rid IS NULL) ORDER BY b.weight, b.module", $rids);
352
        $form['block'] = array('#type' => 'fieldset', '#title' => t('Block configuration'), '#weight' => 3, '#collapsible' => TRUE, '#tree' => TRUE);
353
354
355
        while ($block = db_fetch_object($result)) {
          $data = module_invoke($block->module, 'block', 'list');
          if ($data[$block->delta]['info']) {
356
            $return = TRUE;
357
            $form['block'][$block->module][$block->delta] = array('#type' => 'checkbox', '#title' => check_plain($data[$block->delta]['info']), '#default_value' => isset($account->block[$block->module][$block->delta]) ? $account->block[$block->module][$block->delta] : ($block->custom == 1));
358
          }
Kjartan's avatar
Kjartan committed
359
360
        }

361
        if (!empty($return)) {
362
          return $form;
363
        }
364
      }
Dries's avatar
   
Dries committed
365
366

      break;
Dries's avatar
   
Dries committed
367
    case 'validate':
368
      if (empty($edit['block'])) {
Dries's avatar
   
Dries committed
369
        $edit['block'] = array();
370
371
      }
      return $edit;
372
373
374
  }
}

375
376
377
378
379
380
381
382
383
384
385
386
387
/**
 * Return all blocks in the specified region for the current user.
 *
 * @param $region
 *   The name of a region.
 *
 * @return
 *   An array of block objects, indexed with <i>module</i>_<i>delta</i>.
 *   If you are displaying your blocks in one or two sidebars, you may check
 *   whether this array is empty to see how many columns are going to be
 *   displayed.
 *
 * @todo
388
389
 *   Now that the blocks table has a primary key, we should use that as the
 *   array key instead of <i>module</i>_<i>delta</i>.
390
391
 */
function block_list($region) {
392
393
  static $blocks = array();

394
  if (!count($blocks)) {
395
396
397
    $blocks = _block_load_blocks();
  }

398
  // Create an empty array if there were no entries.
399
400
401
402
403
404
405
406
407
408
  if (!isset($blocks[$region])) {
    $blocks[$region] = array();
  }

  $blocks[$region] = _block_render_blocks($blocks[$region]);

  return $blocks[$region];
}

/**
409
 * Load blocks information from the database.
410
411
412
413
414
415
 */
function _block_load_blocks() {
  global $user, $theme_key;

  $blocks = array();
  $rids = array_keys($user->roles);
416
  $result = db_query(db_rewrite_sql("SELECT DISTINCT b.* FROM {blocks} b LEFT JOIN {blocks_roles} r ON b.module = r.module AND b.delta = r.delta WHERE b.theme = '%s' AND b.status = 1 AND (r.rid IN (" . db_placeholders($rids) . ") OR r.rid IS NULL) ORDER BY b.region, b.weight, b.module", 'b', 'bid'), array_merge(array($theme_key), $rids));
417
418
419
420
  while ($block = db_fetch_object($result)) {
    if (!isset($blocks[$block->region])) {
      $blocks[$block->region] = array();
    }
421
    // Use the user's block visibility setting, if necessary.
422
423
424
    if ($block->custom != 0) {
      if ($user->uid && isset($user->block[$block->module][$block->delta])) {
        $enabled = $user->block[$block->module][$block->delta];
425
426
      }
      else {
427
        $enabled = ($block->custom == 1);
428
      }
429
430
431
432
    }
    else {
      $enabled = TRUE;
    }
433

434
    // Match path if necessary.
435
436
437
438
439
440
441
    if ($block->pages) {
      if ($block->visibility < 2) {
        $path = drupal_get_path_alias($_GET['q']);
        // Compare with the internal and path alias (if any).
        $page_match = drupal_match_path($path, $block->pages);
        if ($path != $_GET['q']) {
          $page_match = $page_match || drupal_match_path($_GET['q'], $block->pages);
442
        }
443
444
445
446
        // When $block->visibility has a value of 0, the block is displayed on
        // all pages except those listed in $block->pages. When set to 1, it
        // is displayed only on those pages listed in $block->pages.
        $page_match = !($block->visibility xor $page_match);
447
448
      }
      else {
449
        $page_match = drupal_eval($block->pages);
450
      }
451
452
453
454
455
456
457
458
    }
    else {
      $page_match = TRUE;
    }
    $block->enabled = $enabled;
    $block->page_match = $page_match;
    $blocks[$block->region]["{$block->module}_{$block->delta}"] = $block;
  }
459

460
461
  return $blocks;
}
462

463
464
465
466
/**
 * Render the content and subject for a set of blocks.
 *
 * @param $region_blocks
467
 *   An array of block objects such as returned for one region by _block_load_blocks().
468
469
 *
 * @return
470
 *   An array of visible blocks with subject and content rendered.
471
472
473
474
475
476
477
478
 */
function _block_render_blocks($region_blocks) {
  foreach ($region_blocks as $key => $block) {
    // Render the block content if it has not been created already.
    if (!isset($block->content)) {
      // Erase the block from the static array - we'll put it back if it has content.
      unset($region_blocks[$key]);
      if ($block->enabled && $block->page_match) {
479
480
481
482
483
484
485
486
487
488
        // Try fetching the block from cache. Block caching is not compatible with
        // node_access modules. We also preserve the submission of forms in blocks,
        // by fetching from cache only if the request method is 'GET'.
        if (!count(module_implements('node_grants')) && $_SERVER['REQUEST_METHOD'] == 'GET' && ($cid = _block_get_cache_id($block)) && ($cache = cache_get($cid, 'cache_block'))) {
          $array = $cache->data;
        }
        else {
          $array = module_invoke($block->module, 'block', 'view', $block->delta);
          if (isset($cid)) {
            cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY);
489
          }
490
        }
491

492
493
494
        if (isset($array) && is_array($array)) {
          foreach ($array as $k => $v) {
            $block->$k = $v;
495
          }
Dries's avatar
   
Dries committed
496
        }
497
        if (isset($block->content) && $block->content) {
498
499
500
501
502
          // Override default block title if a custom display title is present.
          if ($block->title) {
            // Check plain here to allow module generated titles to keep any markup.
            $block->subject = $block->title == '<none>' ? '' : check_plain($block->title);
          }
503
504
505
          if (!isset($block->subject)) {
            $block->subject = '';
          }
506
          $region_blocks["{$block->module}_{$block->delta}"] = $block;
507
508
509
510
        }
      }
    }
  }
511
  return $region_blocks;
512
}
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551

/**
 * Assemble the cache_id to use for a given block.
 *
 * The cache_id string reflects the viewing context for the current block
 * instance, obtained by concatenating the relevant context information
 * (user, page, ...) according to the block's cache settings (BLOCK_CACHE_*
 * constants). Two block instances can use the same cached content when
 * they share the same cache_id.
 *
 * Theme and language contexts are automatically differenciated.
 *
 * @param $block
 * @return
 *   The string used as cache_id for the block.
 */
function _block_get_cache_id($block) {
  global $theme, $base_root, $user;

  // User 1 being out of the regular 'roles define permissions' schema,
  // it brings too many chances of having unwanted output get in the cache
  // and later be served to other users. We therefore exclude user 1 from
  // block caching.
  if (variable_get('block_cache', 0) && $block->cache != BLOCK_NO_CACHE && $user->uid != 1) {
    $cid_parts = array();

    // Start with common sub-patterns: block identification, theme, language.
    $cid_parts[] = $block->module;
    $cid_parts[] = $block->delta;
    $cid_parts[] = $theme;
    if (module_exists('locale')) {
      global $language;
      $cid_parts[] = $language->language;
    }

    // 'PER_ROLE' and 'PER_USER' are mutually exclusive. 'PER_USER' can be a
    // resource drag for sites with many users, so when a module is being
    // equivocal, we favor the less expensive 'PER_ROLE' pattern.
    if ($block->cache & BLOCK_CACHE_PER_ROLE) {
552
      $cid_parts[] = 'r.' . implode(',', array_keys($user->roles));
553
554
555
556
557
558
559
560
561
562
563
    }
    elseif ($block->cache & BLOCK_CACHE_PER_USER) {
      $cid_parts[] = "u.$user->uid";
    }

    if ($block->cache & BLOCK_CACHE_PER_PAGE) {
      $cid_parts[] = $base_root . request_uri();
    }

    return implode(':', $cid_parts);
  }
564
}