You are here

fe_block.module in Features Extra 7

Provide features components for exporting core blocks and settings.

File

fe_block/fe_block.module
View source
<?php

/**
 * @file
 * Provide features components for exporting core blocks and settings.
 */

/**
 * Version number for the current fe_block export definition.
 */
define('FE_BLOCK_VERSION', '2.0');

/**
 * Implements hook_features_api().
 */
function fe_block_features_api() {
  $info = array();
  $key = 'fe_block_settings';
  $info[$key] = array(
    'name' => t('Block settings'),
    'feature_source' => TRUE,
    'default_hook' => 'default_' . $key,
    'default_file' => FEATURES_DEFAULTS_INCLUDED,
  );
  $key = 'fe_block_boxes';
  $info[$key] = array(
    'name' => t('Block contents (boxes)'),
    'feature_source' => TRUE,
    'default_hook' => 'default_' . $key,
    'default_file' => FEATURES_DEFAULTS_INCLUDED,
  );
  return $info;
}

/**
 * Implements hook_features_export_options().
 */
function fe_block_settings_features_export_options() {
  $options = array();
  $blocks = _fe_block_get_blocks();
  usort($blocks, '_fe_block_compare');
  foreach ($blocks as $block) {

    // @see features.block.inc
    if (strpos($block['module'], '-') !== FALSE) {
      continue;
    }
    $block_id = _fe_block_build_id($block);
    if (empty($block_id)) {
      continue;
    }
    $options[$block_id] = '[' . $block_id . '] ' . $block['info'];
  }
  return $options;
}

/**
 * Implements hook_features_export().
 */
function fe_block_settings_features_export($data, &$export, $module_name = '') {
  $pipe = array();
  $export['dependencies']['fe_block'] = 'fe_block';
  $component = 'fe_block_settings';

  // Add the components.
  foreach ($data as $object_name) {
    $export['features'][$component][$object_name] = $object_name;

    // Boxes.
    if (strpos($object_name, 'block-') === 0) {
      $machine_name = substr($object_name, strlen('block-'));
      $pipe['fe_block_boxes'][$machine_name] = $machine_name;
    }
    else {
      $pipe['block'][$object_name] = $object_name;
    }
  }
  return $pipe;
}

/**
 * Implements hook_features_export_render().
 */
function fe_block_settings_features_export_render($module_name = '', $data) {
  $code = array();
  $code[] = '  $export = array();';
  $code[] = '';

  // The way the blocks are exported has changed throughout the history of the
  // module. We provide an export format version string to provide backwards
  // compatibility. Note that it is ok to use the array key "version" here.
  // Block ids always have a '-' in their string.
  $code[] = '  $export[\'version\'] = \'' . FE_BLOCK_VERSION . '\';';
  $code[] = '';

  // Get a list of all active themes to cycle through.
  $themes = _fe_block_get_active_themes();

  // Retrieve block settings for all blocks in all active themes.
  $blocks = array();
  foreach ($themes as $theme) {
    $blocks[$theme] = _fe_block_info_by_theme($theme);
  }

  // We use the first theme's block settings as master settings. Some settings
  // are specific to each theme, but these are processed later in the loop.
  $default_theme = reset($themes);

  // We try to build an export for each defined data element.
  foreach ($data as $name) {

    // Check if the block still exists in the block definitions.
    if (!empty($blocks[$default_theme][$name])) {
      $block = $blocks[$default_theme][$name];

      // We start to build the export object for this block.
      // First we retrieve data that is valid for any theme.
      $export_block = _fe_block_get_global_settings($block);

      // Ensure core custom block export keys are transformed.
      $export_block = _fe_block_prepare_custom_blocks_for_export($export_block);

      // Add node type settings.
      $export_block['node_types'] = _fe_block_get_block_node_types($block);

      // Add role visibility settings.
      $export_block['roles'] = _fe_block_get_block_roles($block);

      // Add block_class support.
      if (module_exists('block_class')) {
        $export_block['css_class'] = _fe_block_get_block_css_class($block);
      }

      // Add i18n_block support.
      if (module_exists('i18n_block')) {
        $export_block['i18n_block_language'] = _fe_block_get_block_i18n_block_language($block);
      }

      // Add theme specific settings for every active theme.
      $export_block['themes'] = array();
      foreach ($themes as $theme) {
        $export_block['themes'][$theme] = _fe_block_get_theme_specific_settings($blocks[$theme][$name]);
      }

      // Sort export array keys.
      ksort($export_block);

      // Export to code.
      $code[] = '  $export[\'' . $name . '\'] = ' . features_var_export($export_block, '  ') . ';';

      // Add an empty line.
      $code[] = '';
    }
  }
  $code[] = '  return $export;';
  $code = implode("\n", $code);
  return array(
    'default_fe_block_settings' => $code,
  );
}

/**
 * Returns the block definitions for a specific theme.
 *
 * @param string $theme
 *   Machine name of the theme.
 *
 * @return array
 *   Array of block definitions.
 */
function _fe_block_info_by_theme($theme) {
  $blocks = array();
  foreach (_fe_block_get_blocks($theme) as $block) {

    // Blocks are only valid for export if we got a machine name for them.
    if ($id = _fe_block_build_id($block)) {
      $blocks[$id] = $block;
    }
  }

  // Sort blocks by keys to get a consistent order.
  ksort($blocks);
  return $blocks;
}

/**
 * Retrieve the global (non-theme-specific) part of a block definition.
 *
 * @param array $block
 *   A block definition.
 *
 * @return array
 *   The block definition filtered on non-theme-specific settings.
 */
function _fe_block_get_global_settings($block) {
  $theme_specific_defaults = _fe_block_theme_specific_defaults();

  // Filter on any keys other than the theme specific ones.
  $return = array_diff_key($block, $theme_specific_defaults);

  // Remove the serial.
  if (isset($return['bid'])) {
    unset($return['bid']);
  }

  // Remove the info from hook_block_info().
  if (isset($return['info'])) {
    unset($return['info']);
  }
  return $return;
}

/**
 * Helper to prepare a core custom block for export.
 *
 * Replaces the block delta that is used by the core block module with a unique
 * machine name.
 *
 * @param array $block
 *   Block definition - can be only part of the original definition.
 *
 * @return array
 *   Altered block array.
 */
function _fe_block_prepare_custom_blocks_for_export($block) {
  if ($block['module'] == 'block') {
    $block['machine_name'] = fe_block_get_machine_name($block['delta']);
    unset($block['delta']);
  }
  return $block;
}

/**
 * Helper function. Prepares an exported core custom block for import.
 *
 * @param array $block
 *   Block definition from the import code.
 *
 * @return array
 *   Altered array with machine_name replaced by delta.
 */
function _fe_block_prepare_custom_blocks_for_import($block) {
  if ($block['module'] == 'block') {
    $block['delta'] = fe_block_get_bid($block['machine_name'], TRUE);
    unset($block['machine_name']);
  }
  return $block;
}

/**
 * Helper function to get the theme specific settings for a block.
 *
 * @param array $block
 *   A single block definition.
 *
 * @return array
 *   A filtered block definition with only theme-specific settings.
 */
function _fe_block_get_theme_specific_settings($block) {
  $defaults = _fe_block_theme_specific_defaults();
  $settings = array_intersect_key($block, $defaults);

  // Region.
  if ($settings['region'] == BLOCK_REGION_NONE) {
    $settings['status'] = 0;
    $settings['region'] = '';
  }
  ksort($settings);
  return $settings;
}

/**
 * Helper function for filtering theme specific settings.
 *
 * @see _fe_block_get_global_settings()
 * @see _fe_block_get_theme_specific_settings()
 *
 * @return array
 *   An array of default settings, keyed by name.
 */
function _fe_block_theme_specific_defaults() {
  return array(
    'theme' => '',
    'status' => '',
    'weight' => 0,
    'region' => '',
    'visibility' => 0,
    'pages' => 0,
    'title' => 0,
    'roles' => array(),
  );
}

/**
 * Get node type visibility settings for the specified block.
 *
 * @param array $block
 *   Block definition array.
 *
 * @return array
 *   Array of node types associated with the block.
 */
function _fe_block_get_block_node_types($block) {
  $query = db_select('block_node_type', 'bnt')
    ->condition('module', $block['module'])
    ->condition('delta', $block['delta'])
    ->fields('bnt', array(
    'type',
  ))
    ->orderBy('bnt.type', 'ASC');
  return $query
    ->execute()
    ->fetchCol();
}

/**
 * Returns the blocks currently exported by modules.
 *
 * This is derived from _block_rehash().
 *
 * @param string $theme
 *   The theme to retrieve blocks for. If not provided, defaults to the
 *   currently used theme.
 *
 * @return array
 *   Blocks currently exported by modules.
 */
function _fe_block_get_blocks($theme = NULL) {
  global $theme_key;
  $blocks = array();
  drupal_theme_initialize();
  if (!isset($theme)) {

    // If theme is not specifically set, rehash for the current theme.
    $theme = $theme_key;
  }
  $regions = system_region_list($theme);

  // These are the blocks defined by code and modified by the database.
  $current_blocks = array();
  $or = db_or();

  // Gather the blocks defined by modules.
  foreach (module_implements('block_info') as $module) {
    $module_blocks = module_invoke($module, 'block_info');
    foreach ($module_blocks as $delta => $block) {

      // Compile a condition to retrieve this block from the database.
      $condition = db_and()
        ->condition('module', $module)
        ->condition('delta', $delta);
      $or
        ->condition($condition);

      // Add identifiers.
      $block['module'] = $module;
      $block['delta'] = $delta;
      $block['theme'] = $theme;
      $current_blocks[$module][$delta] = $block;
    }
  }

  // Retrieve database settings for all blocks that are defined by modules.
  $code_blocks = $current_blocks;
  $database_blocks = db_select('block', 'b')
    ->fields('b')
    ->condition($or)
    ->condition('theme', $theme)
    ->execute();
  foreach ($database_blocks as $block) {

    // Preserve info which is not in the database.
    $block->info = $current_blocks[$block->module][$block->delta]['info'];

    // The cache mode can only by set from hook_block_info(), so that has
    // precedence over the database's value.
    if (isset($current_blocks[$block->module][$block->delta]['cache'])) {
      $block->cache = $current_blocks[$block->module][$block->delta]['cache'];
    }

    // Blocks stored in the database override the blocks defined in code.
    $current_blocks[$block->module][$block->delta] = get_object_vars($block);
  }
  drupal_alter('block_info', $current_blocks, $theme, $code_blocks);
  foreach ($current_blocks as $module => $module_blocks) {
    foreach ($module_blocks as $delta => $block) {
      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'] = '';
      }

      // Make sure weight is set.
      if (!isset($block['weight'])) {
        $block['weight'] = 0;
      }

      // Disable blocks that are not assigned to a region in the theme.
      if (!empty($block['region']) && $block['region'] != BLOCK_REGION_NONE && !isset($regions[$block['region']]) && !empty($block['status']) && $block['status'] == 1) {

        // Disabled modules are moved into the BLOCK_REGION_NONE later so no
        // need to move the block to another region.
        $block['status'] = 0;
      }

      // Set region to none if not enabled and make sure status is set.
      if (empty($block['status'])) {
        $block['status'] = 0;
        $block['region'] = BLOCK_REGION_NONE;
      }

      // Add to the list of blocks we return.
      $blocks[] = $block;
    }
  }
  return $blocks;
}

/**
 * Returns a list of machine names of active themes.
 *
 * @return array
 *   An array of theme machine names.
 */
function _fe_block_get_active_themes() {
  $theme_names = array();
  foreach (system_list('theme') as $machine_name => $theme) {
    if (!empty($theme->status)) {
      $theme_names[] = $machine_name;
    }
  }
  sort($theme_names);
  return $theme_names;
}

/**
 * Implements hook_features_revert().
 */
function fe_block_settings_features_revert($module_name = NULL) {
  $component = 'fe_block_settings';
  $defaults = features_get_default($component, $module_name);
  if (empty($defaults)) {
    return;
  }

  // We remove the version, as we now want to deal with actual block settings.
  unset($defaults['version']);
  $themes_rehashed = array();
  $active_themes = _fe_block_get_active_themes();

  // The fallback theme for theme specific settings.
  $theme_default = variable_get('theme_default', 'bartik');
  foreach ($defaults as $block) {

    // Core custom blocks are prepared with a delta value.
    $block = _fe_block_prepare_custom_blocks_for_import($block);

    // Remove the additional settings from the block array, to process them
    // later. We explicitely set NULL, if no setting was given in the defaults.
    $block_themes = $block['themes'];
    $block_node_types = isset($block['node_types']) ? $block['node_types'] : NULL;
    $block_roles = isset($block['roles']) ? $block['roles'] : NULL;
    $block_css_class = isset($block['css_class']) ? $block['css_class'] : NULL;
    $block_i18n_block_language = isset($block['i18n_block_language']) ? $block['i18n_block_language'] : NULL;
    unset($block['themes']);
    unset($block['node_types']);
    unset($block['roles']);
    unset($block['css_class']);
    unset($block['i18n_block_language']);

    // Restore theme specific settings for every active theme.
    foreach ($active_themes as $theme) {

      // Rehash if we did not yet.
      if (empty($themes_rehashed[$theme])) {
        _block_rehash($theme);
        $themes_rehashed[$theme] = TRUE;
      }

      // Get the theme specific setting for the active theme.
      if (isset($block_themes[$theme])) {
        $key = $theme;
      }
      elseif (isset($block_themes[$theme_default])) {
        $key = $theme_default;
      }
      else {
        $key = key($block_themes);
      }

      // Write block settings.
      $write = array_merge($block, $block_themes[$key]);
      drupal_write_record('block', $write, array(
        'module',
        'delta',
        'theme',
      ));
    }

    // Ensure global settings.
    _fe_block_settings_update_global_settings($block);

    // Set node type settings
    // (only if there were some defined, to avoid overwriting not yet exported
    // data).
    if (isset($block_node_types)) {
      _fe_block_settings_update_block_node_type_settings($block, $block_node_types);
    }

    // Apply role visibility settings.
    if (isset($block_roles)) {
      _fe_block_settings_update_block_roles($block, $block_roles);
    }

    // Update block CSS classes.
    if (isset($block_css_class) && module_exists('block_class')) {
      _fe_block_settings_update_block_css_class($block, $block_css_class);
    }

    // Set i18n_block languages.
    if (module_exists('i18n_block') && isset($block_i18n_block_language)) {
      _fe_block_settings_update_i18n_block_language($block, $block_i18n_block_language);
    }

    // Apply blockcache_alter settings.
    if (module_exists('blockcache_alter')) {
      _fe_block_settings_update_block_cache_alter($block);
    }
  }

  // Clear block cache.
  cache_clear_all(NULL, 'cache_block');
  return TRUE;
}

/**
 * Helper to update global block settings for a specific block.
 *
 * @param array $block
 *   Block definition.
 */
function _fe_block_settings_update_global_settings($block) {
  $globals = _fe_block_get_global_settings($block);

  // Filter out any keys that do not correspond to fields in the block table.
  $fields = drupal_schema_fields_sql('block');
  $globals = array_intersect_key($globals, array_flip($fields));
  db_update('block')
    ->fields($globals)
    ->condition('module', $block['module'])
    ->condition('delta', $block['delta'])
    ->execute();
}

/**
 * Helper to update node type settings for a given block.
 *
 * @param array $block
 *   Block definition.
 * @param array $node_types
 *   Array of node types.
 */
function _fe_block_settings_update_block_node_type_settings($block, $node_types) {

  // First delete the old node type settings.
  db_delete('block_node_type')
    ->condition('module', $block['module'])
    ->condition('delta', $block['delta'])
    ->execute();
  if (!empty($node_types)) {
    $insert = db_insert('block_node_type')
      ->fields(array(
      'module',
      'delta',
      'type',
    ));
    foreach ($node_types as $type) {
      $insert
        ->values(array(
        'module' => $block['module'],
        'delta' => $block['delta'],
        'type' => $type,
      ));
    }
    $insert
      ->execute();
  }
}

/**
 * Helper to update the block role settings for a given block.
 *
 * @param array $block
 *   Block definition.
 * @param array $block_roles
 *   Associative array of roles.
 *   - key: role name.
 *   - value: (foreign) role id.
 */
function _fe_block_settings_update_block_roles($block, $block_roles) {
  static $roles;

  // First get the current set of roles, so we can match role names to rids.
  if (!isset($roles)) {
    $roles = db_select('role', 'r')
      ->fields('r', array(
      'rid',
      'name',
    ))
      ->execute()
      ->fetchAllKeyed(1, 0);
  }

  // First delete the old block role settings.
  db_delete('block_role')
    ->condition('module', $block['module'])
    ->condition('delta', $block['delta'])
    ->execute();

  // Then write the new settings, if any are present.
  if (!empty($block_roles)) {
    $insert = db_insert('block_role')
      ->fields(array(
      'module',
      'delta',
      'rid',
    ));

    // We use a found flag, to avoid empty inserts if no role names match.
    $found = FALSE;
    foreach ($block_roles as $name => $rid) {

      // We only write for roles, matching the given role name.
      if (isset($roles[$name])) {
        $insert
          ->values(array(
          'module' => $block['module'],
          'delta' => $block['delta'],
          'rid' => $roles[$name],
        ));
        $found = TRUE;
      }
    }
    if ($found) {
      $insert
        ->execute();
    }
  }
}

/**
 * Helper to update the block class settings for a given block.
 *
 * @param array $block
 *   Block definition of the block to update.
 * @param string $block_css_class
 *   List of CSS classes to apply to the block.
 */
function _fe_block_settings_update_block_css_class($block, $block_css_class) {

  // This functionality is provided by the Block Class module.
  if (module_exists('block_class')) {

    // Block Class 1.x maintained its own table.
    if (db_table_exists('block_class')) {

      // First delete the old block_class settings, if any.
      db_delete('block_class')
        ->condition('module', $block['module'])
        ->condition('delta', $block['delta'])
        ->execute();

      // Then write the new settings, if any are present.
      if (!empty($block_css_class)) {
        db_insert('block_class')
          ->fields(array(
          'module',
          'delta',
          'css_class',
        ))
          ->values(array(
          'module' => $block['module'],
          'delta' => $block['delta'],
          'css_class' => $block_css_class,
        ))
          ->execute();
      }
    }
    elseif (db_field_exists('block', 'css_class')) {
      db_update('block')
        ->fields(array(
        'css_class' => $block_css_class,
      ))
        ->condition('module', $block['module'])
        ->condition('delta', $block['delta'])
        ->execute();
    }
  }
}

/**
 * Helper to update the i18n block language settings for a specific block.
 *
 * @param array $block
 *   Block definition.
 * @param array $block_languages
 *   Array of associated languages.
 */
function _fe_block_settings_update_i18n_block_language($block, $block_languages) {

  // First remove the old settings.
  db_delete('i18n_block_language')
    ->condition('module', $block['module'])
    ->condition('delta', $block['delta'])
    ->execute();

  // Then write the new settings.
  if (!empty($block_languages)) {
    $insert = db_insert('i18n_block_language')
      ->fields(array(
      'module',
      'delta',
      'language',
    ));
    foreach ($block_languages as $langcode) {
      $insert
        ->values(array(
        'module' => $block['module'],
        'delta' => $block['delta'],
        'language' => $langcode,
      ));
    }
    $insert
      ->execute();
  }
}

/**
 * Helper to update the blockcache_alter settings for a specific block.
 *
 * @param array $block
 *   Block definition.
 */
function _fe_block_settings_update_block_cache_alter($block) {
  $bids = db_select('block', 'b')
    ->fields('b', array(
    'bid',
  ))
    ->condition('module', $block['module'])
    ->condition('delta', $block['delta'])
    ->execute()
    ->fetchCol();
  $blockcache_alter = db_select('blockcache_alter', 'b')
    ->fields('b', array(
    'bid',
  ))
    ->condition('module', $block['module'])
    ->condition('delta', $block['delta'])
    ->execute()
    ->fetchCol();
  foreach ($bids as $bid) {
    $block['bid'] = $bid;
    if (in_array($bid, $blockcache_alter)) {
      drupal_write_record('blockcache_alter', $block, array(
        'bid',
      ));
    }
    else {
      drupal_write_record('blockcache_alter', $block);
    }
  }
}

/**
 * Implements hook_features_disable_feature().
 */
function fe_block_settings_features_disable_feature($module) {
}

/**
 * Implements hook_features_enable_feature().
 */
function fe_block_settings_features_enable_feature($module) {
  fe_block_settings_features_revert($module);
}

/**
 * Implements hook_features_rebuild().
 */
function fe_block_settings_features_rebuild($module) {
  fe_block_settings_features_revert($module);
}

/**
 * Implements hook_features_export_options().
 */
function fe_block_boxes_features_export_options() {
  $table = 'fe_block_boxes';
  $options = array();

  // Defaults.
  $schema = ctools_export_get_schema($table);
  $export = $schema['export'];
  $defaults = _ctools_export_get_defaults($table, $export);
  foreach ($defaults as $obj) {
    $options[$obj->machine_name] = t('@name [@machine_name]', array(
      '@name' => $obj->info,
      '@machine_name' => $obj->machine_name,
    ));
  }

  // Normals.
  $query = "SELECT * FROM {{$table}} {$table} INNER JOIN {block_custom} b ON b.bid = {$table}.bid ORDER BY b.bid ASC";
  $result = db_query($query);
  foreach ($result as $obj) {
    $options[$obj->machine_name] = t('@name [@machine_name]', array(
      '@name' => $obj->info,
      '@machine_name' => $obj->machine_name,
    ));
  }
  ksort($options);
  return $options;
}

/**
 * Implements hook_features_export().
 */
function fe_block_boxes_features_export($data, &$export, $module_name = '') {
  $pipe = array();
  $export['dependencies']['fe_block'] = 'fe_block';
  $table = 'fe_block_boxes';

  // Add the components.
  foreach ($data as $object_name) {
    $export['features'][$table][$object_name] = $object_name;
  }
  return $pipe;
}

/**
 * Implements hook_features_export_render().
 */
function fe_block_boxes_features_export_render($module_name = '', $data) {
  ctools_include('export');
  $component = 'fe_block_boxes';
  $schema = ctools_export_get_schema($component);
  $objects = ctools_export_load_object($component);
  $code = array();
  $code[] = '  $export = array();';
  $code[] = '';
  foreach ($data as $machine_name) {

    // The object to be exported.
    if (isset($objects[$machine_name]) && ($object = $objects[$machine_name])) {
      $additions = array();

      // Load box.
      if (!empty($object->bid) && ($box = block_custom_block_get($object->bid))) {
        $additions = (array) $box;
        unset($additions['bid'], $additions['body']);

        // Code.
        $identifier = $schema['export']['identifier'];
        $code[] = ctools_export_object($component, $object, '  ', $identifier, $additions) . '  $' . $identifier . '->body = ' . features_var_export($box['body']) . ';';
        $code[] = '';
        $code[] = '  $export[\'' . $machine_name . '\'] = $' . $identifier . ';';
        $code[] = '';
      }
    }
  }
  $code[] = '  return $export;';
  $code = implode("\n", $code);
  return array(
    $schema['export']['default hook'] => $code,
  );
}

/**
 * Implements hook_features_revert().
 */
function fe_block_boxes_features_revert($module_name = NULL) {
  $defaults = features_get_default('fe_block_boxes', $module_name);
  if (empty($defaults)) {
    return;
  }

  // Revert.
  foreach ($defaults as $object) {
    if (empty($object->machine_name)) {
      continue;
    }
    $bid = fe_block_get_bid($object->machine_name);
    if (empty($bid) || !block_custom_block_get($bid)) {
      $result = _fe_block_save_box((array) $object);
      if (!empty($result['bid'])) {
        $or = db_or()
          ->condition('bid', $result['bid'])
          ->condition('machine_name', $object->machine_name);
        db_delete('fe_block_boxes')
          ->condition($or)
          ->execute();
        db_insert('fe_block_boxes')
          ->fields(array(
          'bid' => $result['bid'],
          'machine_name' => $object->machine_name,
        ))
          ->execute();
      }
    }
    else {
      $object->bid = $bid;
      $result = _fe_block_save_box((array) $object);
    }
  }

  // Clear block cache.
  cache_clear_all(NULL, 'cache_block');
  return TRUE;
}

/**
 * Implements hook_features_disable_feature().
 */
function fe_block_boxes_features_disable_feature($module) {
}

/**
 * Implements hook_features_enable_feature().
 */
function fe_block_boxes_features_enable_feature($module) {
  fe_block_boxes_features_revert($module);
}

/**
 * Implements hook_features_rebuild().
 */
function fe_block_boxes_features_rebuild($module) {
  fe_block_boxes_features_revert($module);
}

/**
 * Implements hook_form_alter().
 */
function fe_block_form_alter(&$form, $form_state, $form_id) {
  $default_values = array();
  if ($form_id == 'block_add_block_form' && $form['module']['#value'] == 'block' && user_access('administer features')) {
    $default_values['machine_name'] = '';
    $default_values['bid'] = 0;
  }
  elseif ($form_id == 'block_admin_configure' && $form['module']['#value'] == 'block' && user_access('administer features')) {
    $bid = $form['delta']['#value'];
    $machine_name = fe_block_get_machine_name($bid);
    $default_values['machine_name'] = empty($machine_name) ? '' : $machine_name;
    $default_values['bid'] = $bid;
  }
  elseif ($form_id == 'block_box_delete') {
    $form['#submit'][] = 'fe_block_machine_name_delete';
  }

  // Add & edit.
  if (!empty($default_values)) {
    $form['settings']['machine_name'] = array(
      '#type' => 'textfield',
      '#title' => t('Machine name'),
      '#default_value' => $default_values['machine_name'],
      '#maxlength' => 32,
      '#description' => t('Give the block a machine name to make it exportable with "!features" module.', array(
        '!features' => l('Features', 'http://drupal.org/project/features'),
      )),
      '#weight' => -50,
    );
    $form['bid'] = array(
      '#type' => 'value',
      '#value' => $default_values['bid'],
    );

    // Validate & submit.
    $form['#validate'][] = 'fe_block_machine_name_validate';
    $form['#submit'][] = 'fe_block_machine_name_submit';
  }
}

/**
 * Validate machine name.
 */
function fe_block_machine_name_validate($form, &$form_state) {
  if (empty($form_state['values']['machine_name'])) {
    return;
  }
  $table = 'fe_block_boxes';
  $query = db_select($table)
    ->condition('bid', $form_state['values']['bid'], '<>')
    ->condition('machine_name', $form_state['values']['machine_name']);
  $count = $query
    ->countQuery()
    ->execute()
    ->fetchField();
  if (!preg_match('!^[a-z0-9_]+$!', $form_state['values']['machine_name'])) {
    form_set_error('machine_name', t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
  }
  elseif ($count > 0) {
    form_set_error('machine_name', t('The machine-readable name has been taken. Please pick another one.'));
  }
}

/**
 * Save machine name.
 */
function fe_block_machine_name_submit($form, &$form_state) {

  // If a block id is not given, retrieve it from the database.
  if (empty($form_state['values']['bid'])) {
    $form_state['values']['bid'] = db_select('block_custom')
      ->fields('block_custom', array(
      'bid',
    ))
      ->condition('info', $form_state['values']['info'])
      ->execute()
      ->fetch()->bid;
  }
  if (empty($form_state['values']['bid'])) {
    return;
  }
  $table = 'fe_block_boxes';
  db_delete($table)
    ->condition('bid', $form_state['values']['bid'])
    ->execute();
  if (!empty($form_state['values']['machine_name'])) {
    drupal_write_record($table, $form_state['values']);
  }
}

/**
 * Delete machine name.
 */
function fe_block_machine_name_delete($form, &$form_state) {
  $table = 'fe_block_boxes';
  db_delete($table)
    ->condition('bid', $form_state['values']['bid']);
}

/**
 * Callback for usort(). Sorts blocks on "module" and "delta".
 */
function _fe_block_compare($a, $b) {
  $module_cmp = strcmp($a['module'], $b['module']);
  if (!empty($module_cmp)) {
    return $module_cmp;
  }
  return strcmp($a['delta'], $b['delta']);
}

/**
 * Provided for backwards compatibility. Use fe_block_get_machine_name().
 */
function _fe_block_get_machine_name($bid) {
  debug(t('The function @function is deprecated.', array(
    '@function' => __FUNCTION__ . '()',
  )));
  return fe_block_get_machine_name($bid);
}

/**
 * Returns the machine name that corresponds to a given block id.
 *
 * @param int $bid
 *   The block id for which to retrieve the machine name.
 *
 * @return string | FALSE
 *   The machine name, or FALSE if it could not be found.
 */
function fe_block_get_machine_name($bid) {
  $machine_names =& drupal_static(__FUNCTION__);
  if (!isset($machine_names[$bid])) {
    $result = db_select('fe_block_boxes')
      ->fields('fe_block_boxes', array(
      'machine_name',
    ))
      ->condition('bid', $bid)
      ->execute()
      ->fetch();
    if (empty($result)) {
      return FALSE;
    }
    $machine_names[$bid] = $result->machine_name;
  }
  return $machine_names[$bid];
}

/**
 * Provided for backwards compatibility. Use fe_block_get_bid() instead.
 */
function _fe_block_get_bid($machine_name, $reset = FALSE) {
  debug(t('The function @function is deprecated.', array(
    '@function' => __FUNCTION__ . '()',
  )));
  return fe_block_get_bid($machine_name, $reset);
}

/**
 * Returns the block id that corresponds to a given machine name.
 *
 * @param string $machine_name
 *   The machine name of a block for which to retrieve the block id.
 *
 * @return int | FALSE
 *   The block id, or FALSE if the machine name was not found.
 */
function fe_block_get_bid($machine_name, $reset = FALSE) {
  $bids =& drupal_static(__FUNCTION__);
  if (!isset($bids[$machine_name]) || $reset) {
    $result = db_select('fe_block_boxes')
      ->fields('fe_block_boxes', array(
      'bid',
    ))
      ->condition('machine_name', $machine_name)
      ->execute()
      ->fetch();
    if (empty($result)) {
      return FALSE;
    }
    $bids[$machine_name] = (int) $result->bid;
  }
  return $bids[$machine_name];
}

/**
 * Generate block ID.
 */
function _fe_block_build_id($block) {
  if (empty($block['module']) || empty($block['delta']) && !is_numeric($block['delta'])) {
    return NULL;
  }
  if ($block['module'] == 'block') {
    $machine_name = fe_block_get_machine_name($block['delta']);
    if (empty($machine_name)) {
      return NULL;
    }
    return $block['module'] . '-' . $machine_name;
  }
  else {
    return $block['module'] . '-' . $block['delta'];
  }
}

/**
 * Save a box.
 *
 * @param array $settings
 *   A box settings array.
 *
 * @return array
 *   Updated settings array.
 */
function _fe_block_save_box($settings = array()) {
  if (empty($settings['info'])) {
    return FALSE;
  }

  // 'info' must be unique.
  if (empty($settings['bid'])) {
    $conflict = db_query("SELECT COUNT(*) as count FROM {block_custom} WHERE info = :info", array(
      'info' => $settings['info'],
    ));
  }
  else {
    $conflict = db_query("SELECT COUNT(*) as count FROM {block_custom} WHERE info = :info AND bid <> :bid", array(
      'info' => $settings['info'],
      ':bid' => $settings['bid'],
    ));
  }
  if (!empty($conflict
    ->fetch()->count)) {
    return FALSE;
  }

  // Provide some default settings.
  $default_settings = array(
    'info' => '',
    'body' => '',
    'format' => 'FILTER_FORMAT_DEFAULT',
  );
  $settings = array_merge($default_settings, $settings);

  // Save the block settings.
  if (empty($settings['bid'])) {
    drupal_write_record('block_custom', $settings);
  }
  else {
    drupal_write_record('block_custom', $settings, 'bid');
  }
  return $settings;
}

/**
 * Implements hook_module_implements_alter().
 */
function fe_block_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'default_fe_block_settings_alter') {

    // Ensure fe_block is the first imlementation to be called, so we can
    // convert to the newest format.
    $group = $implementations['fe_block'];
    unset($implementations['fe_block']);
    $rest = array_reverse($implementations, TRUE);
    $rest['fe_block'] = $group;
    $implementations = array_reverse($rest, TRUE);
  }
}

/**
 * Implements hook_default_fe_block_settings_alter().
 */
function fe_block_default_fe_block_settings_alter(&$defaults) {

  // Convert the settings in the newest format.
  $defaults = _fe_block_settings_convert($defaults);
}

/**
 * Helper function to get the block roles visibility settings.
 *
 * @param array $block
 *   the block definition array
 *
 * @return array
 *   associated role settings for the block
 *   - key: role name
 *   - value: role id
 */
function _fe_block_get_block_roles($block) {
  $query = db_select('block_role', 'br')
    ->condition('br.module', $block['module'])
    ->condition('br.delta', $block['delta']);
  $query
    ->innerJoin('role', 'r', 'r.rid = br.rid');
  $query
    ->fields('r', array(
    'name',
    'rid',
  ))
    ->orderBy('r.name', 'ASC');
  $roles = $query
    ->execute()
    ->fetchAllKeyed(0, 1);
  return $roles;
}

/**
 * Helper function to get block class settings.
 *
 * @param array $block
 *   The block definition.
 *
 * @return string
 *   Class name(s) for the block.
 */
function _fe_block_get_block_css_class($block) {

  // This functionality depends on the Block Class module.
  if (module_exists('block_class')) {

    // Block Class 2.x extends the core Block table, so we have the data.
    if (!empty($block['css_class'])) {
      return $block['css_class'];
    }

    // Block Class 1.x maintains its own table.
    if (db_table_exists('block_class')) {
      $css_class = db_select('block_class', 'b')
        ->fields('b', array(
        'css_class',
      ))
        ->condition('module', $block['module'])
        ->condition('delta', $block['delta'])
        ->execute()
        ->fetchField();
    }
  }
  return !empty($css_class) ? $css_class : '';
}

/**
 * Get i18n block language from i18n_block.
 *
 * @param array $block
 *   Block definition array.
 *
 * @return array
 *   Array of language codes for the specified block.
 */
function _fe_block_get_block_i18n_block_language($block) {
  $query = db_select('i18n_block_language', 'bl')
    ->condition('module', $block['module'])
    ->condition('delta', $block['delta'])
    ->fields('bl', array(
    'language',
  ))
    ->orderBy('bl.language');
  return $query
    ->execute()
    ->fetchCol();
}

/**
 * Helper function to convert an older export into the new format.
 *
 * @param array $defaults
 *   array of fe_block_settings definition.
 *
 * @return array
 *   array of current fe_block_settings definition
 */
function _fe_block_settings_convert($defaults) {
  $version = isset($defaults['version']) ? $defaults['version'] : 0;

  // Directly return if the version is the current one.
  if ($version == FE_BLOCK_VERSION) {
    return $defaults;
  }
  elseif ($version == '1.0') {

    // We try to get the default theme for the global definitions, else we take
    // the first.
    $theme_default = variable_get('theme_default', 'bartik');
    if (!isset($defaults['theme'][$theme_default])) {
      $theme_default = key($defaults['theme']);
    }
    $blocks = array();

    // We get the basic blocks from the visibility array.
    foreach ($defaults['visibility'] as $block_id => $base) {
      $node_types = array();
      if (isset($base['node_type'])) {

        // Node types were specified in node_type => TRUE/FALSE. Now we simply
        // list the selected node types.
        $node_types = array_keys(array_filter($base));
        unset($base['node_type']);
      }
      $block = $base;
      $block['node_types'] = $node_types;

      // Global settings.
      $globals = _fe_block_get_global_settings($defaults['theme'][$theme_default][$block_id]);
      $block = array_merge($globals, $block);

      // Build theme specific array.
      $block['themes'] = array();
      foreach ($defaults['theme'] as $theme => $items) {
        $block['themes'][$theme] = _fe_block_get_theme_specific_settings($items[$block_id]);
      }
      $blocks[$block_id] = $block;
    }

    // Set current version so we can compare it with current version defaults.
    $blocks['version'] = FE_BLOCK_VERSION;
    return $blocks;
  }
  elseif ($version == 0) {

    // We try to get the default theme for the global definitions, else we take
    // the first.
    $theme_default = variable_get('theme_default', 'bartik');
    if (!isset($defaults[$theme_default])) {
      $theme_default = key($defaults);
    }
    $blocks = array();
    foreach ($defaults as $theme => $items) {
      foreach ($items as $block_id => $item) {

        // Avoid php notices.
        if (!isset($blocks[$block_id])) {
          $blocks[$block_id] = array(
            'themes' => array(),
          );
        }

        // Set theme specific settings.
        $blocks[$block_id]['themes'][$theme] = _fe_block_get_theme_specific_settings($item);

        // We add the global settings for the default theme.
        if ($theme == $theme_default) {
          $globals = _fe_block_get_global_settings($item);
          $blocks[$block_id] = array_merge($blocks[$block_id], $globals);
        }
      }
    }

    // Set current version so we can compare it with current version defaults.
    $blocks['version'] = FE_BLOCK_VERSION;
    return $blocks;
  }
}

Functions

Namesort descending Description
fe_block_boxes_features_disable_feature Implements hook_features_disable_feature().
fe_block_boxes_features_enable_feature Implements hook_features_enable_feature().
fe_block_boxes_features_export Implements hook_features_export().
fe_block_boxes_features_export_options Implements hook_features_export_options().
fe_block_boxes_features_export_render Implements hook_features_export_render().
fe_block_boxes_features_rebuild Implements hook_features_rebuild().
fe_block_boxes_features_revert Implements hook_features_revert().
fe_block_default_fe_block_settings_alter Implements hook_default_fe_block_settings_alter().
fe_block_features_api Implements hook_features_api().
fe_block_form_alter Implements hook_form_alter().
fe_block_get_bid Returns the block id that corresponds to a given machine name.
fe_block_get_machine_name Returns the machine name that corresponds to a given block id.
fe_block_machine_name_delete Delete machine name.
fe_block_machine_name_submit Save machine name.
fe_block_machine_name_validate Validate machine name.
fe_block_module_implements_alter Implements hook_module_implements_alter().
fe_block_settings_features_disable_feature Implements hook_features_disable_feature().
fe_block_settings_features_enable_feature Implements hook_features_enable_feature().
fe_block_settings_features_export Implements hook_features_export().
fe_block_settings_features_export_options Implements hook_features_export_options().
fe_block_settings_features_export_render Implements hook_features_export_render().
fe_block_settings_features_rebuild Implements hook_features_rebuild().
fe_block_settings_features_revert Implements hook_features_revert().
_fe_block_build_id Generate block ID.
_fe_block_compare Callback for usort(). Sorts blocks on "module" and "delta".
_fe_block_get_active_themes Returns a list of machine names of active themes.
_fe_block_get_bid Provided for backwards compatibility. Use fe_block_get_bid() instead.
_fe_block_get_blocks Returns the blocks currently exported by modules.
_fe_block_get_block_css_class Helper function to get block class settings.
_fe_block_get_block_i18n_block_language Get i18n block language from i18n_block.
_fe_block_get_block_node_types Get node type visibility settings for the specified block.
_fe_block_get_block_roles Helper function to get the block roles visibility settings.
_fe_block_get_global_settings Retrieve the global (non-theme-specific) part of a block definition.
_fe_block_get_machine_name Provided for backwards compatibility. Use fe_block_get_machine_name().
_fe_block_get_theme_specific_settings Helper function to get the theme specific settings for a block.
_fe_block_info_by_theme Returns the block definitions for a specific theme.
_fe_block_prepare_custom_blocks_for_export Helper to prepare a core custom block for export.
_fe_block_prepare_custom_blocks_for_import Helper function. Prepares an exported core custom block for import.
_fe_block_save_box Save a box.
_fe_block_settings_convert Helper function to convert an older export into the new format.
_fe_block_settings_update_block_cache_alter Helper to update the blockcache_alter settings for a specific block.
_fe_block_settings_update_block_css_class Helper to update the block class settings for a given block.
_fe_block_settings_update_block_node_type_settings Helper to update node type settings for a given block.
_fe_block_settings_update_block_roles Helper to update the block role settings for a given block.
_fe_block_settings_update_global_settings Helper to update global block settings for a specific block.
_fe_block_settings_update_i18n_block_language Helper to update the i18n block language settings for a specific block.
_fe_block_theme_specific_defaults Helper function for filtering theme specific settings.

Constants

Namesort descending Description
FE_BLOCK_VERSION Version number for the current fe_block export definition.