' . 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 blocks administration page 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/structure/block'))) . '

'; $output .= '

' . t('Although blocks are usually generated automatically by modules (like the User login 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 text format.', array('@text-format' => url('admin/settings/filter'))) . '

'; $output .= '

' . t('When working with blocks, remember that:') . '

'; $output .= ''; $output .= '

' . t('For more information, see the online handbook entry for Block module.', array('@block' => 'http://drupal.org/handbook/modules/block/')) . '

'; return $output; case 'admin/structure/block': $output = '

' . 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. 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 Save blocks button at the bottom of the page.') . '

'; $output .= '

' . t('Click the configure link next to each block to configure its specific title and visibility settings. Use the add block page to create a custom block.', array('@add-block' => url('admin/structure/block/add'))) . '

'; return $output; case 'admin/structure/block/add': return '

' . 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 blocks administration page to be visible.', array('@blocks' => url('admin/structure/block'))) . '

'; } } /** * Implement hook_theme(). */ function block_theme() { return array( 'block' => array( 'arguments' => array('elements' => NULL), 'template' => 'block', ), 'block_admin_display_form' => array( 'template' => 'block-admin-display-form', 'file' => 'block.admin.inc', 'arguments' => array('form' => NULL), ), ); } /** * Implement hook_permission(). */ function block_permission() { return array( 'administer blocks' => array( 'title' => t('Administer blocks'), 'description' => t('Select which blocks are displayed, and arrange them on the page.'), ), ); } /** * Implement hook_menu(). */ function block_menu() { $items['admin/structure/block'] = array( 'title' => 'Blocks', 'description' => 'Configure what block content appears in your site\'s sidebars and other regions.', 'page callback' => 'block_admin_display', 'access arguments' => array('administer blocks'), 'file' => 'block.admin.inc', ); $items['admin/structure/block/list'] = array( 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); $items['admin/structure/block/list/js'] = array( 'title' => 'JavaScript List Form', 'page callback' => 'block_admin_display_js', 'access arguments' => array('administer blocks'), 'type' => MENU_CALLBACK, 'file' => 'block.admin.inc', ); $items['admin/structure/block/configure'] = array( 'title' => 'Configure block', 'page callback' => 'drupal_get_form', 'page arguments' => array('block_admin_configure'), 'access arguments' => array('administer blocks'), 'type' => MENU_CALLBACK, 'file' => 'block.admin.inc', ); $items['admin/structure/block/delete'] = array( 'title' => 'Delete block', 'page callback' => 'drupal_get_form', 'page arguments' => array('block_box_delete'), 'access arguments' => array('administer blocks'), 'type' => MENU_CALLBACK, 'file' => 'block.admin.inc', ); $items['admin/structure/block/add'] = array( 'title' => 'Add block', 'page callback' => 'drupal_get_form', 'page arguments' => array('block_add_block_form'), 'access arguments' => array('administer blocks'), 'type' => MENU_LOCAL_ACTION, 'file' => 'block.admin.inc', ); $default = variable_get('theme_default', 'garland'); foreach (list_themes() as $key => $theme) { $items['admin/structure/block/list/' . $key] = array( '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), 'file' => 'block.admin.inc', ); } return $items; } /** * Menu item access callback - only admin or enabled themes can be accessed. */ function _block_themes_access($theme) { $admin_theme = variable_get('admin_theme'); return user_access('administer blocks') && ($theme->status || ($admin_theme && ($theme->name == $admin_theme))); } /** * Implement hook_block_list(). */ function block_block_list() { $blocks = array(); $result = db_query('SELECT bid, info FROM {box} ORDER BY info'); foreach ($result as $block) { $blocks[$block->bid]['info'] = $block->info; // Not worth caching. $blocks[$block->bid]['cache'] = BLOCK_NO_CACHE; } return $blocks; } /** * Implement hook_block_configure(). */ function block_block_configure($delta = 0) { $box = array('format' => FILTER_FORMAT_DEFAULT); if ($delta) { $box = block_box_get($delta); } return block_box_form($box); } /** * Implement hook_block_save(). */ function block_block_save($delta = 0, $edit = array()) { block_box_save($edit, $delta); } /** * Implement hook_block_view(). * * Generates the administrator-defined blocks for display. */ function block_block_view($delta = 0, $edit = array()) { $block = db_query('SELECT body, format FROM {box} WHERE bid = :bid', array(':bid' => $delta))->fetchObject(); $data['content'] = check_markup($block->body, $block->format); return $data; } /** * Implement hook_page_alter(). * * Render blocks into their regions. */ function block_page_alter($page) { global $theme; // The theme system might not yet be initialized. We need $theme. drupal_theme_initialize(); // Populate all block regions $all_regions = system_region_list($theme); // Load all region content assigned via blocks. foreach (array_keys($all_regions) as $region) { // Assign blocks to region. if ($blocks = block_get_blocks_by_region($region)) { $page[$region] = $blocks; } // Append region description if we are rendering the block admin page. $item = menu_get_item(); if ($item['path'] == 'admin/structure/block') { $visible_regions = system_region_list($theme, REGIONS_VISIBLE); if (isset($visible_regions[$region])) { $description = '
' . $all_regions[$region] . '
'; $page[$region]['block_description'] = array( '#markup' => $description, '#weight' => 15, ); } } } } /** * Get a renderable array of a region containing all enabled blocks. * * @param $region * The requested region. */ function block_get_blocks_by_region($region) { $weight = 0; $build = array(); if ($list = block_list($region)) { foreach ($list as $key => $block) { $build[$key] = $block->content; unset($block->content); $build[$key] += array( '#block' => $block, '#weight' => ++$weight, ); $build[$key]['#theme_wrappers'][] ='block'; } $build['#sorted'] = TRUE; } return $build; } /** * Update the 'block' DB table with the blocks currently exported by modules. * * @return * Blocks currently exported by modules. */ function _block_rehash() { global $theme_key; drupal_theme_initialize(); $old_blocks = array(); $result = db_query("SELECT * FROM {block} WHERE theme = :theme", array(':theme' => $theme_key)); foreach ($result as $old_block) { $old_block = is_object($old_block) ? get_object_vars($old_block) : $old_block; $old_blocks[$old_block['module']][$old_block['delta']] = $old_block; } $blocks = array(); // Valid region names for the theme. $regions = system_region_list($theme_key); foreach (module_implements('block_list') as $module) { $module_blocks = module_invoke($module, 'block_list'); if ($module_blocks) { foreach ($module_blocks as $delta => $block) { if (empty($old_blocks[$module][$delta])) { // If it's a new block, add identifiers. $block['module'] = $module; $block['delta'] = $delta; $block['theme'] = $theme_key; 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'] = ''; } // Add defaults and save it into the database. drupal_write_record('block', $block); // Set region to none if not enabled. $block['region'] = $block['status'] ? $block['region'] : BLOCK_REGION_NONE; // Add to the list of blocks we return. $blocks[] = $block; } else { // 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']; // 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; } // Add this block to the list of blocks we return. $blocks[] = $old_blocks[$module][$delta]; // Remove this block from the list of blocks to be deleted. unset($old_blocks[$module][$delta]); } } } } // 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_delete('block') ->condition('module', $module) ->condition('delta', $delta) ->condition('theme', $theme_key) ->execute(); } } return $blocks; } function block_box_get($bid) { return db_query("SELECT * FROM {box} WHERE bid = :bid", array(':bid' => $bid))->fetchAssoc(); } /** * Define the custom block form. */ function block_box_form($edit = array()) { $edit += array( 'info' => '', 'body' => '', ); $form['info'] = array( '#type' => 'textfield', '#title' => t('Block description'), '#default_value' => $edit['info'], '#maxlength' => 64, '#description' => t('A brief description of your block. Used on the blocks administration page.', array('@overview' => url('admin/structure/block'))), '#required' => TRUE, '#weight' => -19, ); $form['body_field']['#weight'] = -17; $form['body_field']['body'] = array( '#type' => 'textarea', '#title' => t('Block body'), '#default_value' => $edit['body'], '#text_format' => isset($edit['format']) ? $edit['format'] : FILTER_FORMAT_DEFAULT, '#rows' => 15, '#description' => t('The content of the block as shown to the user.'), '#required' => TRUE, '#weight' => -17, '#access' => filter_access($edit['format']), ); return $form; } function block_box_save($edit, $delta) { db_update('box') ->fields(array( 'body' => $edit['body'], 'info' => $edit['info'], 'format' => $edit['body_format'], )) ->condition('bid', $delta) ->execute(); return TRUE; } /** * Implement hook_user_form(). */ function block_user_form(&$edit, $account, $category) { if ($category == 'account') { $rids = array_keys($account->roles); $result = db_query("SELECT DISTINCT b.* FROM {block} b LEFT JOIN {block_role} r ON b.module = r.module AND b.delta = r.delta WHERE b.status = 1 AND b.custom <> 0 AND (r.rid IN (:rids) OR r.rid IS NULL) ORDER BY b.weight, b.module", array(':rids' => $rids)); $form['block'] = array( '#type' => 'fieldset', '#title' => t('Personalize blocks'), '#description' => t('Blocks consist of content or information that complements the main content of the page. Enable or disable optional blocks using the checkboxes below.'), '#weight' => 3, '#collapsible' => TRUE, '#tree' => TRUE ); foreach ($result as $block) { $data = module_invoke($block->module, 'block_list'); if ($data[$block->delta]['info']) { $return = TRUE; $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), ); } } if (!empty($return)) { return $form; } } } /** * Implement hook_user_validate(). */ function block_user_validate(&$edit, $account, $category) { if (empty($edit['block'])) { $edit['block'] = array(); } return $edit; } /** * Implement hook_form_FORM_ID_alter(). */ function block_form_system_themes_form_alter(&$form, &$form_state) { // This function needs to fire before the theme changes are recorded in the // database, otherwise it will populate the default list of blocks from the // new theme, which is empty. array_unshift($form['#submit'], 'block_system_themes_form_submit'); } /** * Initialize blocks for enabled themes. */ function block_system_themes_form_submit(&$form, &$form_state) { if ($form_state['values']['op'] == t('Save configuration')) { if (is_array($form_state['values']['status'])) { foreach ($form_state['values']['status'] as $key => $choice) { if ($choice || $form_state['values']['theme_default'] == $key) { block_theme_initialize($key); } } } if ($form_state['values']['admin_theme'] && $form_state['values']['admin_theme'] !== variable_get('admin_theme', 0)) { // If we're changing themes, make sure the theme has its blocks initialized. $has_blocks = (bool) db_query_range('SELECT 1 FROM {block} WHERE theme = :theme', array(':theme' => $form_state['values']['admin_theme']), 0, 1)->fetchField(); if (!$has_blocks) { block_theme_initialize($form_state['values']['admin_theme']); } } } } /** * Assign an initial, default set of blocks for a theme. * * This function is called the first time a new theme is enabled. The new theme * gets a copy of the default theme's blocks, with the difference that if a * particular region isn't available in the new theme, the block is assigned * to the new theme's default region. * * @param $theme * The name of a theme. */ function block_theme_initialize($theme) { // Initialize theme's blocks if none already registered. $has_blocks = (bool) db_query_range('SELECT 1 FROM {block} WHERE theme = :theme', array(':theme' => $theme), 0, 1)->fetchField(); if (!$has_blocks) { $default_theme = variable_get('theme_default', 'garland'); $regions = system_region_list($theme); $result = db_query("SELECT * FROM {block} WHERE theme = :theme", array(':theme' => $default_theme), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $block) { // If the region isn't supported by the theme, assign the block to the theme's default region. if (!array_key_exists($block['region'], $regions)) { $block['region'] = system_default_region($theme); } $block['theme'] = $theme; unset($block['bid']); drupal_write_record('block', $block); } } } /** * 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 module_delta. * 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 * Now that the blocks table has a primary key, we should use that as the * array key instead of module_delta. */ function block_list($region) { $blocks = &drupal_static(__FUNCTION__, array()); if (empty($blocks)) { $blocks = _block_load_blocks(); } // Create an empty array if there were no entries. if (!isset($blocks[$region])) { $blocks[$region] = array(); } $blocks[$region] = _block_render_blocks($blocks[$region]); return $blocks[$region]; } /** * Load blocks information from the database. */ function _block_load_blocks() { global $theme_key; $query = db_select('block', 'b'); $result = $query ->fields('b') ->condition('b.theme', $theme_key) ->condition('b.status', 1) ->orderBy('b.region') ->orderBy('b.weight') ->orderBy('b.module') ->addTag('block_load') ->execute(); $block_list = $result->fetchAllAssoc('bid'); // Allow modules to modify the block list. drupal_alter('block_list', $block_list); $blocks = array(); foreach ($block_list as $block) { $blocks[$block->region]["{$block->module}_{$block->delta}"] = $block; } return $blocks; } /** * Implement hook_block_list_alter(). * * Check the page, user role, content type and user specific visibilty settings. * Remove the block if the visibility conditions are not met. */ function block_block_list_alter(&$blocks) { global $user, $theme_key; // Build an array of roles for each block. $block_roles = array(); $result = db_query('SELECT module, delta, rid FROM {block_role}'); foreach ($result as $record) { $block_roles[$record->module][$record->delta][] = $record->rid; } // Build an array of node types for each block. $block_node_types = array(); $result = db_query('SELECT module, delta, type FROM {block_node_type}'); foreach ($result as $record) { $block_node_types[$record->module][$record->delta][] = $record->type; } foreach ($blocks as $key => $block) { if ($block->theme != $theme_key || $block->status != 1) { // This block was added by a contrib module, leave it in the list. continue; } // If a block has no roles associated, it is displayed for every role. // For blocks with roles associated, if none of the user's roles matches // the settings from this block, remove it from the block list. if (isset($block_roles[$block->module][$block->delta]) && !array_intersect($block_roles[$block->module][$block->delta], array_keys($user->roles))) { // No match. unset($blocks[$key]); continue; } // If a block has no node types associated, it is displayed for every type. // For blocks with node types associated, if the node type does not match // the settings from this block, remove it from the block list. if (isset($block_node_types[$block->module][$block->delta])) { $node = menu_get_object(); if (!empty($node)) { // This is a node or node edit page. if (!in_array($node->type, $block_node_types[$block->module][$block->delta])) { // This block should not be displayed for this node type. unset($blocks[$key]); continue; } } elseif (arg(0) == 'node' && arg(1) == 'add' && in_array(arg(2), array_keys(node_type_get_types()))) { // This is a node creation page if (!in_array(arg(2), $block_node_types[$block->module][$block->delta])) { // This block should not be displayed for this node type. unset($blocks[$key]); continue; } } else { // This is not a node page, remove the block. unset($blocks[$key]); continue; } } // Use the user's block visibility setting, if necessary. if ($block->custom != 0) { if ($user->uid && isset($user->block[$block->module][$block->delta])) { $enabled = $user->block[$block->module][$block->delta]; } else { $enabled = ($block->custom == 1); } } else { $enabled = TRUE; } if (!$enabled) { unset($blocks[$key]); continue; } // Match path if necessary. 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); } // 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); } elseif (module_exists('php')) { $page_match = php_eval($block->pages); } else { $page_match = FALSE; } } else { $page_match = TRUE; } if (!$page_match) { unset($blocks[$key]); } } } /** * Render the content and subject for a set of blocks. * * @param $region_blocks * An array of block objects such as returned for one region by _block_load_blocks(). * * @return * An array of visible blocks with subject and content rendered. */ 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]); // 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' // (or 'HEAD'). if (!count(module_implements('node_grants')) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && ($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); } } if (isset($array) && is_array($array)) { foreach ($array as $k => $v) { $block->$k = $v; } } if (isset($block->content) && $block->content) { // Normalize to the drupal_render() structure. if (is_string($block->content)) { $block->content = array('#markup' => $block->content); } // 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 == '' ? '' : check_plain($block->title); } if (!isset($block->subject)) { $block->subject = ''; } $region_blocks["{$block->module}_{$block->delta}"] = $block; } } } return $region_blocks; } /** * 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 differentiated. * * @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) { $cid_parts[] = 'r.' . implode(',', array_keys($user->roles)); } 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); } } /** * Implement hook_flush_caches(). */ function block_flush_caches() { return array('cache_block'); } /** * Process variables for block.tpl.php * * Prepare the values passed to the theme_block function to be passed * into a pluggable template engine. Uses block properties to generate a * series of template file suggestions. If none are found, the default * block.tpl.php is used. * * Most themes utilize their own copy of block.tpl.php. The default is located * inside "modules/block/block.tpl.php". Look in there for the full list of * variables. * * The $variables array contains the following arguments: * - $block * * @see block.tpl.php */ function template_preprocess_block(&$variables) { $block_counter = &drupal_static(__FUNCTION__, array()); $variables['block'] = $variables['elements']['#block']; // All blocks get an independent counter for each region. if (!isset($block_counter[$variables['block']->region])) { $block_counter[$variables['block']->region] = 1; } // Same with zebra striping. $variables['block_zebra'] = ($block_counter[$variables['block']->region] % 2) ? 'odd' : 'even'; $variables['block_id'] = $block_counter[$variables['block']->region]++; // Create the $content variable that templates expect. $variables['content'] = $variables['elements']['#children']; $variables['classes_array'][] = 'block-' . $variables['block']->module; $variables['template_files'][] = 'block-' . $variables['block']->region; $variables['template_files'][] = 'block-' . $variables['block']->module; $variables['template_files'][] = 'block-' . $variables['block']->module . '-' . $variables['block']->delta; } /** * Implement hook_filter_format_delete(). */ function block_filter_format_delete($format, $default) { db_update('box') ->fields(array('format' => $default->format)) ->condition('format', $format->format) ->execute(); }