You are here

blockreference.module in Block reference 7.2

Same filename and directory in other branches
  1. 6 blockreference.module
  2. 7 blockreference.module

File

blockreference.module
View source
<?php

define('BLOCKREFERENCE_AC_REGEX', '#\\[([\\w\\-]+):([\\w\\-]+)\\]$#');
include_once __DIR__ . '/blockreference.field.inc';

/**
 * Implements hook_menu().
 */
function blockreference_menu() {
  $items = array();
  $items['blockreference/autocomplete/%/%/%/%'] = array(
    'title' => 'blockreference autocomplete',
    'page callback' => 'blockreference_autocomplete',
    'page arguments' => array(
      2,
      3,
      4,
      5,
    ),
    'access callback' => 'blockreference_autocomplete_access',
    'access arguments' => array(
      2,
      3,
      4,
      5,
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Menu access callback for the autocomplete path.
 */
function blockreference_autocomplete_access($entity_type, $bundle, $field_name, $eid) {
  if ($eid == 0) {
    $entity_info = entity_get_info($entity_type);
    $entity_dummy = entity_create($entity_type, array(
      $entity_info['entity keys']['bundle'] => $bundle,
    ));
    $entity_access = entity_access('create', $entity_type, $entity_dummy);
  }
  else {
    $entity = entity_load($entity_type, array(
      $eid,
    ));
    $entity = reset($entity) ?: FALSE;
    $entity_access = entity_access('update', $entity_type, $entity);
  }
  return user_access('access content') && $entity_access;
}

/**
 * Menu callback for the autocomplete results.
 */
function blockreference_autocomplete($entity_type, $bundle, $field_name, $entity_id, $search_string) {
  $search_string = trim(implode('/', array_slice(func_get_args(), 4)), '/');
  $entity = FALSE;
  if ($entity_id) {
    $entity_load = entity_load($entity_type, array(
      $entity_id,
    ));
    if ($entity_load) {
      $entity = reset($entity_load);
    }
  }
  $instance = field_info_instance($entity_type, $field_name, $bundle);
  $context = array(
    'type' => 'autocomplete',
    'entity' => $entity,
  );
  $blocks = _blockreference_find_blocks($instance, $search_string, $context);
  $options = _blockreference_options($blocks, TRUE);
  drupal_json_output($options);
}

/**
 * Value callback for a blockreference autocomplete element.
 */
function blockreference_autocomplete_value($element, $input = FALSE, $form_state) {
  $ac_string = $input !== FALSE ? $input : (isset($element['#value']) ? $element['#value'] : (string) @$element['#default_value']);
  $module = $delta = '';

  // Find module & delta from an ac string.
  if (is_string($ac_string)) {
    $block_arr = _blockreference_block_from_ac_string($ac_string);
    if ($block_arr) {
      list($module, $delta) = array_values($block_arr);
    }
  }

  // Found it? Does is exist?
  if ($module && $delta) {
    $infos = module_invoke($module, 'block_info');
    if (isset($infos[$delta])) {
      $block = _blockreference_block($module, $delta, $infos[$delta]);
      $value = trim($block->info) . '   [' . $block->module . ':' . $block->delta . ']';
      return $value;
    }
  }
  return '';
}

/**
 * Validation callback for a blockreference autocomplete element.
 */
function blockreference_autocomplete_validate($element, &$form_state, $form) {
  $instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
  $value = $element['#value'];
  if (!$value) {
    form_set_value($element, '', $form_state);
    return;
  }
  $block_arr = _blockreference_block_from_ac_string($value);

  // Validate input to be a block.
  if (!$block_arr) {
    return form_error($element, t('%name: Title mismatch. Please check your selection.', array(
      '%name' => $instance['label'],
    )));
  }

  // Validate block's usage in this context.
  $allowed_blocks = _blockreference_find_blocks($instance, '', array(
    'entity' => $element['#entity'],
    'type' => 'autocomplete',
  ));
  $moddelta = implode(':', $block_arr);
  if (!isset($allowed_blocks[$moddelta])) {
    return form_error($element, t('This block is not allowed in this field/context.'));
  }
  form_set_value($element, $moddelta, $form_state);
}

/**
 * Implements hook_theme().
 */
function blockreference_theme() {
  return array(
    'blockreference_formatter_default' => array(
      'variables' => array(
        'element' => NULL,
      ),
    ),
    'blockreference_formatter_without_title' => array(
      'variables' => array(
        'element' => NULL,
      ),
    ),
    'blockreference_formatter_title' => array(
      'variables' => array(
        'element' => NULL,
      ),
    ),
    'blockreference_formatter_plain' => array(
      'variables' => array(
        'element' => NULL,
      ),
    ),
    'blockreference_formatter_config_link' => array(
      'variables' => array(
        'element' => NULL,
      ),
    ),
  );
}

/**
 * Theme function for 'default' blockreference field formatter.
 */
function theme_blockreference_formatter_default($variables) {
  $element = $variables['element'];
  $block = _blockreference_formatter_get_block($element);
  if ($block) {
    $bid = $block->module . '_' . $block->delta;
    if (!isset($block->title)) {
      $block->title = '';
    }
    if (!isset($block->region)) {
      $block->region = 'none';
    }
    $block_content = _block_render_blocks(array(
      $block,
    ));
    $build = _block_get_renderable_array($block_content);
    $build[$bid]['#blockreference_element'] = $element;
    return drupal_render($build);
  }
  return '';
}

/**
 * Theme function for 'without_title' blockreference field formatter.
 */
function theme_blockreference_formatter_without_title($variables) {
  $element = $variables['element'];
  $block = _blockreference_formatter_get_block($element);
  if ($block) {
    $bid = $block->module . '_' . $block->delta;
    if (!isset($block->title)) {
      $block->title = '';
    }
    if (!isset($block->region)) {
      $block->region = 'none';
    }
    $block_content = _block_render_blocks(array(
      $block,
    ));
    if ($block_content) {
      reset($block_content)->subject = '';
      $build = _block_get_renderable_array($block_content);
      $build[$bid]['#blockreference_element'] = $element;
      return drupal_render($build);
    }
  }
  return '';
}

/**
 * Theme function for 'plain' blockreference field formatter.
 */
function theme_blockreference_formatter_plain($variables) {
  $element = $variables['element'];
  $block = _blockreference_formatter_get_block($element);
  if ($block) {
    $info = module_invoke($block->module, 'block_info');
    return $info[$block->delta]['info'];
  }
  return '';
}

/**
 * Theme function for 'title' blockreference field formatter.
 */
function theme_blockreference_formatter_title($variables) {
  $element = $variables['element'];
  $block = _blockreference_formatter_get_block($element);
  if ($block) {
    $block_content = _block_render_blocks(array(
      $block,
    ));
    if ($block_content) {
      return reset($block_content)->subject;
    }
  }
  return '';
}

/**
 * Theme function for 'config_link' blockreference field formatter.
 */
function theme_blockreference_formatter_config_link($variables) {
  $element = $variables['element'];
  $item = $element['item'];
  if (@$item['moddelta']) {
    list($module, $delta) = explode(':', $item['moddelta']);
    $settings = $element['display']['settings'];
    $label = '';
    switch ($settings['label']) {
      case 'custom':
        $label = t($settings['custom_label']);
        break;
      case 'info':
        $label = theme_blockreference_formatter_plain($variables);
        break;
      case 'config':
        $label = db_query('
          SELECT title
          FROM {block}
          WHERE module = ? AND delta = ?
        ', array(
          $module,
          $delta,
        ))
          ->fetchField();
        break;
      case 'rendered':
        $label = theme_blockreference_formatter_title($variables);
        break;
    }
    if (!$label) {
      $label = t('configure block');
    }
    return l($label, 'admin/structure/block/manage/' . $module . '/' . $delta . '/configure');
  }
  return '';
}

/**
 * The referenceable blocks in field instance settings.
 */
function _blockreference_get_block_modules_from_field($instance, $field = NULL) {
  $modules = @$instance['settings']['blockreference_modules'];
  if (!$modules) {
    $field or $field = field_info_field($instance['field_name']);
    $modules = @$field['settings']['referenceable_modules'];
  }
  $modules = drupal_map_assoc(array_keys(array_filter($modules)));
  return (array) $modules;
}

/**
 * The autocomplete match method in field instance settings.
 */
function _blockreference_get_ac_match_method_from_field($instance, $field = NULL) {
  $method = @$instance['widget']['settings']['blockreference_ac_match_method'];
  return $method ?: 'contains';
}

/**
 * Return options list of modules that provide blocks, keyed by module machine
 * name.
 */
function _blockreference_get_block_modules() {
  $modules =& drupal_static(__FUNCTION__);
  if (!$modules) {

    // Get modules that define blocks.
    $modules = module_implements('block_info');
    $modules = array_flip($modules);

    // And get their pretty names.
    $all_modules = system_list('module_enabled');
    foreach ($modules as $machine_name => $foo) {
      $modules[$machine_name] = @$all_modules[$machine_name]->info['name'] ?: $machine_name;
    }
    natcasesort($modules);
  }
  return $modules;
}

/**
 * Helper to create an autocomplete string from a pretty block.
 */
function _blockreference_block_string($block) {
  if (!$block) {
    return '';
  }
  $moddelta = $block->module . ':' . $block->delta;
  $label = $block->info;
  return $label . '   [' . $moddelta . ']';
}

/**
 * Helper to return a array(module => ..., delta => ...) array from an AC string.
 */
function _blockreference_block_from_ac_string($ac_string) {
  if (preg_match(BLOCKREFERENCE_AC_REGEX, $ac_string, $match)) {
    $module = $match[1];
    $delta = $match[2];
    return compact('module', 'delta');
  }
}

/**
 * Helper to create a pretty block object from a moddelta.
 */
function _blockreference_block($module, $delta, $info = NULL) {
  if (!$info) {
    $infos = module_invoke($module, 'block_info');
    $info = @$infos[$delta];
  }
  if ($info) {
    $info = compact('module', 'delta') + $info;
    $info['info'] = trim($info['info']);
    return (object) $info;
  }
}

/**
 * Helper to find blocks that potentially match a search string, in the right
 * order, altered by custom modules.
 */
function _blockreference_find_blocks($instance, $search_string = '', $context = array()) {
  $all_block_infos =& drupal_static(__FUNCTION__, array());
  $iid = $instance['id'];

  // Cache ALL potential block objects for this instance.
  if (!isset($all_block_infos[$iid])) {
    $referenceable_modules = _blockreference_get_block_modules_from_field($instance);
    $all_block_infos[$iid] = array();
    foreach (module_implements('block_info') as $module) {
      if (!$referenceable_modules || isset($referenceable_modules[$module])) {
        if ($blocks = module_invoke($module, 'block_info')) {
          foreach ($blocks as $delta => $info) {
            $moddelta = $module . ':' . $delta;
            $all_block_infos[$iid][$moddelta] = _blockreference_block($module, $delta, $info);
          }
        }
      }
    }
  }
  $blocks = $all_block_infos[$iid];
  $context['instance'] = $instance;
  drupal_alter('blockreference_blocks_pre', $blocks, $context);

  // Filter by search string.
  if ($search_string) {
    $check_string = drupal_strtolower($search_string);
    foreach ($blocks as $moddelta => $block) {
      if (strpos(drupal_strtolower($block->info), $check_string) === FALSE) {
        unset($blocks[$moddelta]);
      }
    }
  }

  // Sort.
  uasort($blocks, function ($a, $b) {
    return strnatcasecmp($a->info, $b->info);
  });
  drupal_alter('blockreference_blocks_post', $blocks, $context);
  return $blocks;
}

/**
 * Helper to make pretty options from block objects.
 */
function _blockreference_options($blocks, $ac_style = FALSE) {
  $options = array();
  foreach ($blocks as $block) {
    $moddelta = $block->module . ':' . $block->delta;
    $label = $block->info;
    $value = $ac_style ? _blockreference_block_string($block) : $moddelta;
    $options[$value] = $label;
  }
  return $options;
}

/**
 * Implements hook_options_list().
 */
function blockreference_options_list($field, $instance, $entity_type, $entity) {
  $context = array(
    'type' => 'options_list',
    'entity' => $entity,
  );
  $blocks = _blockreference_find_blocks($instance, '', $context);
  return _blockreference_options($blocks);
}

/**
 * Helper function for theming normal block views, returns appropriate block.
 */
function _blockreference_formatter_get_block($element) {
  $item = $element['item'];
  if (@$item['moddelta']) {
    list($module, $delta) = explode(':', $item['moddelta']);
    $block = _blockreference_block($module, $delta);
    if ($block) {

      // Fetch all `block` columns for this block, any theme.
      $query = db_select('block', 'b')
        ->fields('b')
        ->condition('module', $module)
        ->condition('delta', $delta)
        ->execute();
      $result = $query
        ->fetchAssoc();
      if ($result) {

        // Copy all themes columns over to our block object, except the useless, theme-specific.
        foreach ($result as $column => $value) {
          if (!isset($block->{$column}) && !in_array($column, array(
            'bid',
            'theme',
          ))) {
            $block->{$column} = $value;
          }
        }
      }
      $block->region = 'none';
      return $block;
    }
  }
  return FALSE;
}

/**
 * Implements hook_entity_view().
 *
 * Pre-render blockreference fields attached to entities to ensure forms are
 * rendered early enough.
 */
function blockreference_entity_view($entity, $entity_type, $view_mode, $langcode) {
  if (empty($entity->content)) {
    return;
  }
  foreach (element_children($entity->content) as $field_name) {
    $field = $entity->content[$field_name];

    // This is a blockreference field.
    if (isset($field['#field_type']) && $field['#field_type'] == 'blockreference') {

      // Unset renderable field items.
      foreach (element_children($field) as $n) {
        unset($entity->content[$field_name][$n]);
      }

      // Render.
      $html = render($field);

      // Add back to entity content.
      $keep_meta = array_flip(array(
        '#weight',
        '#title',
        '#access',
        '#label_display',
        '#field_name',
        '#entity_type',
        '#bundle',
      ));
      $entity->content[$field_name] = array(
        '#markup' => $html,
      ) + array_intersect_key($field, $keep_meta);
    }
  }
}

/**
 * Implements hook_migrate_api().
 */
function blockreference_migrate_api() {
  return array(
    'api' => 2,
    'field handlers' => array(
      'MigrateBlockReferenceFieldHandler',
    ),
  );
}

/**
 * Implements hook_block_usage().
 */
function blockreference_block_usage($module, $delta) {
  $moddelta = $module . ':' . $delta;

  // Cycle through potential fields/sources to find field items.
  $fields = field_info_field_map();
  $entity_loads = $loads_by_field_name = array();
  foreach ($fields as $field_name => $field) {
    if ($field['type'] == 'blockreference') {
      $table_name = 'field_data_' . $field_name;
      $column_name = $field_name . '_moddelta';

      // Query this field table for above bids.
      $items = db_query("SELECT entity_type, entity_id FROM {$table_name} WHERE {$column_name} = :moddelta", array(
        ':moddelta' => $moddelta,
      ));

      // Collect entity_type's and entity_id's associatively.
      foreach ($items as $item) {
        $entity_loads[$item->entity_type][] = $item->entity_id;
        $loads_by_field_name[$item->entity_type][$item->entity_id][] = $field_name;
      }
    }
  }

  // Load entities and stack 'em up into results.
  $matches = array();
  foreach ($entity_loads as $entity_type => $ids) {
    $entities = entity_load($entity_type, $ids);
    foreach ($entities as $id => $entity) {
      $uri = entity_uri($entity_type, $entity);
      $path = $uri['path'];
      $options = @$uri['options'] ?: array();
      $fields = $loads_by_field_name[$entity_type][$id];
      $matches[] = l($entity_type . ' # ' . $id, $path, $options) . ' (' . implode(', ', $fields) . ')';
    }
  }
  return $matches;
}

/**
 * Implements hook_entityreference_link_config().
 */
function blockreference_entityreference_link_config() {
  return array(
    'blockreference' => array(
      'value_column' => 'moddelta',
      'links_callback' => '_blockreference_entityreference_link_links',
    ),
  );
}

/**
 * Helper to make simple links from blocks for entityreference_link.
 */
function _blockreference_entityreference_link_links($entity_type, $moddeltas, $options, $context, $element) {
  $links = array();
  foreach ($moddeltas as $moddelta) {
    list($module, $delta) = explode(':', $moddelta);
    $block = _blockreference_block($module, $delta);
    if ($block) {
      $links[] = l($block->info, 'admin/structure/block/manage/' . $module . '/' . $delta . '/configure', $options);
    }
  }
  return $links;
}

Functions

Namesort descending Description
blockreference_autocomplete Menu callback for the autocomplete results.
blockreference_autocomplete_access Menu access callback for the autocomplete path.
blockreference_autocomplete_validate Validation callback for a blockreference autocomplete element.
blockreference_autocomplete_value Value callback for a blockreference autocomplete element.
blockreference_block_usage Implements hook_block_usage().
blockreference_entityreference_link_config Implements hook_entityreference_link_config().
blockreference_entity_view Implements hook_entity_view().
blockreference_menu Implements hook_menu().
blockreference_migrate_api Implements hook_migrate_api().
blockreference_options_list Implements hook_options_list().
blockreference_theme Implements hook_theme().
theme_blockreference_formatter_config_link Theme function for 'config_link' blockreference field formatter.
theme_blockreference_formatter_default Theme function for 'default' blockreference field formatter.
theme_blockreference_formatter_plain Theme function for 'plain' blockreference field formatter.
theme_blockreference_formatter_title Theme function for 'title' blockreference field formatter.
theme_blockreference_formatter_without_title Theme function for 'without_title' blockreference field formatter.
_blockreference_block Helper to create a pretty block object from a moddelta.
_blockreference_block_from_ac_string Helper to return a array(module => ..., delta => ...) array from an AC string.
_blockreference_block_string Helper to create an autocomplete string from a pretty block.
_blockreference_entityreference_link_links Helper to make simple links from blocks for entityreference_link.
_blockreference_find_blocks Helper to find blocks that potentially match a search string, in the right order, altered by custom modules.
_blockreference_formatter_get_block Helper function for theming normal block views, returns appropriate block.
_blockreference_get_ac_match_method_from_field The autocomplete match method in field instance settings.
_blockreference_get_block_modules Return options list of modules that provide blocks, keyed by module machine name.
_blockreference_get_block_modules_from_field The referenceable blocks in field instance settings.
_blockreference_options Helper to make pretty options from block objects.

Constants