You are here

mongodb_block.module in MongoDB 7

Controls the visual building blocks a page is constructed with.

Resembles very closely the core block module but without an UI. The best way to read this code is to compare it with block.module.

File

mongodb_block/mongodb_block.module
View source
<?php

/**
 * @file
 * Controls the visual building blocks a page is constructed with.
 *
 * Resembles very closely the core block module but without an UI. The best way
 * to read this code is to compare it with block.module.
 */

// Denotes that a block is not enabled in any region and should not be shown.
define('MONGODB_BLOCK_REGION_NONE', -1);

/**
 * Create indexes for mongo blocks.
 *
 * @param array $theme_list
 *   Array of themes to create indexes for.
 *
 * @throws \MongoConnectionException
 */
function _mongodb_block_ensure_indexes(array $theme_list) {
  foreach ($theme_list as $theme) {
    $index = [
      '_id.module' => 1,
      '_id.delta' => 1,
    ];
    mongodb_collection('block', $theme)
      ->ensureIndex($index);
    mongodb_collection('block_customized', $theme)
      ->ensureIndex($index);
  }
}

/**
 * Implements hook_menu().
 */
function mongodb_block_menu() {
  return [
    'admin/structure/block-rebuild' => [
      'title' => 'Block rebuild',
      'description' => 'Rebuilds blocks in MongoDB',
      'page callback' => 'mongodb_block_rehash',
      'access arguments' => [
        'administer site configuration',
      ],
    ],
  ];
}

/**
 * Implements hook_flush_caches().
 */
function mongodb_block_flush_caches() {
  return [
    'cache_block',
  ];
}

/**
 * Implements hook_theme().
 *
 * @see \block_theme()
 */
function mongodb_block_theme() {
  return [
    'block' => [
      'render element' => 'elements',
      'template' => 'block',
      'path' => drupal_get_path('module', 'block'),
    ],
  ];
}

/**
 * Implements hook_form_system_modules_alter().
 *
 * Hides block, dashboard module.
 */
function mongodb_block_form_system_modules_alter(&$form) {
  unset($form['modules']['Core']['block']);
  unset($form['modules']['Core']['dashboard']);
}

/**
 * Implements hook_page_build().
 *
 * Renders blocks into their regions.
 *
 * @see \block_page_build()
 */
function mongodb_block_page_build(&$page) {
  global $theme;

  // The theme system might not yet be initialized. We need $theme.
  drupal_theme_initialize();

  // Fetch a list of regions for the current theme.
  $all_regions = system_region_list($theme);
  $collection = mongodb_collection('block', $theme);

  // Fastest way to check if collection is not empty, per #2072135.
  if (!$collection
    ->find()
    ->limit(1)
    ->hasNext()) {
    _mongodb_block_rehash($theme);
  }
  $query = [];
  if ($node = menu_get_object()) {
    $query['node_type']['$in'] = [
      '*',
      $node->type,
    ];
  }
  $parts = array_slice(arg(), 0, MENU_MAX_PARTS);
  $ancestors = menu_get_ancestors($parts);
  if ($_GET['q'] != request_path()) {
    $request_parts = array_slice(explode('/', request_path()), 0, MENU_MAX_PARTS);
    $ancestors = array_merge($ancestors, menu_get_ancestors($request_parts));
  }
  $ancestors[] = '%';
  if (drupal_is_front_page()) {
    $ancestors[] = '<front>';
  }
  $query['pages']['$in'] = $ancestors;
  $query['pages_exclude']['$nin'] = $ancestors;
  $query['region']['$in'] = array_keys($all_regions);
  $blocks_result = $collection
    ->find($query)
    ->sort(array(
    'weight' => 1,
  ));
  $regions_blocks = array();
  foreach ($blocks_result as $block) {
    if (!isset($regions_blocks[$block['region']])) {
      $regions_blocks[$block['region']] = array();
    }
    $regions_blocks[$block['region']][] = $block;
  }
  foreach ($regions_blocks as $region => $region_blocks) {
    $region_blocks = mongodb_block_render_blocks($region_blocks);
    if (isset($page[$region])) {
      $page[$region] = array_merge($page[$region], mongodb_block_get_renderable_array($region_blocks));
    }
    else {
      $page[$region] = mongodb_block_get_renderable_array($region_blocks);
    }
  }
}

/**
 * Render the content and subject for a set of blocks.
 *
 * @param array $blocks_result
 *   An array of block objects such as returned for one region by
 *   _block_load_blocks()
 *
 * @return array
 *   A render array for the blocks.
 */
function mongodb_block_render_blocks(array $blocks_result) {
  $return = [];
  foreach ($blocks_result as $block) {

    // Copy module and delta out of the _id.
    if (is_array($block) && isset($block['_id'])) {
      $block += $block['_id'];
    }
    $block = (object) $block;

    // Render the block content if it has not been created already.
    if (!isset($block->content)) {

      // 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 (($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && ($cid = mongodb_block_get_cache_id($block)) && ($cache = cache_get($cid, 'cache_block'))) {
        $array = $cache->data;
      }
      else {
        $array = module_invoke($block->module, 'block_view', $block->delta);

        // Allow modules to modify the block before it is viewed, via either
        // hook_block_view_MODULE_DELTA_alter() or hook_block_view_alter().
        drupal_alter("block_view_{$block->module}_{$block->delta}", $array, $block);
        drupal_alter('block_view', $array, $block);
        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 = [
          '#markup' => $block->content,
        ];
      }

      // Override default block title if a custom display title is present.
      if (!empty($block->title)) {

        // Check plain here to allow module generated titles to keep any
        // markup.
        $block->subject = $block->title == '<none>' ? '' : check_plain($block->title);
      }
      if (!isset($block->subject)) {
        $block->subject = '';
      }
      $return["{$block->module}_{$block->delta}"] = $block;
    }
  }
  return $return;
}

/**
 * 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 object $block
 *   The block object.
 *
 * @return string
 *   The string used as cache_id for the block.
 */
function mongodb_block_get_cache_id($block) {
  global $user;

  // Not all block definitions define caching.
  if (!isset($block->cache)) {
    $block->cache = DRUPAL_NO_CACHE;
  }

  // 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) && !in_array($block->cache, [
    DRUPAL_NO_CACHE,
    DRUPAL_CACHE_CUSTOM,
  ]) && $user->uid != 1) {

    // Start with common sub-patterns: block identification, theme, language.
    $cid_parts[] = $block->module;
    $cid_parts[] = $block->delta;
    $cid_parts = array_merge($cid_parts, drupal_render_cid_parts($block->cache));
    return implode(':', $cid_parts);
  }
}

/**
 * Gets an array of blocks suitable for drupal_render().
 *
 * @param array $list
 *   A list of blocks such as that returned by block_list().
 *
 * @return array
 *   A renderable array.
 *
 * @see _block_get_renderable_array()
 */
function mongodb_block_get_renderable_array(array $list = []) {
  global $theme;
  $weight = 0;
  $build = [];
  foreach ($list as $key => $block) {
    $build[$key] = $block->content;
    unset($block->content);

    // Add contextual links for this block; skip the main content block, since
    // contextual links are basically output as tabs/local tasks already. Also
    // skip the help block, since we assume that most users do not need or want
    // to perform contextual actions on the help block, and the links needlessly
    // draw attention on it.
    if (module_exists('mongodb_block_ui') && $key != 'system_main' && $key != 'system_help') {
      $build[$key]['#contextual_links']['mongodb_block_ui'] = [
        'admin/structure/mongodb_block/manage',
        [
          $theme,
          $block->module,
          $block->delta,
        ],
      ];
    }
    $build[$key] += [
      '#block' => $block,
      '#weight' => ++$weight,
    ];
    $build[$key]['#theme_wrappers'][] = 'block';
  }
  $build['#sorted'] = TRUE;
  return $build;
}

/**
 * Implements hook_mongodb_block_info_alter().
 */
function mongodb_block_mongodb_block_info_alter(&$blocks) {

  // Enable the main content block if it is not already enabled.
  if (empty($blocks['system_main']['status'])) {
    $blocks['system_main']['region'] = 'content';
    $blocks['system_main']['weight'] = 0;
    $blocks['system_main']['status'] = 1;
  }
}

/**
 * Run _mongodb_block_rehash and go to admin/structure.
 */
function mongodb_block_rehash() {
  _mongodb_block_rehash(variable_get('theme_default', 'garland'));
  drupal_set_message(t('Blocks rebuild done.'));
  drupal_goto('admin/structure');
}

/**
 * Updates the block collection with the blocks currently exported by modules.
 *
 * @param string $theme
 *   The theme to rehash blocks for. If not provided, defaults to the currently
 *   used theme.
 *
 * @return mixed
 *   Blocks currently exported by modules.
 *
 * @throws \MongoConnectionException
 * @throws \MongoCursorException
 * @throws \MongoCursorTimeoutException
 */
function _mongodb_block_rehash($theme) {
  $collection = mongodb_collection('block', $theme);
  $regions = system_region_list($theme);
  $ids = [];
  foreach (module_implements('block_info') as $module) {
    if ($blocks_current = module_invoke($module, 'block_info')) {
      foreach ($blocks_current as $delta => $block) {
        $block = [
          '_id' => [
            'module' => $module,
            'delta' => $delta,
          ],
          'module' => $module,
          'delta' => $delta,
        ] + $block + [
          'weight' => 0,
          'pages' => [
            '%',
          ],
          'pages_exclude' => [],
          'status' => 0,
          'region' => MONGODB_BLOCK_REGION_NONE,
        ];
        if (isset($block['node_type']) && $block['pages'] == [
          '%',
        ]) {
          $block['pages'] = [
            'node/%',
          ];
        }
        else {
          if (!isset($block['node_type'])) {
            $block['node_type'] = [
              '*',
            ];
          }
        }
        $blocks[$module . '_' . $delta] = $block;
      }
    }
  }
  drupal_alter('mongodb_block_info', $blocks, $theme);

  // Only store the block in MongoDB if the status is enabled.
  foreach ($blocks as $key => $block) {
    if ($block['region'] != MONGODB_BLOCK_REGION_NONE && !isset($regions[$block['region']])) {
      $blocks[$key]['status'] = 0;
      $blocks[$key]['region'] = MONGODB_BLOCK_REGION_NONE;
      $block = $blocks[$key];
    }
    if (!empty($block['status'])) {
      $ids[] = $block['_id'];
      mongodb_block_save_block($collection, $block);
    }
    else {
      $blocks[$key]['status'] = 0;
      $blocks[$key]['region'] = MONGODB_BLOCK_REGION_NONE;
    }
  }
  $collection
    ->remove([
    '_id' => [
      '$nin' => $ids,
    ],
  ], mongodb_default_write_options());
  return $blocks;
}

/**
 * Unset block data and save the block collection.
 *
 * @param object $collection
 *   MongoDB collection to use.
 * @param array $block
 *   The block array.
 */
function mongodb_block_save_block($collection, array $block) {
  unset($block['module'], $block['delta'], $block['status']);
  $collection
    ->save($block, mongodb_default_write_options());
}

/**
 * Processes variables for block.tpl.php.
 *
 * Prepares 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 mongodb_block_preprocess_block(&$variables) {
  $block_counter =& drupal_static(__FUNCTION__, []);
  $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'][] = drupal_html_class('block-' . $variables['block']->module);
  $variables['theme_hook_suggestions'][] = 'block__' . $variables['block']->region;
  $variables['theme_hook_suggestions'][] = 'block__' . $variables['block']->module;

  // Hyphens (-) and underscores (_) play a special role in theme suggestions.
  // Theme suggestions should only contain underscores, because within
  // drupal_find_theme_templates(), underscores are converted to hyphens to
  // match template file names, and then converted back to underscores to match
  // pre-processing and other function names. So if your theme suggestion
  // contains a hyphen, it will end up as an underscore after this conversion,
  // and your function names won't be recognized. So, we need to convert
  // hyphens to underscores in block deltas for the theme suggestions.
  $variables['theme_hook_suggestions'][] = 'block__' . $variables['block']->module . '__' . strtr($variables['block']->delta, '-', '_');

  // Create a valid HTML ID and make sure it is unique.
  $variables['block_html_id'] = drupal_html_id('block-' . $variables['block']->module . '-' . $variables['block']->delta);
}

/**
 * Implements hook_themes_enabled().
 */
function mongodb_block_themes_enabled($theme_list) {
  _mongodb_block_ensure_indexes($theme_list);
}

Functions

Namesort descending Description
mongodb_block_flush_caches Implements hook_flush_caches().
mongodb_block_form_system_modules_alter Implements hook_form_system_modules_alter().
mongodb_block_get_cache_id Assemble the cache_id to use for a given block.
mongodb_block_get_renderable_array Gets an array of blocks suitable for drupal_render().
mongodb_block_menu Implements hook_menu().
mongodb_block_mongodb_block_info_alter Implements hook_mongodb_block_info_alter().
mongodb_block_page_build Implements hook_page_build().
mongodb_block_preprocess_block Processes variables for block.tpl.php.
mongodb_block_rehash Run _mongodb_block_rehash and go to admin/structure.
mongodb_block_render_blocks Render the content and subject for a set of blocks.
mongodb_block_save_block Unset block data and save the block collection.
mongodb_block_theme Implements hook_theme().
mongodb_block_themes_enabled Implements hook_themes_enabled().
_mongodb_block_ensure_indexes Create indexes for mongo blocks.
_mongodb_block_rehash Updates the block collection with the blocks currently exported by modules.

Constants